package decision_test import ( "context" "os" "path/filepath" "testing" "gopkg.in/yaml.v3" "github.com/netkingdom/flex-auth/internal/decision" "github.com/netkingdom/flex-auth/internal/policy" "github.com/netkingdom/flex-auth/internal/registry" "github.com/netkingdom/flex-auth/pkg/api" ) func TestCheckUsesExplicitCaringContext(t *testing.T) { engine := newTestEngine(t) var request api.CheckRequest loadYAML(t, filepath.Join("..", "..", "examples", "caring", "check_request.yaml"), &request) got, err := engine.Check(context.Background(), request) if err != nil { t.Fatalf("Check: %v", err) } again, err := engine.Check(context.Background(), request) if err != nil { t.Fatalf("Check again: %v", err) } if got.ID != again.ID { t.Fatalf("decision id is not deterministic: %q != %q", got.ID, again.ID) } if got.Effect != api.DecisionEffectAllow { t.Fatalf("got.Effect = %q; want allow", got.Effect) } if got.Reason != "reader_relation" { t.Errorf("got.Reason = %q; want reader_relation", got.Reason) } if got.MatchedPolicyVersion != "v1" { t.Errorf("got.MatchedPolicyVersion = %q; want v1", got.MatchedPolicyVersion) } if got.Subject.Type != api.SubjectTypeHuman || got.Subject.Attributes["groups"] == nil { t.Errorf("got.Subject = %+v; want enriched human subject with groups", got.Subject) } if got.Resource.Type != "document" || got.Resource.Attributes["trust_zone"] != "internal" { t.Errorf("got.Resource = %+v; want enriched document resource", got.Resource) } if got.Caring == nil || got.Caring.Descriptor == nil { t.Fatal("got.Caring.Descriptor is nil") } if got.Caring.Descriptor.ID != "descriptor:tenant-alpha-document-reader" { t.Errorf("got.Caring.Descriptor.ID = %q", got.Caring.Descriptor.ID) } if len(got.Caring.RestrictionsEvaluated) != 1 || got.Caring.RestrictionsEvaluated[0] != api.RestrictionExportBlocked { t.Errorf("got.Caring.RestrictionsEvaluated = %v; want [ExportBlocked]", got.Caring.RestrictionsEvaluated) } } func TestCheckMatchesRegistryRelationshipDescriptor(t *testing.T) { engine := newTestEngine(t) got, err := engine.Check(context.Background(), api.CheckRequest{ ID: "check:registry-descriptor", Subject: api.SubjectRef{ ID: "user:alice", }, Action: "read", Resource: api.ResourceRef{ ID: "document:internal-note", System: "markitect-tool", }, }) if err != nil { t.Fatalf("Check: %v", err) } if got.Effect != api.DecisionEffectAllow { t.Fatalf("got.Effect = %q; want allow", got.Effect) } if got.Caring == nil || got.Caring.Descriptor == nil { t.Fatal("got.Caring.Descriptor is nil") } if got.Caring.Descriptor.SubjectType != api.SubjectTypeGroup { t.Errorf("got.Caring.Descriptor.SubjectType = %q; want Group", got.Caring.Descriptor.SubjectType) } if got.Diagnostics["matched_relationship"] != "rel:alice-reader-internal-note" { t.Errorf("matched_relationship = %v", got.Diagnostics["matched_relationship"]) } } func TestBatchCheckPreservesResourceOrder(t *testing.T) { engine := newTestEngine(t) got, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{ ID: "batch:read-documents", Subject: api.SubjectRef{ ID: "user:alice", }, Action: "read", Resources: []api.ResourceRef{ {ID: "document:internal-note", System: "markitect-tool"}, {ID: "document:missing", Type: "document", System: "markitect-tool"}, }, }) if err != nil { t.Fatalf("BatchCheck: %v", err) } if len(got) != 2 { t.Fatalf("len(got) = %d; want 2", len(got)) } if got[0].Resource.ID != "document:internal-note" || got[0].Effect != api.DecisionEffectAllow { t.Fatalf("first decision = %+v; want allow for document:internal-note", got[0]) } if got[1].Resource.ID != "document:missing" || got[1].Effect != api.DecisionEffectDeny { t.Fatalf("second decision = %+v; want deny for document:missing", got[1]) } if got[0].ID == got[1].ID { t.Fatalf("batch decisions have duplicate deterministic ids: %q", got[0].ID) } if got[1].Caring == nil || len(got[1].Caring.ConformanceFindings) == 0 { t.Fatal("missing descriptor deny should carry a CARING conformance finding") } } func newTestEngine(t *testing.T) *decision.Engine { t.Helper() store, err := registry.LoadFile(filepath.Join("..", "..", "examples", "caring", "registry_snapshot.json")) if err != nil { t.Fatalf("LoadFile registry: %v", err) } policyPackage, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "caring", "policy_package.md")) if err != nil { t.Fatalf("LoadAndValidateFile policy: %v", err) } engine, err := decision.NewEngine(store, policyPackage) if err != nil { t.Fatalf("NewEngine: %v", err) } return engine } func loadYAML(t *testing.T, path string, out any) { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } if err := yaml.Unmarshal(data, out); err != nil { t.Fatalf("unmarshal %s: %v", path, err) } }