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>
This commit is contained in:
618
ai-compliance-sdk/internal/ucca/financial_policy_test.go
Normal file
618
ai-compliance-sdk/internal/ucca/financial_policy_test.go
Normal file
@@ -0,0 +1,618 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user