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  }