Files
releases/cmd/check_test.go
T
Anders Eknert afb30d3f9d Add gocritic linter, fix a bunch of stuff (#7377)
Brace yourselves! For there are many touched files here. No changes
in semantics however.

Spent a long time trying out the various optional rules gocritic
provides, and settled for a few of them. There are more I really
like, but that would take many hours to address across the codebase.

Perhaps others find gocritic too pedantic? If so, we can merge the
fixes without enabling the rule.

Signed-off-by: Anders Eknert <anders@styra.com>
2025-02-24 16:28:41 +01:00

1293 lines
29 KiB
Go

// Copyright 2022 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 (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/open-policy-agent/opa/internal/file/archive"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/util/test"
)
func TestCheckRespectsCapabilities(t *testing.T) {
tests := []struct {
note string
caps string
policy string
err string
bundleMode bool // check with "-b" flag
}{
{
note: "builtin defined in caps",
caps: `{
"builtins": [
{
"name": "is_foo",
"decl": {
"args": [
{
"type": "string"
}
],
"result": {
"type": "boolean"
},
"type": "function"
}
}
]
}`,
policy: `package test
p { is_foo("bar") }`,
},
{
note: "future kw NOT defined in caps",
caps: func() string {
c := ast.CapabilitiesForThisVersion()
c.FutureKeywords = []string{"in"}
c.Features = []string{}
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
return string(j)
}(),
policy: `package test
import future.keywords.if
import future.keywords.in
p if "opa" in input.tools`,
err: "rego_parse_error: unexpected keyword, must be one of [in]",
},
{
note: "future kw NOT defined in caps, rego-v1 feature",
caps: func() string {
c := ast.CapabilitiesForThisVersion()
c.FutureKeywords = []string{"in"}
c.Features = []string{ast.FeatureRegoV1}
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
return string(j)
}(),
policy: `package test
import future.keywords.if
import future.keywords.in
p if "opa" in input.tools`,
},
{
note: "future kw are defined in caps",
caps: func() string {
c := ast.CapabilitiesForThisVersion()
c.FutureKeywords = []string{"in", "if"}
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
return string(j)
}(),
policy: `package test
import future.keywords.if
import future.keywords.in
p if "opa" in input.tools`,
},
{
note: "rego.v1 imported but NOT defined in capabilities",
caps: func() string {
c := ast.CapabilitiesForThisVersion()
c.Features = []string{}
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
return string(j)
}(),
policy: `package test
import rego.v1`,
err: "rego_parse_error: invalid import, `rego.v1` is not supported by current capabilities",
},
{
note: "rego.v1 imported AND defined in capabilities",
caps: func() string {
c := ast.CapabilitiesForThisVersion()
c.Features = []string{ast.FeatureRegoV1Import}
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
return string(j)
}(),
policy: `package test
import rego.v1`,
},
{
note: "rego.v1 imported AND rego-v1 in capabilities",
caps: func() string {
c := ast.CapabilitiesForThisVersion()
c.Features = []string{ast.FeatureRegoV1}
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
return string(j)
}(),
policy: `package test
import rego.v1`,
},
}
// add same tests for bundle-mode == true:
for i := range tests {
tc := tests[i]
tc.bundleMode = true
tc.note += " (as bundle)"
tests = append(tests, tc)
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
files := map[string]string{
"capabilities.json": tc.caps,
"test.rego": tc.policy,
}
test.WithTempFS(files, func(root string) {
caps := newcapabilitiesFlag()
if err := caps.Set(path.Join(root, "capabilities.json")); err != nil {
t.Fatal(err)
}
params := newCheckParams()
params.capabilities = caps
params.bundleMode = tc.bundleMode
// Capabilities in test cases is pre v1
params.v0Compatible = true
err := checkModules(params, []string{root})
switch {
case err != nil && tc.err != "":
if !strings.Contains(err.Error(), tc.err) {
t.Fatalf("expected err %v, got %v", tc.err, err)
}
return // don't read back bundle below
case err != nil && tc.err == "":
t.Fatalf("unexpected error: %v", err)
case err == nil && tc.err != "":
t.Fatalf("expected error %v, got nil", tc.err)
}
})
})
}
}
func testCheckWithSchemasAnnotationButNoSchemaFlag(policy string) error {
files := map[string]string{
"test.rego": policy,
}
var err error
test.WithTempFS(files, func(path string) {
params := newCheckParams()
err = checkModules(params, []string{path})
})
return err
}
func TestCheckIgnoresNonRegoFiles(t *testing.T) {
files := map[string]string{
"test.rego": `package test`,
"test.json": `{"foo": "bar"}`,
"test.yaml": `foo: bar`,
}
test.WithTempFS(files, func(root string) {
params := newCheckParams()
err := checkModules(params, []string{root})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
func TestCheckIgnoreBundleMode(t *testing.T) {
t.Parallel()
files := map[string]string{
"ignore.rego": `invalid rego`,
"include.rego": `package valid`,
}
test.WithTempFS(files, func(root string) {
params := newCheckParams()
params.ignore = []string{"ignore.rego"}
params.bundleMode = true
err := checkModules(params, []string{root})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
func TestCheckFailsOnInvalidRego(t *testing.T) {
files := map[string]string{
"test.rego": `package test
{}`,
"test.json": `{"foo": "bar"}`,
}
expectedError := "rego_parse_error: object cannot be used for rule name"
test.WithTempFS(files, func(root string) {
params := newCheckParams()
err := checkModules(params, []string{root})
if err == nil {
t.Fatalf("expected error %v but received none", expectedError)
}
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("expected error %v but received %v", expectedError, err)
}
})
}
// Assert that 'schemas' annotations with schema refs are only informing the type checker when the --schema flag is used
func TestCheckWithSchemasAnnotationButNoSchemaFlag(t *testing.T) {
policiesWithSchemaRef := []string{`
package test
import rego.v1
# METADATA
# schemas:
# - input: schema["input"]
p if {
rego.metadata.rule() # presence of rego.metadata.* calls must not trigger unwanted schema evaluation
input.foo == 42 # type mismatch with schema that should be ignored
}`,
`
package p
# METADATA
# schemas:
# - data.p.x: schema["nope"]
bug := data.p.x
`}
for i, pol := range policiesWithSchemaRef {
err := testCheckWithSchemasAnnotationButNoSchemaFlag(pol)
if err != nil {
t.Fatalf("unexpected error from eval policy %d with schema ref: %v", i, err)
}
}
policyWithInlinedSchema := `
package test
import rego.v1
# METADATA
# schemas:
# - input.foo: {"type": "boolean"}
p if {
rego.metadata.rule() # presence of rego.metadata.* calls must not trigger unwanted schema evaluation
input.foo == 42 # type mismatch with schema that should be ignored
}`
err := testCheckWithSchemasAnnotationButNoSchemaFlag(policyWithInlinedSchema)
// We expect an error here, as inlined schemas are always used for type checking
if !strings.Contains(err.Error(), "rego_type_error: match error") {
t.Fatalf("unexpected error from eval with inlined schema, got: %v", err)
}
}
func TestCheckRegoV1(t *testing.T) {
cases := []struct {
note string
policy string
expErrs []string
}{
{
note: "rego.v1 imported, v1 compliant",
policy: `package test
import rego.v1
p contains x if {
x := [1,2,3]
}`,
},
{
note: "rego.v1 imported, NOT v1 compliant (parser)",
policy: `package test
import rego.v1
p contains x {
x := [1,2,3]
}
q.r`,
expErrs: []string{
"test.rego:3: rego_parse_error: `if` keyword is required before rule body",
"test.rego:7: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "rego.v1 imported, NOT v1 compliant (compiler)",
policy: `package test
import rego.v1
import data.foo
import data.bar as foo
`,
expErrs: []string{
"test.rego:5: rego_compile_error: import must not shadow import data.foo",
},
},
{
note: "keywords imported, v1 compliant",
policy: `package test
import future.keywords.if
import future.keywords.contains
p contains x if {
x := [1,2,3]
}`,
},
{
note: "keywords imported, NOT v1 compliant",
policy: `package test
import future.keywords.contains
p contains x {
x := [1,2,3]
}
q.r`,
expErrs: []string{
"test.rego:3: rego_parse_error: `if` keyword is required before rule body",
"test.rego:7: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "keywords imported, NOT v1 compliant (compiler)",
policy: `package test
import future.keywords.if
input := 1 if {
1 == 2
}`,
expErrs: []string{
"test.rego:4: rego_compile_error: rules must not shadow input (use a different rule name)",
},
},
{
note: "no imports, v1 compliant",
policy: `package test
p := 1
`,
},
{
note: "no imports, NOT v1 compliant but v0 compliant (compiler)",
policy: `package test
p.x`,
expErrs: []string{
"test.rego:2: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "no imports, v1 compliant but NOT v0 compliant",
policy: `package test
p contains x if {
x := [1,2,3]
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: var cannot be used for rule name", // This error actually appears three times: once for 'p'; once for 'contains'; and once for 'x'. All are interpreted as [invalid] rule declarations with no value and body.
"test.rego:2: rego_parse_error: `if` keyword is required before rule body",
},
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
files := map[string]string{
"test.rego": tc.policy,
}
test.WithTempFS(files, func(root string) {
params := newCheckParams()
params.regoV1 = true
err := checkModules(params, []string{root})
switch {
case err != nil && len(tc.expErrs) > 0:
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("expected err:\n\n%v\n\ngot:\n\n%v", expErr, err)
}
}
return // don't read back bundle below
case err != nil && len(tc.expErrs) == 0:
t.Fatalf("unexpected error: %v", err)
case err == nil && len(tc.expErrs) > 0:
t.Fatalf("expected error:\n\n%v\n\ngot: none", tc.expErrs)
}
})
})
}
}
func TestCheck_DefaultRegoVersion(t *testing.T) {
cases := []struct {
note string
policy string
expErrs []string
}{
{
note: "v0 module",
policy: `package test
a[x] {
x := 42
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: `if` keyword is required before rule body",
"test.rego:2: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1 module",
policy: `package test
a contains x if {
x := 42
}`,
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
files := map[string]string{
"test.rego": tc.policy,
}
test.WithTempFS(files, func(root string) {
params := newCheckParams()
err := checkModules(params, []string{root})
switch {
case err != nil && len(tc.expErrs) > 0:
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("expected err:\n\n%v\n\ngot:\n\n%v", expErr, err)
}
}
return // don't read back bundle below
case err != nil && len(tc.expErrs) == 0:
t.Fatalf("unexpected error: %v", err)
case err == nil && len(tc.expErrs) > 0:
t.Fatalf("expected error:\n\n%v\n\ngot: none", tc.expErrs)
}
})
})
}
}
func TestCheckWithRegoV1Capability(t *testing.T) {
cases := []struct {
note string
v0Compatible bool
capabilities *ast.Capabilities
policy string
expErrs []string
}{
{
note: "v0 module, v0-compatible, no capabilities",
v0Compatible: true,
policy: `package test
a[x] {
x := 42
}`,
},
{
note: "v0 module, v0-compatible, v0 capabilities",
v0Compatible: true,
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV0)),
policy: `package test
a[x] {
x := 42
}`,
},
{
note: "v0 module, v0-compatible, v1 capabilities",
v0Compatible: true,
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV1)),
policy: `package test
a[x] {
x := 42
}`,
},
{
note: "v0 module, not v0-compatible, no capabilities",
policy: `package test
a[x] {
x := 42
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: `if` keyword is required before rule body",
"test.rego:2: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v0 module, not v0-compatible, v0 capabilities",
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV0)),
policy: `package test
a[x] {
x := 42
}`,
expErrs: []string{
"rego_parse_error: illegal capabilities: rego_v1 feature required for parsing v1 Rego",
},
},
{
note: "v0 module, not v0-compatible, v1 capabilities",
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV1)),
policy: `package test
a[x] {
x := 42
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: `if` keyword is required before rule body",
"test.rego:2: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1 module, v0-compatible, no capabilities",
v0Compatible: true,
policy: `package test
a contains x if {
x := 42
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v1 module, v0-compatible, v0 capabilities",
v0Compatible: true,
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV0)),
policy: `package test
a contains x if {
x := 42
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v1 module, v0-compatible, v1 capabilities",
v0Compatible: true,
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV1)),
policy: `package test
a contains x if {
x := 42
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v1 module, not v0-compatible, no capabilities",
policy: `package test
a contains x if {
x := 42
}`,
},
{
note: "v1 module, not v0-compatible, v0 capabilities",
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV0)),
policy: `package test
a contains x if {
x := 42
}`,
expErrs: []string{
"rego_parse_error: illegal capabilities: rego_v1 feature required for parsing v1 Rego",
},
},
{
note: "v1 module, not v0-compatible, v1 capabilities",
capabilities: ast.CapabilitiesForThisVersion(ast.CapabilitiesRegoVersion(ast.RegoV1)),
policy: `package test
a contains x if {
x := 42
}`,
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
files := map[string]string{
"test.rego": tc.policy,
}
test.WithTempFS(files, func(root string) {
params := newCheckParams()
params.v0Compatible = tc.v0Compatible
params.capabilities.C = tc.capabilities
err := checkModules(params, []string{root})
switch {
case err != nil && len(tc.expErrs) > 0:
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("expected err:\n\n%v\n\ngot:\n\n%v", expErr, err)
}
}
return // don't read back bundle below
case err != nil && len(tc.expErrs) == 0:
t.Fatalf("unexpected error: %v", err)
case err == nil && len(tc.expErrs) > 0:
t.Fatalf("expected error:\n\n%v\n\ngot: none", tc.expErrs)
}
})
})
}
}
func TestCheckCompatibleFlags(t *testing.T) {
cases := []struct {
note string
v0Compatible bool
v1Compatible bool
policy string
expErrs []string
}{
{
note: "v0, no illegal keywords",
v0Compatible: true,
policy: `package test
p[x] {
x := [1,2,3]
}`,
},
{
note: "v0, illegal keywords",
v0Compatible: true,
policy: `package test
p contains x if {
x := [1,2,3]
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v0, future.keywords imported",
v0Compatible: true,
policy: `package test
import future.keywords
p contains x if {
x := [1,2,3]
}`,
},
{
note: "v0, rego.v1 imported",
v0Compatible: true,
policy: `package test
import rego.v1
p contains x if {
x := [1,2,3]
}`,
},
{
note: "v1, rego.v1 imported, v1 compliant",
v1Compatible: true,
policy: `package test
import rego.v1
p contains x if {
x := [1,2,3]
}`,
},
{
note: "v1, rego.v1 imported, NOT v1 compliant (parser)",
v1Compatible: true,
policy: `package test
import rego.v1
p contains x {
x := [1,2,3]
}
q.r`,
expErrs: []string{
"test.rego:3: rego_parse_error: `if` keyword is required before rule body",
"test.rego:7: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1, rego.v1 imported, NOT v1 compliant (compiler)",
v1Compatible: true,
policy: `package test
import rego.v1
import data.foo
import data.bar as foo
`,
expErrs: []string{
"test.rego:5: rego_compile_error: import must not shadow import data.foo",
},
},
{
note: "v1, keywords imported, v1 compliant",
v1Compatible: true,
policy: `package test
import future.keywords.if
import future.keywords.contains
p contains x if {
x := [1,2,3]
}`,
},
{
note: "v1, keywords imported, NOT v1 compliant",
v1Compatible: true,
policy: `package test
import future.keywords.contains
p contains x {
x := [1,2,3]
}
q.r`,
expErrs: []string{
"test.rego:3: rego_parse_error: `if` keyword is required before rule body",
"test.rego:7: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1, keywords imported, NOT v1 compliant (compiler)",
v1Compatible: true,
policy: `package test
import future.keywords.if
input := 1 if {
1 == 2
}`,
expErrs: []string{
"test.rego:4: rego_compile_error: rules must not shadow input (use a different rule name)",
},
},
{
note: "v1, no imports, v1 compliant",
v1Compatible: true,
policy: `package test
p := 1
`,
},
{
note: "v1, no imports, NOT v1 compliant but v0 compliant (compiler)",
v1Compatible: true,
policy: `package test
p.x`,
expErrs: []string{
"test.rego:2: rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1, no imports, v1 compliant but NOT v0 compliant",
v1Compatible: true,
policy: `package test
p contains x if {
x := [1,2,3]
}`,
},
// v0 takes precedence over v1
{
note: "v0+v1, no illegal keywords",
v0Compatible: true,
v1Compatible: true,
policy: `package test
p[x] {
x := [1,2,3]
}`,
},
{
note: "v0+v1, illegal keywords",
v0Compatible: true,
v1Compatible: true,
policy: `package test
p contains x if {
x := [1,2,3]
}`,
expErrs: []string{
"test.rego:2: rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v0+v1, future.keywords imported",
v0Compatible: true,
v1Compatible: true,
policy: `package test
import future.keywords
p contains x if {
x := [1,2,3]
}`,
},
{
note: "v0+v1, rego.v1 imported",
v0Compatible: true,
v1Compatible: true,
policy: `package test
import rego.v1
p contains x if {
x := [1,2,3]
}`,
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
files := map[string]string{
"test.rego": tc.policy,
}
test.WithTempFS(files, func(root string) {
params := newCheckParams()
params.v0Compatible = tc.v0Compatible
params.v1Compatible = tc.v1Compatible
err := checkModules(params, []string{root})
switch {
case err != nil && len(tc.expErrs) > 0:
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("expected err:\n\n%v\n\ngot:\n\n%v", expErr, err)
}
}
return // don't read back bundle below
case err != nil && len(tc.expErrs) == 0:
t.Fatalf("unexpected error: %v", err)
case err == nil && len(tc.expErrs) > 0:
t.Fatalf("expected error:\n\n%v\n\ngot: none", tc.expErrs)
}
})
})
}
}
func TestCheckWithBundleRegoVersion(t *testing.T) {
cases := []struct {
note string
files map[string]string
expErrs []string
}{
{
note: "v0.x bundle, illegal keywords",
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
p contains x if {
x := [1,2,3]
}`,
},
expErrs: []string{
"rego_parse_error: var cannot be used for rule name",
},
},
{
note: "v0.x bundle, rego.v1 imported, v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
import rego.v1
p contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v0.x bundle, rego.v1 imported, NOT v1 compliant (parser)",
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
import rego.v1
p contains x {
x := [1,2,3]
}
q.r`,
},
expErrs: []string{
"rego_parse_error: `if` keyword is required before rule body",
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v0.x bundle, rego.v1 imported, NOT v1 compliant (compiler)",
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
import rego.v1
import data.foo
import data.bar as foo
`,
},
expErrs: []string{
"rego_compile_error: import must not shadow import data.foo",
},
},
{
note: "v0.x bundle, keywords imported, v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
import future.keywords.if
import future.keywords.contains
p contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v0.x bundle, no imports, v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 0}`,
"policy.rego": `package test
p := 1
`,
},
},
{
note: "v0 bundle, v1 per-file overrides, compliant",
files: map[string]string{
".manifest": `{
"rego_version": 0,
"file_rego_versions": {
"/policy2.rego": 1
}
}`,
"policy1.rego": `package test
p[x] {
x := [1,2,3]
}`,
"policy2.rego": `package test
q contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v0 bundle, v1 per-file overrides (glob), compliant",
files: map[string]string{
".manifest": `{
"rego_version": 0,
"file_rego_versions": {
"*/policy2.rego": 1
}
}`,
"policy1.rego": `package test
p[x] {
x := [1,2,3]
}`,
"policy2.rego": `package test
q contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v0 bundle, v1 per-file overrides, incompliant",
files: map[string]string{
".manifest": `{
"rego_version": 0,
"file_rego_versions": {
"/policy2.rego": 1
}
}`,
"policy1.rego": `package test
p[x] {
x := [1,2,3]
}`,
"policy2.rego": `package test
q[x] {
x := [1,2,3]
}`,
},
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 used but not imported",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
p contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v1.0 bundle, rego.v1 imported, v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import rego.v1
p contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v1.0 bundle, rego.v1 imported, NOT v1 compliant (parser)",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import rego.v1
p contains x {
x := [1,2,3]
}
q.r`,
},
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, rego.v1 imported, NOT v1 compliant (compiler)",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import rego.v1
import data.foo
import data.bar as foo
`,
},
expErrs: []string{
"rego_compile_error: import must not shadow import data.foo",
},
},
{
note: "v1.0 bundle, keywords imported, v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import future.keywords.if
import future.keywords.contains
p contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v1.0 bundle, keywords imported, NOT v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import future.keywords.contains
p contains x {
x := [1,2,3]
}
q.r`,
},
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 imported, NOT v1 compliant (compiler)",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
import future.keywords.if
input := 1 if {
1 == 2
}`,
},
expErrs: []string{
"rego_compile_error: rules must not shadow input (use a different rule name)",
},
},
{
note: "v1.0 bundle, no imports, v1 compliant",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
p := 1
`,
},
},
{
note: "v1.0 bundle, no imports, NOT v1 compliant but v0 compliant (compiler)",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
p.x`,
},
expErrs: []string{
"rego_parse_error: `contains` keyword is required for partial set rules",
},
},
{
note: "v1.0 bundle, no imports, v1 compliant but NOT v0 compliant",
files: map[string]string{
".manifest": `{"rego_version": 1}`,
"policy.rego": `package test
p contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v1 bundle, v0 per-file overrides, compliant",
files: map[string]string{
".manifest": `{
"rego_version": 1,
"file_rego_versions": {
"/policy1.rego": 0
}
}`,
"policy1.rego": `package test
p[x] {
x := [1,2,3]
}`,
"policy2.rego": `package test
q contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v1 bundle, v0 per-file overrides (glob), compliant",
files: map[string]string{
".manifest": `{
"rego_version": 1,
"file_rego_versions": {
"*/policy1.rego": 0
}
}`,
"policy1.rego": `package test
p[x] {
x := [1,2,3]
}`,
"policy2.rego": `package test
q contains x if {
x := [1,2,3]
}`,
},
},
{
note: "v1 bundle, v0 per-file overrides, incompliant",
files: map[string]string{
".manifest": `{
"rego_version": 1,
"file_rego_versions": {
"/policy1.rego": 0
}
}`,
"policy1.rego": `package test
p contains x if {
x := [1,2,3]
}`,
"policy2.rego": `package test
q contains x if {
x := [1,2,3]
}`,
},
expErrs: []string{
"rego_parse_error: var 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 cases {
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 {
for k, v := range tc.files {
files[k] = v
}
}
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)
}
}
params := newCheckParams()
params.bundleMode = true
params.v1Compatible = v1CompatibleFlag.used
err := checkModules(params, []string{p})
switch {
case err != nil && len(tc.expErrs) > 0:
for _, expErr := range tc.expErrs {
if !strings.Contains(err.Error(), expErr) {
t.Fatalf("expected err:\n\n%v\n\ngot:\n\n%v", expErr, err)
}
}
return // don't read back bundle below
case err != nil && len(tc.expErrs) == 0:
t.Fatalf("unexpected error: %v", err)
case err == nil && len(tc.expErrs) > 0:
t.Fatalf("expected error:\n\n%v\n\ngot: none", tc.expErrs)
}
})
})
}
}
}
}