mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
2378494a23
Mostly automated fixes from running: ``` go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest --fix ./... ``` But carefully reviewed, and several fixes reverted as they looked like they potentially could be less performant, and in a few cases due to bugs in the analyzer that changed semantics of the code. Will report these upstream. Mostly good fixes though! Signed-off-by: Anders Eknert <anders.eknert@apple.com>
170 lines
4.9 KiB
Go
170 lines
4.9 KiB
Go
// Copyright 2025 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 config
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"slices"
|
|
"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/topdown"
|
|
)
|
|
|
|
func TestParseConfigWarnsOnUnknownOption(t *testing.T) {
|
|
// The motivating example from issue #2745.
|
|
conf, err := ParseConfig([]byte(`{"decision_log": {"console": true}}`), "id")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
want := `unknown configuration option "decision_log" encountered`
|
|
if !slices.Contains(conf.Warnings, want) {
|
|
t.Fatalf("expected warning %q, got %v", want, conf.Warnings)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigNoWarningsForValidConfig(t *testing.T) {
|
|
conf, err := ParseConfig([]byte(`{"decision_logs": {"console": true}}`), "id")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(conf.Warnings) != 0 {
|
|
t.Fatalf("expected no warnings, got %v", conf.Warnings)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigEmptyInjectsDefaults(t *testing.T) {
|
|
// The SDK and other callers parse an absent configuration (nil/empty bytes);
|
|
// defaults must still be injected and no error returned.
|
|
for name, raw := range map[string][]byte{
|
|
"nil": nil,
|
|
"empty": []byte(``),
|
|
"empty-obj": []byte(`{}`),
|
|
"null": []byte(`null`),
|
|
"whitespace": []byte(` `),
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
conf, err := ParseConfig(raw, "id")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if conf.DefaultDecision == nil || *conf.DefaultDecision != "/system/main" {
|
|
t.Fatalf("expected default decision to be injected, got %v", conf.DefaultDecision)
|
|
}
|
|
if conf.Labels["id"] != "id" {
|
|
t.Fatalf("expected id label to be injected, got %v", conf.Labels)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseConfigNullDecisionDefaults(t *testing.T) {
|
|
// A field explicitly set to null must fall back to the default (not error),
|
|
// matching the pre-Rego behavior where a nil pointer was treated as unset.
|
|
conf, err := ParseConfig([]byte(`{"default_decision": null}`), "id")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if conf.DefaultDecision == nil || *conf.DefaultDecision != "/system/main" {
|
|
t.Fatalf("expected default decision to be injected, got %v", conf.DefaultDecision)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigNonStringDecisionErrors(t *testing.T) {
|
|
// A present, non-null, non-string value is still a fatal error.
|
|
if _, err := ParseConfig([]byte(`{"default_decision": 42}`), "id"); err == nil {
|
|
t.Fatal("expected error for non-string default_decision, got nil")
|
|
}
|
|
}
|
|
|
|
// TestCoreValidationRootSpecMatchesConfigStruct is a drift guard: the set of
|
|
// top-level keys known to the core validation policy must exactly match the
|
|
// JSON-tagged fields of the Config struct. If a field is added to Config without
|
|
// updating validate.rego (or vice versa), this test fails.
|
|
func TestCoreValidationRootSpecMatchesConfigStruct(t *testing.T) {
|
|
structKeys := map[string]struct{}{}
|
|
objType := reflect.TypeFor[Config]()
|
|
for i := range objType.NumField() {
|
|
name, _, _ := strings.Cut(objType.Field(i).Tag.Get("json"), ",")
|
|
if name == "" || name == "-" {
|
|
continue
|
|
}
|
|
structKeys[name] = struct{}{}
|
|
}
|
|
|
|
policyKeys := rootSpecKeys(t)
|
|
|
|
for k := range structKeys {
|
|
if _, ok := policyKeys[k]; !ok {
|
|
t.Errorf("config key %q is present in Config struct but missing from validate.rego root spec", k)
|
|
}
|
|
}
|
|
for k := range policyKeys {
|
|
if _, ok := structKeys[k]; !ok {
|
|
t.Errorf("config key %q is present in validate.rego root spec but not in Config struct", k)
|
|
}
|
|
}
|
|
}
|
|
|
|
// rootSpecKeys evaluates the core policy and returns the key set of the spec
|
|
// whose pattern is empty (i.e. the top-level configuration object).
|
|
func rootSpecKeys(t *testing.T) map[string]struct{} {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
compiler, err := compileValidationPolicy()
|
|
if err != nil {
|
|
t.Fatalf("compile: %v", err)
|
|
}
|
|
|
|
store := inmem.New()
|
|
txn, err := store.NewTransaction(ctx)
|
|
if err != nil {
|
|
t.Fatalf("txn: %v", err)
|
|
}
|
|
defer store.Abort(ctx, txn)
|
|
|
|
qrs, err := topdown.NewQuery(ast.MustParseBody("data.opa.config._specs = x")).
|
|
WithCompiler(compiler).
|
|
WithStore(store).
|
|
WithTransaction(txn).
|
|
Run(ctx)
|
|
if err != nil {
|
|
t.Fatalf("eval _specs: %v", err)
|
|
}
|
|
if len(qrs) != 1 {
|
|
t.Fatalf("unexpected result set: %v", qrs)
|
|
}
|
|
|
|
value, err := ast.JSON(qrs[0][ast.Var("x")].Value)
|
|
if err != nil {
|
|
t.Fatalf("convert _specs: %v", err)
|
|
}
|
|
specs, ok := value.([]any)
|
|
if !ok {
|
|
t.Fatalf("unexpected _specs type %T", value)
|
|
}
|
|
|
|
for _, s := range specs {
|
|
spec := s.(map[string]any)
|
|
pattern := spec["pattern"].([]any)
|
|
if len(pattern) != 0 {
|
|
continue
|
|
}
|
|
keys := map[string]struct{}{}
|
|
for _, k := range spec["keys"].([]any) {
|
|
keys[k.(string)] = struct{}{}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
t.Fatal("no root spec (empty pattern) found in validate.rego")
|
|
return nil
|
|
}
|