package ucca import ( "testing" ) // ============================================================================ // Financial Policy Engine Tests // ============================================================================ func TestFinancialPolicyEngine_NewEngine(t *testing.T) { // Try to load the financial policy engine engine, err := NewFinancialPolicyEngineFromPath("../../policies/financial_regulations_policy.yaml") if err != nil { t.Skipf("Skipping test - policy file not found: %v", err) } if engine == nil { t.Fatal("Engine should not be nil") } version := engine.GetPolicyVersion() if version == "" { t.Error("Policy version should not be empty") } } func TestFinancialPolicyEngine_IsApplicable(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } tests := []struct { name string domain Domain expected bool }{ {"Banking domain is applicable", DomainBanking, true}, {"Finance domain is applicable", DomainFinance, true}, {"Insurance domain is applicable", DomainInsurance, true}, {"Investment domain is applicable", DomainInvestment, true}, {"Healthcare is not applicable", DomainHealthcare, false}, {"Retail is not applicable", DomainRetail, false}, {"Education is not applicable", DomainEducation, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { intake := &UseCaseIntake{ Domain: tt.domain, } result := engine.IsApplicable(intake) if result != tt.expected { t.Errorf("IsApplicable() = %v, want %v", result, tt.expected) } }) } } func TestFinancialPolicyEngine_Evaluate_NonApplicableDomain(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainHealthcare, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, }, } result := engine.Evaluate(intake) if result.IsApplicable { t.Error("Should not be applicable for healthcare domain") } if len(result.TriggeredRules) > 0 { t.Error("No rules should trigger for non-applicable domain") } } func TestFinancialPolicyEngine_Evaluate_MissingContext(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: nil, // No financial context provided } result := engine.Evaluate(intake) if !result.IsApplicable { t.Error("Should be applicable for banking domain") } if !result.MissingContext { t.Error("Should indicate missing financial context") } } func TestFinancialPolicyEngine_Evaluate_RegulatedBank(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, SizeCategory: SizeCategoryLessSignificant, }, ICTService: ICTService{ IsCritical: false, IsOutsourced: false, ProviderLocation: ProviderLocationEU, }, AIApplication: FinancialAIApplication{ AffectsCustomerDecisions: false, RiskAssessment: false, }, }, } result := engine.Evaluate(intake) if !result.IsApplicable { t.Error("Should be applicable for banking domain") } if result.MissingContext { t.Error("Context should not be missing") } // Should trigger basic DORA/BAIT rules for regulated banks if len(result.TriggeredRules) == 0 { t.Error("Should trigger at least basic regulatory rules") } // Check for DORA control requirements hasDORAControl := false for _, ctrl := range result.RequiredControls { if ctrl.Category == "DORA" || ctrl.Category == "BAIT" { hasDORAControl = true break } } if !hasDORAControl { t.Error("Should require DORA or BAIT controls for regulated bank") } } func TestFinancialPolicyEngine_Evaluate_CriticalICTOutsourcing(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, ICTService: ICTService{ IsCritical: true, IsOutsourced: true, ProviderLocation: ProviderLocationEU, }, }, } result := engine.Evaluate(intake) // Should have elevated risk for critical ICT outsourcing if result.RiskScore == 0 { t.Error("Risk score should be elevated for critical ICT outsourcing") } // Should require TPP risk management control hasTPPControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == "CTRL-DORA-TPP-RISK-MANAGEMENT" || ctrl.ID == "CTRL-MARISK-OUTSOURCING" { hasTPPControl = true break } } if !hasTPPControl { t.Error("Should require TPP risk management for critical outsourcing") } // Should trigger escalation if result.EscalationLevel == "" { t.Error("Should trigger escalation for critical ICT outsourcing") } } func TestFinancialPolicyEngine_Evaluate_UnvalidatedRiskModel(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, ICTService: ICTService{ IsCritical: false, IsOutsourced: false, }, AIApplication: FinancialAIApplication{ RiskAssessment: true, ModelValidationDone: false, // Not validated! }, }, } result := engine.Evaluate(intake) // Should block unvalidated risk models if result.Feasibility != FeasibilityNO { t.Error("Should block use case with unvalidated risk model") } // Should require model validation control hasValidationControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == "CTRL-MARISK-MODEL-VALIDATION" { hasValidationControl = true break } } if !hasValidationControl { t.Error("Should require MaRisk model validation control") } } func TestFinancialPolicyEngine_Evaluate_ValidatedRiskModel(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, ICTService: ICTService{ IsCritical: false, IsOutsourced: false, }, AIApplication: FinancialAIApplication{ RiskAssessment: true, ModelValidationDone: true, // Validated! }, }, } result := engine.Evaluate(intake) // Should not block validated risk models if result.Feasibility == FeasibilityNO { t.Error("Should not block use case with validated risk model") } } func TestFinancialPolicyEngine_Evaluate_AlgorithmicTrading(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainInvestment, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityInvestmentFirm, Regulated: true, }, AIApplication: FinancialAIApplication{ AlgorithmicTrading: true, }, }, } result := engine.Evaluate(intake) // Should require algorithmic trading control hasAlgoControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == "CTRL-FIN-ALGO-TRADING" { hasAlgoControl = true break } } if !hasAlgoControl { t.Error("Should require algorithmic trading control") } // Should trigger highest escalation if result.EscalationLevel != "E3" { t.Errorf("Should trigger E3 escalation for algo trading, got %s", result.EscalationLevel) } } func TestFinancialPolicyEngine_Evaluate_CustomerDecisions(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, AIApplication: FinancialAIApplication{ AffectsCustomerDecisions: true, }, }, } result := engine.Evaluate(intake) // Should require explainability control hasExplainControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == "CTRL-FIN-AI-EXPLAINABILITY" { hasExplainControl = true break } } if !hasExplainControl { t.Error("Should require AI explainability control for customer decisions") } // Should trigger E2 escalation if result.EscalationLevel != "E2" { t.Errorf("Should trigger E2 escalation for customer decisions, got %s", result.EscalationLevel) } } func TestFinancialPolicyEngine_Evaluate_AMLKYC(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, AIApplication: FinancialAIApplication{ AMLKYC: true, }, }, } result := engine.Evaluate(intake) // Should require AML control hasAMLControl := false for _, ctrl := range result.RequiredControls { if ctrl.ID == "CTRL-FIN-AML-AI" { hasAMLControl = true break } } if !hasAMLControl { t.Error("Should require AML AI control for KYC/AML use cases") } } func TestFinancialPolicyEngine_Evaluate_ThirdCountryCritical(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, ICTService: ICTService{ IsCritical: true, IsOutsourced: true, ProviderLocation: ProviderLocationThirdCountry, }, }, } result := engine.Evaluate(intake) // Should be conditional at minimum if result.Feasibility == FeasibilityYES { t.Error("Should not allow critical ICT in third country without conditions") } // Should have elevated risk if result.RiskScore < 30 { t.Error("Should have elevated risk score for third country critical ICT") } } func TestFinancialPolicyEngine_Evaluate_ConcentrationRisk(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, ICTService: ICTService{ IsOutsourced: true, ConcentrationRisk: true, }, }, } result := engine.Evaluate(intake) // Should trigger escalation for concentration risk if result.EscalationLevel == "" { t.Error("Should trigger escalation for concentration risk") } // Should add risk if result.RiskScore == 0 { t.Error("Should add risk for concentration risk") } } func TestFinancialPolicyEngine_Evaluate_InsuranceCompany(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainInsurance, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityInsuranceCompany, Regulated: true, }, }, } result := engine.Evaluate(intake) if !result.IsApplicable { t.Error("Should be applicable for insurance domain") } } func TestFinancialPolicyEngine_GetAllControls(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } controls := engine.GetAllControls() if len(controls) == 0 { t.Error("Should have controls defined") } // Check for key DORA controls keyControls := []string{ "CTRL-DORA-ICT-RISK-FRAMEWORK", "CTRL-DORA-ICT-INCIDENT-MANAGEMENT", "CTRL-DORA-TPP-RISK-MANAGEMENT", "CTRL-MARISK-MODEL-VALIDATION", "CTRL-BAIT-SDLC", } for _, key := range keyControls { if _, ok := controls[key]; !ok { t.Errorf("Should have control %s", key) } } } func TestFinancialPolicyEngine_GetAllGaps(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } gaps := engine.GetAllGaps() if len(gaps) == 0 { t.Error("Should have gaps defined") } // Check for key gaps keyGaps := []string{ "GAP_DORA_NOT_IMPLEMENTED", "GAP_MARISK_MODEL_NOT_VALIDATED", } for _, key := range keyGaps { if _, ok := gaps[key]; !ok { t.Errorf("Should have gap %s", key) } } } func TestFinancialPolicyEngine_GetAllStopLines(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } stopLines := engine.GetAllStopLines() if len(stopLines) == 0 { t.Error("Should have stop lines defined") } // Check for key stop lines keyStopLines := []string{ "STOP_MARISK_UNVALIDATED_RISK_MODEL", "STOP_ALGO_TRADING_WITHOUT_APPROVAL", } for _, key := range keyStopLines { if _, ok := stopLines[key]; !ok { t.Errorf("Should have stop line %s", key) } } } func TestFinancialPolicyEngine_Determinism(t *testing.T) { engine := createTestFinancialEngine(t) if engine == nil { return } intake := &UseCaseIntake{ Domain: DomainBanking, FinancialContext: &FinancialContext{ FinancialEntity: FinancialEntity{ Type: FinancialEntityCreditInstitution, Regulated: true, }, ICTService: ICTService{ IsCritical: true, IsOutsourced: true, }, AIApplication: FinancialAIApplication{ AffectsCustomerDecisions: true, RiskAssessment: true, ModelValidationDone: true, }, }, } // Run evaluation multiple times var lastResult *FinancialAssessmentResult for i := 0; i < 10; i++ { result := engine.Evaluate(intake) if lastResult != nil { if result.Feasibility != lastResult.Feasibility { t.Error("Feasibility should be deterministic") } if result.RiskScore != lastResult.RiskScore { t.Error("Risk score should be deterministic") } if len(result.TriggeredRules) != len(lastResult.TriggeredRules) { t.Error("Triggered rules should be deterministic") } if len(result.RequiredControls) != len(lastResult.RequiredControls) { t.Error("Required controls should be deterministic") } } lastResult = result } } // ============================================================================ // Helper Functions // ============================================================================ func createTestFinancialEngine(t *testing.T) *FinancialPolicyEngine { engine, err := NewFinancialPolicyEngineFromPath("../../policies/financial_regulations_policy.yaml") if err != nil { t.Skipf("Skipping test - policy file not found: %v", err) return nil } return engine }