// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package ruleengine import ( "errors" "strings" "testing" api "github.com/projectcapsule/capsule/pkg/api/rules" ) type testObject struct { Values []Value } type testRule struct { Name string ShouldMatch bool MatchedValue any Detail string Err error } type enforceSpec struct { action api.ActionType items []testRule } type testFixture struct { items map[*api.NamespaceRuleEnforceBody][]testRule } func TestEvaluateEnforce_ValidationErrors(t *testing.T) { t.Parallel() validFixture := newTestFixture() validSet := validFixture.set("registry", nil) tests := []struct { name string set Set[testRule, testObject] wantErr string }{ { name: "empty set name", set: Set[testRule, testObject]{ Values: validSet.Values, Rules: validSet.Rules, Matches: validSet.Matches, }, wantErr: "rule set name is empty", }, { name: "nil values extractor", set: Set[testRule, testObject]{ Name: "registry", Rules: validSet.Rules, Matches: validSet.Matches, }, wantErr: "registry: values extractor is nil", }, { name: "nil rules extractor", set: Set[testRule, testObject]{ Name: "registry", Values: validSet.Values, Matches: validSet.Matches, }, wantErr: "registry: rules extractor is nil", }, { name: "nil matcher", set: Set[testRule, testObject]{ Name: "registry", Values: validSet.Values, Rules: validSet.Rules, }, wantErr: "registry: matcher is nil", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() evaluation, err := EvaluateEnforce( testObject{Values: []Value{{Value: "harbor/app:1", Path: "spec.containers[0].image"}}}, []*api.NamespaceRuleEnforceBody{{Action: api.ActionTypeAllow}}, tt.set, ) if err == nil { t.Fatalf("expected error, got nil") } if evaluation != nil { t.Fatalf("expected nil evaluation on setup validation error, got %#v", evaluation) } if !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("expected error containing %q, got %q", tt.wantErr, err.Error()) } }) } } func TestEvaluateEnforce_RulesWithError(t *testing.T) { t.Parallel() wantErr := errors.New("invalid selector") evaluation, err := EvaluateEnforce( testObject{Values: []Value{{Value: "prod", Path: "metadata.labels[env]"}}}, []*api.NamespaceRuleEnforceBody{{Action: api.ActionTypeAllow}}, Set[testRule, testObject]{ Name: "metadata labels", Values: func(obj testObject) []Value { return obj.Values }, RulesWithError: func(*api.NamespaceRuleEnforceBody) ([]testRule, error) { return nil, wantErr }, Matches: func(testRule, Value) (Match, error) { return Match{}, nil }, }, ) if !errors.Is(err, wantErr) { t.Fatalf("EvaluateEnforce() error = %v, want %v", err, wantErr) } if evaluation == nil { t.Fatal("EvaluateEnforce() evaluation = nil, want partial evaluation") } } func TestEvaluateEnforce_EmptyInputs(t *testing.T) { t.Parallel() tests := []struct { name string obj testObject enforceSpecs []enforceSpec includeNilBody bool }{ { name: "no values", obj: testObject{}, enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "deny", ShouldMatch: true}, }, }, }, }, { name: "empty value is skipped", obj: testObject{ Values: []Value{ {Value: "", Path: "spec.value"}, }, }, enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "deny", ShouldMatch: true}, }, }, }, }, { name: "nil enforce bodies", obj: testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, }, { name: "empty enforce bodies", obj: testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, enforceSpecs: []enforceSpec{}, }, { name: "nil enforce body is ignored", obj: testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, includeNilBody: true, }, { name: "enforce body without rule items is ignored", obj: testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, }, }, }, { name: "non matching rule item is ignored", obj: testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "deny", ShouldMatch: false}, }, }, }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() fixture := newTestFixture() enforceBodies := buildEnforceBodies(fixture, tt.enforceSpecs) if tt.includeNilBody { enforceBodies = append(enforceBodies, nil) } evaluation, err := EvaluateEnforce(tt.obj, enforceBodies, fixture.set("registry", nil)) if err != nil { t.Fatalf("unexpected error: %v", err) } assertNoBlocking(t, evaluation) assertNoFinal(t, evaluation) if len(evaluation.Audits) != 0 { t.Fatalf("expected no audits, got %d", len(evaluation.Audits)) } }) } } func TestEvaluateEnforce_LastMatchingAllowDenyWins(t *testing.T) { t.Parallel() tests := []struct { name string enforceSpecs []enforceSpec wantFinalAction api.ActionType wantBlocking bool wantMessage string }{ { name: "single allow allows", enforceSpecs: []enforceSpec{ { action: api.ActionTypeAllow, items: []testRule{ {Name: "harbor/.*", ShouldMatch: true}, }, }, }, wantFinalAction: api.ActionTypeAllow, wantMessage: `registry "harbor/app:1" at spec.containers[0].image is allowed by namespace rule: matched allowed rule harbor/.*`, }, { name: "single deny denies", enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "harbor/blocked/.*", ShouldMatch: true}, }, }, }, wantFinalAction: api.ActionTypeDeny, wantBlocking: true, wantMessage: `registry "harbor/app:1" at spec.containers[0].image is denied by namespace rule: matched denied rule harbor/blocked/.*`, }, { name: "deny then allow allows", enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "harbor/.*", ShouldMatch: true}, }, }, { action: api.ActionTypeAllow, items: []testRule{ {Name: "harbor/app:.*", ShouldMatch: true}, }, }, }, wantFinalAction: api.ActionTypeAllow, wantMessage: `registry "harbor/app:1" at spec.containers[0].image is allowed by namespace rule: matched allowed rule harbor/app:.*`, }, { name: "allow then deny denies", enforceSpecs: []enforceSpec{ { action: api.ActionTypeAllow, items: []testRule{ {Name: "harbor/.*", ShouldMatch: true}, }, }, { action: api.ActionTypeDeny, items: []testRule{ {Name: "harbor/app:.*", ShouldMatch: true}, }, }, }, wantFinalAction: api.ActionTypeDeny, wantBlocking: true, wantMessage: `registry "harbor/app:1" at spec.containers[0].image is denied by namespace rule: matched denied rule harbor/app:.*`, }, { name: "last matching rule wins while non matching later rules are ignored", enforceSpecs: []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "first-deny", ShouldMatch: true}, }, }, { action: api.ActionTypeAllow, items: []testRule{ {Name: "allow", ShouldMatch: true}, }, }, { action: api.ActionTypeDeny, items: []testRule{ {Name: "later-non-matching-deny", ShouldMatch: false}, }, }, }, wantFinalAction: api.ActionTypeAllow, wantMessage: `registry "harbor/app:1" at spec.containers[0].image is allowed by namespace rule: matched allowed rule allow`, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, tt.enforceSpecs), fixture.set("registry", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertFinalAction(t, evaluation, tt.wantFinalAction) if tt.wantBlocking { assertBlockingAction(t, evaluation, api.ActionTypeDeny) } else { assertNoBlocking(t, evaluation) } if evaluation.Final.Message != tt.wantMessage { t.Fatalf("final message = %q, want %q", evaluation.Final.Message, tt.wantMessage) } }) } } func TestEvaluateEnforce_DefaultActionIsDeny(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: "", items: []testRule{ {Name: "default-deny", ShouldMatch: true}, }, }, }), fixture.set("registry", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) if evaluation.Blocking.Message != `registry "harbor/app:1" at spec.containers[0].image is denied by namespace rule: matched denied rule default-deny` { t.Fatalf("blocking message = %q", evaluation.Blocking.Message) } } func TestEvaluateEnforce_AllowListMissDenies(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "docker.io/library/nginx:latest", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeAllow, items: []testRule{ {Name: "harbor/.*", ShouldMatch: false}, {Name: "registry.local/.*", ShouldMatch: false}, }, }, }), fixture.set("registry", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) assertNoFinal(t, evaluation) want := `registry "docker.io/library/nginx:latest" at spec.containers[0].image is not allowed by namespace rule: value did not match any allowed rule. Allowed registries: harbor/.*, registry.local/.*` if evaluation.Blocking.Message != want { t.Fatalf("blocking message = %q, want %q", evaluation.Blocking.Message, want) } } func TestEvaluateEnforce_AllowMissWithoutRuleDescriptionsUsesBaseMessage(t *testing.T) { t.Parallel() fixture := newTestFixture() set := fixture.set("registry", nil) set.RuleDescription = nil set.AllowedDescription = "" evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "docker.io/library/nginx:latest", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeAllow, items: []testRule{ {Name: "harbor/.*", ShouldMatch: false}, }, }, }), set, ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) want := `registry "docker.io/library/nginx:latest" at spec.containers[0].image is not allowed by namespace rule` if evaluation.Blocking.Message != want { t.Fatalf("blocking message = %q, want %q", evaluation.Blocking.Message, want) } } func TestEvaluateEnforce_AllowMissDescriptionsAreLimited(t *testing.T) { t.Parallel() fixture := newTestFixture() items := make([]testRule, 0, 12) for i := 0; i < 12; i++ { items = append(items, testRule{ Name: "rule-" + string(rune('a'+i)), ShouldMatch: false, }) } evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "unmatched", Path: "spec.value"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeAllow, items: items, }, }), fixture.set("registry", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) msg := evaluation.Blocking.Message for _, expected := range []string{ "rule-a", "rule-j", "and 2 more", } { if !strings.Contains(msg, expected) { t.Fatalf("expected message %q to contain %q", msg, expected) } } for _, unexpected := range []string{ "rule-k", "rule-l", } { if strings.Contains(msg, unexpected) { t.Fatalf("expected message %q not to contain %q", msg, unexpected) } } } func TestEvaluateEnforce_AuditIsObservational(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "audit/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeAudit, items: []testRule{ { Name: "audit/.*", ShouldMatch: true, Detail: `"audit/app:1" matched registry rule audit/.*`, }, }, }, }), fixture.set("registry", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertNoBlocking(t, evaluation) assertNoFinal(t, evaluation) if len(evaluation.Audits) != 1 { t.Fatalf("expected 1 audit, got %d", len(evaluation.Audits)) } audit := evaluation.Audits[0] if audit.Action != api.ActionTypeAudit { t.Fatalf("audit action = %q, want %q", audit.Action, api.ActionTypeAudit) } if audit.MatchedRule != "audit/.*" { t.Fatalf("audit matched rule = %q, want %q", audit.MatchedRule, "audit/.*") } if audit.MatchDetail != `"audit/app:1" matched registry rule audit/.*` { t.Fatalf("audit match detail = %q", audit.MatchDetail) } want := `registry "audit/app:1" at spec.containers[0].image matched audit namespace rule: "audit/app:1" matched registry rule audit/.*` if audit.Message != want { t.Fatalf("audit message = %q, want %q", audit.Message, want) } } func TestEvaluateEnforce_AuditDoesNotSatisfyAllowList(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "audit/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeAudit, items: []testRule{ {Name: "audit/.*", ShouldMatch: true}, }, }, { action: api.ActionTypeAllow, items: []testRule{ {Name: "allowed/.*", ShouldMatch: false}, }, }, }), fixture.set("registry", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(evaluation.Audits) != 1 { t.Fatalf("expected 1 audit, got %d", len(evaluation.Audits)) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) assertNoFinal(t, evaluation) if !strings.Contains(evaluation.Blocking.Message, "Allowed registries: allowed/.*") { t.Fatalf("blocking message = %q", evaluation.Blocking.Message) } } func TestEvaluateEnforce_MatchDetailOverridesMatchedRuleInMessage(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "10.0.171.239", Path: "spec.loadBalancerIP"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ { Name: "10.0.0.0/8", ShouldMatch: true, MatchedValue: "10.0.0.0/8", Detail: "10.0.171.239 is contained in 10.0.0.0/8", }, }, }, }), fixture.set("loadBalancer CIDR", nil), ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) blocking := evaluation.Blocking if blocking.MatchedRule != "10.0.0.0/8" { t.Fatalf("matched rule = %q, want %q", blocking.MatchedRule, "10.0.0.0/8") } if blocking.MatchDetail != "10.0.171.239 is contained in 10.0.0.0/8" { t.Fatalf("match detail = %q", blocking.MatchDetail) } want := `loadBalancer CIDR "10.0.171.239" at spec.loadBalancerIP is denied by namespace rule: 10.0.171.239 is contained in 10.0.0.0/8` if blocking.Message != want { t.Fatalf("blocking message = %q, want %q", blocking.Message, want) } } func TestEvaluateEnforce_CustomMessageOverridesDefaultMessage(t *testing.T) { t.Parallel() fixture := newTestFixture() set := fixture.set("registry", func(action api.ActionType, value Value, matched any) string { return "custom message" }) evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ {Name: "harbor/.*", ShouldMatch: true}, }, }, }), set, ) if err != nil { t.Fatalf("unexpected error: %v", err) } assertBlockingAction(t, evaluation, api.ActionTypeDeny) if evaluation.Blocking.Message != "custom message" { t.Fatalf("blocking message = %q, want custom message", evaluation.Blocking.Message) } } func TestEvaluateEnforce_MatcherError(t *testing.T) { t.Parallel() fixture := newTestFixture() matchErr := errors.New("invalid regex") evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionTypeDeny, items: []testRule{ { Name: "bad-rule", ShouldMatch: true, Err: matchErr, }, }, }, }), fixture.set("registry", nil), ) if err == nil { t.Fatalf("expected error, got nil") } if evaluation == nil { t.Fatalf("expected non-nil evaluation after runtime matcher error") } if !strings.Contains(err.Error(), "registry: invalid rule: invalid regex") { t.Fatalf("error = %q", err.Error()) } } func TestEvaluateEnforce_UnsupportedAction(t *testing.T) { t.Parallel() fixture := newTestFixture() evaluation, err := EvaluateEnforce( testObject{ Values: []Value{ {Value: "harbor/app:1", Path: "spec.containers[0].image"}, }, }, buildEnforceBodies(fixture, []enforceSpec{ { action: api.ActionType("invalid"), items: []testRule{ {Name: "harbor/.*", ShouldMatch: true}, }, }, }), fixture.set("registry", nil), ) if err == nil { t.Fatalf("expected error, got nil") } if evaluation == nil { t.Fatalf("expected non-nil evaluation after runtime action error") } if !strings.Contains(err.Error(), `registry: unsupported rule action "invalid"`) { t.Fatalf("error = %q", err.Error()) } } func TestEvaluation_BlockingError(t *testing.T) { t.Parallel() if err := (*Evaluation)(nil).BlockingError(); err != nil { t.Fatalf("nil evaluation BlockingError() = %v, want nil", err) } evaluation := &Evaluation{} if err := evaluation.BlockingError(); err != nil { t.Fatalf("empty evaluation BlockingError() = %v, want nil", err) } evaluation.Blocking = &Decision{ Message: "blocked by rule", } err := evaluation.BlockingError() if err == nil { t.Fatalf("expected blocking error, got nil") } if err.Error() != "blocked by rule" { t.Fatalf("blocking error = %q, want %q", err.Error(), "blocked by rule") } } func TestDecisionError_ErrorFallback(t *testing.T) { t.Parallel() tests := []struct { name string err *DecisionError }{ { name: "nil error", err: nil, }, { name: "nil decision", err: &DecisionError{}, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if tt.err.Error() != "namespace rule decision denied request" { t.Fatalf("Error() = %q", tt.err.Error()) } }) } } func TestEvaluation_Append(t *testing.T) { t.Parallel() t.Run("nil receiver or nil other does nothing", func(t *testing.T) { t.Parallel() var evaluation *Evaluation evaluation.Append(&Evaluation{}) nonNil := &Evaluation{} nonNil.Append(nil) assertNoBlocking(t, nonNil) assertNoFinal(t, nonNil) if len(nonNil.Audits) != 0 { t.Fatalf("expected no audits, got %d", len(nonNil.Audits)) } }) t.Run("appends audits and overrides final and blocking", func(t *testing.T) { t.Parallel() firstAudit := &Decision{Action: api.ActionTypeAudit, Message: "audit-1"} secondAudit := &Decision{Action: api.ActionTypeAudit, Message: "audit-2"} final := &Decision{Action: api.ActionTypeAllow, Message: "final"} blocking := &Decision{Action: api.ActionTypeDeny, Message: "blocking"} evaluation := &Evaluation{ Audits: []*Decision{ firstAudit, }, } evaluation.Append(&Evaluation{ Audits: []*Decision{ secondAudit, }, Final: final, Blocking: blocking, }) if len(evaluation.Audits) != 2 { t.Fatalf("audits = %d, want 2", len(evaluation.Audits)) } if evaluation.Audits[0] != firstAudit { t.Fatalf("first audit pointer was not preserved") } if evaluation.Audits[1] != secondAudit { t.Fatalf("second audit pointer was not appended") } if evaluation.Final != final { t.Fatalf("final was not updated") } if evaluation.Blocking != blocking { t.Fatalf("blocking was not updated") } }) } func TestEvaluateEnforce_SkipsEmptyExtractedValues(t *testing.T) { t.Parallel() type testCase struct { name string action api.ActionType values []Value rules []string wantMatcherCalls int wantBlocking bool wantFinal bool wantAudits int wantBlockingPath string } tests := []testCase{ { name: "empty value is skipped before deny evaluation", action: api.ActionTypeDeny, values: []Value{ { Value: "", Path: "spec.value", }, }, rules: []string{ "", }, wantMatcherCalls: 0, wantBlocking: false, wantFinal: false, wantAudits: 0, }, { name: "empty value is skipped before allow miss", action: api.ActionTypeAllow, values: []Value{ { Value: "", Path: "spec.value", }, }, rules: []string{ "allowed", }, wantMatcherCalls: 0, wantBlocking: false, wantFinal: false, wantAudits: 0, }, { name: "empty value is skipped before audit evaluation", action: api.ActionTypeAudit, values: []Value{ { Value: "", Path: "spec.value", }, }, rules: []string{ "", }, wantMatcherCalls: 0, wantBlocking: false, wantFinal: false, wantAudits: 0, }, { name: "empty value is skipped but later non empty value is evaluated", action: api.ActionTypeDeny, values: []Value{ { Value: "", Path: "spec.empty", }, { Value: "deny", Path: "spec.nonEmpty", }, }, rules: []string{ "deny", }, wantMatcherCalls: 1, wantBlocking: true, wantFinal: true, wantAudits: 0, wantBlockingPath: "spec.nonEmpty", }, { name: "whitespace value is not skipped", action: api.ActionTypeDeny, values: []Value{ { Value: " ", Path: "spec.value", }, }, rules: []string{ " ", }, wantMatcherCalls: 1, wantBlocking: true, wantFinal: true, wantAudits: 0, wantBlockingPath: "spec.value", }, { name: "empty values are skipped before non matching allow value triggers allow miss", action: api.ActionTypeAllow, values: []Value{ { Value: "", Path: "spec.empty", }, { Value: "actual", Path: "spec.actual", }, }, rules: []string{ "allowed", }, wantMatcherCalls: 1, wantBlocking: true, wantFinal: false, wantAudits: 0, wantBlockingPath: "spec.actual", }, { name: "all empty values are skipped", action: api.ActionTypeDeny, values: []Value{ { Value: "", Path: "spec.first", }, { Value: "", Path: "spec.second", }, }, rules: []string{ "", }, wantMatcherCalls: 0, wantBlocking: false, wantFinal: false, wantAudits: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() matcherCalls := 0 evaluation, err := EvaluateEnforce( struct{}{}, []*api.NamespaceRuleEnforceBody{ { Action: tt.action, }, }, Set[string, struct{}]{ Name: "registry", EventReason: "NamespaceRuleViolation", Values: func(struct{}) []Value { return tt.values }, Rules: func(*api.NamespaceRuleEnforceBody) []string { return tt.rules }, Matches: func(rule string, value Value) (Match, error) { matcherCalls++ return Match{ Matched: value.Value == rule, MatchedValue: rule, }, nil }, RuleDescription: func(rule string) string { return rule }, }, ) if err != nil { t.Fatalf("expected no error, got %v", err) } if evaluation == nil { t.Fatalf("expected evaluation") } if matcherCalls != tt.wantMatcherCalls { t.Fatalf("expected %d matcher calls, got %d", tt.wantMatcherCalls, matcherCalls) } if got := evaluation.Blocking != nil; got != tt.wantBlocking { t.Fatalf("expected blocking=%t, got %t: %#v", tt.wantBlocking, got, evaluation.Blocking) } if got := evaluation.Final != nil; got != tt.wantFinal { t.Fatalf("expected final=%t, got %t: %#v", tt.wantFinal, got, evaluation.Final) } if len(evaluation.Audits) != tt.wantAudits { t.Fatalf("expected %d audits, got %d", tt.wantAudits, len(evaluation.Audits)) } if tt.wantBlockingPath != "" { if evaluation.Blocking == nil { t.Fatalf("expected blocking decision") } if evaluation.Blocking.Value.Path != tt.wantBlockingPath { t.Fatalf("expected blocking path %q, got %q", tt.wantBlockingPath, evaluation.Blocking.Value.Path) } } }) } } func TestMessageHelpers(t *testing.T) { t.Parallel() set := Set[testRule, testObject]{ Name: "registry", AllowedDescription: "Allowed registries", RuleDescription: func(rule testRule) string { return rule.Name }, } t.Run("allowedLabel default", func(t *testing.T) { t.Parallel() defaultSet := Set[testRule, testObject]{} if got := allowedLabel(defaultSet); got != "Allowed values" { t.Fatalf("allowedLabel() = %q, want %q", got, "Allowed values") } }) t.Run("appendMatchContext uses detail before rule", func(t *testing.T) { t.Parallel() got := appendMatchContext("message", "rule", "detail", "matched rule") if got != "message: detail" { t.Fatalf("appendMatchContext() = %q, want %q", got, "message: detail") } }) t.Run("appendMatchContext uses rule when detail is empty", func(t *testing.T) { t.Parallel() got := appendMatchContext("message", "rule", "", "matched rule") if got != "message: matched rule rule" { t.Fatalf("appendMatchContext() = %q", got) } }) t.Run("appendMatchContext leaves message unchanged without context", func(t *testing.T) { t.Parallel() got := appendMatchContext("message", "", "", "matched rule") if got != "message" { t.Fatalf("appendMatchContext() = %q, want message", got) } }) t.Run("describeRules skips empty descriptions", func(t *testing.T) { t.Parallel() got := describeRules(set, []testRule{ {Name: "first"}, {Name: ""}, {Name: "second"}, }) if got != "first, second" { t.Fatalf("describeRules() = %q, want %q", got, "first, second") } }) } func newTestFixture() *testFixture { return &testFixture{ items: map[*api.NamespaceRuleEnforceBody][]testRule{}, } } func (f *testFixture) set( name string, message func(api.ActionType, Value, any) string, ) Set[testRule, testObject] { return Set[testRule, testObject]{ Name: name, EventReason: "NamespaceRuleViolation", AllowedDescription: "Allowed registries", Values: func(obj testObject) []Value { return obj.Values }, Rules: func(enforce *api.NamespaceRuleEnforceBody) []testRule { return f.items[enforce] }, Matches: func(rule testRule, value Value) (Match, error) { if rule.Err != nil { return Match{}, rule.Err } if !rule.ShouldMatch { return Match{}, nil } matchedValue := rule.MatchedValue if matchedValue == nil { matchedValue = rule.Name } return Match{ Matched: true, MatchedValue: matchedValue, Detail: rule.Detail, }, nil }, Message: message, RuleDescription: func(rule testRule) string { return rule.Name }, } } func buildEnforceBodies( fixture *testFixture, specs []enforceSpec, ) []*api.NamespaceRuleEnforceBody { if len(specs) == 0 { return nil } out := make([]*api.NamespaceRuleEnforceBody, 0, len(specs)) for _, spec := range specs { enforce := &api.NamespaceRuleEnforceBody{ Action: spec.action, } fixture.items[enforce] = spec.items out = append(out, enforce) } return out } func assertBlockingAction(t *testing.T, evaluation *Evaluation, action api.ActionType) { t.Helper() if evaluation == nil { t.Fatalf("expected evaluation, got nil") } if evaluation.Blocking == nil { t.Fatalf("expected blocking decision, got nil") } if evaluation.Blocking.Action != action { t.Fatalf("blocking action = %q, want %q", evaluation.Blocking.Action, action) } } func assertNoBlocking(t *testing.T, evaluation *Evaluation) { t.Helper() if evaluation == nil { t.Fatalf("expected evaluation, got nil") } if evaluation.Blocking != nil { t.Fatalf("expected no blocking decision, got %#v", evaluation.Blocking) } } func assertFinalAction(t *testing.T, evaluation *Evaluation, action api.ActionType) { t.Helper() if evaluation == nil { t.Fatalf("expected evaluation, got nil") } if evaluation.Final == nil { t.Fatalf("expected final decision, got nil") } if evaluation.Final.Action != action { t.Fatalf("final action = %q, want %q", evaluation.Final.Action, action) } } func assertNoFinal(t *testing.T, evaluation *Evaluation) { t.Helper() if evaluation == nil { t.Fatalf("expected evaluation, got nil") } if evaluation.Final != nil { t.Fatalf("expected no final decision, got %#v", evaluation.Final) } }