Files
flex-auth/internal/policy/package_test.go
tegwick 18054bd160
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Add CARING examples and coverage
2026-05-17 06:05:18 +02:00

213 lines
6.4 KiB
Go

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)
}
}