package rule_test import ( "context" "errors" "path/filepath" "testing" "github.com/netkingdom/flex-auth/internal/adapters/rule" "github.com/netkingdom/flex-auth/internal/policy" "github.com/netkingdom/flex-auth/pkg/api" ) func TestCanonicalInputFromCheck(t *testing.T) { adapter := newAdapter(t, &fakeBackend{}) decision, err := adapter.Check(context.Background(), api.CheckRequest{ ID: "check:input", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, Context: map[string]any{"purpose": "support"}, CaringContext: caringDescriptor(), }) if err != nil { t.Fatalf("Check: %v", err) } if decision.Diagnostics["input_seen"] != true { t.Fatalf("backend did not receive canonical input: %+v", decision.Diagnostics) } } func TestPolicyArtifactFromPackagePreservesRegoAndFixtures(t *testing.T) { pkg := loadPolicy(t) artifact := rule.PolicyArtifactFromPackage(pkg) if artifact.ID != "markitect.documents.internal-read" || artifact.Version != "v1" { t.Fatalf("artifact metadata = %+v", artifact) } if artifact.Language != rule.LanguageRego { t.Fatalf("Language = %q", artifact.Language) } if artifact.Module != pkg.RegoModule { t.Fatal("Rego module changed during artifact creation") } if len(artifact.Fixtures) != len(pkg.Fixtures) { t.Fatalf("fixtures = %d; want %d", len(artifact.Fixtures), len(pkg.Fixtures)) } if artifact.Caring.Profile != api.CaringProfileCaring040RC2 { t.Fatalf("Caring profile = %q", artifact.Caring.Profile) } } func TestAdapterCheckWrapsRuleResult(t *testing.T) { backend := &fakeBackend{ result: rule.EvaluationResult{ Effect: api.DecisionEffectRedact, Reason: "masked_internal_document", MatchedRule: "rule.mask_internal", PolicyVersion: "v2", Obligations: []api.Obligation{ {Type: "mask_fields", Parameters: map[string]any{"fields": []string{"email"}}}, }, Diagnostics: map[string]any{"backend_trace": "trace-1"}, CaringDescriptor: caringDescriptor(), ConformanceFindings: []api.CaringConformanceFinding{ {Code: "RULE-MASKED", Severity: "info", Message: "masked internal document"}, }, }, } adapter := newAdapter(t, backend) got, err := adapter.Check(context.Background(), api.CheckRequest{ ID: "check:redact", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, }) if err != nil { t.Fatalf("Check: %v", err) } if got.Effect != api.DecisionEffectRedact || got.Reason != "masked_internal_document" { t.Fatalf("decision = %s/%s", got.Effect, got.Reason) } if got.Provenance.Evaluator != "rule-pdp/opa" || got.MatchedPolicyVersion != "v2" { t.Fatalf("provenance = %+v matched=%s", got.Provenance, got.MatchedPolicyVersion) } if len(got.Obligations) != 1 || got.Obligations[0].Type != "mask_fields" { t.Fatalf("obligations = %+v", got.Obligations) } if got.Caring == nil || got.Caring.Descriptor == nil || got.Caring.ExposureEvent == nil { t.Fatalf("CARING metadata = %+v", got.Caring) } } func TestAdapterFailsClosedOnStalePolicy(t *testing.T) { backend := &fakeBackend{ err: rule.NewBackendError(rule.FailureStalePolicy, "evaluate", errors.New("policy revision too old")), } adapter := newAdapter(t, backend) got, err := adapter.Check(context.Background(), api.CheckRequest{ ID: "check:stale", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, CaringContext: caringDescriptor(), }) if err != nil { t.Fatalf("Check: %v", err) } if got.Effect != api.DecisionEffectDeny || got.Reason != "rule_policy_stale" { t.Fatalf("decision = %s/%s; want stale deny", got.Effect, got.Reason) } if got.Diagnostics["rule_failure"] != "stale_policy" { t.Fatalf("diagnostics = %+v", got.Diagnostics) } if got.Caring.ConformanceFindings[0].Code != "RULE-POLICY-STALE" { t.Fatalf("finding = %+v", got.Caring.ConformanceFindings[0]) } } func TestBatchCheckPreservesOrder(t *testing.T) { backend := &fakeBackend{ batch: []rule.EvaluationResult{ {Effect: api.DecisionEffectAllow, Reason: "first"}, {Effect: api.DecisionEffectDeny, Reason: "second"}, }, } adapter := newAdapter(t, backend) got, err := adapter.BatchCheck(context.Background(), api.BatchCheckRequest{ ID: "batch:rule", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resources: []api.ResourceRef{ {ID: "document:internal-note", Type: "document", System: "markitect-tool"}, {ID: "document:missing", Type: "document", System: "markitect-tool"}, }, }) if err != nil { t.Fatalf("BatchCheck: %v", err) } if len(got) != 2 || got[0].Reason != "first" || got[1].Reason != "second" { t.Fatalf("batch = %+v", got) } } func TestEvaluateFixturesComparesExpectations(t *testing.T) { backend := &fakeBackend{ result: rule.EvaluationResult{Effect: api.DecisionEffectDeny, Reason: "no_matching_rule"}, } adapter := newAdapter(t, backend) results := adapter.EvaluateFixtures(context.Background(), []api.PolicyFixture{ { ID: "fixture:deny", Request: api.CheckRequest{ Subject: api.SubjectRef{ID: "user:bob"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, }, Expect: api.DecisionExpectation{Effect: api.DecisionEffectDeny, Reason: "no_matching_rule"}, }, }) if len(results) != 1 || !results[0].Passed { t.Fatalf("fixture results = %+v", results) } } func TestImportPolicyDelegatesArtifact(t *testing.T) { backend := &fakeBackend{} adapter := newAdapter(t, backend) pkg := loadPolicy(t) report, err := adapter.ImportPolicy(context.Background(), pkg) if err != nil { t.Fatalf("ImportPolicy: %v", err) } if report.ArtifactID != pkg.Metadata.ID || backend.artifact.Module != pkg.RegoModule { t.Fatalf("report = %+v artifact = %+v", report, backend.artifact) } } func newAdapter(t *testing.T, backend *fakeBackend) *rule.Adapter { t.Helper() adapter, err := rule.New(backend, rule.Options{ BackendName: "opa", PolicyPackage: "markitect.documents.internal-read", PolicyVersion: "v1", Language: rule.LanguageRego, Caring: api.CaringPolicyMetadata{ Profile: api.CaringProfileCaring040RC2, CanonicalRoles: []api.CanonicalRole{api.CanonicalRoleDoer}, Planes: []api.Plane{api.PlaneData}, Capabilities: []api.Capability{api.CapabilityView}, ExposureModes: []api.ExposureMode{api.ExposureModeMasked}, }, }) if err != nil { t.Fatalf("New: %v", err) } return adapter } func loadPolicy(t *testing.T) *policy.Package { t.Helper() pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "..", "examples", "caring", "policy_package.md")) if err != nil { t.Fatalf("LoadAndValidateFile: %v", err) } return pkg } func caringDescriptor() *api.CaringAccessDescriptor { return &api.CaringAccessDescriptor{ ID: "descriptor:rule-reader", Profile: api.CaringProfileCaring040RC2, SubjectType: api.SubjectTypeHuman, OrganizationRelation: api.OrganizationRelationCustomer, CanonicalRole: api.CanonicalRoleDoer, Scope: api.CaringScope{Level: api.ScopeLevelResource, ID: "document:internal-note"}, Planes: []api.Plane{api.PlaneData}, Capabilities: []api.Capability{api.CapabilityView}, ExposureModes: []api.ExposureMode{api.ExposureModeMasked}, Restrictions: []api.Restriction{api.RestrictionExportBlocked}, ExposureEvent: api.ExposureEventSupport, } } type fakeBackend struct { result rule.EvaluationResult err error batch []rule.EvaluationResult artifact rule.PolicyArtifact } func (b *fakeBackend) Evaluate(_ context.Context, request rule.EvaluationRequest) (rule.EvaluationResult, error) { if request.Input["subject"] != nil && b.result.Diagnostics == nil { b.result.Diagnostics = map[string]any{"input_seen": true} } return b.result, b.err } func (b *fakeBackend) BatchEvaluate(_ context.Context, requests []rule.EvaluationRequest) ([]rule.EvaluationResult, error) { if b.batch != nil { return b.batch, nil } results := make([]rule.EvaluationResult, len(requests)) for i := range results { results[i] = b.result } return results, b.err } func (b *fakeBackend) ImportPolicy(_ context.Context, artifact rule.PolicyArtifact) (rule.PolicyImportReport, error) { b.artifact = artifact return rule.PolicyImportReport{ ArtifactID: artifact.ID, Version: artifact.Version, Language: artifact.Language, BackendRef: "opa:" + artifact.ID, }, nil } func (b *fakeBackend) Health(context.Context) error { return nil }