Files
releases/loader/loader_test.go
T
Torin Sandall 3be55ed660 loader: Update to accept file:// URLs.
The file loader splits paths on the first colon character and uses the
left-hand side for the prefix to root the document at under data. On
windows this is problematic because of drive lettesr (e.g., C:\X\Y\Z
is interpreted as load file at \X\Y\Z under data.C.

This change updates the loader to accept file:// URLs. This way
callers can unambiguously specify filenames that contain colon
characters. For now this will mainly be used by VS Code and other
programmatic callers. In future we can support other schemes (e.g., http).

Fixes #1505

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
2019-08-22 10:44:35 -04:00

482 lines
11 KiB
Go

// Copyright 2017 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package loader
import (
"bytes"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/bundle"
"github.com/open-policy-agent/opa/util"
"github.com/open-policy-agent/opa/util/test"
)
func TestLoadJSON(t *testing.T) {
files := map[string]string{
"/foo.json": `{"a": [1,2,3]}`,
}
test.WithTempFS(files, func(rootDir string) {
loaded, err := All([]string{filepath.Join(rootDir, "foo.json")})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expected := parseJSON(files["/foo.json"])
if !reflect.DeepEqual(loaded.Documents, expected) {
t.Fatalf("Expected %v but got: %v", expected, loaded.Documents)
}
})
}
func TestLoadRego(t *testing.T) {
files := map[string]string{
"/foo.rego": `package ex
p = true { true }`}
test.WithTempFS(files, func(rootDir string) {
moduleFile := filepath.Join(rootDir, "foo.rego")
loaded, err := All([]string{moduleFile})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expected := ast.MustParseModule(files["/foo.rego"])
if !expected.Equal(loaded.Modules[CleanPath(moduleFile)].Parsed) {
t.Fatalf("Expected:\n%v\n\nGot:\n%v", expected, loaded.Modules[moduleFile])
}
})
}
func TestLoadYAML(t *testing.T) {
files := map[string]string{
"/foo.yml": `
a:
- 1
- b
- "c"
- null
- true
- false
`,
}
test.WithTempFS(files, func(rootDir string) {
yamlFile := filepath.Join(rootDir, "foo.yml")
loaded, err := All([]string{yamlFile})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expected := parseJSON(`
{"a": [1, "b", "c", null, true, false]}`)
if !reflect.DeepEqual(loaded.Documents, expected) {
t.Fatalf("Expected %v but got: %v", expected, loaded.Documents)
}
})
}
func TestLoadGuessYAML(t *testing.T) {
files := map[string]string{
"/foo": `
a: b
`,
}
test.WithTempFS(files, func(rootDir string) {
yamlFile := filepath.Join(rootDir, "foo")
loaded, err := All([]string{yamlFile})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expected := parseJSON(`{"a": "b"}`)
if !reflect.DeepEqual(loaded.Documents, expected) {
t.Fatalf("Expected %v but got: %v", expected, loaded.Documents)
}
})
}
func TestLoadDirRecursive(t *testing.T) {
files := map[string]string{
"/a/data1.json": `{"a": [1,2,3]}`,
"/a/e.rego": `package q`,
"/b/data2.yaml": `{"aaa": {"bbb": 1}}`,
"/b/data3.yaml": `{"aaa": {"ccc": 2}}`,
"/b/d/x.json": "null",
"/b/d/e.rego": `package p`,
"/b/d/ignore": `deadbeef`,
"/foo": `{"zzz": "b"}`,
}
test.WithTempFS(files, func(rootDir string) {
loaded, err := All(mustListPaths(rootDir, false)[1:])
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expectedDocuments := parseJSON(`
{
"zzz": "b",
"a": [1,2,3],
"aaa": {
"bbb": 1,
"ccc": 2
},
"d": null
}
`)
if !reflect.DeepEqual(loaded.Documents, expectedDocuments) {
t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedDocuments, loaded.Documents)
}
mod1 := ast.MustParseModule(files["/a/e.rego"])
mod2 := ast.MustParseModule(files["/b/d/e.rego"])
expectedMod1 := loaded.Modules[CleanPath(filepath.Join(rootDir, "/a/e.rego"))].Parsed
expectedMod2 := loaded.Modules[CleanPath(filepath.Join(rootDir, "/b/d/e.rego"))].Parsed
if !mod1.Equal(expectedMod1) {
t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedMod1, mod1)
}
if !mod2.Equal(expectedMod2) {
t.Fatalf("Expected:\n%v\n\nGot:\n%v", expectedMod2, mod2)
}
})
}
func TestLoadBundle(t *testing.T) {
test.WithTempFS(nil, func(rootDir string) {
f, err := os.Create(filepath.Join(rootDir, "bundle.tar.gz"))
if err != nil {
t.Fatal(err)
}
var testBundle = bundle.Bundle{
Modules: []bundle.ModuleFile{
{
Path: "x.rego",
Raw: []byte(`
package baz
p = 1`),
},
},
Data: map[string]interface{}{
"foo": "bar",
},
Manifest: bundle.Manifest{
Revision: "",
Roots: &[]string{""},
},
}
if err := bundle.Write(f, testBundle); err != nil {
t.Fatal(err)
}
paths := mustListPaths(rootDir, false)[1:]
loaded, err := All(paths)
if err != nil {
t.Fatal(err)
}
actualData := testBundle.Data
actualData["system"] = map[string]interface{}{"bundle": map[string]interface{}{"manifest": map[string]interface{}{"revision": "", "roots": []interface{}{""}}}}
if !reflect.DeepEqual(actualData, loaded.Documents) {
t.Fatalf("Expected %v but got: %v", actualData, loaded.Documents)
}
if !bytes.Equal(testBundle.Modules[0].Raw, loaded.Modules["/x.rego"].Raw) {
t.Fatalf("Expected %v but got: %v", string(testBundle.Modules[0].Raw), loaded.Modules["x.rego"].Raw)
}
})
}
func TestLoadBundleSubDir(t *testing.T) {
test.WithTempFS(nil, func(rootDir string) {
if err := os.MkdirAll(filepath.Join(rootDir, "a", "b"), 0777); err != nil {
t.Fatal(err)
}
f, err := os.Create(filepath.Join(rootDir, "a", "b", "bundle.tar.gz"))
if err != nil {
t.Fatal(err)
}
var testBundle = bundle.Bundle{
Modules: []bundle.ModuleFile{
{
Path: "x.rego",
Raw: []byte(`
package baz
p = 1`),
},
},
Data: map[string]interface{}{
"foo": "bar",
},
Manifest: bundle.Manifest{
Revision: "",
Roots: &[]string{""},
},
}
if err := bundle.Write(f, testBundle); err != nil {
t.Fatal(err)
}
paths := mustListPaths(rootDir, false)[1:]
loaded, err := All(paths)
if err != nil {
t.Fatal(err)
}
actualData := testBundle.Data
actualData["system"] = map[string]interface{}{"bundle": map[string]interface{}{"manifest": map[string]interface{}{"revision": "", "roots": []interface{}{""}}}}
if !reflect.DeepEqual(map[string]interface{}{"b": testBundle.Data}, loaded.Documents) {
t.Fatalf("Expected %v but got: %v", testBundle.Data, loaded.Documents)
}
if !bytes.Equal(testBundle.Modules[0].Raw, loaded.Modules["/x.rego"].Raw) {
t.Fatalf("Expected %v but got: %v", string(testBundle.Modules[0].Raw), loaded.Modules["x.rego"].Raw)
}
})
}
func TestLoadRooted(t *testing.T) {
files := map[string]string{
"/foo.json": "[1,2,3]",
"/bar/bar.yaml": "abc",
"/baz/qux/qux.json": "null",
}
test.WithTempFS(files, func(rootDir string) {
paths := mustListPaths(rootDir, false)[1:]
sort.Strings(paths)
paths[0] = "one.two:" + paths[0]
paths[1] = "three:" + paths[1]
paths[2] = "four:" + paths[2]
loaded, err := All(paths)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expected := parseJSON(`
{"four": [1,2,3], "one": {"two": "abc"}, "three": {"qux": null}}
`)
if !reflect.DeepEqual(loaded.Documents, expected) {
t.Fatalf("Expected %v but got: %v", expected, loaded.Documents)
}
})
}
func TestGlobExcludeName(t *testing.T) {
files := map[string]string{
"/.data.json": `{"x":1}`,
"/.y/data.json": `{"y": 2}`,
"/.y/z/data.json": `3`,
"/z/.hidden/data.json": `"donotinclude"`,
"/z/a/.hidden.json": `"donotinclude"`,
}
test.WithTempFS(files, func(rootDir string) {
paths := mustListPaths(rootDir, false)[1:]
sort.Strings(paths)
result, err := Filtered(paths, GlobExcludeName(".*", 1))
if err != nil {
t.Fatal(err)
}
exp := parseJSON(`{
"x": 1,
"y": 2,
"z": 3
}`)
if !reflect.DeepEqual(exp, result.Documents) {
t.Fatalf("Expected %v but got %v", exp, result.Documents)
}
})
}
func TestLoadErrors(t *testing.T) {
files := map[string]string{
"/x1.json": `{"x": [1,2,3]}`,
"/x2.json": `{"x": {"y": 1}}`,
"/empty.rego": ` `,
"/dir/a.json": ``,
"/dir/b.yaml": `
foo:
- bar:
`,
"/bad_doc.json": "[1,2,3]",
}
test.WithTempFS(files, func(rootDir string) {
paths := mustListPaths(rootDir, false)[1:]
sort.Strings(paths)
_, err := All(paths)
if err == nil {
t.Fatalf("Expected failure")
}
expected := []string{
"bad_doc.json: bad document type",
"a.json: EOF",
"b.yaml: error converting YAML to JSON",
"empty.rego: empty policy",
"x2.json: merge error",
}
for _, s := range expected {
if !strings.Contains(err.Error(), s) {
t.Fatalf("Expected error to contain %v but got:\n%v", s, err)
}
}
})
}
func TestLoadFileURL(t *testing.T) {
files := map[string]string{
"/a/a/1.json": `1`, // this will load as a directory (e.g., file://a/a)
"b.json": `{"b": 2}`, // this will load as a normal file
"c.json": `3`, // this will loas as rooted file
}
test.WithTempFS(files, func(rootDir string) {
paths := mustListPaths(rootDir, false)[1:]
sort.Strings(paths)
for i := range paths {
paths[i] = "file://" + paths[i]
}
paths[2] = "c:" + paths[2]
result, err := All(paths)
if err != nil {
t.Fatal(err)
}
exp := parseJSON(`{"a": 1, "b": 2, "c": 3}`)
if !reflect.DeepEqual(exp, result.Documents) {
t.Fatalf("Expected %v but got %v", exp, result.Documents)
}
})
}
func TestUnsupportedURLScheme(t *testing.T) {
_, err := All([]string{"http://openpolicyagent.org"})
if err == nil || !strings.Contains(err.Error(), "unsupported URL scheme: http://openpolicyagent.org") {
t.Fatal(err)
}
}
func TestSplitPrefix(t *testing.T) {
tests := []struct {
input string
wantParts []string
wantPath string
}{
{
input: "foo/bar",
wantPath: "foo/bar",
},
{
input: "foo:/bar",
wantParts: []string{"foo"},
wantPath: "/bar",
},
{
input: "foo.bar:/baz",
wantParts: []string{"foo", "bar"},
wantPath: "/baz",
},
{
input: "file:///a/b/c",
wantPath: "file:///a/b/c",
},
{
input: "x.y:file:///a/b/c",
wantParts: []string{"x", "y"},
wantPath: "file:///a/b/c",
},
{
input: "file:///c:/a/b/c",
wantPath: "file:///c:/a/b/c",
},
{
input: "x.y:file:///c:/a/b/c",
wantParts: []string{"x", "y"},
wantPath: "file:///c:/a/b/c",
},
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
parts, path := SplitPrefix(tc.input)
if !reflect.DeepEqual(parts, tc.wantParts) {
t.Errorf("wanted parts %v but got %v", tc.wantParts, parts)
}
if path != tc.wantPath {
t.Errorf("wanted path %q but got %q", path, tc.wantPath)
}
})
}
}
func TestLoadRegos(t *testing.T) {
files := map[string]string{
"/x.rego": `
package x
p = true
`,
"/y.reg": `
package x
p = true { # syntax error missing }
`,
"/subdir/z.rego": `
package x
q = true
`,
}
test.WithTempFS(files, func(rootDir string) {
paths := mustListPaths(rootDir, false)[1:]
sort.Strings(paths)
result, err := AllRegos(paths)
if err != nil {
t.Fatal(err)
}
if len(result.Modules) != 2 {
t.Fatalf("Expected exactly two modules but found: %v", result)
}
})
}
func parseJSON(x string) interface{} {
return util.MustUnmarshalJSON([]byte(x))
}
func mustListPaths(path string, recurse bool) (paths []string) {
paths, err := Paths(path, recurse)
if err != nil {
panic(err)
}
return paths
}