1// Copyright 2017 Santhosh Kumar Tekuri. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package jsonschema_test 6 7import ( 8 "bytes" 9 "crypto/tls" 10 "encoding/json" 11 "io/ioutil" 12 "net/http" 13 "net/http/httptest" 14 "net/url" 15 "os" 16 "path/filepath" 17 "runtime" 18 "strings" 19 "testing" 20 21 "github.com/santhosh-tekuri/jsonschema" 22 _ "github.com/santhosh-tekuri/jsonschema/httploader" 23) 24 25var draft4, draft6, draft7 []byte 26 27func init() { 28 var err error 29 draft4, err = ioutil.ReadFile("testdata/draft4.json") 30 if err != nil { 31 panic(err) 32 } 33 draft6, err = ioutil.ReadFile("testdata/draft6.json") 34 if err != nil { 35 panic(err) 36 } 37 draft7, err = ioutil.ReadFile("testdata/draft7.json") 38 if err != nil { 39 panic(err) 40 } 41} 42func TestDraft4(t *testing.T) { 43 testFolder(t, "testdata/draft4", jsonschema.Draft4) 44} 45 46func TestDraft6(t *testing.T) { 47 testFolder(t, "testdata/draft6", jsonschema.Draft6) 48} 49 50func TestDraft7(t *testing.T) { 51 testFolder(t, "testdata/draft7", jsonschema.Draft7) 52} 53 54type testGroup struct { 55 Description string 56 Schema json.RawMessage 57 Tests []struct { 58 Description string 59 Data json.RawMessage 60 Valid bool 61 Skip *string 62 } 63} 64 65func testFolder(t *testing.T, folder string, draft *jsonschema.Draft) { 66 server := &http.Server{Addr: "localhost:1234", Handler: http.FileServer(http.Dir("testdata/remotes"))} 67 go func() { 68 if err := server.ListenAndServe(); err != http.ErrServerClosed { 69 t.Fatal(err) 70 } 71 }() 72 defer server.Close() 73 74 err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error { 75 if err != nil { 76 t.Error(err) 77 return nil 78 } 79 if info.IsDir() { 80 return nil 81 } 82 if filepath.Ext(info.Name()) != ".json" { 83 return nil 84 } 85 86 t.Log(info.Name()) 87 data, err := ioutil.ReadFile(path) 88 if err != nil { 89 t.Errorf(" FAIL: %v\n", err) 90 return nil 91 } 92 var tg []testGroup 93 if err = json.Unmarshal(data, &tg); err != nil { 94 t.Errorf(" FAIL: %v\n", err) 95 return nil 96 } 97 for _, group := range tg { 98 t.Logf(" %s\n", group.Description) 99 c := jsonschema.NewCompiler() 100 if err := c.AddResource("http://json-schema.org/draft-04/schema", bytes.NewReader(draft4)); err != nil { 101 t.Errorf(" FAIL: add resource failed, reason: %v\n", err) 102 continue 103 } 104 if err := c.AddResource("http://json-schema.org/draft-06/schema", bytes.NewReader(draft6)); err != nil { 105 t.Errorf(" FAIL: add resource failed, reason: %v\n", err) 106 continue 107 } 108 if err := c.AddResource("http://json-schema.org/draft-07/schema", bytes.NewReader(draft7)); err != nil { 109 t.Errorf(" FAIL: add resource failed, reason: %v\n", err) 110 continue 111 } 112 c.Draft = draft 113 if err := c.AddResource("test.json", bytes.NewReader(group.Schema)); err != nil { 114 t.Errorf(" FAIL: add resource failed, reason: %v\n", err) 115 continue 116 } 117 schema, err := c.Compile("test.json") 118 if err != nil { 119 t.Errorf(" FAIL: schema compilation failed, reason: %v\n", err) 120 continue 121 } 122 for _, test := range group.Tests { 123 t.Logf(" %s\n", test.Description) 124 if test.Skip != nil { 125 t.Logf(" skipping: %s\n", *test.Skip) 126 continue 127 } 128 err = schema.Validate(bytes.NewReader(test.Data)) 129 valid := err == nil 130 if !valid { 131 for _, line := range strings.Split(err.Error(), "\n") { 132 t.Logf(" %s\n", line) 133 } 134 } 135 if test.Valid != valid { 136 t.Errorf(" FAIL: expected valid=%t got valid=%t\n", test.Valid, valid) 137 } 138 } 139 } 140 return nil 141 }) 142 if err != nil { 143 t.Fatal(err) 144 } 145 146 invalidDocTests := []struct { 147 description string 148 doc string 149 }{ 150 {"non json instance", "{"}, 151 {"multiple json instance", "{}{}"}, 152 } 153 for _, test := range invalidDocTests { 154 t.Run(test.description, func(t *testing.T) { 155 c := jsonschema.NewCompiler() 156 if err := c.AddResource("test.json", strings.NewReader("{}")); err != nil { 157 t.Fatal(err) 158 } 159 s, err := c.Compile("test.json") 160 if err != nil { 161 t.Fatal(err) 162 } 163 if err := s.Validate(strings.NewReader(test.doc)); err != nil { 164 t.Log(err) 165 } else { 166 t.Error("error expected") 167 } 168 }) 169 } 170} 171 172func TestInvalidSchema(t *testing.T) { 173 t.Run("MustCompile with panic", func(t *testing.T) { 174 defer func() { 175 if r := recover(); r == nil { 176 t.Error("panic expected") 177 } 178 }() 179 jsonschema.MustCompile("testdata/invalid_schema.json") 180 }) 181 182 t.Run("MustCompile without panic", func(t *testing.T) { 183 defer func() { 184 if r := recover(); r != nil { 185 t.Error("panic not expected") 186 } 187 }() 188 jsonschema.MustCompile("testdata/customer_schema.json#/0") 189 }) 190 191 t.Run("invalid json", func(t *testing.T) { 192 if err := jsonschema.NewCompiler().AddResource("test.json", strings.NewReader("{")); err == nil { 193 t.Error("error expected") 194 } else { 195 t.Log(err) 196 } 197 }) 198 199 t.Run("multiple json", func(t *testing.T) { 200 if err := jsonschema.NewCompiler().AddResource("test.json", strings.NewReader("{}{}")); err == nil { 201 t.Error("error expected") 202 } else { 203 t.Log(err) 204 } 205 }) 206 207 type test struct { 208 Description string 209 Schema json.RawMessage 210 Fragment string 211 } 212 data, err := ioutil.ReadFile("testdata/invalid_schemas.json") 213 if err != nil { 214 t.Fatal(err) 215 } 216 var tests []test 217 if err = json.Unmarshal(data, &tests); err != nil { 218 t.Fatal(err) 219 } 220 for _, test := range tests { 221 t.Run(test.Description, func(t *testing.T) { 222 c := jsonschema.NewCompiler() 223 url := "test.json" 224 if err := c.AddResource(url, bytes.NewReader(test.Schema)); err != nil { 225 t.Fatal(err) 226 } 227 if len(test.Fragment) > 0 { 228 url += test.Fragment 229 } 230 if _, err = c.Compile(url); err == nil { 231 t.Error("error expected") 232 } else { 233 t.Log(err) 234 } 235 }) 236 } 237} 238 239func TestCompileURL(t *testing.T) { 240 tr := http.DefaultTransport.(*http.Transport) 241 if tr.TLSClientConfig == nil { 242 tr.TLSClientConfig = &tls.Config{} 243 } 244 tr.TLSClientConfig.InsecureSkipVerify = true 245 246 handler := http.FileServer(http.Dir("testdata")) 247 httpServer := httptest.NewServer(handler) 248 defer httpServer.Close() 249 httpsServer := httptest.NewTLSServer(handler) 250 defer httpsServer.Close() 251 252 validTests := []struct { 253 schema, doc string 254 }{ 255 {"testdata/customer_schema.json#/0", "testdata/customer.json"}, 256 {toFileURL("testdata/customer_schema.json") + "#/0", "testdata/customer.json"}, 257 {httpServer.URL + "/customer_schema.json#/0", "testdata/customer.json"}, 258 {httpsServer.URL + "/customer_schema.json#/0", "testdata/customer.json"}, 259 {toFileURL("testdata/empty schema.json"), "testdata/empty schema.json"}, 260 } 261 for i, test := range validTests { 262 t.Logf("valid #%d: %+v", i, test) 263 s, err := jsonschema.Compile(test.schema) 264 if err != nil { 265 t.Errorf("valid #%d: %v", i, err) 266 return 267 } 268 f, err := os.Open(test.doc) 269 if err != nil { 270 t.Errorf("valid #%d: %v", i, err) 271 return 272 } 273 err = s.Validate(f) 274 _ = f.Close() 275 if err != nil { 276 t.Errorf("valid #%d: %v", i, err) 277 } 278 } 279 280 invalidTests := []string{ 281 "testdata/syntax_error.json", 282 "testdata/missing.json", 283 toFileURL("testdata/missing.json"), 284 httpServer.URL + "/missing.json", 285 httpsServer.URL + "/missing.json", 286 } 287 for i, test := range invalidTests { 288 t.Logf("invalid #%d: %v", i, test) 289 if _, err := jsonschema.Compile(test); err == nil { 290 t.Errorf("invalid #%d: expected error", i) 291 } else { 292 t.Logf("invalid #%d: %v", i, err) 293 } 294 } 295} 296 297func TestValidateInterface(t *testing.T) { 298 files := []string{ 299 "testdata/draft4/type.json", 300 "testdata/draft4/minimum.json", 301 "testdata/draft4/maximum.json", 302 } 303 for _, file := range files { 304 t.Log(filepath.Base(file)) 305 data, err := ioutil.ReadFile(file) 306 if err != nil { 307 t.Errorf(" FAIL: %v\n", err) 308 return 309 } 310 var tg []testGroup 311 if err = json.Unmarshal(data, &tg); err != nil { 312 t.Errorf(" FAIL: %v\n", err) 313 return 314 } 315 for _, group := range tg { 316 t.Logf(" %s\n", group.Description) 317 c := jsonschema.NewCompiler() 318 if err := c.AddResource("test.json", bytes.NewReader(group.Schema)); err != nil { 319 t.Errorf(" FAIL: add resource failed, reason: %v\n", err) 320 continue 321 } 322 c.Draft = jsonschema.Draft4 323 schema, err := c.Compile("test.json") 324 if err != nil { 325 t.Errorf(" FAIL: schema compilation failed, reason: %v\n", err) 326 continue 327 } 328 for _, test := range group.Tests { 329 t.Logf(" %s\n", test.Description) 330 331 decoder := json.NewDecoder(bytes.NewReader(test.Data)) 332 var doc interface{} 333 if err := decoder.Decode(&doc); err != nil { 334 t.Errorf(" FAIL: decode json failed, reason: %v\n", err) 335 continue 336 } 337 338 err = schema.ValidateInterface(doc) 339 valid := err == nil 340 if !valid { 341 for _, line := range strings.Split(err.Error(), "\n") { 342 t.Logf(" %s\n", line) 343 } 344 } 345 if test.Valid != valid { 346 t.Errorf(" FAIL: expected valid=%t got valid=%t\n", test.Valid, valid) 347 } 348 } 349 } 350 } 351} 352 353func TestInvalidJsonTypeError(t *testing.T) { 354 compiler := jsonschema.NewCompiler() 355 err := compiler.AddResource("test.json", strings.NewReader(`{ "type": "string"}`)) 356 if err != nil { 357 t.Fatalf("addResource failed. reason: %v\n", err) 358 } 359 schema, err := compiler.Compile("test.json") 360 if err != nil { 361 t.Fatalf("schema compilation failed. reason: %v\n", err) 362 } 363 v := struct{ name string }{"hello world"} 364 err = schema.ValidateInterface(v) 365 switch err.(type) { 366 case jsonschema.InvalidJSONTypeError: 367 // passed 368 default: 369 t.Fatalf("got %v. want InvalidJSONTypeErr", err) 370 } 371} 372 373func TestExtractAnnotations(t *testing.T) { 374 t.Run("false", func(t *testing.T) { 375 compiler := jsonschema.NewCompiler() 376 377 err := compiler.AddResource("test.json", strings.NewReader(`{ 378 "title": "this is title" 379 }`)) 380 if err != nil { 381 t.Fatalf("addResource failed. reason: %v\n", err) 382 } 383 384 schema, err := compiler.Compile("test.json") 385 if err != nil { 386 t.Fatalf("schema compilation failed. reason: %v\n", err) 387 } 388 389 if schema.Title != "" { 390 t.Error("title should not be extracted") 391 } 392 }) 393 394 t.Run("true", func(t *testing.T) { 395 compiler := jsonschema.NewCompiler() 396 compiler.ExtractAnnotations = true 397 398 err := compiler.AddResource("test.json", strings.NewReader(`{ 399 "title": "this is title" 400 }`)) 401 if err != nil { 402 t.Fatalf("addResource failed. reason: %v\n", err) 403 } 404 405 schema, err := compiler.Compile("test.json") 406 if err != nil { 407 t.Fatalf("schema compilation failed. reason: %v\n", err) 408 } 409 410 if schema.Title != "this is title" { 411 t.Errorf("title: got %q, want %q", schema.Title, "this is title") 412 } 413 }) 414} 415 416func toFileURL(path string) string { 417 path, err := filepath.Abs(path) 418 if err != nil { 419 panic(err) 420 } 421 path = filepath.ToSlash(path) 422 if runtime.GOOS == "windows" { 423 path = "/" + path 424 } 425 u, err := url.Parse("file://" + path) 426 if err != nil { 427 panic(err) 428 } 429 return u.String() 430} 431 432// TestPanic tests https://github.com/santhosh-tekuri/jsonschema/issues/18 433func TestPanic(t *testing.T) { 434 schema_d := ` 435 { 436 "type": "object", 437 "properties": { 438 "myid": { "type": "integer" }, 439 "otype": { "$ref": "defs.json#someid" } 440 } 441 } 442 ` 443 defs_d := ` 444 { 445 "definitions": { 446 "stt": { 447 "$schema": "http://json-schema.org/draft-07/schema#", 448 "$id": "#someid", 449 "type": "object", 450 "enum": [ { "name": "stainless" }, { "name": "zinc" } ] 451 } 452 } 453 } 454 ` 455 c := jsonschema.NewCompiler() 456 c.Draft = jsonschema.Draft7 457 c.AddResource("schema.json", strings.NewReader(schema_d)) 458 c.AddResource("defs.json", strings.NewReader(defs_d)) 459 460 if _, err := c.Compile("schema.json"); err != nil { 461 t.Error("no error expected") 462 return 463 } 464} 465