Files
releases/cmd/parse_test.go
T
2023-09-21 09:40:07 +01:00

954 lines
21 KiB
Go

package cmd
import (
"bytes"
"path/filepath"
"strings"
"testing"
"github.com/open-policy-agent/opa/util"
"github.com/open-policy-agent/opa/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: util.NewEnumFlag(parseFormatJSON, []string{parseFormatPretty, parseFormatJSON}),
})
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: util.NewEnumFlag(parseFormatJSON, []string{parseFormatPretty, parseFormatJSON}),
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.Replace(`{
"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, -1)
gotLines := strings.Split(string(stdout), "\n")
wantLines := strings.Split(expectedOutput, "\n")
min := len(gotLines)
if len(wantLines) < min {
min = len(wantLines)
}
for i := 0; i < min; i++ {
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: util.NewEnumFlag(parseFormatJSON, []string{parseFormatPretty, parseFormatJSON}),
})
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: util.NewEnumFlag(parseFormatJSON, []string{parseFormatPretty, parseFormatJSON}),
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.Replace(`{
"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, -1)
gotLines := strings.Split(string(stdout), "\n")
wantLines := strings.Split(expectedOutput, "\n")
min := len(gotLines)
if len(wantLines) < min {
min = len(wantLines)
}
for i := 0; i < min; i++ {
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
default allow = false
allow = true {
input.method == "GET"
input.path = ["getUser", user]
input.user == user
}
`,
}
errc, stdout, stderr, tempDirPath := testParse(t, files, &parseParams{
format: util.NewEnumFlag(parseFormatJSON, []string{parseFormatPretty, parseFormatJSON}),
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.Replace(`{
"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": 1,
"text": "ZGVmYXVsdA=="
},
"terms": {
"location": {
"file": "TEMPDIR/x.rego",
"row": 3,
"col": 1,
"text": "ZGVmYXVsdA=="
},
"type": "boolean",
"value": true
}
}
],
"default": true,
"head": {
"name": "allow",
"value": {
"location": {
"file": "TEMPDIR/x.rego",
"row": 3,
"col": 17,
"text": "ZmFsc2U="
},
"type": "boolean",
"value": false
},
"ref": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 3,
"col": 9,
"text": "YWxsb3c="
},
"type": "var",
"value": "allow"
}
],
"location": {
"file": "TEMPDIR/x.rego",
"row": 3,
"col": 9,
"text": "YWxsb3cgPSBmYWxzZQ=="
}
},
"location": {
"file": "TEMPDIR/x.rego",
"row": 3,
"col": 1,
"text": "ZGVmYXVsdA=="
}
},
{
"body": [
{
"index": 0,
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 3,
"text": "aW5wdXQubWV0aG9kID09ICJHRVQi"
},
"terms": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 16,
"text": "PT0="
},
"type": "ref",
"value": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 16,
"text": "PT0="
},
"type": "var",
"value": "equal"
}
]
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 3,
"text": "aW5wdXQubWV0aG9k"
},
"type": "ref",
"value": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 3,
"text": "aW5wdXQ="
},
"type": "var",
"value": "input"
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 9,
"text": "bWV0aG9k"
},
"type": "string",
"value": "method"
}
]
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 5,
"col": 19,
"text": "IkdFVCI="
},
"type": "string",
"value": "GET"
}
]
},
{
"index": 1,
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 3,
"text": "aW5wdXQucGF0aCA9IFsiZ2V0VXNlciIsIHVzZXJd"
},
"terms": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 14,
"text": "PQ=="
},
"type": "ref",
"value": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 14,
"text": "PQ=="
},
"type": "var",
"value": "eq"
}
]
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 3,
"text": "aW5wdXQucGF0aA=="
},
"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": "cGF0aA=="
},
"type": "string",
"value": "path"
}
]
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 16,
"text": "WyJnZXRVc2VyIiwgdXNlcl0="
},
"type": "array",
"value": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 17,
"text": "ImdldFVzZXIi"
},
"type": "string",
"value": "getUser"
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 6,
"col": 28,
"text": "dXNlcg=="
},
"type": "var",
"value": "user"
}
]
}
]
},
{
"index": 2,
"location": {
"file": "TEMPDIR/x.rego",
"row": 7,
"col": 3,
"text": "aW5wdXQudXNlciA9PSB1c2Vy"
},
"terms": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 7,
"col": 14,
"text": "PT0="
},
"type": "ref",
"value": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 7,
"col": 14,
"text": "PT0="
},
"type": "var",
"value": "equal"
}
]
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 7,
"col": 3,
"text": "aW5wdXQudXNlcg=="
},
"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": "dXNlcg=="
},
"type": "string",
"value": "user"
}
]
},
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 7,
"col": 17,
"text": "dXNlcg=="
},
"type": "var",
"value": "user"
}
]
}
],
"head": {
"name": "allow",
"value": {
"location": {
"file": "TEMPDIR/x.rego",
"row": 4,
"col": 9,
"text": "dHJ1ZQ=="
},
"type": "boolean",
"value": true
},
"ref": [
{
"location": {
"file": "TEMPDIR/x.rego",
"row": 4,
"col": 1,
"text": "YWxsb3c="
},
"type": "var",
"value": "allow"
}
],
"location": {
"file": "TEMPDIR/x.rego",
"row": 4,
"col": 1,
"text": "YWxsb3cgPSB0cnVl"
}
},
"location": {
"file": "TEMPDIR/x.rego",
"row": 4,
"col": 1,
"text": "YWxsb3cgPSB0cnVlIHsKICBpbnB1dC5tZXRob2QgPT0gIkdFVCIKICBpbnB1dC5wYXRoID0gWyJnZXRVc2VyIiwgdXNlcl0KICBpbnB1dC51c2VyID09IHVzZXIKfQ=="
}
}
]
}
`, "TEMPDIR", tempDirPath, -1)
gotLines := strings.Split(string(stdout), "\n")
wantLines := strings.Split(expectedOutput, "\n")
min := len(gotLines)
if len(wantLines) < min {
min = len(wantLines)
}
for i := 0; i < min; i++ {
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: util.NewEnumFlag(parseFormatJSON, []string{parseFormatPretty, parseFormatJSON}),
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))
}
}
// 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
}