Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/policy_engine_test.go
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

941 lines
23 KiB
Go

package ucca
import (
"os"
"path/filepath"
"testing"
)
// Helper to get the project root for testing
func getProjectRoot(t *testing.T) string {
// Start from the current directory and walk up to find go.mod
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get working directory: %v", err)
}
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
t.Fatalf("Could not find project root (no go.mod found)")
}
dir = parent
}
}
func TestNewPolicyEngineFromPath(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
if engine.GetPolicyVersion() != "1.0.0" {
t.Errorf("Expected policy version 1.0.0, got %s", engine.GetPolicyVersion())
}
}
func TestPolicyEngine_EvaluateSimpleCase(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Simple RAG chatbot for utilities (low risk)
intake := &UseCaseIntake{
UseCaseText: "Chatbot für Stadtwerke mit FAQ-Suche",
Domain: DomainUtilities,
DataTypes: DataTypes{
PersonalData: false,
PublicData: true,
},
Purpose: Purpose{
CustomerSupport: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{
RAG: true,
Training: false,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.Feasibility != FeasibilityYES {
t.Errorf("Expected feasibility YES, got %s", result.Feasibility)
}
if result.RiskLevel != RiskLevelMINIMAL {
t.Errorf("Expected risk level MINIMAL, got %s", result.RiskLevel)
}
}
func TestPolicyEngine_EvaluateHighRiskCase(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: HR scoring with full automation (should be blocked)
intake := &UseCaseIntake{
UseCaseText: "Automatische Mitarbeiterbewertung",
Domain: DomainHR,
DataTypes: DataTypes{
PersonalData: true,
EmployeeData: true,
},
Purpose: Purpose{
EvaluationScoring: true,
},
Automation: AutomationFullyAutomated,
Outputs: Outputs{
RankingsOrScores: true,
},
ModelUsage: ModelUsage{
Training: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.Feasibility != FeasibilityNO {
t.Errorf("Expected feasibility NO for HR scoring, got %s", result.Feasibility)
}
// Should have at least one BLOCK severity rule triggered
hasBlock := false
for _, rule := range result.TriggeredRules {
if rule.Severity == SeverityBLOCK {
hasBlock = true
break
}
}
if !hasBlock {
t.Error("Expected at least one BLOCK severity rule for HR scoring")
}
}
func TestPolicyEngine_EvaluateConditionalCase(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Personal data with marketing (should be conditional)
intake := &UseCaseIntake{
UseCaseText: "Marketing-Personalisierung",
Domain: DomainMarketing,
DataTypes: DataTypes{
PersonalData: true,
},
Purpose: Purpose{
Marketing: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{
RAG: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.Feasibility != FeasibilityCONDITIONAL {
t.Errorf("Expected feasibility CONDITIONAL for marketing with PII, got %s", result.Feasibility)
}
// Should require consent control
hasConsentControl := false
for _, ctrl := range result.RequiredControls {
if ctrl.ID == "C_EXPLICIT_CONSENT" {
hasConsentControl = true
break
}
}
if !hasConsentControl {
t.Error("Expected C_EXPLICIT_CONSENT control for marketing with PII")
}
}
func TestPolicyEngine_EvaluateArticle9Data(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Healthcare with Art. 9 data
intake := &UseCaseIntake{
UseCaseText: "Patientendaten-Analyse",
Domain: DomainHealthcare,
DataTypes: DataTypes{
PersonalData: true,
Article9Data: true,
},
Purpose: Purpose{
Analytics: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{
RAG: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
// Art. 9 data should trigger DSFA recommendation
if !result.DSFARecommended {
t.Error("Expected DSFA recommended for Art. 9 data")
}
// Should have triggered Art. 9 rule
hasArt9Rule := false
for _, rule := range result.TriggeredRules {
if rule.Code == "R-A002" {
hasArt9Rule = true
break
}
}
if !hasArt9Rule {
t.Error("Expected R-A002 (Art. 9 data) rule to be triggered")
}
}
func TestPolicyEngine_EvaluateLicensePlates(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Parking with license plates
intake := &UseCaseIntake{
UseCaseText: "Parkhaus-Kennzeichenerkennung",
Domain: DomainRealEstate,
DataTypes: DataTypes{
PersonalData: true,
LicensePlates: true,
},
Purpose: Purpose{
Automation: true,
},
Automation: AutomationSemiAutomated,
ModelUsage: ModelUsage{
Inference: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
// Should suggest pixelization pattern
hasPixelization := false
for _, pattern := range result.RecommendedArchitecture {
if pattern.PatternID == "P_PIXELIZATION" {
hasPixelization = true
break
}
}
if !hasPixelization {
t.Error("Expected P_PIXELIZATION pattern to be recommended for license plates")
}
}
func TestPolicyEngine_EvaluateThirdCountryTransfer(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Third country hosting with PII
intake := &UseCaseIntake{
UseCaseText: "US-hosted AI service",
Domain: DomainITServices,
DataTypes: DataTypes{
PersonalData: true,
},
Purpose: Purpose{
InternalTools: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{
RAG: true,
},
Hosting: Hosting{
Region: "third_country",
},
}
result := engine.Evaluate(intake)
// Should require SCC control
hasSCC := false
for _, ctrl := range result.RequiredControls {
if ctrl.ID == "C_SCC" {
hasSCC = true
break
}
}
if !hasSCC {
t.Error("Expected C_SCC control for third country transfer with PII")
}
}
func TestPolicyEngine_GetAllRules(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
rules := engine.GetAllRules()
if len(rules) == 0 {
t.Error("Expected at least some rules")
}
// Check that rules have required fields
for _, rule := range rules {
if rule.ID == "" {
t.Error("Found rule without ID")
}
if rule.Category == "" {
t.Error("Found rule without category")
}
if rule.Title == "" {
t.Error("Found rule without title")
}
}
}
func TestPolicyEngine_GetAllPatterns(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
patterns := engine.GetAllPatterns()
if len(patterns) == 0 {
t.Error("Expected at least some patterns")
}
// Verify expected patterns exist
expectedPatterns := []string{"P_RAG_ONLY", "P_PRE_ANON", "P_PIXELIZATION", "P_HITL_ENFORCED"}
for _, expected := range expectedPatterns {
if _, exists := patterns[expected]; !exists {
t.Errorf("Expected pattern %s to exist", expected)
}
}
}
func TestPolicyEngine_GetAllControls(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
controls := engine.GetAllControls()
if len(controls) == 0 {
t.Error("Expected at least some controls")
}
// Verify expected controls exist
expectedControls := []string{"C_EXPLICIT_CONSENT", "C_DSFA", "C_ENCRYPTION", "C_SCC"}
for _, expected := range expectedControls {
if _, exists := controls[expected]; !exists {
t.Errorf("Expected control %s to exist", expected)
}
}
}
func TestPolicyEngine_TrainingWithMinorData(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Training with minor data (should be blocked)
intake := &UseCaseIntake{
UseCaseText: "KI-Training mit Schülerdaten",
Domain: DomainEducation,
DataTypes: DataTypes{
PersonalData: true,
MinorData: true,
},
Purpose: Purpose{
Research: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{
Training: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.TrainingAllowed != TrainingNO {
t.Errorf("Expected training NOT allowed for minor data, got %s", result.TrainingAllowed)
}
}
func TestPolicyEngine_CompositeConditions(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Test case: Fully automated with legal effects (R-C004 uses all_of)
intake := &UseCaseIntake{
UseCaseText: "Automatische Vertragsgenehmigung",
Domain: DomainLegal,
DataTypes: DataTypes{
PersonalData: true,
},
Purpose: Purpose{
DecisionMaking: true,
},
Automation: AutomationFullyAutomated,
Outputs: Outputs{
LegalEffects: true,
},
ModelUsage: ModelUsage{
Inference: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.Feasibility != FeasibilityNO {
t.Errorf("Expected NO feasibility for fully automated legal decisions, got %s", result.Feasibility)
}
// Check that R-C004 was triggered
hasC004 := false
for _, rule := range result.TriggeredRules {
if rule.Code == "R-C004" {
hasC004 = true
break
}
}
if !hasC004 {
t.Error("Expected R-C004 (automated legal effects) to be triggered")
}
}
// ============================================================================
// Determinism Tests - Ensure consistent results
// ============================================================================
func TestPolicyEngine_Determinism(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
intake := &UseCaseIntake{
UseCaseText: "Test Case für Determinismus",
Domain: DomainEducation,
DataTypes: DataTypes{
PersonalData: true,
MinorData: true,
},
Purpose: Purpose{
EvaluationScoring: true,
},
Automation: AutomationFullyAutomated,
Outputs: Outputs{
RankingsOrScores: true,
},
ModelUsage: ModelUsage{
Training: true,
},
Hosting: Hosting{
Region: "eu",
},
}
// Run evaluation 10 times and ensure identical results
firstResult := engine.Evaluate(intake)
for i := 0; i < 10; i++ {
result := engine.Evaluate(intake)
if result.Feasibility != firstResult.Feasibility {
t.Errorf("Run %d: Feasibility mismatch: %s vs %s", i, result.Feasibility, firstResult.Feasibility)
}
if result.RiskScore != firstResult.RiskScore {
t.Errorf("Run %d: RiskScore mismatch: %d vs %d", i, result.RiskScore, firstResult.RiskScore)
}
if result.RiskLevel != firstResult.RiskLevel {
t.Errorf("Run %d: RiskLevel mismatch: %s vs %s", i, result.RiskLevel, firstResult.RiskLevel)
}
if len(result.TriggeredRules) != len(firstResult.TriggeredRules) {
t.Errorf("Run %d: TriggeredRules count mismatch: %d vs %d", i, len(result.TriggeredRules), len(firstResult.TriggeredRules))
}
}
}
// ============================================================================
// Education Domain Specific Tests
// ============================================================================
func TestPolicyEngine_EducationScoring_AlwaysBlocked(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Education + Scoring + Fully Automated = BLOCK (R-F001)
intake := &UseCaseIntake{
UseCaseText: "Automatische Schülerbewertung",
Domain: DomainEducation,
DataTypes: DataTypes{
PersonalData: true,
MinorData: true,
},
Purpose: Purpose{
EvaluationScoring: true,
},
Automation: AutomationFullyAutomated,
Outputs: Outputs{
RankingsOrScores: true,
},
ModelUsage: ModelUsage{
Inference: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.Feasibility != FeasibilityNO {
t.Errorf("Expected NO for education automated scoring, got %s", result.Feasibility)
}
// Should have Art. 22 risk flagged
if !result.Art22Risk {
t.Error("Expected Art. 22 risk for automated individual decisions in education")
}
}
// ============================================================================
// RAG-Only Use Cases (Low Risk)
// ============================================================================
func TestPolicyEngine_RAGOnly_LowRisk(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
intake := &UseCaseIntake{
UseCaseText: "FAQ-Suche mit öffentlichen Dokumenten",
Domain: DomainUtilities,
DataTypes: DataTypes{
PublicData: true,
},
Purpose: Purpose{
CustomerSupport: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{
RAG: true,
},
Hosting: Hosting{
Region: "eu",
},
}
result := engine.Evaluate(intake)
if result.Feasibility != FeasibilityYES {
t.Errorf("Expected YES for RAG-only public data, got %s", result.Feasibility)
}
if result.RiskLevel != RiskLevelMINIMAL {
t.Errorf("Expected MINIMAL risk for RAG-only, got %s", result.RiskLevel)
}
// Should recommend P_RAG_ONLY pattern
hasRAGPattern := false
for _, pattern := range result.RecommendedArchitecture {
if pattern.PatternID == "P_RAG_ONLY" {
hasRAGPattern = true
break
}
}
if !hasRAGPattern {
t.Error("Expected P_RAG_ONLY pattern recommendation")
}
}
// ============================================================================
// Risk Score Calculation Tests
// ============================================================================
func TestPolicyEngine_RiskScoreCalculation(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
tests := []struct {
name string
intake *UseCaseIntake
minScore int
maxScore int
expectedRiskLevel RiskLevel
}{
{
name: "Public data only → minimal risk",
intake: &UseCaseIntake{
Domain: DomainUtilities,
DataTypes: DataTypes{
PublicData: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{RAG: true},
Hosting: Hosting{Region: "eu"},
},
minScore: 0,
maxScore: 20,
expectedRiskLevel: RiskLevelMINIMAL,
},
{
name: "Personal data → low risk",
intake: &UseCaseIntake{
Domain: DomainITServices,
DataTypes: DataTypes{
PersonalData: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{RAG: true},
Hosting: Hosting{Region: "eu"},
},
minScore: 5,
maxScore: 40,
expectedRiskLevel: RiskLevelLOW,
},
{
name: "Art. 9 data → medium risk",
intake: &UseCaseIntake{
Domain: DomainHealthcare,
DataTypes: DataTypes{
PersonalData: true,
Article9Data: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{RAG: true},
Hosting: Hosting{Region: "eu"},
},
minScore: 20,
maxScore: 60,
expectedRiskLevel: RiskLevelMEDIUM,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := engine.Evaluate(tt.intake)
if result.RiskScore < tt.minScore || result.RiskScore > tt.maxScore {
t.Errorf("RiskScore %d outside expected range [%d, %d]", result.RiskScore, tt.minScore, tt.maxScore)
}
})
}
}
// ============================================================================
// Training Allowed Tests
// ============================================================================
func TestPolicyEngine_TrainingAllowed_Scenarios(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
tests := []struct {
name string
intake *UseCaseIntake
expectedAllowed TrainingAllowed
}{
{
name: "Public data training → allowed",
intake: &UseCaseIntake{
Domain: DomainUtilities,
DataTypes: DataTypes{
PublicData: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{Training: true},
Hosting: Hosting{Region: "eu"},
},
expectedAllowed: TrainingYES,
},
{
name: "Minor data training → not allowed",
intake: &UseCaseIntake{
Domain: DomainEducation,
DataTypes: DataTypes{
PersonalData: true,
MinorData: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{Training: true},
Hosting: Hosting{Region: "eu"},
},
expectedAllowed: TrainingNO,
},
{
name: "Art. 9 data training → not allowed",
intake: &UseCaseIntake{
Domain: DomainHealthcare,
DataTypes: DataTypes{
PersonalData: true,
Article9Data: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{Training: true},
Hosting: Hosting{Region: "eu"},
},
expectedAllowed: TrainingNO,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := engine.Evaluate(tt.intake)
if result.TrainingAllowed != tt.expectedAllowed {
t.Errorf("Expected TrainingAllowed=%s, got %s", tt.expectedAllowed, result.TrainingAllowed)
}
})
}
}
// ============================================================================
// DSFA Recommendation Tests
// ============================================================================
func TestPolicyEngine_DSFARecommendation(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
tests := []struct {
name string
intake *UseCaseIntake
expectDSFA bool
expectArt22 bool
}{
{
name: "Art. 9 data → DSFA required",
intake: &UseCaseIntake{
Domain: DomainHealthcare,
DataTypes: DataTypes{
PersonalData: true,
Article9Data: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{RAG: true},
Hosting: Hosting{Region: "eu"},
},
expectDSFA: true,
expectArt22: false,
},
{
name: "Systematic evaluation → DSFA + Art. 22",
intake: &UseCaseIntake{
Domain: DomainHR,
DataTypes: DataTypes{
PersonalData: true,
EmployeeData: true,
},
Purpose: Purpose{
EvaluationScoring: true,
},
Automation: AutomationFullyAutomated,
Outputs: Outputs{
RankingsOrScores: true,
},
ModelUsage: ModelUsage{Inference: true},
Hosting: Hosting{Region: "eu"},
},
expectDSFA: true,
expectArt22: true,
},
{
name: "Public data RAG → no DSFA",
intake: &UseCaseIntake{
Domain: DomainUtilities,
DataTypes: DataTypes{
PublicData: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{RAG: true},
Hosting: Hosting{Region: "eu"},
},
expectDSFA: false,
expectArt22: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := engine.Evaluate(tt.intake)
if result.DSFARecommended != tt.expectDSFA {
t.Errorf("Expected DSFARecommended=%v, got %v", tt.expectDSFA, result.DSFARecommended)
}
if result.Art22Risk != tt.expectArt22 {
t.Errorf("Expected Art22Risk=%v, got %v", tt.expectArt22, result.Art22Risk)
}
})
}
}
// ============================================================================
// Required Controls Tests
// ============================================================================
func TestPolicyEngine_RequiredControls(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
// Third country with PII should require SCC
intake := &UseCaseIntake{
Domain: DomainITServices,
DataTypes: DataTypes{
PersonalData: true,
},
Automation: AutomationAssistive,
ModelUsage: ModelUsage{RAG: true},
Hosting: Hosting{
Region: "third_country",
},
}
result := engine.Evaluate(intake)
controlIDs := make(map[string]bool)
for _, ctrl := range result.RequiredControls {
controlIDs[ctrl.ID] = true
}
if !controlIDs["C_SCC"] {
t.Error("Expected C_SCC control for third country transfer")
}
}
// ============================================================================
// Policy Version Tests
// ============================================================================
func TestPolicyEngine_PolicyVersion(t *testing.T) {
root := getProjectRoot(t)
policyPath := filepath.Join(root, "policies", "ucca_policy_v1.yaml")
engine, err := NewPolicyEngineFromPath(policyPath)
if err != nil {
t.Fatalf("Failed to create policy engine: %v", err)
}
version := engine.GetPolicyVersion()
// Version should be non-empty and follow semver pattern
if version == "" {
t.Error("Policy version should not be empty")
}
// Check for basic semver pattern (x.y.z)
parts := 0
for _, c := range version {
if c == '.' {
parts++
}
}
if parts < 2 {
t.Errorf("Policy version should follow semver (x.y.z), got %s", version)
}
}