mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
7bb6dbe36b
Moving (most) source to v1 root package to prepare for v0/v1 API separation. Signed-off-by: Johan Fylling <johan.dev@fylling.se>
1054 lines
21 KiB
Go
1054 lines
21 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/v1/ast"
|
|
"github.com/open-policy-agent/opa/v1/format"
|
|
"github.com/open-policy-agent/opa/v1/util/test"
|
|
)
|
|
|
|
const formattedV0 = `package test
|
|
|
|
p {
|
|
a == 1
|
|
true
|
|
1 + 3
|
|
}
|
|
`
|
|
|
|
const formattedV1 = `package test
|
|
|
|
p if {
|
|
a == 1
|
|
true
|
|
1 + 3
|
|
}
|
|
`
|
|
|
|
const unformattedV0 = `
|
|
package test
|
|
|
|
p { a == 1; true
|
|
1 + 3
|
|
}
|
|
|
|
|
|
`
|
|
|
|
const unformattedV1 = `
|
|
package test
|
|
|
|
p if{ a == 1; true
|
|
1 + 3
|
|
}
|
|
|
|
|
|
`
|
|
|
|
const singleWrongArity = `package test
|
|
import rego.v1
|
|
|
|
p if {
|
|
a := 1
|
|
b := 2
|
|
plus(a, b, c) == 3
|
|
}
|
|
`
|
|
|
|
const MultipleWrongArity = `package test
|
|
import rego.v1
|
|
|
|
p if {
|
|
x:=5
|
|
y:=7
|
|
z:=6
|
|
plus([x, y]) == 3
|
|
and(true, false, false) == false
|
|
plus(a, x, y, z)
|
|
}
|
|
`
|
|
|
|
const ComprehensionCommentShouldNotMoveFormatted = `package test
|
|
|
|
f(x) := [x |
|
|
some v in x
|
|
|
|
# regal ignore:external-reference
|
|
x in data.foo
|
|
][0]
|
|
`
|
|
|
|
const ComprehensionCommentShouldNotMoveUnformatted = `package test
|
|
|
|
f(x) := [x |
|
|
some v in x
|
|
# regal ignore:external-reference
|
|
x in data.foo
|
|
][0]
|
|
`
|
|
|
|
type errorWriter struct {
|
|
ErrMsg string
|
|
}
|
|
|
|
func (ew errorWriter) Write([]byte) (int, error) {
|
|
return 0, errors.New(ew.ErrMsg)
|
|
}
|
|
|
|
func TestFmtFormatFile(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
unformatted string
|
|
formatted string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{v0Compatible: true},
|
|
unformatted: unformattedV0,
|
|
formatted: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{v1Compatible: true},
|
|
unformatted: unformattedV1,
|
|
formatted: formattedV1,
|
|
},
|
|
{
|
|
note: "comment in comprehension",
|
|
params: fmtCommandParams{v1Compatible: true},
|
|
unformatted: ComprehensionCommentShouldNotMoveUnformatted,
|
|
formatted: ComprehensionCommentShouldNotMoveFormatted,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.unformatted,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
actual := stdout.String()
|
|
if actual != tc.formatted {
|
|
t.Fatalf("Expected:\n%s\n\nGot:\n%s\n\n", tc.formatted, actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFormatFileFailToReadFile(t *testing.T) {
|
|
|
|
params := fmtCommandParams{
|
|
diff: true,
|
|
}
|
|
|
|
var stdout = bytes.Buffer{}
|
|
|
|
files := map[string]string{
|
|
"policy.rego": unformattedV0,
|
|
}
|
|
|
|
notThere := "notThere.rego"
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(¶ms, &stdout, notThere, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Expected error, found none")
|
|
}
|
|
|
|
actual := err.Error()
|
|
|
|
if !strings.Contains(actual, notThere) {
|
|
t.Fatalf("Expected error message to include %s, got:\n%s\n\n", notThere, actual)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFmtFormatFileNoChanges(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{v0Compatible: true},
|
|
module: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{v1Compatible: true},
|
|
module: formattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
actual := stdout.String()
|
|
if actual != tc.module {
|
|
t.Fatalf("Expected:%s\n\nGot:\n%s\n\n", tc.module, actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFailFormatFileNoChanges(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
fail: true,
|
|
diff: true,
|
|
},
|
|
module: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
fail: true,
|
|
diff: true,
|
|
},
|
|
module: formattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
actual := stdout.String()
|
|
if len(actual) > 0 {
|
|
t.Fatalf("Expected no output, got:\n%v\n\n", actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFormatFileDiff(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
diff: true,
|
|
},
|
|
module: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
diff: true,
|
|
},
|
|
module: formattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
actual := stdout.String()
|
|
|
|
if len(actual) > 0 {
|
|
t.Fatalf("Expected no output, got:\n%s\n\n", actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFormatFileFailToPrintDiff(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
diff: true,
|
|
},
|
|
module: unformattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
diff: true,
|
|
},
|
|
module: unformattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
errMsg := "io.Write error"
|
|
var stdout = errorWriter{ErrMsg: errMsg}
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Expected error, found none")
|
|
}
|
|
|
|
actual := err.Error()
|
|
|
|
if !strings.Contains(actual, errMsg) {
|
|
t.Fatalf("Expected error message to include %s, got:\n%s\n\n", errMsg, actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFormatFileList(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
list: true,
|
|
},
|
|
module: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
list: true,
|
|
},
|
|
module: formattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(stdout.String())
|
|
|
|
if len(actual) > 0 {
|
|
t.Fatalf("Expected no output, got:\n%s\n\n", actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFailFormatFileList(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
fail: true,
|
|
list: true,
|
|
},
|
|
module: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
fail: true,
|
|
list: true,
|
|
},
|
|
module: formattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(stdout.String())
|
|
if len(actual) > 0 {
|
|
t.Fatalf("Expected no output, got:\n%v\n\n", actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFailFormatFileChangesList(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
fail: true,
|
|
list: true,
|
|
},
|
|
module: unformattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
fail: true,
|
|
list: true,
|
|
},
|
|
module: unformattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(stdout.String())
|
|
if len(actual) == 0 {
|
|
t.Fatalf("Expected output, got:\n%v\n\n", actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFailFileNoChanges(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
fail: true,
|
|
},
|
|
module: formattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
fail: true,
|
|
},
|
|
module: formattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, io.Discard, policyFile, info, err)
|
|
if err != nil {
|
|
t.Fatalf("Expected error but did not receive one")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFailFileChanges(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
fail: true,
|
|
},
|
|
module: unformattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
fail: true,
|
|
},
|
|
module: unformattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, io.Discard, policyFile, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtFailFileChangesDiff(t *testing.T) {
|
|
cases := []struct {
|
|
note string
|
|
params fmtCommandParams
|
|
module string
|
|
}{
|
|
{
|
|
note: "v0",
|
|
params: fmtCommandParams{
|
|
v0Compatible: true,
|
|
diff: true,
|
|
fail: true,
|
|
},
|
|
module: unformattedV0,
|
|
},
|
|
{
|
|
note: "v1",
|
|
params: fmtCommandParams{
|
|
v1Compatible: true,
|
|
diff: true,
|
|
fail: true,
|
|
},
|
|
module: unformattedV1,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.module,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(&tc.params, &stdout, policyFile, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
actual := strings.TrimSpace(stdout.String())
|
|
if len(actual) == 0 {
|
|
t.Fatalf("Expected output, got:\n%v\n\n", actual)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtSingleWrongArityError(t *testing.T) {
|
|
params := fmtCommandParams{}
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": singleWrongArity,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(¶ms, &stdout, policyFile, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Expected error but did not receive one")
|
|
}
|
|
|
|
loc := ast.Location{File: policyFile, Row: 7}
|
|
errExp := ast.NewError(ast.TypeErr, &loc, "%s: %s", "plus", "arity mismatch")
|
|
errExp.Details = &format.ArityFormatErrDetail{
|
|
Have: []string{"var", "var", "var"},
|
|
Want: []string{"number", "number"},
|
|
}
|
|
expectedErrs := ast.Errors(make([]*ast.Error, 1))
|
|
expectedErrs[0] = errExp
|
|
expectedSingleWrongArityErr := newError("failed to format Rego source file: %v", fmt.Errorf("%s: %v", policyFile, expectedErrs))
|
|
|
|
if err != expectedSingleWrongArityErr {
|
|
t.Fatalf("Expected:%s\n\nGot:%s\n\n", expectedSingleWrongArityErr, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFmtMultipleWrongArityError(t *testing.T) {
|
|
params := fmtCommandParams{}
|
|
var stdout bytes.Buffer
|
|
|
|
files := map[string]string{
|
|
"policy.rego": MultipleWrongArity,
|
|
}
|
|
|
|
test.WithTempFS(files, func(path string) {
|
|
policyFile := filepath.Join(path, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(¶ms, &stdout, policyFile, info, err)
|
|
if err == nil {
|
|
t.Fatalf("Expected error but did not receive one")
|
|
}
|
|
|
|
locations := []ast.Location{
|
|
{File: policyFile, Row: 8},
|
|
{File: policyFile, Row: 9},
|
|
{File: policyFile, Row: 10},
|
|
}
|
|
haveStrings := [][]string{
|
|
{"array"},
|
|
{"boolean", "boolean", "boolean"},
|
|
{"var", "var", "var", "var"},
|
|
}
|
|
wantStrings := [][]string{
|
|
{"number", "number"},
|
|
{"set[any]", "set[any]"},
|
|
{"number", "number"},
|
|
}
|
|
operators := []string{
|
|
"plus",
|
|
"and",
|
|
"plus",
|
|
}
|
|
expectedErrs := ast.Errors(make([]*ast.Error, 3))
|
|
for i := 0; i < 3; i++ {
|
|
loc := locations[i]
|
|
errExp := ast.NewError(ast.TypeErr, &loc, "%s: %s", operators[i], "arity mismatch")
|
|
errExp.Details = &format.ArityFormatErrDetail{
|
|
Have: haveStrings[i],
|
|
Want: wantStrings[i],
|
|
}
|
|
expectedErrs[i] = errExp
|
|
}
|
|
expectedMultipleWrongArityErr := newError("failed to format Rego source file: %v", fmt.Errorf("%s: %v", policyFile, expectedErrs))
|
|
|
|
if err != expectedMultipleWrongArityErr {
|
|
t.Fatalf("Expected:%s\n\nGot:%s\n\n", expectedMultipleWrongArityErr, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFmtRegoV1(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
input string
|
|
expected string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
note: "no future imports",
|
|
input: `package test
|
|
p {
|
|
input.x == 1
|
|
}
|
|
|
|
q.foo {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
expected: `package test
|
|
|
|
import rego.v1
|
|
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" if {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
note: "future imports",
|
|
input: `package test
|
|
import future.keywords
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
expected: `package test
|
|
|
|
import rego.v1
|
|
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" if {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
note: "duplicate imports",
|
|
input: `package test
|
|
import data.foo
|
|
import data.bar as foo
|
|
`,
|
|
expectedErr: `failed to format Rego source file: 1 error occurred: %ROOT%/policy.rego:3: rego_compile_error: import must not shadow import data.foo`,
|
|
},
|
|
{
|
|
note: "root document overrides",
|
|
input: `package test
|
|
input {
|
|
1 == 1
|
|
}
|
|
|
|
p {
|
|
data := 2
|
|
}`,
|
|
expectedErr: `failed to format Rego source file: 2 errors occurred:
|
|
%ROOT%/policy.rego:2: rego_compile_error: rules must not shadow input (use a different rule name)
|
|
%ROOT%/policy.rego:7: rego_compile_error: variables must not shadow data (use a different variable name)`,
|
|
},
|
|
{
|
|
note: "deprecated built-in",
|
|
input: `package test
|
|
p {
|
|
any([true, false])
|
|
}
|
|
|
|
q := all([true, false])
|
|
`,
|
|
expectedErr: `failed to format Rego source file: 2 errors occurred:
|
|
%ROOT%/policy.rego:3: rego_type_error: deprecated built-in function calls in expression: any
|
|
%ROOT%/policy.rego:6: rego_type_error: deprecated built-in function calls in expression: all`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
params := fmtCommandParams{
|
|
// Locking rego-version to v0, as it's only then the --rego-v1 flag is relevant
|
|
v0Compatible: true,
|
|
regoV1: true,
|
|
}
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.input,
|
|
}
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
test.WithTempFS(files, func(root string) {
|
|
policyFile := filepath.Join(root, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(¶ms, &stdout, policyFile, info, err)
|
|
|
|
if tc.expectedErr != "" {
|
|
if err == nil {
|
|
t.Fatalf("Expected error but got: %s", stdout.String())
|
|
}
|
|
expectedErr := strings.ReplaceAll(tc.expectedErr, "%ROOT%", root)
|
|
var actualErr string
|
|
switch err := err.(type) {
|
|
case fmtError:
|
|
actualErr = err.msg
|
|
default:
|
|
actualErr = err.Error()
|
|
}
|
|
if actualErr != expectedErr {
|
|
t.Fatalf("Expected error:\n\n%s\n\nGot error:\n\n%s\n\n", expectedErr, actualErr)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
actual := stdout.String()
|
|
if actual != tc.expected {
|
|
t.Fatalf("Expected:%s\n\nGot:\n%s\n\n", tc.expected, actual)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFmtV1Compatible(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
input string
|
|
expected string
|
|
expectedErrs []string
|
|
}{
|
|
{
|
|
note: "no keywords used",
|
|
input: `package test
|
|
p {
|
|
input.x == 1
|
|
}
|
|
|
|
q.foo {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
expectedErrs: []string{
|
|
"policy.rego:2: rego_parse_error: `if` keyword is required before rule body",
|
|
"policy.rego:6: rego_parse_error: `if` keyword is required before rule body",
|
|
"policy.rego:6: rego_parse_error: `contains` keyword is required for partial set rules",
|
|
},
|
|
},
|
|
{
|
|
note: "no imports",
|
|
input: `package test
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" if {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
expected: `package test
|
|
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" if {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
note: "future imports",
|
|
input: `package test
|
|
import future.keywords
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" if {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
expected: `package test
|
|
|
|
p if {
|
|
input.x == 1
|
|
}
|
|
|
|
q contains "foo" if {
|
|
input.x == 2
|
|
}
|
|
`,
|
|
},
|
|
{
|
|
note: "duplicate imports",
|
|
input: `package test
|
|
import data.foo
|
|
import data.bar as foo
|
|
`,
|
|
expectedErrs: []string{
|
|
`policy.rego:3: rego_compile_error: import must not shadow import data.foo`,
|
|
},
|
|
},
|
|
{
|
|
note: "root document overrides",
|
|
input: `package test
|
|
input if {
|
|
1 == 1
|
|
}
|
|
|
|
p if {
|
|
data := 2
|
|
}`,
|
|
expectedErrs: []string{
|
|
`policy.rego:2: rego_compile_error: rules must not shadow input (use a different rule name)`,
|
|
`policy.rego:7: rego_compile_error: variables must not shadow data (use a different variable name)`,
|
|
},
|
|
},
|
|
{
|
|
note: "deprecated built-in",
|
|
input: `package test
|
|
p if {
|
|
any([true, false])
|
|
}
|
|
|
|
q := all([true, false])
|
|
`,
|
|
expectedErrs: []string{
|
|
`policy.rego:3: rego_type_error: deprecated built-in function calls in expression: any`,
|
|
`policy.rego:6: rego_type_error: deprecated built-in function calls in expression: all`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
params := fmtCommandParams{
|
|
v1Compatible: true,
|
|
}
|
|
|
|
files := map[string]string{
|
|
"policy.rego": tc.input,
|
|
}
|
|
|
|
var stdout bytes.Buffer
|
|
|
|
test.WithTempFS(files, func(root string) {
|
|
policyFile := filepath.Join(root, "policy.rego")
|
|
info, err := os.Stat(policyFile)
|
|
err = formatFile(¶ms, &stdout, policyFile, info, err)
|
|
|
|
if len(tc.expectedErrs) > 0 {
|
|
if err == nil {
|
|
t.Fatalf("Expected error but got: %s", stdout.String())
|
|
}
|
|
|
|
for _, expectedErr := range tc.expectedErrs {
|
|
var actualErr string
|
|
switch err := err.(type) {
|
|
case fmtError:
|
|
actualErr = err.msg
|
|
default:
|
|
actualErr = err.Error()
|
|
}
|
|
if !strings.Contains(actualErr, expectedErr) {
|
|
t.Fatalf("Expected error to contain:\n\n%s\n\nGot error:\n\n%s\n\n", expectedErr, actualErr)
|
|
}
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
actual := stdout.String()
|
|
if actual != tc.expected {
|
|
t.Fatalf("Expected:%s\n\nGot:\n%s\n\n", tc.expected, actual)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|