package policy_test import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" "gopkg.in/yaml.v3" "github.com/netkingdom/flex-auth/internal/policy" "github.com/netkingdom/flex-auth/pkg/api" ) func TestLoadPolicyPackageMarkdownValidates(t *testing.T) { pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "caring", "policy_package.md")) if err != nil { t.Fatalf("LoadAndValidateFile: %v", err) } if !pkg.Valid { t.Fatalf("pkg.Valid = false\n%s", formatValidation(pkg.Validation)) } if pkg.Metadata.Caring.Profile != api.CaringProfileCaring040RC2 { t.Fatalf("metadata.Caring.Profile = %q; want %q", pkg.Metadata.Caring.Profile, api.CaringProfileCaring040RC2) } if pkg.Metadata.Namespace != "markitect:document" { t.Errorf("metadata.Namespace = %q; want markitect:document", pkg.Metadata.Namespace) } if !strings.HasPrefix(pkg.RegoModule, "package flexauth.markitect.documents") { t.Errorf("RegoModule prefix = %q; want flexauth.markitect.documents package", pkg.RegoModule[:min(len(pkg.RegoModule), 80)]) } if len(pkg.RuleBlocks) != 1 || len(pkg.TestBlocks) != 1 || len(pkg.Fixtures) != 2 { t.Fatalf("blocks/fixtures = rules:%d tests:%d fixtures:%d; want 1/1/2", len(pkg.RuleBlocks), len(pkg.TestBlocks), len(pkg.Fixtures)) } if len(pkg.Validation.Tests) != 2 { t.Fatalf("Validation.Tests len = %d; want 2", len(pkg.Validation.Tests)) } for _, test := range pkg.Validation.Tests { if !test.Passed { t.Fatalf("test %s failed: %s", test.Name, test.Error) } } for _, fixture := range pkg.Validation.Fixtures { if !fixture.Passed { t.Fatalf("fixture %s failed: %s\nactual: %+v", fixture.ID, fixture.Error, fixture.Actual) } } } func TestRedactPolicyPackageMarkdownValidates(t *testing.T) { pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "caring", "redact_policy_package.md")) if err != nil { t.Fatalf("LoadAndValidateFile: %v", err) } if !pkg.Valid { t.Fatalf("pkg.Valid = false\n%s", formatValidation(pkg.Validation)) } if len(pkg.Validation.Fixtures) != 1 { t.Fatalf("Validation.Fixtures len = %d; want 1", len(pkg.Validation.Fixtures)) } fixture := pkg.Validation.Fixtures[0] if !fixture.Passed { t.Fatalf("fixture failed: %s\nactual: %+v", fixture.Error, fixture.Actual) } if fixture.Actual.Effect != api.DecisionEffectRedact { t.Fatalf("fixture.Actual.Effect = %q; want redact", fixture.Actual.Effect) } if len(fixture.Actual.Obligations) != 1 || fixture.Actual.Obligations[0].Type != "mask_fields" { t.Fatalf("fixture.Actual.Obligations = %+v; want mask_fields", fixture.Actual.Obligations) } } func TestCaringFindingsAreAdvisoryUntilEnforced(t *testing.T) { doc := inlinePolicy(false, "allow") pkg, err := policy.Load([]byte(doc), "inline-policy.md") if err != nil { t.Fatalf("Load: %v", err) } result := pkg.Validate(context.Background()) if !result.Valid { t.Fatalf("result.Valid = false without CARING enforcement\n%s", formatValidation(result)) } if len(result.CaringFindings) == 0 { t.Fatal("expected advisory CARING findings for missing metadata dimensions") } enforced := strings.Replace(doc, "enforce: false", "enforce: true", 1) pkg, err = policy.Load([]byte(enforced), "inline-policy.md") if err != nil { t.Fatalf("Load enforced: %v", err) } result = pkg.Validate(context.Background()) if result.Valid { t.Fatalf("result.Valid = true with CARING enforcement; want invalid\n%s", formatValidation(result)) } } func TestFixtureMismatchInvalidatesPackage(t *testing.T) { pkg, err := policy.Load([]byte(inlinePolicy(false, "deny")), "inline-policy.md") if err != nil { t.Fatalf("Load: %v", err) } result := pkg.Validate(context.Background()) if result.Valid { t.Fatalf("result.Valid = true; want fixture mismatch to invalidate package\n%s", formatValidation(result)) } if len(result.Fixtures) != 1 || result.Fixtures[0].Passed { t.Fatalf("fixture result = %+v; want one failed fixture", result.Fixtures) } } func TestPolicyPackageMetadataParses(t *testing.T) { var metadata api.PolicyPackageMetadata loadYAML(t, filepath.Join("..", "..", "examples", "caring", "policy_package.yaml"), &metadata) if metadata.Caring.Profile != api.CaringProfileCaring040RC2 { t.Fatalf("metadata.Caring.Profile = %q; want %q", metadata.Caring.Profile, api.CaringProfileCaring040RC2) } if len(metadata.Caring.Capabilities) != 1 || metadata.Caring.Capabilities[0] != api.CapabilityView { t.Errorf("metadata.Caring.Capabilities = %v; want [View]", metadata.Caring.Capabilities) } if len(metadata.Caring.Restrictions) != 1 || metadata.Caring.Restrictions[0] != api.RestrictionExportBlocked { t.Errorf("metadata.Caring.Restrictions = %v; want [ExportBlocked]", metadata.Caring.Restrictions) } } func TestPolicyFixtureParses(t *testing.T) { var fixture api.PolicyFixture loadYAML(t, filepath.Join("..", "..", "examples", "caring", "policy_fixture.yaml"), &fixture) if fixture.Expect.Effect != api.DecisionEffectAllow { t.Errorf("fixture.Expect.Effect = %q; want allow", fixture.Expect.Effect) } if fixture.Request.CaringContext == nil { t.Fatal("fixture.Request.CaringContext is nil") } } func inlinePolicy(enforce bool, expectedEffect string) string { enforceValue := "false" if enforce { enforceValue = "true" } fence := "```" doc := strings.ReplaceAll(`--- id: inline.allow version: v1 package: flexauth.inline caring: profile: caring-0.4.0-rc2 enforce: ENFORCE --- # Inline Policy `+fence+`rego default decision := {"effect": "allow", "reason": "ok"} `+fence+` `+fence+`rego test package flexauth.inline_test import future.keywords.if import data.flexauth.inline test_allow if { inline.decision.effect == "allow" } `+fence+` `+fence+`yaml fixture id: fixture:inline request: subject: id: user:alice action: read resource: id: document:inline expect: effect: EXPECTED reason: ok `+fence+` `, "ENFORCE", enforceValue) return strings.ReplaceAll(doc, "EXPECTED", expectedEffect) } func formatValidation(result policy.ValidationResult) string { data, err := json.MarshalIndent(result, "", " ") if err != nil { return err.Error() } return string(data) } 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) } }