mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-27 18:55:54 -06:00
36fdcef93f
Fixing an issue where if a `rego.metadata.*` function is called in a rule, any present `schemas` annotation will be used to inform type checking, even if no `--schema` flag was used for the command. Only commands that takes the `--schema` flag can now use the `schemas` annotation to inform type checking; i.e. `eval` and `check`. Fixes: 5430 Signed-off-by: Johan Fylling <johan.dev@fylling.se>
271 lines
6.1 KiB
Go
271 lines
6.1 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/open-policy-agent/opa/rego"
|
|
"github.com/open-policy-agent/opa/topdown"
|
|
"github.com/open-policy-agent/opa/util/test"
|
|
)
|
|
|
|
func TestFilterTraceDefault(t *testing.T) {
|
|
p := newTestCommandParams()
|
|
p.verbose = false
|
|
expected := `Enter data.testing.test_p = _
|
|
| Enter data.testing.test_p
|
|
| | Enter data.testing.p
|
|
| | | Enter data.testing.q
|
|
| | | | Enter data.testing.r
|
|
| | | | | Fail x = data.x
|
|
| | | | Fail data.testing.r[x]
|
|
| | | Fail data.testing.q.foo
|
|
| | Fail data.testing.p with data.x as "bar"
|
|
| Fail data.testing.test_p = _
|
|
`
|
|
verifyFilteredTrace(t, p, expected)
|
|
}
|
|
|
|
func TestFilterTraceVerbose(t *testing.T) {
|
|
p := newTestCommandParams()
|
|
p.verbose = true
|
|
expected := `Enter data.testing.test_p = _
|
|
| Enter data.testing.test_p
|
|
| | Enter data.testing.p
|
|
| | | Note "test test"
|
|
| | | Enter data.testing.q
|
|
| | | | Note "got this far"
|
|
| | | | Enter data.testing.r
|
|
| | | | | Note "got this far2"
|
|
| | | | | Fail x = data.x
|
|
| | | | Fail data.testing.r[x]
|
|
| | | Fail data.testing.q.foo
|
|
| | Fail data.testing.p with data.x as "bar"
|
|
| Fail data.testing.test_p = _
|
|
`
|
|
verifyFilteredTrace(t, p, expected)
|
|
}
|
|
|
|
func TestFilterTraceExplainFails(t *testing.T) {
|
|
p := newTestCommandParams()
|
|
err := p.explain.Set(explainModeFails)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expected := `Enter data.testing.test_p = _
|
|
| Enter data.testing.test_p
|
|
| | Enter data.testing.p
|
|
| | | Enter data.testing.q
|
|
| | | | Enter data.testing.r
|
|
| | | | | Fail x = data.x
|
|
| | | | Fail data.testing.r[x]
|
|
| | | Fail data.testing.q.foo
|
|
| | Fail data.testing.p with data.x as "bar"
|
|
| Fail data.testing.test_p = _
|
|
`
|
|
verifyFilteredTrace(t, p, expected)
|
|
}
|
|
|
|
func TestFilterTraceExplainNotes(t *testing.T) {
|
|
p := newTestCommandParams()
|
|
err := p.explain.Set(explainModeNotes)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expected := `Enter data.testing.test_p = _
|
|
| Enter data.testing.test_p
|
|
| | Enter data.testing.p
|
|
| | | Note "test test"
|
|
| | | Enter data.testing.q
|
|
| | | | Note "got this far"
|
|
| | | | Enter data.testing.r
|
|
| | | | | Note "got this far2"
|
|
`
|
|
verifyFilteredTrace(t, p, expected)
|
|
}
|
|
|
|
func TestFilterTraceExplainFull(t *testing.T) {
|
|
p := newTestCommandParams()
|
|
err := p.explain.Set(explainModeFull)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
expected := `Enter data.testing.test_p = _
|
|
| Eval data.testing.test_p = _
|
|
| Index data.testing.test_p (matched 1 rule, early exit)
|
|
| Enter data.testing.test_p
|
|
| | Eval data.testing.p with data.x as "bar"
|
|
| | Index data.testing.p (matched 1 rule, early exit)
|
|
| | Enter data.testing.p
|
|
| | | Eval data.testing.x
|
|
| | | Index data.testing.x (matched 1 rule, early exit)
|
|
| | | Enter data.testing.x
|
|
| | | | Eval data.testing.y
|
|
| | | | Index data.testing.y (matched 1 rule, early exit)
|
|
| | | | Enter data.testing.y
|
|
| | | | | Eval true
|
|
| | | | | Exit data.testing.y early
|
|
| | | | Exit data.testing.x early
|
|
| | | Eval trace("test test")
|
|
| | | Note "test test"
|
|
| | | Eval data.testing.q.foo
|
|
| | | Index data.testing.q (matched 1 rule)
|
|
| | | Enter data.testing.q
|
|
| | | | Eval trace("got this far")
|
|
| | | | Note "got this far"
|
|
| | | | Eval data.testing.r[x]
|
|
| | | | Index data.testing.r (matched 1 rule)
|
|
| | | | Enter data.testing.r
|
|
| | | | | Eval trace("got this far2")
|
|
| | | | | Note "got this far2"
|
|
| | | | | Eval x = data.x
|
|
| | | | | Fail x = data.x
|
|
| | | | | Redo trace("got this far2")
|
|
| | | | Fail data.testing.r[x]
|
|
| | | | Redo trace("got this far")
|
|
| | | Fail data.testing.q.foo
|
|
| | | Redo trace("test test")
|
|
| | | Redo data.testing.x
|
|
| | | Redo data.testing.x
|
|
| | | | Redo data.testing.y
|
|
| | | | | Redo true
|
|
| | Fail data.testing.p with data.x as "bar"
|
|
| Fail data.testing.test_p = _
|
|
`
|
|
verifyFilteredTrace(t, p, expected)
|
|
}
|
|
|
|
func TestThresholdRange(t *testing.T) {
|
|
thresholds := []float64{-1, 101}
|
|
for _, threshold := range thresholds {
|
|
if isThresholdValid(threshold) {
|
|
t.Fatalf("invalid threshold %2f shoul be reported", threshold)
|
|
}
|
|
}
|
|
}
|
|
|
|
func verifyFilteredTrace(t *testing.T, params *testCommandParams, expected string) {
|
|
filtered := filterTrace(params, failTrace(t))
|
|
|
|
var buff bytes.Buffer
|
|
topdown.PrettyTrace(&buff, filtered)
|
|
actual := buff.String()
|
|
|
|
if actual != expected {
|
|
t.Fatalf("Expected:\n\n%s\n\nGot:\n\n%s\n\n", expected, actual)
|
|
}
|
|
}
|
|
|
|
func failTrace(t *testing.T) []*topdown.Event {
|
|
t.Helper()
|
|
mod := `
|
|
package testing
|
|
|
|
p {
|
|
x # Always true
|
|
trace("test test")
|
|
q["foo"]
|
|
}
|
|
|
|
x {
|
|
y
|
|
}
|
|
|
|
y {
|
|
true
|
|
}
|
|
|
|
q[x] {
|
|
some x
|
|
trace("got this far")
|
|
r[x]
|
|
trace("got this far1")
|
|
}
|
|
|
|
r[x] {
|
|
trace("got this far2")
|
|
x := data.x
|
|
}
|
|
|
|
test_p {
|
|
p with data.x as "bar"
|
|
}
|
|
`
|
|
|
|
tracer := topdown.NewBufferTracer()
|
|
|
|
_, err := rego.New(
|
|
rego.Module("test.rego", mod),
|
|
rego.Trace(true),
|
|
rego.QueryTracer(tracer),
|
|
rego.Query("data.testing.test_p"),
|
|
).Eval(context.Background())
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
return *tracer
|
|
}
|
|
|
|
func testSchemasAnnotation(mod string) error {
|
|
query := "data.test.test_p"
|
|
|
|
files := map[string]string{
|
|
"test.rego": mod,
|
|
}
|
|
|
|
var err error
|
|
test.WithTempFS(files, func(path string) {
|
|
_, err = rego.New(
|
|
rego.Module("test.rego", mod),
|
|
rego.Trace(true),
|
|
rego.Query(query),
|
|
).Eval(context.Background())
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// Assert that 'schemas' annotations are ignored
|
|
func TestSchemasAnnotation(t *testing.T) {
|
|
policyWithSchemaRef := `
|
|
package test
|
|
# METADATA
|
|
# schemas:
|
|
# - input: schema["input"]
|
|
p {
|
|
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
|
|
}
|
|
|
|
test_p {
|
|
p with input.foo as 42
|
|
}`
|
|
|
|
err := testSchemasAnnotation(policyWithSchemaRef)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when schema ref is present: %v", err)
|
|
}
|
|
|
|
policyWithInlinedSchema := `
|
|
package test
|
|
# METADATA
|
|
# schemas:
|
|
# - input.foo: {"type": "boolean"}
|
|
p {
|
|
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
|
|
}
|
|
|
|
test_p {
|
|
p with input.foo as 42
|
|
}`
|
|
|
|
err = testSchemasAnnotation(policyWithInlinedSchema)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error when inlined schema is present: %v", err)
|
|
}
|
|
}
|