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