github.com/kaptinlin/jsonschema@v0.4.6/keywords_test.go (about)

     1  package jsonschema_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/kaptinlin/jsonschema"
     7  )
     8  
     9  func TestStringKeywords(t *testing.T) {
    10  	tests := []struct {
    11  		name    string
    12  		schema  *jsonschema.Schema
    13  		valid   interface{}
    14  		invalid interface{}
    15  	}{
    16  		{
    17  			name:    "MinLen valid",
    18  			schema:  jsonschema.String(jsonschema.MinLen(3)),
    19  			valid:   "hello",
    20  			invalid: "hi",
    21  		},
    22  		{
    23  			name:    "MinLen invalid",
    24  			schema:  jsonschema.String(jsonschema.MinLen(5)),
    25  			valid:   "hello",
    26  			invalid: "hi",
    27  		},
    28  		{
    29  			name:    "MaxLen valid",
    30  			schema:  jsonschema.String(jsonschema.MaxLen(5)),
    31  			valid:   "hello",
    32  			invalid: "hello world",
    33  		},
    34  		{
    35  			name:    "MaxLen invalid",
    36  			schema:  jsonschema.String(jsonschema.MaxLen(3)),
    37  			valid:   "hi",
    38  			invalid: "hello",
    39  		},
    40  		{
    41  			name:    "Pattern valid",
    42  			schema:  jsonschema.String(jsonschema.Pattern("^[a-z]+$")),
    43  			valid:   "hello",
    44  			invalid: "Hello123",
    45  		},
    46  		{
    47  			name:    "Pattern invalid",
    48  			schema:  jsonschema.String(jsonschema.Pattern("^\\d+$")),
    49  			valid:   "123",
    50  			invalid: "abc",
    51  		},
    52  		{
    53  			name: "Combined string keywords",
    54  			schema: jsonschema.String(
    55  				jsonschema.MinLen(3),
    56  				jsonschema.MaxLen(10),
    57  				jsonschema.Pattern("^[a-z]+$"),
    58  			),
    59  			valid:   "hello",
    60  			invalid: "Hi",
    61  		},
    62  	}
    63  
    64  	for _, tt := range tests {
    65  		t.Run(tt.name, func(t *testing.T) {
    66  			// Test valid data
    67  			result := tt.schema.Validate(tt.valid)
    68  			if !result.IsValid() {
    69  				t.Errorf("Expected valid data to pass validation, got errors: %v", result.Errors)
    70  			}
    71  
    72  			// Test invalid data
    73  			result = tt.schema.Validate(tt.invalid)
    74  			if result.IsValid() {
    75  				t.Errorf("Expected invalid data to fail validation")
    76  			}
    77  		})
    78  	}
    79  }
    80  
    81  func TestNumberKeywords(t *testing.T) {
    82  	tests := []struct {
    83  		name    string
    84  		schema  *jsonschema.Schema
    85  		valid   interface{}
    86  		invalid interface{}
    87  	}{
    88  		{
    89  			name:    "Min valid",
    90  			schema:  jsonschema.Number(jsonschema.Min(5)),
    91  			valid:   10.5,
    92  			invalid: 3.2,
    93  		},
    94  		{
    95  			name:    "Min invalid",
    96  			schema:  jsonschema.Integer(jsonschema.Min(10)),
    97  			valid:   15,
    98  			invalid: 5,
    99  		},
   100  		{
   101  			name:    "Max valid",
   102  			schema:  jsonschema.Number(jsonschema.Max(100)),
   103  			valid:   50.5,
   104  			invalid: 150.2,
   105  		},
   106  		{
   107  			name:    "Max invalid",
   108  			schema:  jsonschema.Integer(jsonschema.Max(50)),
   109  			valid:   25,
   110  			invalid: 75,
   111  		},
   112  		{
   113  			name:    "ExclusiveMin valid",
   114  			schema:  jsonschema.Number(jsonschema.ExclusiveMin(0)),
   115  			valid:   0.1,
   116  			invalid: 0,
   117  		},
   118  		{
   119  			name:    "ExclusiveMin invalid",
   120  			schema:  jsonschema.Number(jsonschema.ExclusiveMin(10)),
   121  			valid:   10.1,
   122  			invalid: 10,
   123  		},
   124  		{
   125  			name:    "ExclusiveMax valid",
   126  			schema:  jsonschema.Number(jsonschema.ExclusiveMax(100)),
   127  			valid:   99.9,
   128  			invalid: 100,
   129  		},
   130  		{
   131  			name:    "ExclusiveMax invalid",
   132  			schema:  jsonschema.Number(jsonschema.ExclusiveMax(50)),
   133  			valid:   49.9,
   134  			invalid: 50,
   135  		},
   136  		{
   137  			name:    "MultipleOf valid",
   138  			schema:  jsonschema.Number(jsonschema.MultipleOf(2.5)),
   139  			valid:   10.0,
   140  			invalid: 11.0,
   141  		},
   142  		{
   143  			name:    "MultipleOf invalid",
   144  			schema:  jsonschema.Integer(jsonschema.MultipleOf(3)),
   145  			valid:   9,
   146  			invalid: 10,
   147  		},
   148  		{
   149  			name: "Combined number keywords",
   150  			schema: jsonschema.Number(
   151  				jsonschema.Min(0),
   152  				jsonschema.Max(100),
   153  				jsonschema.MultipleOf(5),
   154  			),
   155  			valid:   25.0,
   156  			invalid: 23.0,
   157  		},
   158  	}
   159  
   160  	for _, tt := range tests {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			// Test valid data
   163  			result := tt.schema.Validate(tt.valid)
   164  			if !result.IsValid() {
   165  				t.Errorf("Expected valid data to pass validation, got errors: %v", result.Errors)
   166  			}
   167  
   168  			// Test invalid data
   169  			result = tt.schema.Validate(tt.invalid)
   170  			if result.IsValid() {
   171  				t.Errorf("Expected invalid data to fail validation")
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func TestArrayKeywords(t *testing.T) {
   178  	tests := []struct {
   179  		name    string
   180  		schema  *jsonschema.Schema
   181  		valid   interface{}
   182  		invalid interface{}
   183  	}{
   184  		{
   185  			name:    "Items valid",
   186  			schema:  jsonschema.Array(jsonschema.Items(jsonschema.String())),
   187  			valid:   []interface{}{"a", "b", "c"},
   188  			invalid: []interface{}{"a", 123, "c"},
   189  		},
   190  		{
   191  			name:    "Items invalid",
   192  			schema:  jsonschema.Array(jsonschema.Items(jsonschema.Integer())),
   193  			valid:   []interface{}{1, 2, 3},
   194  			invalid: []interface{}{1, "two", 3},
   195  		},
   196  		{
   197  			name:    "MinItems valid",
   198  			schema:  jsonschema.Array(jsonschema.MinItems(2)),
   199  			valid:   []interface{}{1, 2, 3},
   200  			invalid: []interface{}{1},
   201  		},
   202  		{
   203  			name:    "MinItems invalid",
   204  			schema:  jsonschema.Array(jsonschema.MinItems(3)),
   205  			valid:   []interface{}{1, 2, 3, 4},
   206  			invalid: []interface{}{1, 2},
   207  		},
   208  		{
   209  			name:    "MaxItems valid",
   210  			schema:  jsonschema.Array(jsonschema.MaxItems(3)),
   211  			valid:   []interface{}{1, 2},
   212  			invalid: []interface{}{1, 2, 3, 4},
   213  		},
   214  		{
   215  			name:    "MaxItems invalid",
   216  			schema:  jsonschema.Array(jsonschema.MaxItems(2)),
   217  			valid:   []interface{}{1, 2},
   218  			invalid: []interface{}{1, 2, 3},
   219  		},
   220  		{
   221  			name:    "UniqueItems valid",
   222  			schema:  jsonschema.Array(jsonschema.UniqueItems(true)),
   223  			valid:   []interface{}{1, 2, 3},
   224  			invalid: []interface{}{1, 2, 2, 3},
   225  		},
   226  		{
   227  			name:    "UniqueItems invalid",
   228  			schema:  jsonschema.Array(jsonschema.UniqueItems(true)),
   229  			valid:   []interface{}{"a", "b", "c"},
   230  			invalid: []interface{}{"a", "b", "a"},
   231  		},
   232  		{
   233  			name: "Combined array keywords",
   234  			schema: jsonschema.Array(
   235  				jsonschema.Items(jsonschema.String()),
   236  				jsonschema.MinItems(2),
   237  				jsonschema.MaxItems(5),
   238  				jsonschema.UniqueItems(true),
   239  			),
   240  			valid:   []interface{}{"a", "b", "c"},
   241  			invalid: []interface{}{"a"},
   242  		},
   243  	}
   244  
   245  	for _, tt := range tests {
   246  		t.Run(tt.name, func(t *testing.T) {
   247  			// Test valid data
   248  			result := tt.schema.Validate(tt.valid)
   249  			if !result.IsValid() {
   250  				t.Errorf("Expected valid data to pass validation, got errors: %v", result.Errors)
   251  			}
   252  
   253  			// Test invalid data
   254  			result = tt.schema.Validate(tt.invalid)
   255  			if result.IsValid() {
   256  				t.Errorf("Expected invalid data to fail validation")
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func TestObjectKeywords(t *testing.T) {
   263  	tests := []struct {
   264  		name    string
   265  		schema  *jsonschema.Schema
   266  		valid   interface{}
   267  		invalid interface{}
   268  	}{
   269  		{
   270  			name: "Required valid",
   271  			schema: jsonschema.Object(
   272  				jsonschema.Prop("name", jsonschema.String()),
   273  				jsonschema.Required("name"),
   274  			),
   275  			valid:   map[string]interface{}{"name": "John"},
   276  			invalid: map[string]interface{}{"age": 25},
   277  		},
   278  		{
   279  			name: "Required invalid",
   280  			schema: jsonschema.Object(
   281  				jsonschema.Prop("name", jsonschema.String()),
   282  				jsonschema.Prop("age", jsonschema.Integer()),
   283  				jsonschema.Required("name", "age"),
   284  			),
   285  			valid:   map[string]interface{}{"name": "John", "age": 25},
   286  			invalid: map[string]interface{}{"name": "John"},
   287  		},
   288  		{
   289  			name: "MinProps valid",
   290  			schema: jsonschema.Object(
   291  				jsonschema.MinProps(2),
   292  			),
   293  			valid:   map[string]interface{}{"a": 1, "b": 2, "c": 3},
   294  			invalid: map[string]interface{}{"a": 1},
   295  		},
   296  		{
   297  			name: "MinProps invalid",
   298  			schema: jsonschema.Object(
   299  				jsonschema.MinProps(3),
   300  			),
   301  			valid:   map[string]interface{}{"a": 1, "b": 2, "c": 3},
   302  			invalid: map[string]interface{}{"a": 1, "b": 2},
   303  		},
   304  		{
   305  			name: "MaxProps valid",
   306  			schema: jsonschema.Object(
   307  				jsonschema.MaxProps(3),
   308  			),
   309  			valid:   map[string]interface{}{"a": 1, "b": 2},
   310  			invalid: map[string]interface{}{"a": 1, "b": 2, "c": 3, "d": 4},
   311  		},
   312  		{
   313  			name: "MaxProps invalid",
   314  			schema: jsonschema.Object(
   315  				jsonschema.MaxProps(2),
   316  			),
   317  			valid:   map[string]interface{}{"a": 1, "b": 2},
   318  			invalid: map[string]interface{}{"a": 1, "b": 2, "c": 3},
   319  		},
   320  		{
   321  			name: "AdditionalProps false valid",
   322  			schema: jsonschema.Object(
   323  				jsonschema.Prop("name", jsonschema.String()),
   324  				jsonschema.AdditionalProps(false),
   325  			),
   326  			valid:   map[string]interface{}{"name": "John"},
   327  			invalid: map[string]interface{}{"name": "John", "age": 25},
   328  		},
   329  		{
   330  			name: "AdditionalProps false invalid",
   331  			schema: jsonschema.Object(
   332  				jsonschema.Prop("name", jsonschema.String()),
   333  				jsonschema.AdditionalProps(false),
   334  			),
   335  			valid:   map[string]interface{}{"name": "John"},
   336  			invalid: map[string]interface{}{"name": "John", "extra": "value"},
   337  		},
   338  	}
   339  
   340  	for _, tt := range tests {
   341  		t.Run(tt.name, func(t *testing.T) {
   342  			// Test valid data
   343  			result := tt.schema.Validate(tt.valid)
   344  			if !result.IsValid() {
   345  				t.Errorf("Expected valid data to pass validation, got errors: %v", result.Errors)
   346  			}
   347  
   348  			// Test invalid data
   349  			result = tt.schema.Validate(tt.invalid)
   350  			if result.IsValid() {
   351  				t.Errorf("Expected invalid data to fail validation")
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  func TestBasicConvenienceFunctions(t *testing.T) {
   358  	tests := []struct {
   359  		name    string
   360  		schema  *jsonschema.Schema
   361  		valid   interface{}
   362  		invalid interface{}
   363  	}{
   364  		{
   365  			name:    "PositiveInt valid",
   366  			schema:  jsonschema.PositiveInt(),
   367  			valid:   5,
   368  			invalid: 0,
   369  		},
   370  		{
   371  			name:    "PositiveInt invalid",
   372  			schema:  jsonschema.PositiveInt(),
   373  			valid:   1,
   374  			invalid: -1,
   375  		},
   376  		{
   377  			name:    "NonNegativeInt valid",
   378  			schema:  jsonschema.NonNegativeInt(),
   379  			valid:   0,
   380  			invalid: -1,
   381  		},
   382  		{
   383  			name:    "NonNegativeInt invalid",
   384  			schema:  jsonschema.NonNegativeInt(),
   385  			valid:   5,
   386  			invalid: -5,
   387  		},
   388  	}
   389  
   390  	for _, tt := range tests {
   391  		t.Run(tt.name, func(t *testing.T) {
   392  			// Test valid data
   393  			result := tt.schema.Validate(tt.valid)
   394  			if !result.IsValid() {
   395  				t.Errorf("Expected valid data to pass validation, got errors: %v", result.Errors)
   396  			}
   397  
   398  			// Test invalid data
   399  			result = tt.schema.Validate(tt.invalid)
   400  			if result.IsValid() {
   401  				t.Errorf("Expected invalid data to fail validation")
   402  			}
   403  		})
   404  	}
   405  }
   406  
   407  func TestAnnotationKeywords(t *testing.T) {
   408  	// Test that annotation keywords don't affect validation
   409  	schema := jsonschema.String(
   410  		jsonschema.Title("User Name"),
   411  		jsonschema.Description("The user's display name"),
   412  		jsonschema.Default("Anonymous"),
   413  		jsonschema.Examples("John", "Jane"),
   414  		jsonschema.MinLen(1),
   415  	)
   416  
   417  	result := schema.Validate("Alice")
   418  	if !result.IsValid() {
   419  		t.Errorf("Expected valid string to pass validation, got errors: %v", result.Errors)
   420  	}
   421  
   422  	result = schema.Validate("")
   423  	if result.IsValid() {
   424  		t.Errorf("Expected empty string to fail validation due to minLength")
   425  	}
   426  }
   427  
   428  func TestKeywordCombinations(t *testing.T) {
   429  	// Test complex combinations of different keyword types
   430  	schema := jsonschema.Object(
   431  		jsonschema.Prop("username", jsonschema.String(
   432  			jsonschema.MinLen(3),
   433  			jsonschema.MaxLen(20),
   434  			jsonschema.Pattern("^[a-zA-Z0-9_]+$"),
   435  			jsonschema.Title("Username"),
   436  			jsonschema.Description("User's login name"),
   437  		)),
   438  		jsonschema.Prop("age", jsonschema.Integer(
   439  			jsonschema.Min(0),
   440  			jsonschema.Max(150),
   441  			jsonschema.Title("Age"),
   442  		)),
   443  		jsonschema.Prop("tags", jsonschema.Array(
   444  			jsonschema.Items(jsonschema.String(jsonschema.MinLen(1))),
   445  			jsonschema.UniqueItems(true),
   446  			jsonschema.MaxItems(10),
   447  		)),
   448  		jsonschema.Required("username"),
   449  		jsonschema.AdditionalProps(false),
   450  		jsonschema.Title("User Registration"),
   451  		jsonschema.Description("Schema for user registration data"),
   452  	)
   453  
   454  	validData := map[string]interface{}{
   455  		"username": "john_doe",
   456  		"age":      25,
   457  		"tags":     []interface{}{"developer", "golang"},
   458  	}
   459  
   460  	result := schema.Validate(validData)
   461  	if !result.IsValid() {
   462  		t.Errorf("Expected valid data to pass validation, got errors: %v", result.Errors)
   463  	}
   464  
   465  	invalidData := map[string]interface{}{
   466  		"username": "jo", // Too short
   467  		"age":      200,  // Too old
   468  		"extra":    "not allowed",
   469  	}
   470  
   471  	result = schema.Validate(invalidData)
   472  	if result.IsValid() {
   473  		t.Errorf("Expected invalid data to fail validation")
   474  	}
   475  }