github.com/kaptinlin/jsonschema@v0.4.6/schema_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  func TestGetRootSchema(t *testing.T) {
    11  	compiler := NewCompiler()
    12  	root := &Schema{ID: "root"}
    13  	child := &Schema{ID: "child"}
    14  	grandChild := &Schema{ID: "grandChild"}
    15  
    16  	child.initializeSchema(compiler, root)
    17  	grandChild.initializeSchema(compiler, child)
    18  
    19  	if grandChild.getRootSchema().ID != "root" {
    20  		t.Errorf("Expected root schema ID to be 'root', got '%s'", grandChild.getRootSchema().ID)
    21  	}
    22  }
    23  
    24  func TestSchemaInitialization(t *testing.T) {
    25  	compiler := NewCompiler().SetDefaultBaseURI("http://default.com/")
    26  
    27  	tests := []struct {
    28  		name            string
    29  		id              string
    30  		expectedID      string
    31  		expectedURI     string
    32  		expectedBaseURI string
    33  	}{
    34  		{
    35  			name:            "Schema with absolute $id",
    36  			id:              "http://example.com/schema",
    37  			expectedID:      "http://example.com/schema",
    38  			expectedURI:     "http://example.com/schema",
    39  			expectedBaseURI: "http://example.com/",
    40  		},
    41  		{
    42  			name:            "Schema with relative $id",
    43  			id:              "schema",
    44  			expectedID:      "schema",
    45  			expectedURI:     "http://default.com/schema",
    46  			expectedBaseURI: "http://default.com/",
    47  		},
    48  		{
    49  			name:            "Schema without $id",
    50  			id:              "",
    51  			expectedID:      "",
    52  			expectedURI:     "",
    53  			expectedBaseURI: "http://default.com/",
    54  		},
    55  	}
    56  
    57  	for _, tt := range tests {
    58  		t.Run(tt.name, func(t *testing.T) {
    59  			schemaJSON := createTestSchemaJSON(tt.id, map[string]string{"name": "string"}, []string{"name"})
    60  			schema, err := compiler.Compile([]byte(schemaJSON))
    61  
    62  			assert.NoError(t, err)
    63  			assert.Equal(t, tt.expectedID, schema.ID)
    64  			assert.Equal(t, tt.expectedURI, schema.uri)
    65  			assert.Equal(t, tt.expectedBaseURI, schema.baseURI)
    66  		})
    67  	}
    68  }
    69  
    70  func TestSetCompiler(t *testing.T) {
    71  	// Create a custom compiler
    72  	customCompiler := NewCompiler()
    73  	customCompiler.RegisterDefaultFunc("testFunc", func(args ...any) (any, error) {
    74  		return "custom_result", nil
    75  	})
    76  
    77  	// Test SetCompiler returns the schema for chaining
    78  	schema := &Schema{}
    79  	result := schema.SetCompiler(customCompiler)
    80  	assert.Same(t, schema, result, "SetCompiler should return the schema for chaining")
    81  	assert.Same(t, customCompiler, schema.compiler, "Schema should have the custom compiler set")
    82  }
    83  
    84  func TestGetCompiler(t *testing.T) {
    85  	tests := []struct {
    86  		name           string
    87  		setupFunc      func() *Schema
    88  		expectedResult *Compiler
    89  	}{
    90  		{
    91  			name: "Schema with custom compiler",
    92  			setupFunc: func() *Schema {
    93  				customCompiler := NewCompiler()
    94  				schema := &Schema{}
    95  				schema.SetCompiler(customCompiler)
    96  				return schema
    97  			},
    98  			expectedResult: NewCompiler(), // Same as custom compiler
    99  		},
   100  		{
   101  			name: "Schema without compiler, no parent",
   102  			setupFunc: func() *Schema {
   103  				return &Schema{}
   104  			},
   105  			expectedResult: defaultCompiler,
   106  		},
   107  		{
   108  			name: "Child schema inherits from parent",
   109  			setupFunc: func() *Schema {
   110  				customCompiler := NewCompiler()
   111  				parent := &Schema{}
   112  				parent.SetCompiler(customCompiler)
   113  
   114  				child := &Schema{parent: parent}
   115  				return child
   116  			},
   117  			expectedResult: NewCompiler(), // Same as parent's custom compiler
   118  		},
   119  		{
   120  			name: "Nested inheritance chain",
   121  			setupFunc: func() *Schema {
   122  				customCompiler := NewCompiler()
   123  
   124  				// Create inheritance chain: grandparent -> parent -> child
   125  				grandparent := &Schema{}
   126  				grandparent.SetCompiler(customCompiler)
   127  
   128  				parent := &Schema{parent: grandparent}
   129  				child := &Schema{parent: parent}
   130  
   131  				return child
   132  			},
   133  			expectedResult: NewCompiler(), // Same as grandparent's custom compiler
   134  		},
   135  	}
   136  
   137  	for _, tt := range tests {
   138  		t.Run(tt.name, func(t *testing.T) {
   139  			schema := tt.setupFunc()
   140  			result := schema.GetCompiler()
   141  
   142  			// We can't directly compare compiler instances, so we check they're not nil
   143  			// and that they have the same type
   144  			assert.NotNil(t, result, "GetCompiler should never return nil")
   145  			assert.IsType(t, &Compiler{}, result, "GetCompiler should return a Compiler")
   146  		})
   147  	}
   148  }
   149  
   150  func TestGetCompilerInheritance(t *testing.T) {
   151  	// Create a custom compiler with a test function
   152  	customCompiler := NewCompiler()
   153  	customCompiler.RegisterDefaultFunc("testFunc", func(args ...any) (any, error) {
   154  		return "inherited_result", nil
   155  	})
   156  
   157  	// Create parent-child relationship
   158  	parent := &Schema{}
   159  	parent.SetCompiler(customCompiler)
   160  
   161  	child := &Schema{parent: parent}
   162  
   163  	// Test that child inherits parent's compiler
   164  	childCompiler := child.GetCompiler()
   165  	assert.NotNil(t, childCompiler, "Child should inherit compiler from parent")
   166  
   167  	// Verify the inherited compiler has the custom function
   168  	fn, exists := childCompiler.getDefaultFunc("testFunc")
   169  	assert.True(t, exists, "Child's compiler should have inherited the custom function")
   170  
   171  	result, err := fn()
   172  	assert.NoError(t, err)
   173  	assert.Equal(t, "inherited_result", result, "Inherited function should work correctly")
   174  }
   175  
   176  func TestSetCompilerWithConstructors(t *testing.T) {
   177  	// Create a custom compiler
   178  	customCompiler := NewCompiler()
   179  	customCompiler.RegisterDefaultFunc("now", DefaultNowFunc)
   180  
   181  	// Test that constructors work with SetCompiler
   182  	schema := Object(
   183  		Prop("timestamp", String(Default("now()"))),
   184  	).SetCompiler(customCompiler)
   185  
   186  	// Verify the child schema can use the parent's compiler
   187  	data := map[string]interface{}{}
   188  	var result map[string]interface{}
   189  	err := schema.Unmarshal(&result, data)
   190  	assert.NoError(t, err)
   191  	assert.Contains(t, result, "timestamp", "Default value should be applied")
   192  	assert.IsType(t, "", result["timestamp"], "Timestamp should be a string")
   193  }
   194  
   195  func TestConstructorCompilerBehavior(t *testing.T) {
   196  	// Test that constructors don't force defaultCompiler
   197  	// This verifies SetCompiler works correctly after construction
   198  
   199  	// Create custom compiler with a unique function
   200  	customCompiler := NewCompiler()
   201  	customCompiler.RegisterDefaultFunc("customFunc", func(args ...any) (any, error) {
   202  		return "custom_value", nil
   203  	})
   204  
   205  	// Create schema using constructor, then set custom compiler
   206  	schema := Object(
   207  		Prop("field", String(Default("customFunc()"))),
   208  	)
   209  
   210  	// Before SetCompiler, the child schema should not have a compiler set
   211  	// This verifies constructors don't force defaultCompiler
   212  	childSchema := (*schema.Properties)["field"]
   213  	assert.Nil(t, childSchema.compiler, "Child schema should not have compiler set by constructor")
   214  
   215  	// Set custom compiler on parent
   216  	schema.SetCompiler(customCompiler)
   217  
   218  	// Test that child inherits parent's compiler for function execution
   219  	data := map[string]interface{}{}
   220  	var result map[string]interface{}
   221  	err := schema.Unmarshal(&result, data)
   222  	assert.NoError(t, err)
   223  	assert.Equal(t, "custom_value", result["field"], "Child should inherit parent's custom compiler")
   224  }
   225  
   226  func TestSchemaUnresolvedRefs(t *testing.T) {
   227  	compiler := NewCompiler()
   228  
   229  	refSchemaJSON := `{
   230  		"$id": "http://example.com/ref",
   231  		"type": "object",
   232  		"properties": {
   233  			"userInfo": {"$ref": "http://example.com/base"}
   234  		}
   235  	}`
   236  
   237  	schema, err := compiler.Compile([]byte(refSchemaJSON))
   238  	require.NoError(t, err, "Failed to resolve reference")
   239  
   240  	unresolved := schema.GetUnresolvedReferenceURIs()
   241  	assert.Len(t, unresolved, 1, "Should have 1 unresolved ref")
   242  	assert.Equal(t, []string{"http://example.com/base"}, unresolved, "Should have correct unresolved schema")
   243  }