github.com/kaptinlin/jsonschema@v0.4.6/validate_test.go (about) 1 package jsonschema 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 ) 9 10 // TestValidateMethodDelegation tests that the main Validate method properly delegates to type-specific methods 11 func TestValidateMethodDelegation(t *testing.T) { 12 compiler := NewCompiler() 13 schema, err := compiler.Compile([]byte(`{ 14 "type": "object", 15 "properties": {"name": {"type": "string"}}, 16 "required": ["name"] 17 }`)) 18 require.NoError(t, err) 19 20 // Test JSON bytes delegation 21 jsonData := []byte(`{"name": "John"}`) 22 result1 := schema.Validate(jsonData) 23 result2 := schema.ValidateJSON(jsonData) 24 assert.Equal(t, result1.IsValid(), result2.IsValid()) 25 26 // Test map delegation 27 mapData := map[string]interface{}{"name": "John"} 28 result3 := schema.Validate(mapData) 29 result4 := schema.ValidateMap(mapData) 30 assert.Equal(t, result3.IsValid(), result4.IsValid()) 31 32 // Test struct delegation 33 type Person struct { 34 Name string `json:"name"` 35 } 36 structData := Person{Name: "John"} 37 result5 := schema.Validate(structData) 38 result6 := schema.ValidateStruct(structData) 39 assert.Equal(t, result5.IsValid(), result6.IsValid()) 40 } 41 42 // TestValidateJSON tests JSON byte validation 43 func TestValidateJSON(t *testing.T) { 44 tests := []struct { 45 name string 46 schema string 47 data []byte 48 expectValid bool 49 }{ 50 { 51 name: "valid JSON object", 52 schema: `{"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}`, 53 data: []byte(`{"name": "John"}`), 54 expectValid: true, 55 }, 56 { 57 name: "invalid JSON object - missing required", 58 schema: `{"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}`, 59 data: []byte(`{}`), 60 expectValid: false, 61 }, 62 { 63 name: "valid JSON array", 64 schema: `{"type": "array", "items": {"type": "string"}, "minItems": 2}`, 65 data: []byte(`["hello", "world"]`), 66 expectValid: true, 67 }, 68 { 69 name: "invalid JSON array - too few items", 70 schema: `{"type": "array", "items": {"type": "string"}, "minItems": 3}`, 71 data: []byte(`["hello"]`), 72 expectValid: false, 73 }, 74 { 75 name: "invalid JSON syntax", 76 schema: `{"type": "object"}`, 77 data: []byte(`{invalid json`), 78 expectValid: false, 79 }, 80 { 81 name: "valid JSON primitives", 82 schema: `{"type": "string", "minLength": 5}`, 83 data: []byte(`"hello world"`), 84 expectValid: true, 85 }, 86 } 87 88 for _, tt := range tests { 89 t.Run(tt.name, func(t *testing.T) { 90 compiler := NewCompiler() 91 schema, err := compiler.Compile([]byte(tt.schema)) 92 require.NoError(t, err) 93 94 result := schema.ValidateJSON(tt.data) 95 assert.Equal(t, tt.expectValid, result.IsValid()) 96 }) 97 } 98 } 99 100 // TestValidateStruct tests struct validation 101 func TestValidateStruct(t *testing.T) { 102 type Person struct { 103 Name string `json:"name"` 104 Age *int `json:"age,omitempty"` // use pointer to distinguish between zero value and missing 105 Email *string `json:"email,omitempty"` 106 } 107 108 tests := []struct { 109 name string 110 schema string 111 data interface{} 112 expectValid bool 113 }{ 114 { 115 name: "valid struct", 116 schema: `{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, "required": ["name"]}`, 117 data: Person{Name: "John", Age: intPtr(30)}, 118 expectValid: true, 119 }, 120 { 121 name: "struct missing optional field", 122 schema: `{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, "required": ["name"]}`, 123 data: Person{Name: "John"}, // Age is optional 124 expectValid: true, 125 }, 126 { 127 name: "struct with all fields", 128 schema: `{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}, "email": {"type": "string"}}, "required": ["name"]}`, 129 data: Person{Name: "John", Age: intPtr(30), Email: strPtr("john@example.com")}, 130 expectValid: true, 131 }, 132 { 133 name: "struct with invalid type", 134 schema: `{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number", "minimum": 18}}, "required": ["name"]}`, 135 data: Person{Name: "John", Age: intPtr(10)}, // Age is less than the minimum 136 expectValid: false, 137 }, 138 } 139 140 for _, tt := range tests { 141 t.Run(tt.name, func(t *testing.T) { 142 compiler := NewCompiler() 143 schema, err := compiler.Compile([]byte(tt.schema)) 144 require.NoError(t, err) 145 146 result := schema.ValidateStruct(tt.data) 147 assert.Equal(t, tt.expectValid, result.IsValid()) 148 }) 149 } 150 } 151 152 // TestValidateMap tests map validation 153 func TestValidateMap(t *testing.T) { 154 tests := []struct { 155 name string 156 schema string 157 data map[string]interface{} 158 expectValid bool 159 }{ 160 { 161 name: "valid map", 162 schema: `{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}}, "required": ["name"]}`, 163 data: map[string]interface{}{"name": "John", "age": 30}, 164 expectValid: true, 165 }, 166 { 167 name: "map missing required field", 168 schema: `{"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]}`, 169 data: map[string]interface{}{"age": 30}, 170 expectValid: false, 171 }, 172 { 173 name: "map with invalid type", 174 schema: `{"type": "object", "properties": {"age": {"type": "number"}}}`, 175 data: map[string]interface{}{"age": "thirty"}, 176 expectValid: false, 177 }, 178 { 179 name: "empty map with no required fields", 180 schema: `{"type": "object", "properties": {"name": {"type": "string"}}}`, 181 data: map[string]interface{}{}, 182 expectValid: true, 183 }, 184 } 185 186 for _, tt := range tests { 187 t.Run(tt.name, func(t *testing.T) { 188 compiler := NewCompiler() 189 schema, err := compiler.Compile([]byte(tt.schema)) 190 require.NoError(t, err) 191 192 result := schema.ValidateMap(tt.data) 193 assert.Equal(t, tt.expectValid, result.IsValid()) 194 }) 195 } 196 } 197 198 // TestValidateTypeConstraints tests numeric and string validation 199 func TestValidateTypeConstraints(t *testing.T) { 200 t.Run("NumericValidation", func(t *testing.T) { 201 schema := `{ 202 "type": "object", 203 "properties": { 204 "age": {"type": "integer", "minimum": 0, "maximum": 150}, 205 "score": {"type": "number", "multipleOf": 0.1} 206 } 207 }` 208 209 compiler := NewCompiler() 210 compiledSchema, err := compiler.Compile([]byte(schema)) 211 require.NoError(t, err) 212 213 validData := map[string]interface{}{ 214 "age": 25, 215 "score": 95.5, 216 } 217 result := compiledSchema.ValidateMap(validData) 218 assert.True(t, result.IsValid()) 219 220 invalidData := map[string]interface{}{ 221 "age": 200, // Exceeds maximum 222 "score": 95.33, // Not multiple of 0.1 223 } 224 result = compiledSchema.ValidateMap(invalidData) 225 assert.False(t, result.IsValid()) 226 }) 227 228 t.Run("StringValidation", func(t *testing.T) { 229 schema := `{ 230 "type": "object", 231 "properties": { 232 "name": {"type": "string", "minLength": 2, "maxLength": 10, "pattern": "^[A-Za-z]+$"} 233 } 234 }` 235 236 compiler := NewCompiler() 237 compiledSchema, err := compiler.Compile([]byte(schema)) 238 require.NoError(t, err) 239 240 validData := map[string]interface{}{"name": "John"} 241 result := compiledSchema.ValidateMap(validData) 242 assert.True(t, result.IsValid()) 243 244 invalidData := map[string]interface{}{"name": "J"} // Too short 245 result = compiledSchema.ValidateMap(invalidData) 246 assert.False(t, result.IsValid()) 247 }) 248 } 249 250 // TestValidateComplexSchemas tests complex validation scenarios 251 func TestValidateComplexSchemas(t *testing.T) { 252 t.Run("NestedObjects", func(t *testing.T) { 253 schema := `{ 254 "type": "object", 255 "properties": { 256 "user": { 257 "type": "object", 258 "properties": { 259 "name": {"type": "string"}, 260 "profile": { 261 "type": "object", 262 "properties": { 263 "age": {"type": "number", "minimum": 0} 264 } 265 } 266 } 267 } 268 } 269 }` 270 271 compiler := NewCompiler() 272 compiledSchema, err := compiler.Compile([]byte(schema)) 273 require.NoError(t, err) 274 275 validData := []byte(`{"user": {"name": "Alice", "profile": {"age": 25}}}`) 276 result := compiledSchema.ValidateJSON(validData) 277 assert.True(t, result.IsValid()) 278 }) 279 280 t.Run("ArrayOfObjects", func(t *testing.T) { 281 schema := `{ 282 "type": "array", 283 "items": { 284 "type": "object", 285 "properties": { 286 "id": {"type": "number"}, 287 "name": {"type": "string"} 288 }, 289 "required": ["id"] 290 } 291 }` 292 293 compiler := NewCompiler() 294 compiledSchema, err := compiler.Compile([]byte(schema)) 295 require.NoError(t, err) 296 297 validData := []byte(`[{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]`) 298 result := compiledSchema.ValidateJSON(validData) 299 assert.True(t, result.IsValid()) 300 }) 301 } 302 303 // TestValidateInputTypes tests various input type handling 304 func TestValidateInputTypes(t *testing.T) { 305 schema := `{ 306 "type": "object", 307 "properties": { 308 "name": {"type": "string"}, 309 "age": {"type": "integer", "minimum": 0} 310 }, 311 "required": ["name"] 312 }` 313 314 compiler := NewCompiler() 315 compiledSchema, err := compiler.Compile([]byte(schema)) 316 require.NoError(t, err) 317 318 type Person struct { 319 Name string `json:"name"` 320 Age int `json:"age"` 321 } 322 323 tests := []struct { 324 name string 325 data interface{} 326 want bool 327 }{ 328 {"JSON bytes", []byte(`{"name": "John", "age": 30}`), true}, 329 {"Map", map[string]interface{}{"name": "Jane", "age": 25}, true}, 330 {"Struct", Person{Name: "Bob", Age: 35}, true}, 331 {"Invalid JSON", []byte(`{invalid`), false}, 332 {"Missing required", map[string]interface{}{"age": 30}, false}, 333 } 334 335 for _, tt := range tests { 336 t.Run(tt.name, func(t *testing.T) { 337 result := compiledSchema.Validate(tt.data) 338 assert.Equal(t, tt.want, result.IsValid()) 339 }) 340 } 341 } 342 343 // BenchmarkValidate tests performance of validation methods 344 func BenchmarkValidate(b *testing.B) { 345 compiler := NewCompiler() 346 schema, _ := compiler.Compile([]byte(`{ 347 "type": "object", 348 "properties": { 349 "name": {"type": "string"}, 350 "age": {"type": "number", "minimum": 0}, 351 "email": {"type": "string", "format": "email"} 352 }, 353 "required": ["name", "age"] 354 }`)) 355 356 jsonData := []byte(`{"name": "John Doe", "age": 30, "email": "john@example.com"}`) 357 mapData := map[string]interface{}{"name": "John Doe", "age": 30, "email": "john@example.com"} 358 359 b.Run("ValidateJSON", func(b *testing.B) { 360 for i := 0; i < b.N; i++ { 361 result := schema.ValidateJSON(jsonData) 362 if !result.IsValid() { 363 b.Errorf("Expected validation to pass") 364 } 365 } 366 }) 367 368 b.Run("ValidateMap", func(b *testing.B) { 369 for i := 0; i < b.N; i++ { 370 result := schema.ValidateMap(mapData) 371 if !result.IsValid() { 372 b.Errorf("Expected validation to pass") 373 } 374 } 375 }) 376 } 377 378 // Helper functions 379 func strPtr(s string) *string { 380 return &s 381 }