Files
releases/cmd/inspect_test.go
T
Anders Eknert db035b09fc Add support for Go 1.27 & jsonv2 (#8947)
Makes OPA build and pass its tests on Go 1.27, while keeping Go 1.25 and
1.26 working. JSON output is unchanged on every supported version.

Go 1.27 json package honours `encoding.TextAppender`. Many v1 ast types
implement AppendText to build their Rego string cheaply, so on 1.27 they
would have marshalled as Rego text. Files built only with 1.27 now
implement MarshalJSONTo.

Library users should keep using `json.Marshal` etc. The MarshalJSONTo
methods are implementation details, are absent from 1.25 and 1.26
builds, and may change.

---------

Signed-off-by: Anders Eknert <anders.eknert@apple.com>
Signed-off-by: Charlie Egan <charlie_egan@apple.com>
Co-authored-by: Charlie Egan <charlie_egan@apple.com>
2026-07-30 17:41:28 +01:00

2300 lines
47 KiB
Go

//go:build !go1.27
// Copyright 2021 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 cmd
import (
"bytes"
"fmt"
"maps"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/open-policy-agent/opa/cmd/formats"
"github.com/open-policy-agent/opa/internal/file/archive"
"github.com/open-policy-agent/opa/v1/util"
"github.com/open-policy-agent/opa/v1/util/test"
)
func TestDoInspectJSONOutputBytes(t *testing.T) {
files := [][2]string{
{"/.manifest", `{"revision": "rev", "roots": ["foo", "bar", "fuz", "baz", "a", "x"]}`},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
{"/example/foo.rego", `package foo`},
}
buf := archive.MustWriteTarGz(files)
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
if err := os.WriteFile(bundleFile, buf.Bytes(), 0o644); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
params := newInspectCommandParams()
if err := params.outputFormat.Set(formats.JSON); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if err := doInspect(params, bundleFile, &out); err != nil {
t.Fatalf("Unexpected error %v", err)
}
expected := `{
"manifest": {
"revision": "rev",
"roots": [
"foo",
"bar",
"fuz",
"baz",
"a",
"x"
]
},
"signatures_config": {},
"namespaces": {
"data": [
"/data.json"
],
"data.foo": [
"/example/foo.rego"
]
},
"capabilities": {
"features": [
"rego_v1"
]
}
}
`
if diff := cmp.Diff(expected, out.String()); diff != "" {
t.Errorf("unexpected result (-want, +got):\n%s", diff)
}
}
func TestDoInspect(t *testing.T) {
files := [][2]string{
{"/.manifest", `{"revision": "rev", "roots": ["foo", "bar", "fuz", "baz", "a", "x"]}`},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
{"/example/foo.rego", `package foo`},
}
buf := archive.MustWriteTarGz(files)
rootDir := t.TempDir()
bundleFile := filepath.Join(rootDir, "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
params := newInspectCommandParams()
err = params.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = doInspect(params, bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
res := `{
"capabilities": {"features": ["rego_v1"]},
"manifest": {"revision": "rev", "roots": ["foo", "bar", "fuz", "baz", "a", "x"]},
"signatures_config": {},
"namespaces": {"data": ["/data.json"], "data.foo": ["/example/foo.rego"]}
}`
exp := util.MustUnmarshalJSON([]byte(res))
result := util.MustUnmarshalJSON(out.Bytes())
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected inspect output to be:\n\n%v\n\ngot:\n\n%v", exp, result)
}
}
func TestDoInspectPretty(t *testing.T) {
root := fmt.Sprintf("metadata/%v/features", strings.Repeat("foobar", 20))
manifest := fmt.Sprintf(`{"revision": "%s",
"roots": ["foo", "bar", "fuz", "http", "a", "x", "%s"],
"metadata": {"hello": "%s"},
"wasm": [{"entrypoint": "http/example/authz", "module": "/policy.wasm"}, {"entrypoint": "http/example/foo/allow", "module": "/example/policy.wasm"}]}`, strings.Repeat("foobar", 10), root, strings.Repeat("world", 100))
files := [][2]string{
{"/.manifest", manifest},
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
{"/http/example/authz/foo.rego", `package http.example.authz`},
{"/http/example/authz/data.json", `{"faz": "baz"}`},
{"/example/foo.rego", `package foo`},
{"/a/b/y/foo.rego", `package a.b.y`},
{"/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego", `package a.b.y`},
{"/example/policy.wasm", `modules-compiled-as-wasm-binary`},
{"/http/example/policy.wasm", `modules-compiled-as-wasm-binary`},
{"/policy.wasm", `modules-compiled-as-wasm-binary`},
}
buf := archive.MustWriteTarGz(files)
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
err = doInspect(newInspectCommandParams(), bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
output := strings.TrimSpace(out.String())
expected := strings.TrimSpace(`
MANIFEST:
┌──────────┬────────────────────────────────────────────────────┐
│ FIELD │ VALUE │
├──────────┼────────────────────────────────────────────────────┤
│ Revision │ foobarfoobarfoobarfoobarfoobarfoobarfoobarfooba... │
│ Roots │ a │
│ │ bar │
│ │ foo │
│ │ fuz │
│ │ http │
│ │ metadata/...oobarfoobarfoobarfoobarfoobar/features │
│ │ x │
│ Metadata │ {"hello":"worldworldworldworldworldworldworldwo... │
└──────────┴────────────────────────────────────────────────────┘
NAMESPACES:
┌─────────────────────────────┬────────────────────────────────────────────────────┐
│ NAMESPACE │ FILE │
├─────────────────────────────┼────────────────────────────────────────────────────┤
│ data │ /data.json │
│ data.a.b.y │ /a/b/y/foo.rego │
│ │ /a/...xxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego │
│ data.foo │ /example/foo.rego │
│ data.http.example.authz │ /http/example/authz/foo.rego │
│ │ /http/example/authz/data.json │
│ │ /policy.wasm │
│ data.http.example.foo.allow │ /example/policy.wasm │
└─────────────────────────────┴────────────────────────────────────────────────────┘
`)
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
}
}
func TestDoInspectPrettyManifestOnlySingleRoot(t *testing.T) {
root := fmt.Sprintf("metadata/%v/features", strings.Repeat("foobar", 6))
manifest := fmt.Sprintf(`{"roots": ["%s"],
"metadata": {"hello": "world"}}`, root)
files := [][2]string{
{"/.manifest", manifest},
}
buf := archive.MustWriteTarGz(files)
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
err = doInspect(newInspectCommandParams(), bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
output := strings.TrimSpace(out.String())
expected := strings.TrimSpace(`
MANIFEST:
┌──────────┬────────────────────────────────────────────────────┐
│ FIELD │ VALUE │
├──────────┼────────────────────────────────────────────────────┤
│ Roots │ metadata/...oobarfoobarfoobarfoobarfoobar/features │
│ Metadata │ {"hello":"world"} │
└──────────┴────────────────────────────────────────────────────┘
`)
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
}
}
func TestInspectMultiBundleError(t *testing.T) {
params := newInspectCommandParams()
err := validateInspectParams(&params, []string{"foo", "bar"})
if err == nil {
t.Fatal("Expected error but got nil")
}
exp := "specify exactly one OPA bundle or path"
if err.Error() != exp {
t.Fatalf("Expected error %v but got %v", exp, err.Error())
}
}
func TestDoInspectWithAnnotations(t *testing.T) {
files := map[string]string{
"x.rego": `# METADATA
# title: pkg-title
# description: pkg-descr
# organizations:
# - pkg-org
# related_resources:
# - https://pkg
# - ref: https://pkg
# description: rr-pkg-note
# authors:
# - pkg-author
# schemas:
# - input: {"type": "boolean"}
# custom:
# pkg: pkg-custom
package test
# METADATA
# scope: document
# title: doc-title
# description: doc-descr
# organizations:
# - doc-org
# related_resources:
# - https://doc
# - ref: https://doc
# description: rr-doc-note
# authors:
# - doc-author
# schemas:
# - input: {"type": "integer"}
# custom:
# doc: doc-custom
# METADATA
# title: rule-title
# description: rule-title
# organizations:
# - rule-org
# related_resources:
# - https://rule
# - ref: https://rule
# description: rr-rule-note
# authors:
# - rule-author
# schemas:
# - input: {"type": "string"}
# custom:
# rule: rule-custom
p = 1`,
}
t.Run("pretty", func(t *testing.T) {
test.WithTempFS(files, func(rootDir string) {
ps := newInspectCommandParams()
ps.listAnnotations = true
var out bytes.Buffer
err := doInspect(ps, rootDir, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
bs := out.Bytes()
idx := bytes.Index(bs, []byte(`ANNOTATIONS`)) // skip NAMESPACE box
output := strings.TrimSpace(string(bs[idx:]))
expected := strings.TrimSpace(fmt.Sprintf(`
ANNOTATIONS:
pkg-title
=========
pkg-descr
Package: test
Location: %[1]s/x.rego:16
Scope: package
Organizations:
pkg-org
Authors:
pkg-author
Schemas:
input: {"type":"boolean"}
Related Resources:
https://pkg
https://pkg rr-pkg-note
Custom:
pkg: "pkg-custom"
doc-title
=========
doc-descr
Package: test
Rule: p
Location: %[1]s/x.rego:50
Scope: document
Organizations:
doc-org
Authors:
doc-author
Schemas:
input: {"type":"integer"}
Related Resources:
https://doc
https://doc rr-doc-note
Custom:
doc: "doc-custom"
rule-title
==========
rule-title
Package: test
Rule: p
Location: %[1]s/x.rego:50
Scope: rule
Organizations:
rule-org
Authors:
rule-author
Schemas:
input: {"type":"string"}
Related Resources:
https://rule
https://rule rr-rule-note
Custom:
rule: "rule-custom"`, rootDir))
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%q\n\nGot:\n\n%q", expected, output)
}
})
})
t.Run("json", func(t *testing.T) {
test.WithTempFS(files, func(rootDir string) {
ps := newInspectCommandParams()
ps.listAnnotations = true
err := ps.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var out bytes.Buffer
err = doInspect(ps, rootDir, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
bs := out.Bytes()
expected := strings.TrimSpace(fmt.Sprintf(`{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"%[1]s/x.rego"
]
},
"annotations": [
{
"annotations": {
"authors": [
{
"name": "pkg-author"
}
],
"custom": {
"pkg": "pkg-custom"
},
"description": "pkg-descr",
"organizations": [
"pkg-org"
],
"related_resources": [
{
"ref": "https://pkg"
},
{
"description": "rr-pkg-note",
"ref": "https://pkg"
}
],
"schemas": [
{
"path": [
{
"type": "var",
"value": "input"
}
],
"definition": {
"type": "boolean"
}
}
],
"scope": "package",
"title": "pkg-title"
},
"location": {
"file": "%[1]s/x.rego",
"row": 16,
"col": 1
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
}
]
},
{
"annotations": {
"authors": [
{
"name": "doc-author"
}
],
"custom": {
"doc": "doc-custom"
},
"description": "doc-descr",
"organizations": [
"doc-org"
],
"related_resources": [
{
"ref": "https://doc"
},
{
"description": "rr-doc-note",
"ref": "https://doc"
}
],
"schemas": [
{
"path": [
{
"type": "var",
"value": "input"
}
],
"definition": {
"type": "integer"
}
}
],
"scope": "document",
"title": "doc-title"
},
"location": {
"file": "%[1]s/x.rego",
"row": 50,
"col": 1
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
},
{
"type": "string",
"value": "p"
}
]
},
{
"annotations": {
"authors": [
{
"name": "rule-author"
}
],
"custom": {
"rule": "rule-custom"
},
"description": "rule-title",
"organizations": [
"rule-org"
],
"related_resources": [
{
"ref": "https://rule"
},
{
"description": "rr-rule-note",
"ref": "https://rule"
}
],
"schemas": [
{
"path": [
{
"type": "var",
"value": "input"
}
],
"definition": {
"type": "string"
}
}
],
"scope": "rule",
"title": "rule-title"
},
"location": {
"file": "%[1]s/x.rego",
"row": 50,
"col": 1
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
},
{
"type": "string",
"value": "p"
}
]
}
],
"capabilities": {
"features": [
"rego_v1"
]
}
}`, rootDir))
exp := util.MustUnmarshalJSON([]byte(expected))
result := util.MustUnmarshalJSON(bs)
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected inspect output to be:\n\n%v\n\ngot:\n\n%v", exp, result)
}
})
})
}
func TestDoInspectTarballWithAnnotations(t *testing.T) {
files := [][2]string{
{"x.rego", `# METADATA
# title: pkg-title
# description: pkg-descr
# organizations:
# - pkg-org
# related_resources:
# - https://pkg
# - ref: https://pkg
# description: rr-pkg-note
# authors:
# - pkg-author
# schemas:
# - input: {"type": "boolean"}
# custom:
# pkg: pkg-custom
package test
# METADATA
# scope: document
# title: doc-title
# description: doc-descr
# organizations:
# - doc-org
# related_resources:
# - https://doc
# - ref: https://doc
# description: rr-doc-note
# authors:
# - doc-author
# schemas:
# - input: {"type": "integer"}
# custom:
# doc: doc-custom
# METADATA
# title: rule-title
# description: rule-title
# organizations:
# - rule-org
# related_resources:
# - https://rule
# - ref: https://rule
# description: rr-rule-note
# authors:
# - rule-author
# schemas:
# - input: {"type": "string"}
# custom:
# rule: rule-custom
p = 1`},
{".manifest", `
{
"revision": "",
"roots": [
""
],
"wasm": [
{
"entrypoint": "test/a",
"module": "/policy.wasm"
},
{
"entrypoint": "test/b",
"module": "/policy.wasm",
"annotations": [
{
"scope": "rule",
"title": "WASM RULE B",
"entrypoint": true
}
]
}
]
}`},
{"policy.wasm", ""},
}
buf := archive.MustWriteTarGz(files)
t.Run("pretty", func(t *testing.T) {
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ps := newInspectCommandParams()
ps.listAnnotations = true
var out bytes.Buffer
err = doInspect(ps, bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
bs := out.Bytes()
idx := bytes.Index(bs, []byte(`ANNOTATIONS`)) // skip NAMESPACE box
output := strings.TrimSpace(string(bs[idx:]))
expected := strings.TrimSpace(`
ANNOTATIONS:
pkg-title
=========
pkg-descr
Package: test
Location: /x.rego:16
Scope: package
Organizations:
pkg-org
Authors:
pkg-author
Schemas:
input: {"type":"boolean"}
Related Resources:
https://pkg
https://pkg rr-pkg-note
Custom:
pkg: "pkg-custom"
WASM RULE B
===========
Location: /policy.wasm:0
Scope: rule
Entrypoint: true
doc-title
=========
doc-descr
Package: test
Rule: p
Location: /x.rego:50
Scope: document
Organizations:
doc-org
Authors:
doc-author
Schemas:
input: {"type":"integer"}
Related Resources:
https://doc
https://doc rr-doc-note
Custom:
doc: "doc-custom"
rule-title
==========
rule-title
Package: test
Rule: p
Location: /x.rego:50
Scope: rule
Organizations:
rule-org
Authors:
rule-author
Schemas:
input: {"type":"string"}
Related Resources:
https://rule
https://rule rr-rule-note
Custom:
rule: "rule-custom"`)
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%q\n\nGot:\n\n%q", expected, output)
}
})
t.Run("json", func(t *testing.T) {
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ps := newInspectCommandParams()
ps.listAnnotations = true
err = ps.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
var out bytes.Buffer
err = doInspect(ps, bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
expected := strings.TrimSpace(fmt.Sprintf(`{
"manifest": {
"revision": "",
"roots": [
""
],
"wasm": [
{
"entrypoint": "test/a",
"module": "/policy.wasm"
},
{
"entrypoint": "test/b",
"module": "/policy.wasm",
"annotations": [
{
"entrypoint": true,
"scope": "rule",
"title": "WASM RULE B"
}
]
}
]
},
"signatures_config": {},
"wasm_modules": [
{
"entrypoints": [
"data.test.a",
"data.test.b"
],
"path": "/policy.wasm",
"url": "%[1]s/policy.wasm"
}
],
"namespaces": {
"data.test": [
"/x.rego"
],
"data.test.a": [
"/policy.wasm"
],
"data.test.b": [
"/policy.wasm"
]
},
"annotations": [
{
"annotations": {
"authors": [
{
"name": "pkg-author"
}
],
"custom": {
"pkg": "pkg-custom"
},
"description": "pkg-descr",
"organizations": [
"pkg-org"
],
"related_resources": [
{
"ref": "https://pkg"
},
{
"description": "rr-pkg-note",
"ref": "https://pkg"
}
],
"schemas": [
{
"path": [
{
"type": "var",
"value": "input"
}
],
"definition": {
"type": "boolean"
}
}
],
"scope": "package",
"title": "pkg-title"
},
"location": {
"file": "/x.rego",
"row": 16,
"col": 1
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
}
]
},
{
"annotations": {
"entrypoint": true,
"scope": "rule",
"title": "WASM RULE B"
},
"location": {
"file": "/policy.wasm",
"row": 0,
"col": 0
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
},
{
"type": "string",
"value": "b"
}
]
},
{
"annotations": {
"authors": [
{
"name": "doc-author"
}
],
"custom": {
"doc": "doc-custom"
},
"description": "doc-descr",
"organizations": [
"doc-org"
],
"related_resources": [
{
"ref": "https://doc"
},
{
"description": "rr-doc-note",
"ref": "https://doc"
}
],
"schemas": [
{
"path": [
{
"type": "var",
"value": "input"
}
],
"definition": {
"type": "integer"
}
}
],
"scope": "document",
"title": "doc-title"
},
"location": {
"file": "/x.rego",
"row": 50,
"col": 1
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
},
{
"type": "string",
"value": "p"
}
]
},
{
"annotations": {
"authors": [
{
"name": "rule-author"
}
],
"custom": {
"rule": "rule-custom"
},
"description": "rule-title",
"organizations": [
"rule-org"
],
"related_resources": [
{
"ref": "https://rule"
},
{
"description": "rr-rule-note",
"ref": "https://rule"
}
],
"schemas": [
{
"path": [
{
"type": "var",
"value": "input"
}
],
"definition": {
"type": "string"
}
}
],
"scope": "rule",
"title": "rule-title"
},
"location": {
"file": "/x.rego",
"row": 50,
"col": 1
},
"path": [
{
"type": "var",
"value": "data"
},
{
"type": "string",
"value": "test"
},
{
"type": "string",
"value": "p"
}
]
}
],
"capabilities": {
"features": [
"rego_v1"
]
}
}`, bundleFile))
exp := util.MustUnmarshalJSON([]byte(expected))
result := util.MustUnmarshalJSON(out.Bytes())
if !reflect.DeepEqual(exp, result) {
t.Fatalf("expected inspect output to be:\n\n%v\n\ngot:\n\n%v", exp, result)
}
})
}
func TestDoInspect_V0Compatible(t *testing.T) {
tests := []struct {
note string
v0Compatible bool
module string
expErrs []string
}{
{
note: "v0, keywords not used",
v0Compatible: true,
module: `package test
p[v] {
v := input.x
}`,
},
{
note: "v0, no keywords imported, but used",
v0Compatible: true,
module: `package test
p contains v if {
v := input.x
}`,
expErrs: []string{
"rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v0, keywords imported",
module: `package test
import future.keywords
p contains v if {
v := input.x
}`,
},
{
note: "v0, rego.v1 imported",
module: `package test
import rego.v1
p contains v if {
v := input.x
}`,
},
{
note: "v1, keywords not used",
module: `package test
p[v] {
v := input.x
}`,
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1, no keywords imported",
module: `package test
p contains v if {
v := input.x
}`,
},
{
note: "v1, keywords imported",
module: `package test
import future.keywords
p contains v if {
v := input.x
}`,
},
{
note: "v1, rego.v1 imported",
module: `package test
import rego.v1
p contains v if {
v := input.x
}`,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
buf := archive.MustWriteTarGz([][2]string{{"/policy.rego", tc.module}})
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
params := newInspectCommandParams()
params.v0Compatible = tc.v0Compatible
err = params.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = doInspect(params, bundleFile, &out)
if len(tc.expErrs) > 0 {
if err == nil {
t.Fatalf("Expected error but got nil")
}
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("Expected error:\n\n%v\n\nbut got:\n\n%v", expErr, err.Error())
}
}
} else if err != nil {
t.Fatalf("Unexpected error %v", err)
}
})
}
}
func TestDoInspectWithBundleRegoVersion(t *testing.T) {
tests := []struct {
note string
bundleRegoVersion int
files map[string]string
expErrs []string
}{
{
note: "v0.x bundle, keywords not used",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
p[v] {
v := input.x
}`,
},
},
{
note: "v0.x bundle, no keywords imported, but used",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
p contains v if {
v := input.x
}`,
},
expErrs: []string{
"rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v0.x bundle, keywords imported",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
import future.keywords
p contains v if {
v := input.x
}`,
},
},
{
note: "v0.x bundle, rego.v1 imported",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
import rego.v1
p contains v if {
v := input.x
}`,
},
},
{
note: "v0 bundle, v1 per-file override",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{
"rego_version": 0,
"file_rego_versions": {
"/policy2.rego": 1
}
}`,
"policy1.rego": `package test
p[1] {
v := input.x
}`,
"policy2.rego": `package test
p contains 2 if {
v := input.x
}`,
},
},
{
note: "v0 bundle, v1 per-file override (glob)",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{
"rego_version": 0,
"file_rego_versions": {
"/bar/*.rego": 1
}
}`,
"foo/policy1.rego": `package test
p[1] {
v := input.x
}`,
"bar/policy2.rego": `package test
p contains 2 if {
v := input.x
}`,
},
},
{
note: "v0 bundle, v1 per-file override, incompatible",
bundleRegoVersion: 0,
files: map[string]string{
".manifest": `{
"rego_version": 0,
"file_rego_versions": {
"/policy2.rego": 1
}
}`,
"policy1.rego": `package test
p[1] {
v := input.x
}`,
"policy2.rego": `package test
p[2] {
v := input.x
}`,
},
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1.0 bundle, keywords not used",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
p[v] {
v := input.x
}`,
},
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1.0 bundle, no keywords imported",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
p contains v if {
v := input.x
}`,
},
},
{
note: "v1.0 bundle, keywords imported",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import future.keywords
p contains v if {
v := input.x
}`,
},
},
{
note: "v1.0 bundle, rego.v1 imported",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import rego.v1
p contains v if {
v := input.x
}`,
},
},
{
note: "v1 bundle, v0 per-file override",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{
"rego_version": 1,
"file_rego_versions": {
"/policy1.rego": 0
}
}`,
"policy1.rego": `package test
p[1] {
v := input.x
}`,
"policy2.rego": `package test
p contains 2 if {
v := input.x
}`,
},
},
{
note: "v1 bundle, v0 per-file override",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{
"rego_version": 1,
"file_rego_versions": {
"/foo/*.rego": 0
}
}`,
"foo/policy1.rego": `package test
p[1] {
v := input.x
}`,
"bar/policy2.rego": `package test
p contains 2 if {
v := input.x
}`,
},
},
{
note: "v1 bundle, v0 per-file override, incompatible",
bundleRegoVersion: 1,
files: map[string]string{
".manifest": `{
"rego_version": 1,
"file_rego_versions": {
"/policy1.rego": 0
}
}`,
"policy1.rego": `package test
p contains 1 if {
v := input.x
}`,
"policy2.rego": `package test
p contains 2 if {
v := input.x
}`,
},
expErrs: []string{
"rego_parse_error: var cannot be used for rule name",
"rego_parse_error: number cannot be used for rule name",
},
},
}
bundleTypeCases := []struct {
note string
tar bool
}{
{
"bundle dir", false,
},
{
"bundle tar", true,
},
}
v1CompatibleFlagCases := []struct {
note string
used bool
}{
{
"no --v1-compatible", false,
},
{
"--v1-compatible", true,
},
}
for _, bundleType := range bundleTypeCases {
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
for _, tc := range tests {
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
files := map[string]string{}
if bundleType.tar {
files["bundle.tar.gz"] = ""
} else {
maps.Copy(files, tc.files)
}
test.WithTempFS(files, func(root string) {
p := root
if bundleType.tar {
p = filepath.Join(root, "bundle.tar.gz")
files := make([][2]string, 0, len(tc.files))
for k, v := range tc.files {
files = append(files, [2]string{k, v})
}
buf := archive.MustWriteTarGz(files)
bf, err := os.Create(p)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
var out bytes.Buffer
params := newInspectCommandParams()
params.v1Compatible = v1CompatibleFlag.used
err := params.outputFormat.Set(formats.Pretty)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = doInspect(params, p, &out)
if len(tc.expErrs) > 0 {
if err == nil {
t.Fatalf("Expected error but got output: %s", out.String())
}
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("Expected error:\n\n%v\n\nbut got:\n\n%v", expErr, err.Error())
}
}
} else {
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
expOut := fmt.Sprintf(`MANIFEST:
┌──────────────┬───────┐
│ FIELD │ VALUE │
├──────────────┼───────┤
│ Rego Version │ %d │
└──────────────┴───────┘`,
tc.bundleRegoVersion)
if !strings.Contains(out.String(), expOut) {
t.Fatalf("Expected output to contain:\n\n%s\n\nbut got:\n\n%s", expOut, out.String())
}
}
})
})
}
}
}
}
func TestUnknownRefs(t *testing.T) {
tests := []struct {
note string
files [][2]string
expected string
}{
{
note: "unknown built-in func call",
files: [][2]string{
{
"/policy.rego", `package test
p if {
foo.bar(42)
contains("foo", "o")
}`,
},
},
// Note: unknown foo.bar() built-in doesn't appear in the output, but also didn't cause an error.
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"builtins": [
{
"name": "contains",
"decl": {
"args": [
{
"type": "string"
},
{
"type": "string"
}
],
"result": {
"type": "boolean"
},
"type": "function"
}
}
],
"features": [
"rego_v1"
]
}
}`,
},
{
// Happy path
note: "known ref replaced inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
foo.bar(_) := false
p if {
foo.bar(42)
}
mock(_) := true
test_p if {
p with data.test.foo.bar as mock
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"features": [
"rego_v1"
]
}
}`,
},
{
note: "unknown ref replaced inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
p if {
data.foo.bar(42)
}
mock(_) := true
test_p if {
p with data.foo.bar as mock
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"features": [
"rego_v1"
]
}
}`,
},
{
note: "unknown built-in (var) replaced inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
p if {
foo(42)
}
mock(_) := true
test_p if {
p with foo as mock
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"features": [
"rego_v1"
]
}
}`,
},
{
note: "unknown built-in (ref) replaced inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
p if {
foo.bar(42)
}
mock(_) := true
test_p if {
p with foo.bar as mock
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"features": [
"rego_v1"
]
}
}`,
},
{
note: "call replaced by unknown data ref inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
p if {
foo(42)
}
foo(_) := false
test_p if {
p with foo as data.bar
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"builtins": [
{
"name": "eq",
"decl": {
"args": [
{
"type": "any"
},
{
"type": "any"
}
],
"result": {
"type": "boolean"
},
"type": "function"
},
"infix": "="
}
],
"features": [
"rego_v1"
]
}
}`,
},
{
note: "call replaced by unknown built-in (var) inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
p if {
foo(42)
}
foo(_) := false
test_p if {
# bar is unknown built-in
p with foo as bar
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"features": [
"rego_v1"
]
}
}`,
},
{
note: "call replaced by unknown built-in (ref) inside 'with' stmt",
files: [][2]string{
{"/policy.rego", `package test
p if {
foo(42)
}
foo(_) := false
test_p if {
# bar.baz is unknown built-in
p with foo as bar.baz
}`},
},
expected: `{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"builtins": [
{
"name": "eq",
"decl": {
"args": [
{
"type": "any"
},
{
"type": "any"
}
],
"result": {
"type": "boolean"
},
"type": "function"
},
"infix": "="
}
],
"features": [
"rego_v1"
]
}
}`,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
buf := archive.MustWriteTarGz(tc.files)
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
params := newInspectCommandParams()
err = params.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = doInspect(params, bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
bs := out.Bytes()
output := strings.TrimSpace(string(bs))
if output != tc.expected {
t.Fatalf("Unexpected output. Expected:\n\n%s\n\nGot:\n\n%s", tc.expected, output)
}
})
}
}
func TestCallToUnknownRegoFunction(t *testing.T) {
files := [][2]string{
{"/policy.rego", `package test
import data.x.y
p if {
y(1) == true
}
`},
}
buf := archive.MustWriteTarGz(files)
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
bf, err := os.Create(bundleFile)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = bf.Write(buf.Bytes())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var out bytes.Buffer
params := newInspectCommandParams()
err = params.outputFormat.Set(formats.JSON)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
err = doInspect(params, bundleFile, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
bs := out.Bytes()
output := strings.TrimSpace(string(bs))
// Note: unknown data.x.y() function doesn't appear in the output, but also didn't cause an error.
expected := strings.TrimSpace(`{
"manifest": {
"revision": "",
"roots": [
""
]
},
"signatures_config": {},
"namespaces": {
"data.test": [
"/policy.rego"
]
},
"capabilities": {
"builtins": [
{
"name": "eq",
"decl": {
"args": [
{
"type": "any"
},
{
"type": "any"
}
],
"result": {
"type": "boolean"
},
"type": "function"
},
"infix": "="
},
{
"name": "equal",
"decl": {
"args": [
{
"type": "any"
},
{
"type": "any"
}
],
"result": {
"type": "boolean"
},
"type": "function"
},
"infix": "=="
}
],
"features": [
"rego_v1"
]
}
}`)
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%s\n\nGot:\n\n%s", expected, output)
}
}
func TestDoInspectSingleFileWithAnnotations(t *testing.T) {
files := map[string]string{
"/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego": `# METADATA
# title: pkg-title
# description: pkg-descr
# organizations:
# - pkg-org
# related_resources:
# - https://pkg
# - ref: https://pkg
# description: rr-pkg-note
# authors:
# - pkg-author
# schemas:
# - input: {"type": "boolean"}
# custom:
# pkg: pkg-custom
package test
# METADATA
# scope: document
# title: doc-title
# description: doc-descr
# organizations:
# - doc-org
# related_resources:
# - https://doc
# - ref: https://doc
# description: rr-doc-note
# authors:
# - doc-author
# schemas:
# - input: {"type": "integer"}
# custom:
# doc: doc-custom
# METADATA
# title: rule-title
# description: rule-title
# organizations:
# - rule-org
# related_resources:
# - https://rule
# - ref: https://rule
# description: rr-rule-note
# authors:
# - rule-author
# schemas:
# - input: {"type": "string"}
# custom:
# rule: rule-custom
p = 1`,
}
test.WithTempFS(files, func(rootDir string) {
fileName := rootDir + "/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego"
ps := newInspectCommandParams()
ps.listAnnotations = true
var out bytes.Buffer
err := doInspect(ps, fileName, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
shortFileName := truncateFileName(fileName)
output := strings.TrimSpace(out.String())
expected := strings.TrimSpace(fmt.Sprintf(`
NAMESPACES:
┌───────────┬────────────────────────────────────────────────────┐
│ NAMESPACE │ FILE │
├───────────┼────────────────────────────────────────────────────┤
│ data.test │ %[1]s │
└───────────┴────────────────────────────────────────────────────┘
ANNOTATIONS:
pkg-title
=========
pkg-descr
Package: test
Location: %[2]s:16
Scope: package
Organizations:
pkg-org
Authors:
pkg-author
Schemas:
input: {"type":"boolean"}
Related Resources:
https://pkg
https://pkg rr-pkg-note
Custom:
pkg: "pkg-custom"
doc-title
=========
doc-descr
Package: test
Rule: p
Location: %[2]s:50
Scope: document
Organizations:
doc-org
Authors:
doc-author
Schemas:
input: {"type":"integer"}
Related Resources:
https://doc
https://doc rr-doc-note
Custom:
doc: "doc-custom"
rule-title
==========
rule-title
Package: test
Rule: p
Location: %[2]s:50
Scope: rule
Organizations:
rule-org
Authors:
rule-author
Schemas:
input: {"type":"string"}
Related Resources:
https://rule
https://rule rr-rule-note
Custom:
rule: "rule-custom"`, shortFileName, fileName))
if output != expected {
t.Fatalf("Unexpected output. Expected:\n\n%q\n\nGot:\n\n%q", expected, output)
}
})
}
func TestDoInspectSingleFile(t *testing.T) {
files := map[string]string{
"/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego": `# METADATA
# title: pkg-title
# description: pkg-descr
# organizations:
# - pkg-org
# related_resources:
# - https://pkg
# - ref: https://pkg
# description: rr-pkg-note
# authors:
# - pkg-author
# schemas:
# - input: {"type": "boolean"}
# custom:
# pkg: pkg-custom
package test
# METADATA
# scope: document
# title: doc-title
# description: doc-descr
# organizations:
# - doc-org
# related_resources:
# - https://doc
# - ref: https://doc
# description: rr-doc-note
# authors:
# - doc-author
# schemas:
# - input: {"type": "integer"}
# custom:
# doc: doc-custom
# METADATA
# title: rule-title
# description: rule-title
# organizations:
# - rule-org
# related_resources:
# - https://rule
# - ref: https://rule
# description: rr-rule-note
# authors:
# - rule-author
# schemas:
# - input: {"type": "string"}
# custom:
# rule: rule-custom
p = 1`,
}
test.WithTempFS(files, func(rootDir string) {
fileName := rootDir + "/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego"
ps := newInspectCommandParams()
var out bytes.Buffer
err := doInspect(ps, fileName, &out)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
shortFileName := truncateFileName(fileName)
output := strings.TrimSpace(out.String())
expected := strings.TrimSpace(fmt.Sprintf(`
NAMESPACES:
┌───────────┬────────────────────────────────────────────────────┐
│ NAMESPACE │ FILE │
├───────────┼────────────────────────────────────────────────────┤
│ data.test │ %v │
└───────────┴────────────────────────────────────────────────────┘
`, shortFileName))
if output != expected {
t.Fatalf("Unexpected output. Expected:\n%v\nGot:\n%v", expected, output)
}
})
}