Files
releases/bundle/bundle_test.go
T
Patrick East 3fda875eee bundle: Fix for overwriting data file keys
This corrects an issue where data.json (or yaml) files in a bundle
would overwrite data if the file was at the root of the bundle
directory and was loaded after other data files.

This also helps avoid any issues with merging data files that
could have overlapped without any problems.

Fixes: #1763
Signed-off-by: Patrick East <east.patrick@gmail.com>
2019-09-20 12:36:21 -07:00

377 lines
8.5 KiB
Go

// Copyright 2018 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 bundle
import (
"bytes"
"compress/gzip"
"encoding/json"
"strings"
"testing"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/internal/file/archive"
)
func TestRead(t *testing.T) {
files := [][2]string{
{"/a/b/c/data.json", "[1,2,3]"},
{"/a/b/d/data.json", "true"},
{"/a/b/y/data.yaml", `foo: 1`},
{"/example/example.rego", `package example`},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}}`},
}
buf := archive.MustWriteTarGz(files)
bundle, err := NewReader(buf).Read()
if err != nil {
t.Fatal(err)
}
module := `package example`
exp := Bundle{
Data: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
"c": []interface{}{json.Number("1"), json.Number("2"), json.Number("3")},
"d": true,
"y": map[string]interface{}{
"foo": json.Number("1"),
},
"z": true,
},
},
"x": map[string]interface{}{
"y": true,
},
},
Modules: []ModuleFile{
{
Path: "/example/example.rego",
Parsed: ast.MustParseModule(module),
Raw: []byte(module),
},
},
}
if !exp.Equal(bundle) {
t.Fatal("\nExp:", exp, "\n\nGot:", bundle)
}
}
func TestReadWithManifest(t *testing.T) {
files := [][2]string{
{"/.manifest", `{"revision": "quickbrownfaux"}`},
}
buf := archive.MustWriteTarGz(files)
bundle, err := NewReader(buf).Read()
if err != nil {
t.Fatal(err)
}
if bundle.Manifest.Revision != "quickbrownfaux" {
t.Fatalf("Unexpected manifest.revision value: %v", bundle.Manifest.Revision)
}
}
func TestReadWithManifestInData(t *testing.T) {
files := [][2]string{
{"/.manifest", `{"revision": "quickbrownfaux"}`},
}
buf := archive.MustWriteTarGz(files)
bundle, err := NewReader(buf).IncludeManifestInData(true).Read()
if err != nil {
t.Fatal(err)
}
system := bundle.Data["system"].(map[string]interface{})
b := system["bundle"].(map[string]interface{})
m := b["manifest"].(map[string]interface{})
if m["revision"] != "quickbrownfaux" {
t.Fatalf("Unexpected manifest.revision value: %v. Expected: %v", m["revision"], "quickbrownfaux")
}
}
func TestReadRootValidation(t *testing.T) {
cases := []struct {
note string
files [][2]string
err string
}{
{
note: "default: full extent",
files: [][2]string{
{"/.manifest", `{"revision": "abcd"}`},
{"/data.json", `{"a": 1}`},
{"/x.rego", `package foo`},
},
err: "",
},
{
note: "explicit: full extent",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": [""]}`},
{"/data.json", `{"a": 1}`},
{"/x.rego", `package foo`},
},
err: "",
},
{
note: "implicit: prefixed",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": ["a/b", "foo"]}`},
{"/data.json", `{"a": {"b": 1}}`},
{"/x.rego", `package foo.bar`},
},
err: "",
},
{
note: "err: empty",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": []}`},
{"/x.rego", `package foo`},
},
err: "manifest roots do not permit 'package foo' in /x.rego",
},
{
note: "err: overlapped",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": ["a/b", "a"]}`},
},
err: "manifest has overlapped roots: a/b and a",
},
{
note: "edge: overlapped partial segment",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": ["a", "another_root"]}`},
},
err: "",
},
{
note: "err: package outside scope",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": ["a", "b", "c/d"]}`},
{"/a.rego", `package b.c`},
{"/x.rego", `package c.e`},
},
err: "manifest roots do not permit 'package c.e' in /x.rego",
},
{
note: "err: data outside scope",
files: [][2]string{
{"/.manifest", `{"revision": "abcd", "roots": ["a", "b", "c/d"]}`},
{"/data.json", `{"a": 1}`},
{"/c/e/data.json", `"bad bad bad"`},
},
err: "manifest roots do not permit data at path c/e",
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
buf := archive.MustWriteTarGz(tc.files)
_, err := NewReader(buf).IncludeManifestInData(true).Read()
if tc.err == "" && err != nil {
t.Fatal("Unexpected error occurred:", err)
} else if tc.err != "" && err == nil {
t.Fatal("Expected error but got success")
} else if tc.err != "" && err != nil {
if !strings.Contains(err.Error(), tc.err) {
t.Fatalf("Expected error to contain %q but got: %v", tc.err, err)
}
}
})
}
}
func TestReadErrorBadGzip(t *testing.T) {
buf := bytes.NewBufferString("bad gzip bytes")
_, err := NewReader(buf).Read()
if err == nil {
t.Fatal("expected error")
}
}
func TestReadErrorBadTar(t *testing.T) {
var buf bytes.Buffer
gw := gzip.NewWriter(&buf)
gw.Write([]byte("bad tar bytes"))
gw.Close()
_, err := NewReader(&buf).Read()
if err == nil {
t.Fatal("expected error")
}
}
func TestReadErrorBadContents(t *testing.T) {
tests := []struct {
files [][2]string
}{
{[][2]string{{"/test.rego", "lkafjasdkljf"}}},
{[][2]string{{"/data.json", "lskjafkljsdf"}}},
{[][2]string{{"/data.json", "[1,2,3]"}}},
{[][2]string{
{"/a/b/data.json", "[1,2,3]"},
{"a/b/c/data.json", "true"},
}},
{[][2]string{{"/test.rego", ""}}},
{[][2]string{
{"/a/b/data.json", `{"c": "foo"}`},
{"/data.json", `{"a": {"b": {"c": [123]}}}`},
}},
}
for _, test := range tests {
buf := archive.MustWriteTarGz(test.files)
_, err := NewReader(buf).Read()
if err == nil {
t.Fatal("expected error")
}
}
}
func TestRoundtrip(t *testing.T) {
bundle := Bundle{
Data: map[string]interface{}{
"foo": map[string]interface{}{
"bar": []interface{}{json.Number("1"), json.Number("2"), json.Number("3")},
"baz": true,
"qux": "hello",
},
},
Modules: []ModuleFile{
{
Path: "/foo/corge/corge.rego",
Parsed: ast.MustParseModule(`package foo.corge`),
Raw: []byte(`package foo.corge`),
},
},
Manifest: Manifest{
Revision: "quickbrownfaux",
},
}
var buf bytes.Buffer
if err := Write(&buf, bundle); err != nil {
t.Fatal("Unexpected error:", err)
}
bundle2, err := NewReader(&buf).Read()
if err != nil {
t.Fatal("Unexpected error:", err)
}
if !bundle2.Equal(bundle) {
t.Fatal("Exp:", bundle, "\n\nGot:", bundle2)
}
}
func TestRootPathsOverlap(t *testing.T) {
cases := []struct {
note string
rootA string
rootB string
expected bool
}{
{"both empty", "", "", true},
{"a empty", "", "foo/bar", true},
{"b empty", "foo/bar", "", true},
{"no overlap", "a/b/c", "x/y", false},
{"partial segment overlap a", "a/b", "a/banana", false},
{"partial segment overlap b", "a/banana", "a/b", false},
{"overlap a", "a/b", "a/b/c", true},
{"overlap b", "a/b/c", "a/b", true},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
actual := RootPathsOverlap(tc.rootA, tc.rootB)
if actual != tc.expected {
t.Errorf("Expected %t, got %t", tc.expected, actual)
}
})
}
}
func TestParsedModules(t *testing.T) {
cases := []struct {
note string
bundle Bundle
name string
expectedModules []string
}{
{
note: "base",
bundle: Bundle{
Modules: []ModuleFile{
{
Path: "/foo/policy.rego",
Parsed: ast.MustParseModule(`package foo`),
Raw: []byte(`package foo`),
},
},
},
name: "test-bundle",
expectedModules: []string{
"test-bundle/foo/policy.rego",
},
},
{
note: "filepath name",
bundle: Bundle{
Modules: []ModuleFile{
{
Path: "/foo/policy.rego",
Parsed: ast.MustParseModule(`package foo`),
Raw: []byte(`package foo`),
},
},
},
name: "/some/system/path",
expectedModules: []string{
"/some/system/path/foo/policy.rego",
},
},
{
note: "file url name",
bundle: Bundle{
Modules: []ModuleFile{
{
Path: "/foo/policy.rego",
Parsed: ast.MustParseModule(`package foo`),
Raw: []byte(`package foo`),
},
},
},
name: "file:///some/system/path",
expectedModules: []string{
"/some/system/path/foo/policy.rego",
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
parsedMods := tc.bundle.ParsedModules(tc.name)
for _, exp := range tc.expectedModules {
mod, ok := parsedMods[exp]
if !ok {
t.Fatalf("Missing expected module %s, got: %+v", exp, parsedMods)
}
if mod == nil {
t.Fatalf("Expected module to be non-nil")
}
}
})
}
}