mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
d2a415e25d
A tiny first step to have more tooling correctly report virtual and base document conflicts, as detailed in #7694. This PR fixes the `opa check` command to report conflicts of this type when the `-b`/`--bundle` flag is provided. The bundle flag is required as without that, `opa check` should only verify policies and not load data at all. While I was in the `cmd` directory, I got annoyed with how many of these commands store the same constants for their `--format` flag, so I decided to fix that too, even if it wasn't related to what I originally planned to do. I hope it's not too distracting. Signed-off-by: Anders Eknert <anders@styra.com>
1201 lines
26 KiB
Go
1201 lines
26 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/cmd/formats"
|
|
"github.com/open-policy-agent/opa/v1/util/test"
|
|
)
|
|
|
|
func TestParseExit0(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
|
|
p = 1
|
|
`,
|
|
}
|
|
errc, stdout, stderr, _ := testParse(t, files, &configuredParseParams)
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedOutput := `module
|
|
package
|
|
ref
|
|
data
|
|
"x"
|
|
rule
|
|
head
|
|
ref
|
|
p
|
|
1
|
|
body
|
|
expr index=0
|
|
true
|
|
`
|
|
|
|
if got, want := string(stdout), expectedOutput; got != want {
|
|
t.Fatalf("Expected output\n%v\n, got\n%v", want, got)
|
|
}
|
|
}
|
|
|
|
func TestParseExit1(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `???`,
|
|
}
|
|
errc, _, stderr, _ := testParse(t, files, &configuredParseParams)
|
|
if errc != 1 {
|
|
t.Fatalf("Expected exit code 1, got %v", errc)
|
|
}
|
|
if len(stderr) == 0 {
|
|
t.Fatalf("Expected output in stderr")
|
|
}
|
|
}
|
|
|
|
func TestParseJSONOutput(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
|
|
p = 1
|
|
`,
|
|
}
|
|
errc, stdout, stderr, _ := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.JSON, formats.Pretty),
|
|
})
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedOutput := `{
|
|
"package": {
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "x"
|
|
}
|
|
]
|
|
},
|
|
"rules": [
|
|
{
|
|
"body": [
|
|
{
|
|
"index": 0,
|
|
"terms": {
|
|
"type": "boolean",
|
|
"value": true
|
|
}
|
|
}
|
|
],
|
|
"head": {
|
|
"name": "p",
|
|
"value": {
|
|
"type": "number",
|
|
"value": 1
|
|
},
|
|
"ref": [
|
|
{
|
|
"type": "var",
|
|
"value": "p"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
if got, want := string(stdout), expectedOutput; got != want {
|
|
t.Fatalf("Expected output\n%v\n, got\n%v", want, got)
|
|
}
|
|
}
|
|
|
|
func TestParseJSONOutputWithLocations(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
|
|
p = 1
|
|
`,
|
|
}
|
|
errc, stdout, stderr, tempDirPath := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.JSON, formats.Pretty),
|
|
jsonInclude: "locations",
|
|
})
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedOutput := strings.ReplaceAll(`{
|
|
"package": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 1,
|
|
"text": "cGFja2FnZQ=="
|
|
},
|
|
"path": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 9,
|
|
"text": "eA=="
|
|
},
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 9,
|
|
"text": "eA=="
|
|
},
|
|
"type": "string",
|
|
"value": "x"
|
|
}
|
|
]
|
|
},
|
|
"rules": [
|
|
{
|
|
"body": [
|
|
{
|
|
"index": 0,
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 5,
|
|
"text": "MQ=="
|
|
},
|
|
"terms": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 5,
|
|
"text": "MQ=="
|
|
},
|
|
"type": "boolean",
|
|
"value": true
|
|
}
|
|
}
|
|
],
|
|
"head": {
|
|
"name": "p",
|
|
"value": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 5,
|
|
"text": "MQ=="
|
|
},
|
|
"type": "number",
|
|
"value": 1
|
|
},
|
|
"ref": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 1,
|
|
"text": "cA=="
|
|
},
|
|
"type": "var",
|
|
"value": "p"
|
|
}
|
|
],
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 1,
|
|
"text": "cCA9IDE="
|
|
}
|
|
},
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 1,
|
|
"text": "cCA9IDE="
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`, "TEMPDIR", tempDirPath)
|
|
|
|
gotLines := strings.Split(string(stdout), "\n")
|
|
wantLines := strings.Split(expectedOutput, "\n")
|
|
min := len(gotLines)
|
|
if len(wantLines) < min {
|
|
min = len(wantLines)
|
|
}
|
|
|
|
for i := range min {
|
|
if gotLines[i] != wantLines[i] {
|
|
t.Fatalf("Expected line %d to be\n%v\n, got\n%v", i, wantLines[i], gotLines[i])
|
|
}
|
|
}
|
|
|
|
if len(gotLines) != len(wantLines) {
|
|
t.Fatalf("Expected %d lines, got %d", len(wantLines), len(gotLines))
|
|
}
|
|
}
|
|
|
|
func TestParseRefsJSONOutput(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
|
|
a.b.c := true
|
|
`,
|
|
}
|
|
errc, stdout, stderr, _ := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.JSON, formats.Pretty),
|
|
})
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedOutput := `{
|
|
"package": {
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "x"
|
|
}
|
|
]
|
|
},
|
|
"rules": [
|
|
{
|
|
"body": [
|
|
{
|
|
"index": 0,
|
|
"terms": {
|
|
"type": "boolean",
|
|
"value": true
|
|
}
|
|
}
|
|
],
|
|
"head": {
|
|
"value": {
|
|
"type": "boolean",
|
|
"value": true
|
|
},
|
|
"assign": true,
|
|
"ref": [
|
|
{
|
|
"type": "var",
|
|
"value": "a"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "b"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "c"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`
|
|
|
|
if got, want := string(stdout), expectedOutput; got != want {
|
|
t.Fatalf("Expected output\n%v\n, got\n%v", want, got)
|
|
}
|
|
}
|
|
|
|
func TestParseRefsJSONOutputWithLocations(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
|
|
a.b.c := true
|
|
`,
|
|
}
|
|
errc, stdout, stderr, tempDirPath := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.JSON, formats.Pretty),
|
|
jsonInclude: "locations",
|
|
})
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedOutput := strings.ReplaceAll(`{
|
|
"package": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 1,
|
|
"text": "cGFja2FnZQ=="
|
|
},
|
|
"path": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 9,
|
|
"text": "eA=="
|
|
},
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 9,
|
|
"text": "eA=="
|
|
},
|
|
"type": "string",
|
|
"value": "x"
|
|
}
|
|
]
|
|
},
|
|
"rules": [
|
|
{
|
|
"body": [
|
|
{
|
|
"index": 0,
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 10,
|
|
"text": "dHJ1ZQ=="
|
|
},
|
|
"terms": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 10,
|
|
"text": "dHJ1ZQ=="
|
|
},
|
|
"type": "boolean",
|
|
"value": true
|
|
}
|
|
}
|
|
],
|
|
"head": {
|
|
"value": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 10,
|
|
"text": "dHJ1ZQ=="
|
|
},
|
|
"type": "boolean",
|
|
"value": true
|
|
},
|
|
"assign": true,
|
|
"ref": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 1,
|
|
"text": "YQ=="
|
|
},
|
|
"type": "var",
|
|
"value": "a"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 3,
|
|
"text": "Yg=="
|
|
},
|
|
"type": "string",
|
|
"value": "b"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 5,
|
|
"text": "Yw=="
|
|
},
|
|
"type": "string",
|
|
"value": "c"
|
|
}
|
|
],
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 1,
|
|
"text": "YS5iLmMgOj0gdHJ1ZQ=="
|
|
}
|
|
},
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 3,
|
|
"col": 1,
|
|
"text": "YS5iLmMgOj0gdHJ1ZQ=="
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`, "TEMPDIR", tempDirPath)
|
|
|
|
gotLines := strings.Split(string(stdout), "\n")
|
|
wantLines := strings.Split(expectedOutput, "\n")
|
|
min := len(gotLines)
|
|
if len(wantLines) < min {
|
|
min = len(wantLines)
|
|
}
|
|
|
|
for i := range min {
|
|
if gotLines[i] != wantLines[i] {
|
|
t.Fatalf("Expected line %d to be\n%v\n, got\n%v", i, wantLines[i], gotLines[i])
|
|
}
|
|
}
|
|
|
|
if len(gotLines) != len(wantLines) {
|
|
t.Fatalf("Expected %d lines, got %d", len(wantLines), len(gotLines))
|
|
}
|
|
}
|
|
func TestParseRulesBlockJSONOutputWithLocations(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
import rego.v1
|
|
|
|
default allow = false
|
|
allow = true if {
|
|
input.method == "GET"
|
|
input.path = ["getUser", user]
|
|
input.user == user
|
|
}
|
|
`,
|
|
}
|
|
errc, stdout, stderr, tempDirPath := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.JSON, formats.Pretty),
|
|
jsonInclude: "locations",
|
|
})
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedOutput := strings.ReplaceAll(`{
|
|
"package": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 1,
|
|
"text": "cGFja2FnZQ=="
|
|
},
|
|
"path": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 9,
|
|
"text": "eA=="
|
|
},
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 1,
|
|
"col": 9,
|
|
"text": "eA=="
|
|
},
|
|
"type": "string",
|
|
"value": "x"
|
|
}
|
|
]
|
|
},
|
|
"imports": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 2,
|
|
"col": 1,
|
|
"text": "aW1wb3J0"
|
|
},
|
|
"path": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 2,
|
|
"col": 8,
|
|
"text": "cmVnby52MQ=="
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 2,
|
|
"col": 8,
|
|
"text": "cmVnbw=="
|
|
},
|
|
"type": "var",
|
|
"value": "rego"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 2,
|
|
"col": 13,
|
|
"text": "djE="
|
|
},
|
|
"type": "string",
|
|
"value": "v1"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"rules": [
|
|
{
|
|
"body": [
|
|
{
|
|
"index": 0,
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 4,
|
|
"col": 1,
|
|
"text": "ZGVmYXVsdA=="
|
|
},
|
|
"terms": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 4,
|
|
"col": 1,
|
|
"text": "ZGVmYXVsdA=="
|
|
},
|
|
"type": "boolean",
|
|
"value": true
|
|
}
|
|
}
|
|
],
|
|
"default": true,
|
|
"head": {
|
|
"name": "allow",
|
|
"value": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 4,
|
|
"col": 17,
|
|
"text": "ZmFsc2U="
|
|
},
|
|
"type": "boolean",
|
|
"value": false
|
|
},
|
|
"ref": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 4,
|
|
"col": 9,
|
|
"text": "YWxsb3c="
|
|
},
|
|
"type": "var",
|
|
"value": "allow"
|
|
}
|
|
],
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 4,
|
|
"col": 9,
|
|
"text": "YWxsb3cgPSBmYWxzZQ=="
|
|
}
|
|
},
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 4,
|
|
"col": 1,
|
|
"text": "ZGVmYXVsdA=="
|
|
}
|
|
},
|
|
{
|
|
"body": [
|
|
{
|
|
"index": 0,
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 3,
|
|
"text": "aW5wdXQubWV0aG9kID09ICJHRVQi"
|
|
},
|
|
"terms": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 16,
|
|
"text": "PT0="
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 16,
|
|
"text": "PT0="
|
|
},
|
|
"type": "var",
|
|
"value": "equal"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 3,
|
|
"text": "aW5wdXQubWV0aG9k"
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 3,
|
|
"text": "aW5wdXQ="
|
|
},
|
|
"type": "var",
|
|
"value": "input"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 9,
|
|
"text": "bWV0aG9k"
|
|
},
|
|
"type": "string",
|
|
"value": "method"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 6,
|
|
"col": 19,
|
|
"text": "IkdFVCI="
|
|
},
|
|
"type": "string",
|
|
"value": "GET"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"index": 1,
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 3,
|
|
"text": "aW5wdXQucGF0aCA9IFsiZ2V0VXNlciIsIHVzZXJd"
|
|
},
|
|
"terms": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 14,
|
|
"text": "PQ=="
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 14,
|
|
"text": "PQ=="
|
|
},
|
|
"type": "var",
|
|
"value": "eq"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 3,
|
|
"text": "aW5wdXQucGF0aA=="
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 3,
|
|
"text": "aW5wdXQ="
|
|
},
|
|
"type": "var",
|
|
"value": "input"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 9,
|
|
"text": "cGF0aA=="
|
|
},
|
|
"type": "string",
|
|
"value": "path"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 16,
|
|
"text": "WyJnZXRVc2VyIiwgdXNlcl0="
|
|
},
|
|
"type": "array",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 17,
|
|
"text": "ImdldFVzZXIi"
|
|
},
|
|
"type": "string",
|
|
"value": "getUser"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 7,
|
|
"col": 28,
|
|
"text": "dXNlcg=="
|
|
},
|
|
"type": "var",
|
|
"value": "user"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"index": 2,
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 3,
|
|
"text": "aW5wdXQudXNlciA9PSB1c2Vy"
|
|
},
|
|
"terms": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 14,
|
|
"text": "PT0="
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 14,
|
|
"text": "PT0="
|
|
},
|
|
"type": "var",
|
|
"value": "equal"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 3,
|
|
"text": "aW5wdXQudXNlcg=="
|
|
},
|
|
"type": "ref",
|
|
"value": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 3,
|
|
"text": "aW5wdXQ="
|
|
},
|
|
"type": "var",
|
|
"value": "input"
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 9,
|
|
"text": "dXNlcg=="
|
|
},
|
|
"type": "string",
|
|
"value": "user"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 8,
|
|
"col": 17,
|
|
"text": "dXNlcg=="
|
|
},
|
|
"type": "var",
|
|
"value": "user"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"head": {
|
|
"name": "allow",
|
|
"value": {
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 5,
|
|
"col": 9,
|
|
"text": "dHJ1ZQ=="
|
|
},
|
|
"type": "boolean",
|
|
"value": true
|
|
},
|
|
"ref": [
|
|
{
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 5,
|
|
"col": 1,
|
|
"text": "YWxsb3c="
|
|
},
|
|
"type": "var",
|
|
"value": "allow"
|
|
}
|
|
],
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 5,
|
|
"col": 1,
|
|
"text": "YWxsb3cgPSB0cnVl"
|
|
}
|
|
},
|
|
"location": {
|
|
"file": "TEMPDIR/x.rego",
|
|
"row": 5,
|
|
"col": 1,
|
|
"text": "YWxsb3cgPSB0cnVlIGlmIHsKICBpbnB1dC5tZXRob2QgPT0gIkdFVCIKICBpbnB1dC5wYXRoID0gWyJnZXRVc2VyIiwgdXNlcl0KICBpbnB1dC51c2VyID09IHVzZXIKfQ=="
|
|
}
|
|
}
|
|
]
|
|
}
|
|
`, "TEMPDIR", tempDirPath)
|
|
|
|
gotLines := strings.Split(string(stdout), "\n")
|
|
wantLines := strings.Split(expectedOutput, "\n")
|
|
min := len(gotLines)
|
|
if len(wantLines) < min {
|
|
min = len(wantLines)
|
|
}
|
|
|
|
for i := range min {
|
|
if gotLines[i] != wantLines[i] {
|
|
t.Fatalf("Expected line %d to be\n%v\n, got\n%v", i, wantLines[i], gotLines[i])
|
|
}
|
|
}
|
|
|
|
if len(gotLines) != len(wantLines) {
|
|
t.Fatalf("Expected %d lines, got %d", len(wantLines), len(gotLines))
|
|
}
|
|
}
|
|
|
|
func TestParseJSONOutputComments(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `package x
|
|
|
|
# comment
|
|
p = 1
|
|
`,
|
|
}
|
|
errc, stdout, stderr, _ := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.JSON, formats.Pretty),
|
|
jsonInclude: "comments",
|
|
})
|
|
if errc != 0 {
|
|
t.Fatalf("Expected exit code 0, got %v", errc)
|
|
}
|
|
if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
|
|
expectedCommentTextValue := "IGNvbW1lbnQ="
|
|
|
|
if !strings.Contains(string(stdout), expectedCommentTextValue) {
|
|
t.Fatalf("Comment text value %q missing in output: %s", expectedCommentTextValue, string(stdout))
|
|
}
|
|
}
|
|
|
|
func TestParse_DefaultRegoVersion(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
module string
|
|
expErrs []string
|
|
}{
|
|
{
|
|
note: "v0 module",
|
|
module: `package test
|
|
a[x] {
|
|
x := 42
|
|
}`,
|
|
expErrs: []string{
|
|
"`if` keyword is required before rule body",
|
|
"`contains` keyword is required for partial set rules",
|
|
},
|
|
},
|
|
{
|
|
note: "v1 module",
|
|
module: `package test
|
|
a contains x if {
|
|
x := 42
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
files := map[string]string{
|
|
"test.rego": tc.module,
|
|
}
|
|
|
|
_, _, stderr, _ := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.Pretty, formats.JSON),
|
|
})
|
|
|
|
if len(tc.expErrs) > 0 {
|
|
errs := string(stderr)
|
|
for _, expErr := range tc.expErrs {
|
|
if !strings.Contains(errs, expErr) {
|
|
t.Fatalf("Expected error:\n\n%q\n\ngot:\n\n%s", expErr, errs)
|
|
}
|
|
}
|
|
} else if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseCompatibleFlags(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
v0Compatible bool
|
|
v1Compatible bool
|
|
policy string
|
|
expErrs []string
|
|
}{
|
|
{
|
|
note: "v0, keywords not used",
|
|
v0Compatible: true,
|
|
policy: `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v0, keywords not imported",
|
|
v0Compatible: true,
|
|
policy: `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
expErrs: []string{
|
|
"var cannot be used for rule name",
|
|
},
|
|
},
|
|
{
|
|
note: "v0, keywords imported",
|
|
v0Compatible: true,
|
|
policy: `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v0, rego.v1 imported",
|
|
v0Compatible: true,
|
|
policy: `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
|
|
{
|
|
note: "v1, keywords not used",
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
expErrs: []string{
|
|
"`if` keyword is required before rule body",
|
|
"`contains` keyword is required for partial set rules",
|
|
},
|
|
},
|
|
{
|
|
note: "v1, keywords not imported",
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v1, keywords imported",
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v1, rego.v1 imported",
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
|
|
// v0 takes precedence over v1
|
|
{
|
|
note: "v0+v1, keywords not used",
|
|
v0Compatible: true,
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v0+v1, keywords not imported",
|
|
v0Compatible: true,
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
expErrs: []string{
|
|
"var cannot be used for rule name",
|
|
},
|
|
},
|
|
{
|
|
note: "v0+1, keywords imported",
|
|
v0Compatible: true,
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v0+v1, rego.v1 imported",
|
|
v0Compatible: true,
|
|
v1Compatible: true,
|
|
policy: `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
files := map[string]string{
|
|
"policy.rego": tc.policy,
|
|
}
|
|
|
|
_, _, stderr, _ := testParse(t, files, &parseParams{
|
|
format: formats.Flag(formats.Pretty, formats.JSON),
|
|
v0Compatible: tc.v0Compatible,
|
|
v1Compatible: tc.v1Compatible,
|
|
})
|
|
|
|
if len(tc.expErrs) > 0 {
|
|
errs := string(stderr)
|
|
for _, expErr := range tc.expErrs {
|
|
if !strings.Contains(errs, expErr) {
|
|
t.Fatalf("Expected error:\n\n%q\n\ngot:\n\n%s", expErr, errs)
|
|
}
|
|
}
|
|
} else if len(stderr) > 0 {
|
|
t.Fatalf("Expected no stderr output, got:\n%s\n", string(stderr))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Runs parse and returns the exit code, stdout, and stderr contents
|
|
func testParse(t *testing.T, files map[string]string, params *parseParams) (int, []byte, []byte, string) {
|
|
t.Helper()
|
|
|
|
stdout := new(bytes.Buffer)
|
|
stderr := new(bytes.Buffer)
|
|
var errc int
|
|
|
|
var tempDirUsed string
|
|
test.WithTempFS(files, func(path string) {
|
|
var args []string
|
|
for file := range files {
|
|
args = append(args, filepath.Join(path, file))
|
|
}
|
|
errc = parse(args, params, stdout, stderr)
|
|
|
|
tempDirUsed = path
|
|
})
|
|
|
|
return errc, stdout.Bytes(), stderr.Bytes(), tempDirUsed
|
|
}
|