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  }