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 }