github.com/rajeev159/opa@v0.45.0/loader/loader_test.go (about) 1 // Copyright 2017 The OPA Authors. All rights reserved. 2 // Use of this source code is governed by an Apache2 3 // license that can be found in the LICENSE file. 4 5 package loader 6 7 import ( 8 "bytes" 9 "io" 10 "os" 11 "path" 12 "path/filepath" 13 "reflect" 14 "sort" 15 "strings" 16 "testing" 17 18 "github.com/open-policy-agent/opa/ast" 19 "github.com/open-policy-agent/opa/bundle" 20 "github.com/open-policy-agent/opa/util" 21 "github.com/open-policy-agent/opa/util/test" 22 ) 23 24 func TestLoadJSON(t *testing.T) { 25 26 files := map[string]string{ 27 "/foo.json": `{"a": [1,2,3]}`, 28 } 29 30 test.WithTempFS(files, func(rootDir string) { 31 32 loaded, err := NewFileLoader().All([]string{filepath.Join(rootDir, "foo.json")}) 33 34 if err != nil { 35 t.Fatalf("Unexpected error: %v", err) 36 } 37 38 expected := parseJSON(files["/foo.json"]) 39 40 if !reflect.DeepEqual(loaded.Documents, expected) { 41 t.Fatalf("Expected %v but got: %v", expected, loaded.Documents) 42 } 43 }) 44 } 45 46 func TestLoadRego(t *testing.T) { 47 48 files := map[string]string{ 49 "/foo.rego": `package ex 50 51 p = true { true }`} 52 53 test.WithTempFS(files, func(rootDir string) { 54 moduleFile := filepath.Join(rootDir, "foo.rego") 55 loaded, err := NewFileLoader().All([]string{moduleFile}) 56 if err != nil { 57 t.Fatalf("Unexpected error: %v", err) 58 } 59 expected := ast.MustParseModule(files["/foo.rego"]) 60 if !expected.Equal(loaded.Modules[CleanPath(moduleFile)].Parsed) { 61 t.Fatalf("Expected:\n%v\n\nGot:\n%v", expected, loaded.Modules[moduleFile]) 62 } 63 }) 64 } 65 66 func TestLoadYAML(t *testing.T) { 67 68 files := map[string]string{ 69 "/foo.yml": ` 70 a: 71 - 1 72 - b 73 - "c" 74 - null 75 - true 76 - false 77 `, 78 } 79 80 test.WithTempFS(files, func(rootDir string) { 81 yamlFile := filepath.Join(rootDir, "foo.yml") 82 loaded, err := NewFileLoader().All([]string{yamlFile}) 83 if err != nil { 84 t.Fatalf("Unexpected error: %v", err) 85 } 86 expected := parseJSON(` 87 {"a": [1, "b", "c", null, true, false]}`) 88 if !reflect.DeepEqual(loaded.Documents, expected) { 89 t.Fatalf("Expected %v but got: %v", expected, loaded.Documents) 90 } 91 }) 92 } 93 94 func TestLoadGuessYAML(t *testing.T) { 95 files := map[string]string{ 96 "/foo": ` 97 a: b 98 `, 99 } 100 test.WithTempFS(files, func(rootDir string) { 101 yamlFile := filepath.Join(rootDir, "foo") 102 loaded, err := NewFileLoader().All([]string{yamlFile}) 103 if err != nil { 104 t.Fatalf("Unexpected error: %v", err) 105 } 106 expected := parseJSON(`{"a": "b"}`) 107 if !reflect.DeepEqual(loaded.Documents, expected) { 108 t.Fatalf("Expected %v but got: %v", expected, loaded.Documents) 109 } 110 }) 111 } 112 113 func TestLoadDirRecursive(t *testing.T) { 114 files := map[string]string{ 115 "/a/data1.json": `{"a": [1,2,3]}`, 116 "/a/e.rego": `package q`, 117 "/b/data2.yaml": `{"aaa": {"bbb": 1}}`, 118 "/b/data3.yaml": `{"aaa": {"ccc": 2}}`, 119 "/b/d/x.json": "null", 120 "/b/d/e.rego": `package p`, 121 "/b/d/ignore": `deadbeef`, 122 "/foo": `{"zzz": "b"}`, 123 } 124 125 test.WithTempFS(files, func(rootDir string) { 126 loaded, err := NewFileLoader().All(mustListPaths(rootDir, false)[1:]) 127 if err != nil { 128 t.Fatalf("Unexpected error: %v", err) 129 } 130 expectedDocuments := parseJSON(` 131 { 132 "zzz": "b", 133 "a": [1,2,3], 134 "aaa": { 135 "bbb": 1, 136 "ccc": 2 137 }, 138 "d": null 139 } 140 `) 141 if !reflect.DeepEqual(loaded.Documents, expectedDocuments) { 142 t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedDocuments, loaded.Documents) 143 } 144 mod1 := ast.MustParseModule(files["/a/e.rego"]) 145 mod2 := ast.MustParseModule(files["/b/d/e.rego"]) 146 expectedMod1 := loaded.Modules[CleanPath(filepath.Join(rootDir, "/a/e.rego"))].Parsed 147 expectedMod2 := loaded.Modules[CleanPath(filepath.Join(rootDir, "/b/d/e.rego"))].Parsed 148 if !mod1.Equal(expectedMod1) { 149 t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedMod1, mod1) 150 } 151 if !mod2.Equal(expectedMod2) { 152 t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedMod2, mod2) 153 } 154 }) 155 } 156 157 func TestFilteredPaths(t *testing.T) { 158 files := map[string]string{ 159 "/a/data1.json": `{"a": [1,2,3]}`, 160 "/a/e.rego": `package q`, 161 "/b/data2.yaml": `{"aaa": {"bbb": 1}}`, 162 "/b/data3.yaml": `{"aaa": {"ccc": 2}}`, 163 "/b/d/x.json": "null", 164 "/b/d/e.rego": `package p`, 165 "/b/d/ignore": `deadbeef`, 166 "/foo": `{"zzz": "b"}`, 167 } 168 169 test.WithTempFS(files, func(rootDir string) { 170 171 paths := []string{ 172 filepath.Join(rootDir, "a"), 173 filepath.Join(rootDir, "b"), 174 filepath.Join(rootDir, "foo"), 175 } 176 177 result, err := FilteredPaths(paths, nil) 178 if err != nil { 179 t.Fatalf("Unexpected error: %s", err) 180 } 181 182 if len(result) != len(files) { 183 t.Fatalf("Expected %v files across directories but got %v", len(files), len(result)) 184 } 185 }) 186 } 187 188 func TestGetBundleDirectoryLoader(t *testing.T) { 189 files := map[string]string{ 190 "bundle.tar.gz": "", 191 } 192 193 mod := "package b.c\np=1" 194 195 test.WithTempFS(files, func(rootDir string) { 196 197 bundleFile := filepath.Join(rootDir, "bundle.tar.gz") 198 199 f, err := os.Create(bundleFile) 200 if err != nil { 201 t.Fatalf("Unexpected error: %s", err) 202 } 203 204 b := &bundle.Bundle{ 205 Manifest: bundle.Manifest{ 206 Roots: &[]string{"a", "b/c"}, 207 Revision: "123", 208 }, 209 Data: map[string]interface{}{ 210 "a": map[string]interface{}{ 211 "b": []int{4, 5, 6}, 212 }, 213 }, 214 Modules: []bundle.ModuleFile{ 215 { 216 URL: path.Join(bundleFile, "policy.rego"), 217 Path: "/policy.rego", 218 Raw: []byte(mod), 219 Parsed: ast.MustParseModule(mod), 220 }, 221 }, 222 } 223 224 err = bundle.Write(f, *b) 225 if err != nil { 226 t.Fatalf("Unexpected error: %s", err) 227 } 228 err = f.Close() 229 if err != nil { 230 t.Fatalf("Unexpected error: %s", err) 231 } 232 233 bl, isDir, err := GetBundleDirectoryLoader(bundleFile) 234 if err != nil { 235 t.Fatalf("Unexpected error: %s", err) 236 } 237 238 if isDir { 239 t.Fatal("Expected bundle to be gzipped tarball but got directory") 240 } 241 242 // check files 243 var result []string 244 for { 245 f, err := bl.NextFile() 246 if err == io.EOF { 247 break 248 } 249 250 if err != nil { 251 t.Fatalf("Unexpected error: %s", err) 252 } 253 254 result = append(result, f.Path()) 255 } 256 257 if len(result) != 3 { 258 t.Fatalf("Expected 3 files in the bundle but got %v", len(result)) 259 } 260 }) 261 } 262 263 func TestLoadBundle(t *testing.T) { 264 265 test.WithTempFS(nil, func(rootDir string) { 266 267 f, err := os.Create(filepath.Join(rootDir, "bundle.tar.gz")) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 var testBundle = bundle.Bundle{ 273 Modules: []bundle.ModuleFile{ 274 { 275 Path: "x.rego", 276 Raw: []byte(` 277 package baz 278 279 p = 1`), 280 }, 281 }, 282 Data: map[string]interface{}{ 283 "foo": "bar", 284 }, 285 Manifest: bundle.Manifest{ 286 Revision: "", 287 Roots: &[]string{""}, 288 }, 289 } 290 291 if err := bundle.Write(f, testBundle); err != nil { 292 t.Fatal(err) 293 } 294 295 paths := mustListPaths(rootDir, false)[1:] 296 loaded, err := NewFileLoader().All(paths) 297 if err != nil { 298 t.Fatal(err) 299 } 300 301 actualData := testBundle.Data 302 actualData["system"] = map[string]interface{}{"bundle": map[string]interface{}{"manifest": map[string]interface{}{"revision": "", "roots": []interface{}{""}}}} 303 304 if !reflect.DeepEqual(actualData, loaded.Documents) { 305 t.Fatalf("Expected %v but got: %v", actualData, loaded.Documents) 306 } 307 308 if !bytes.Equal(testBundle.Modules[0].Raw, loaded.Modules["/x.rego"].Raw) { 309 t.Fatalf("Expected %v but got: %v", string(testBundle.Modules[0].Raw), loaded.Modules["/x.rego"].Raw) 310 } 311 }) 312 313 } 314 315 func TestLoadBundleSubDir(t *testing.T) { 316 317 test.WithTempFS(nil, func(rootDir string) { 318 319 if err := os.MkdirAll(filepath.Join(rootDir, "a", "b"), 0777); err != nil { 320 t.Fatal(err) 321 } 322 323 f, err := os.Create(filepath.Join(rootDir, "a", "b", "bundle.tar.gz")) 324 if err != nil { 325 t.Fatal(err) 326 } 327 328 var testBundle = bundle.Bundle{ 329 Modules: []bundle.ModuleFile{ 330 { 331 Path: "x.rego", 332 Raw: []byte(` 333 package baz 334 335 p = 1`), 336 }, 337 }, 338 Data: map[string]interface{}{ 339 "foo": "bar", 340 }, 341 Manifest: bundle.Manifest{ 342 Revision: "", 343 Roots: &[]string{""}, 344 }, 345 } 346 347 if err := bundle.Write(f, testBundle); err != nil { 348 t.Fatal(err) 349 } 350 351 paths := mustListPaths(rootDir, false)[1:] 352 loaded, err := NewFileLoader().All(paths) 353 if err != nil { 354 t.Fatal(err) 355 } 356 357 actualData := testBundle.Data 358 actualData["system"] = map[string]interface{}{"bundle": map[string]interface{}{"manifest": map[string]interface{}{"revision": "", "roots": []interface{}{""}}}} 359 360 if !reflect.DeepEqual(map[string]interface{}{"b": testBundle.Data}, loaded.Documents) { 361 t.Fatalf("Expected %v but got: %v", testBundle.Data, loaded.Documents) 362 } 363 364 if !bytes.Equal(testBundle.Modules[0].Raw, loaded.Modules["/x.rego"].Raw) { 365 t.Fatalf("Expected %v but got: %v", string(testBundle.Modules[0].Raw), loaded.Modules["/x.rego"].Raw) 366 } 367 }) 368 } 369 370 func TestAsBundleWithDir(t *testing.T) { 371 files := map[string]string{ 372 "/foo/data.json": "[1,2,3]", 373 "/bar/bar.yaml": "abc", // Should be ignored 374 "/baz/qux/qux.json": "null", // Should be ignored 375 "/foo/policy.rego": "package foo\np = 1", 376 "base.rego": "package bar\nx = 1", 377 "/.manifest": `{"roots": ["foo", "bar", "baz"]}`, 378 } 379 380 test.WithTempFS(files, func(rootDir string) { 381 b, err := NewFileLoader().AsBundle(rootDir) 382 if err != nil { 383 t.Fatalf("Unexpected error: %v", err) 384 } 385 386 if b == nil { 387 t.Fatalf("Expected bundle to be non-nil") 388 } 389 390 if len(b.Modules) != 2 { 391 t.Fatalf("expected 2 modules, got %d", len(b.Modules)) 392 } 393 394 expectedModulePaths := map[string]struct{}{ 395 filepath.Join(rootDir, "foo/policy.rego"): {}, 396 filepath.Join(rootDir, "base.rego"): {}, 397 } 398 for _, mf := range b.Modules { 399 if _, found := expectedModulePaths[mf.Path]; !found { 400 t.Errorf("Unexpected module file with path %s in bundle modules", mf.Path) 401 } 402 } 403 404 expectedData := util.MustUnmarshalJSON([]byte(`{"foo": [1,2,3]}`)) 405 if !reflect.DeepEqual(b.Data, expectedData) { 406 t.Fatalf("expected data %+v, got %+v", expectedData, b.Data) 407 } 408 409 expectedRoots := []string{"foo", "bar", "baz"} 410 if !reflect.DeepEqual(*b.Manifest.Roots, expectedRoots) { 411 t.Fatalf("expected roots %s, got: %s", expectedRoots, *b.Manifest.Roots) 412 } 413 }) 414 } 415 416 func TestAsBundleWithFileURLDir(t *testing.T) { 417 files := map[string]string{ 418 "/foo/data.json": "[1,2,3]", 419 "/foo/policy.rego": "package foo.bar\np = 1", 420 "/.manifest": `{"roots": ["foo"]}`, 421 } 422 423 test.WithTempFS(files, func(rootDir string) { 424 b, err := NewFileLoader().AsBundle("file://" + rootDir) 425 if err != nil { 426 t.Fatalf("Unexpected error: %v", err) 427 } 428 429 if b == nil { 430 t.Fatalf("Expected bundle to be non-nil") 431 } 432 433 if len(b.Modules) != 1 { 434 t.Fatalf("expected 1 modules, got %d", len(b.Modules)) 435 } 436 expectedModulePaths := map[string]struct{}{ 437 filepath.Join(rootDir, "/foo/policy.rego"): {}, 438 } 439 for _, mf := range b.Modules { 440 if _, found := expectedModulePaths[mf.Path]; !found { 441 t.Errorf("Unexpected module file with path %s in bundle modules", mf.Path) 442 } 443 } 444 445 expectedData := util.MustUnmarshalJSON([]byte(`{"foo": [1,2,3]}`)) 446 if !reflect.DeepEqual(b.Data, expectedData) { 447 t.Fatalf("expected data %+v, got %+v", expectedData, b.Data) 448 } 449 450 expectedRoots := []string{"foo"} 451 if !reflect.DeepEqual(*b.Manifest.Roots, expectedRoots) { 452 t.Fatalf("expected roots %s, got: %s", expectedRoots, *b.Manifest.Roots) 453 } 454 }) 455 } 456 457 func TestAsBundleWithFile(t *testing.T) { 458 files := map[string]string{ 459 "bundle.tar.gz": "", 460 } 461 462 mod := "package b.c\np=1" 463 464 test.WithTempFS(files, func(rootDir string) { 465 466 bundleFile := filepath.Join(rootDir, "bundle.tar.gz") 467 468 f, err := os.Create(bundleFile) 469 if err != nil { 470 t.Fatalf("Unexpected error: %s", err) 471 } 472 473 b := &bundle.Bundle{ 474 Manifest: bundle.Manifest{ 475 Roots: &[]string{"a", "b/c"}, 476 Revision: "123", 477 }, 478 Data: map[string]interface{}{ 479 "a": map[string]interface{}{ 480 "b": []int{4, 5, 6}, 481 }, 482 }, 483 Modules: []bundle.ModuleFile{ 484 { 485 URL: path.Join(bundleFile, "policy.rego"), 486 Path: "/policy.rego", 487 Raw: []byte(mod), 488 Parsed: ast.MustParseModule(mod), 489 }, 490 }, 491 } 492 493 err = bundle.Write(f, *b) 494 if err != nil { 495 t.Fatalf("Unexpected error: %s", err) 496 } 497 err = f.Close() 498 if err != nil { 499 t.Fatalf("Unexpected error: %s", err) 500 } 501 502 actual, err := NewFileLoader().AsBundle(bundleFile) 503 if err != nil { 504 t.Fatalf("Unexpected error: %s", err) 505 } 506 507 var tmp interface{} = b 508 err = util.RoundTrip(&tmp) 509 if err != nil { 510 t.Fatalf("Unexpected error: %s", err) 511 } 512 513 if !actual.Equal(*b) { 514 t.Fatalf("Loaded bundle doesn't match expected.\n\nExpected: %+v\n\nActual: %+v\n\n", b, actual) 515 } 516 }) 517 } 518 519 func TestLoadRooted(t *testing.T) { 520 files := map[string]string{ 521 "/foo.json": "[1,2,3]", 522 "/bar/bar.yaml": "abc", 523 "/baz/qux/qux.json": "null", 524 } 525 526 test.WithTempFS(files, func(rootDir string) { 527 paths := mustListPaths(rootDir, false)[1:] 528 sort.Strings(paths) 529 paths[0] = "one.two:" + paths[0] 530 paths[1] = "three:" + paths[1] 531 paths[2] = "four:" + paths[2] 532 loaded, err := NewFileLoader().All(paths) 533 if err != nil { 534 t.Fatalf("Unexpected error: %v", err) 535 } 536 expected := parseJSON(` 537 {"four": [1,2,3], "one": {"two": "abc"}, "three": {"qux": null}} 538 `) 539 if !reflect.DeepEqual(loaded.Documents, expected) { 540 t.Fatalf("Expected %v but got: %v", expected, loaded.Documents) 541 } 542 }) 543 } 544 545 func TestGlobExcludeName(t *testing.T) { 546 547 files := map[string]string{ 548 "/.data.json": `{"x":1}`, 549 "/.y/data.json": `{"y": 2}`, 550 "/.y/z/data.json": `3`, 551 "/z/.hidden/data.json": `"donotinclude"`, 552 "/z/a/.hidden.json": `"donotinclude"`, 553 } 554 555 test.WithTempFS(files, func(rootDir string) { 556 paths := mustListPaths(rootDir, false)[1:] 557 sort.Strings(paths) 558 result, err := NewFileLoader().Filtered(paths, GlobExcludeName(".*", 1)) 559 if err != nil { 560 t.Fatal(err) 561 } 562 exp := parseJSON(`{ 563 "x": 1, 564 "y": 2, 565 "z": 3 566 }`) 567 if !reflect.DeepEqual(exp, result.Documents) { 568 t.Fatalf("Expected %v but got %v", exp, result.Documents) 569 } 570 }) 571 } 572 573 func TestLoadErrors(t *testing.T) { 574 files := map[string]string{ 575 "/x1.json": `{"x": [1,2,3]}`, 576 "/x2.json": `{"x": {"y": 1}}`, 577 "/empty.rego": ` `, 578 "/dir/a.json": ``, 579 "/dir/b.yaml": ` 580 foo: 581 - bar: 582 `, 583 "/bad_doc.json": "[1,2,3]", 584 } 585 test.WithTempFS(files, func(rootDir string) { 586 paths := mustListPaths(rootDir, false)[1:] 587 sort.Strings(paths) 588 _, err := NewFileLoader().All(paths) 589 if err == nil { 590 t.Fatalf("Expected failure") 591 } 592 593 expected := []string{ 594 "bad_doc.json: bad document type", 595 "a.json: EOF", 596 "b.yaml: error converting YAML to JSON", 597 "empty.rego:0: rego_parse_error: empty module", 598 "x2.json: merge error", 599 "rego_parse_error: empty module", 600 } 601 602 for _, s := range expected { 603 if !strings.Contains(err.Error(), s) { 604 t.Fatalf("Expected error to contain %v but got:\n%v", s, err) 605 } 606 } 607 }) 608 } 609 610 func TestLoadFileURL(t *testing.T) { 611 files := map[string]string{ 612 "/a/a/1.json": `1`, // this will load as a directory (e.g., file://a/a) 613 "b.json": `{"b": 2}`, // this will load as a normal file 614 "c.json": `3`, // this will loas as rooted file 615 } 616 test.WithTempFS(files, func(rootDir string) { 617 618 paths := mustListPaths(rootDir, false)[1:] 619 sort.Strings(paths) 620 621 for i := range paths { 622 paths[i] = "file://" + paths[i] 623 } 624 625 paths[2] = "c:" + paths[2] 626 627 result, err := NewFileLoader().All(paths) 628 if err != nil { 629 t.Fatal(err) 630 } 631 632 exp := parseJSON(`{"a": 1, "b": 2, "c": 3}`) 633 if !reflect.DeepEqual(exp, result.Documents) { 634 t.Fatalf("Expected %v but got %v", exp, result.Documents) 635 } 636 }) 637 } 638 639 func TestUnsupportedURLScheme(t *testing.T) { 640 _, err := NewFileLoader().All([]string{"http://openpolicyagent.org"}) 641 if err == nil || !strings.Contains(err.Error(), "unsupported URL scheme: http://openpolicyagent.org") { 642 t.Fatal(err) 643 } 644 } 645 646 func TestSplitPrefix(t *testing.T) { 647 648 tests := []struct { 649 input string 650 wantParts []string 651 wantPath string 652 }{ 653 { 654 input: "foo/bar", 655 wantPath: "foo/bar", 656 }, 657 { 658 input: "foo:/bar", 659 wantParts: []string{"foo"}, 660 wantPath: "/bar", 661 }, 662 { 663 input: "foo.bar:/baz", 664 wantParts: []string{"foo", "bar"}, 665 wantPath: "/baz", 666 }, 667 { 668 input: "file:///a/b/c", 669 wantPath: "file:///a/b/c", 670 }, 671 { 672 input: "x.y:file:///a/b/c", 673 wantParts: []string{"x", "y"}, 674 wantPath: "file:///a/b/c", 675 }, 676 { 677 input: "file:///c:/a/b/c", 678 wantPath: "file:///c:/a/b/c", 679 }, 680 { 681 input: "x.y:file:///c:/a/b/c", 682 wantParts: []string{"x", "y"}, 683 wantPath: "file:///c:/a/b/c", 684 }, 685 } 686 687 for _, tc := range tests { 688 t.Run(tc.input, func(t *testing.T) { 689 parts, gotPath := SplitPrefix(tc.input) 690 if !reflect.DeepEqual(parts, tc.wantParts) { 691 t.Errorf("wanted parts %v but got %v", tc.wantParts, parts) 692 } 693 if gotPath != tc.wantPath { 694 t.Errorf("wanted path %q but got %q", gotPath, tc.wantPath) 695 } 696 }) 697 } 698 } 699 700 func TestLoadRegos(t *testing.T) { 701 files := map[string]string{ 702 "/x.rego": ` 703 package x 704 p = true 705 `, 706 "/y.reg": ` 707 package x 708 p = true { # syntax error missing } 709 `, 710 "/subdir/z.rego": ` 711 package x 712 q = true 713 `, 714 } 715 716 test.WithTempFS(files, func(rootDir string) { 717 paths := mustListPaths(rootDir, false)[1:] 718 sort.Strings(paths) 719 result, err := AllRegos(paths) 720 if err != nil { 721 t.Fatal(err) 722 } 723 if len(result.Modules) != 2 { 724 t.Fatalf("Expected exactly two modules but found: %v", result) 725 } 726 }) 727 } 728 729 func parseJSON(x string) interface{} { 730 return util.MustUnmarshalJSON([]byte(x)) 731 } 732 733 func mustListPaths(path string, recurse bool) (paths []string) { 734 paths, err := Paths(path, recurse) 735 if err != nil { 736 panic(err) 737 } 738 return paths 739 } 740 741 func TestDirs(t *testing.T) { 742 paths := []string{ 743 "/foo/bar.json", "/foo/bar/baz.json", "/foo.json", 744 } 745 746 e := []string{"/", "/foo", "/foo/bar"} 747 sorted := Dirs(paths) 748 if !reflect.DeepEqual(sorted, e) { 749 t.Errorf("got: %q wanted: %q", sorted, e) 750 } 751 } 752 753 func TestSchemas(t *testing.T) { 754 755 tests := []struct { 756 note string 757 path string 758 files map[string]string 759 exp map[string]string 760 expErr string 761 }{ 762 { 763 note: "empty path", 764 path: "", // no error, no files 765 }, 766 { 767 note: "bad file path", 768 path: "foo/bar/baz.json", 769 expErr: "stat foo/bar/baz.json: no such file or directory", 770 }, 771 { 772 note: "bad file content", 773 path: "foo/bar/baz.json", 774 files: map[string]string{ 775 "foo/bar/baz.json": `{ 776 "foo 777 }`, 778 }, 779 expErr: "found unexpected end of stream", 780 }, 781 { 782 note: "one global file", 783 path: "foo/bar/baz.json", 784 files: map[string]string{ 785 "foo/bar/baz.json": `{"type": "string"}`, 786 }, 787 exp: map[string]string{ 788 "schema": `{"type": "string"}`, 789 }, 790 }, 791 { 792 note: "directory loading", 793 path: "foo/", 794 files: map[string]string{ 795 "foo/qux.json": `{"type": "number"}`, 796 "foo/bar/baz.json": `{"type": "string"}`, 797 }, 798 exp: map[string]string{ 799 "schema.qux": `{"type": "number"}`, 800 "schema.bar.baz": `{"type": "string"}`, 801 }, 802 }, 803 } 804 805 for _, tc := range tests { 806 t.Run(tc.note, func(t *testing.T) { 807 test.WithTempFS(tc.files, func(rootDir string) { 808 err := os.Chdir(rootDir) 809 if err != nil { 810 t.Fatal(err) 811 } 812 ss, err := Schemas(tc.path) 813 if tc.expErr != "" { 814 if err == nil { 815 t.Fatal("expected error") 816 } 817 if !strings.Contains(err.Error(), tc.expErr) { 818 t.Fatalf("expected error to contain %q but got %q", tc.expErr, err) 819 } 820 } else { 821 if err != nil { 822 t.Fatal("unexpected error:", err) 823 } 824 for k, v := range tc.exp { 825 var key ast.Ref 826 if k == "schema" { 827 key = ast.SchemaRootRef.Copy() 828 } else { 829 key = ast.MustParseRef(k) 830 } 831 var schema interface{} 832 err = util.Unmarshal([]byte(v), &schema) 833 if err != nil { 834 t.Fatalf("Unexpected error: %v", err) 835 } 836 result := ss.Get(key) 837 if result == nil { 838 t.Fatalf("expected schema with key %v", key) 839 } 840 if !reflect.DeepEqual(schema, result) { 841 t.Fatalf("expected schema %v but got %v", schema, result) 842 } 843 } 844 } 845 }) 846 }) 847 } 848 }