Files
releases/v1/topdown/query_test.go
T
Ville Vesilehto f77322b3fb build: bump Go version requirement to 1.24 (#7839)
Go 1.23 is no longer supported as per Go release policy.

Changes:

- Use Go v1.24.6 as the project SDK requirement
- Apply lint fixes for Go 1.24
- Fix "non-constant format string in call" issues as seen in CI.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-08-24 09:02:09 +02:00

321 lines
6.9 KiB
Go

// Copyright 2020 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 topdown
import (
"context"
"reflect"
"strings"
"testing"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/storage"
inmem "github.com/open-policy-agent/opa/v1/storage/inmem/test"
)
func TestQueryTracerDontPlugLocalVars(t *testing.T) {
t.Parallel()
cases := []struct {
note string
tracerConfs []TraceConfig
expectLocals bool
}{
{
note: "plug locals single tracer",
tracerConfs: []TraceConfig{
{PlugLocalVars: true},
},
expectLocals: true,
},
{
note: "dont plug locals single tracer",
tracerConfs: []TraceConfig{
{PlugLocalVars: false},
},
expectLocals: false,
},
{
note: "plug locals multiple tracers",
tracerConfs: []TraceConfig{
{PlugLocalVars: true},
{PlugLocalVars: true},
{PlugLocalVars: true},
},
expectLocals: true,
},
{
note: "dont plug locals multiple tracers",
tracerConfs: []TraceConfig{
{PlugLocalVars: false},
{PlugLocalVars: false},
{PlugLocalVars: false},
},
expectLocals: false,
},
{
note: "plug locals multiple plugins mixed",
tracerConfs: []TraceConfig{
{PlugLocalVars: false},
{PlugLocalVars: true},
{PlugLocalVars: false},
},
expectLocals: true,
},
}
for _, tc := range cases {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
query := initTracerTestQuery()
var tracers []*testQueryTracer
for _, conf := range tc.tracerConfs {
tt := &testQueryTracer{
events: []*Event{},
conf: conf,
enabled: true,
t: t,
}
tracers = append(tracers, tt)
query = query.WithQueryTracer(tt)
}
_, err := query.Run(t.Context())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Even if the individual tracer didn't specify for local metadata
// they will _all_ either have it or not.
for _, tt := range tracers {
for _, e := range tt.events {
if !tc.expectLocals && e.LocalMetadata != nil {
t.Fatalf("Expected event LocalMetadata to nil")
}
if tc.expectLocals && e.LocalMetadata == nil {
t.Fatalf("Expected event LocalMetadata to be non-nil")
}
}
}
})
}
}
func TestLegacyTracerUpgrade(t *testing.T) {
t.Parallel()
query := initTracerTestQuery()
tracer := &testQueryTracer{
events: []*Event{},
conf: TraceConfig{PlugLocalVars: false},
enabled: true,
t: t,
}
// Call with older API, expect to be "upgraded" to QueryTracer
// If the deprecated Trace() API is called the test will fail.
query.WithTracer(tracer)
_, err := query.Run(t.Context())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestLegacyTracerBackwardsCompatibility(t *testing.T) {
t.Parallel()
query := initTracerTestQuery()
// Using a tracer that does _not_ implement the newer
// QueryTracer interface, only the deprecated Tracer one.
tracer := &testLegacyTracer{
events: []*Event{},
}
query.WithTracer(tracer)
// For comparison use a buffer tracer and the new interface
bt := NewBufferTracer()
query.WithQueryTracer(bt)
_, err := query.Run(t.Context())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(*bt) != len(tracer.events) {
t.Fatalf("Expected %d events on the test tracer, got %d", len(*bt), len(tracer.events))
}
if !reflect.DeepEqual([]*Event(*bt), tracer.events) {
t.Fatalf("Expected same events on test tracer and BufferTracer")
}
}
func TestDisabledTracer(t *testing.T) {
t.Parallel()
query := initTracerTestQuery()
tracer := &testQueryTracer{
events: []*Event{},
conf: TraceConfig{PlugLocalVars: false},
enabled: false,
t: t,
}
// Both API's should ignore the disabled tracer
query.WithTracer(tracer)
query.WithQueryTracer(tracer)
_, err := query.Run(t.Context())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(tracer.events) > 0 {
t.Fatalf("Expected no events on test tracer, got %d", len(tracer.events))
}
}
func TestRegoMetadataBuiltinCall(t *testing.T) {
t.Parallel()
tests := []struct {
note string
query string
expectedError string
}{
{
note: "rego.metadata.chain() call",
query: "rego.metadata.chain()",
expectedError: "rego.metadata.chain(): eval_builtin_error: rego.metadata.chain: the rego.metadata.chain function must only be called within the scope of a rule",
},
{
note: "rego.metadata.rule() call",
query: "rego.metadata.rule()",
expectedError: "rego.metadata.rule(): eval_builtin_error: rego.metadata.rule: the rego.metadata.rule function must only be called within the scope of a rule",
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
t.Parallel()
c := ast.NewCompiler()
q := NewQuery(ast.MustParseBody(tc.query)).WithCompiler(c).
WithStrictBuiltinErrors(true)
_, err := q.Run(t.Context())
if err == nil {
t.Fatalf("expected error")
}
if tc.expectedError != err.Error() {
t.Fatalf("expected error:\n\n%s\n\ngot:\n\n%s", tc.expectedError, err.Error())
}
})
}
}
func TestWithCompilerErrors(t *testing.T) {
t.Parallel()
store := inmem.New()
ctx := t.Context()
txn := storage.NewTransactionOrDie(ctx, store)
defer store.Abort(ctx, txn)
// Policy with reference to non-existing function
modules := map[string]*ast.Module{
"tst": ast.MustParseModule(`package test
p := data.q(42)`),
}
c := ast.NewCompiler()
c.Compile(modules)
q := NewQuery(ast.MustParseBody("data.a = 1")).
WithCompiler(c).
WithStore(store).
WithTransaction(txn)
_, err := q.Run(t.Context())
if err == nil {
t.Fatalf("expected error, got nil")
}
expected := "eval_internal_error: compiler has errors"
if !strings.Contains(err.Error(), expected) {
t.Fatalf("expected error to contain '%s', got: %v", expected, err)
}
}
func initTracerTestQuery() *Query {
ctx := context.Background()
store := inmem.New()
inputTerm := &ast.Term{}
txn := storage.NewTransactionOrDie(ctx, store)
defer store.Abort(ctx, txn)
compiler := compileModules([]string{
`package x
p if {
a := [1, 2, 3]
f(a[_])
}
f(x) if {
x == 3
}
`})
return NewQuery(ast.MustParseBody("data.x.p")).
WithCompiler(compiler).
WithStore(store).
WithTransaction(txn).
WithInput(inputTerm)
}
type testQueryTracer struct {
events []*Event
conf TraceConfig
enabled bool
t *testing.T
}
func (n *testQueryTracer) Enabled() bool {
return n.enabled
}
func (n *testQueryTracer) Trace(e *Event) {
n.t.Errorf("Unexpected call to Trace() with event %v", e)
}
func (n *testQueryTracer) TraceEvent(e Event) {
n.events = append(n.events, &e)
}
func (n *testQueryTracer) Config() TraceConfig {
return n.conf
}
type testLegacyTracer struct {
events []*Event
}
func (*testLegacyTracer) Enabled() bool {
return true
}
func (n *testLegacyTracer) Trace(e *Event) {
n.events = append(n.events, e)
}