package ucca import ( "testing" ) func TestBuildDecisionTreeDefinition_ReturnsValidTree(t *testing.T) { tree := BuildDecisionTreeDefinition() if tree == nil { t.Fatal("Expected non-nil tree definition") } if tree.ID != "ai_act_two_axis" { t.Errorf("Expected ID 'ai_act_two_axis', got '%s'", tree.ID) } if tree.Version != "1.0.0" { t.Errorf("Expected version '1.0.0', got '%s'", tree.Version) } if len(tree.Questions) != 12 { t.Errorf("Expected 12 questions, got %d", len(tree.Questions)) } // Check axis distribution hrCount := 0 gpaiCount := 0 for _, q := range tree.Questions { switch q.Axis { case "high_risk": hrCount++ case "gpai": gpaiCount++ default: t.Errorf("Unexpected axis '%s' for question %s", q.Axis, q.ID) } } if hrCount != 7 { t.Errorf("Expected 7 high_risk questions, got %d", hrCount) } if gpaiCount != 5 { t.Errorf("Expected 5 gpai questions, got %d", gpaiCount) } // Check all questions have required fields for _, q := range tree.Questions { if q.ID == "" { t.Error("Question has empty ID") } if q.Question == "" { t.Errorf("Question %s has empty question text", q.ID) } if q.Description == "" { t.Errorf("Question %s has empty description", q.ID) } if q.ArticleRef == "" { t.Errorf("Question %s has empty article_ref", q.ID) } } } func TestEvaluateDecisionTree_NotApplicable(t *testing.T) { // Q1=No → AI Act not applicable req := &DecisionTreeEvalRequest{ SystemName: "Test System", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: false}, }, } result := EvaluateDecisionTree(req) if result.HighRiskResult != AIActNotApplicable { t.Errorf("Expected not_applicable, got %s", result.HighRiskResult) } if result.GPAIResult.IsGPAI { t.Error("Expected GPAI to be false when Q8 is not answered") } if result.SystemName != "Test System" { t.Errorf("Expected system name 'Test System', got '%s'", result.SystemName) } } func TestEvaluateDecisionTree_MinimalRisk(t *testing.T) { // Q1=Yes, Q2-Q7=No → minimal risk req := &DecisionTreeEvalRequest{ SystemName: "Simple Tool", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q2: {QuestionID: Q2, Value: false}, Q3: {QuestionID: Q3, Value: false}, Q4: {QuestionID: Q4, Value: false}, Q5: {QuestionID: Q5, Value: false}, Q6: {QuestionID: Q6, Value: false}, Q7: {QuestionID: Q7, Value: false}, Q8: {QuestionID: Q8, Value: false}, }, } result := EvaluateDecisionTree(req) if result.HighRiskResult != AIActMinimalRisk { t.Errorf("Expected minimal_risk, got %s", result.HighRiskResult) } if result.GPAIResult.IsGPAI { t.Error("Expected GPAI to be false") } if result.GPAIResult.Category != GPAICategoryNone { t.Errorf("Expected GPAI category 'none', got '%s'", result.GPAIResult.Category) } } func TestEvaluateDecisionTree_HighRisk_Biometric(t *testing.T) { // Q1=Yes, Q2=Yes → high risk (biometric) req := &DecisionTreeEvalRequest{ SystemName: "Face Recognition", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q2: {QuestionID: Q2, Value: true}, Q3: {QuestionID: Q3, Value: false}, Q4: {QuestionID: Q4, Value: false}, Q5: {QuestionID: Q5, Value: false}, Q6: {QuestionID: Q6, Value: false}, Q7: {QuestionID: Q7, Value: false}, }, } result := EvaluateDecisionTree(req) if result.HighRiskResult != AIActHighRisk { t.Errorf("Expected high_risk, got %s", result.HighRiskResult) } // Should have high-risk obligations if len(result.CombinedObligations) == 0 { t.Error("Expected non-empty obligations for high-risk system") } } func TestEvaluateDecisionTree_HighRisk_CriticalInfrastructure(t *testing.T) { // Q1=Yes, Q3=Yes → high risk (critical infrastructure) req := &DecisionTreeEvalRequest{ SystemName: "Energy Grid AI", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q2: {QuestionID: Q2, Value: false}, Q3: {QuestionID: Q3, Value: true}, Q4: {QuestionID: Q4, Value: false}, Q5: {QuestionID: Q5, Value: false}, Q6: {QuestionID: Q6, Value: false}, Q7: {QuestionID: Q7, Value: false}, }, } result := EvaluateDecisionTree(req) if result.HighRiskResult != AIActHighRisk { t.Errorf("Expected high_risk, got %s", result.HighRiskResult) } } func TestEvaluateDecisionTree_HighRisk_Education(t *testing.T) { // Q1=Yes, Q4=Yes → high risk (education/employment) req := &DecisionTreeEvalRequest{ SystemName: "Exam Grading AI", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q2: {QuestionID: Q2, Value: false}, Q3: {QuestionID: Q3, Value: false}, Q4: {QuestionID: Q4, Value: true}, }, } result := EvaluateDecisionTree(req) if result.HighRiskResult != AIActHighRisk { t.Errorf("Expected high_risk, got %s", result.HighRiskResult) } } func TestEvaluateDecisionTree_HighRisk_AutonomousDecisions(t *testing.T) { // Q1=Yes, Q7=Yes → high risk (autonomous decisions) req := &DecisionTreeEvalRequest{ SystemName: "Credit Scoring AI", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q2: {QuestionID: Q2, Value: false}, Q3: {QuestionID: Q3, Value: false}, Q4: {QuestionID: Q4, Value: false}, Q5: {QuestionID: Q5, Value: false}, Q6: {QuestionID: Q6, Value: false}, Q7: {QuestionID: Q7, Value: true}, }, } result := EvaluateDecisionTree(req) if result.HighRiskResult != AIActHighRisk { t.Errorf("Expected high_risk, got %s", result.HighRiskResult) } } func TestEvaluateDecisionTree_GPAI_Standard(t *testing.T) { // Q8=Yes, Q10=No → GPAI standard req := &DecisionTreeEvalRequest{ SystemName: "Custom LLM", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q8: {QuestionID: Q8, Value: true}, Q9: {QuestionID: Q9, Value: true}, Q10: {QuestionID: Q10, Value: false}, Q11: {QuestionID: Q11, Value: false}, Q12: {QuestionID: Q12, Value: false}, }, } result := EvaluateDecisionTree(req) if !result.GPAIResult.IsGPAI { t.Error("Expected IsGPAI to be true") } if result.GPAIResult.Category != GPAICategoryStandard { t.Errorf("Expected category 'standard', got '%s'", result.GPAIResult.Category) } if result.GPAIResult.IsSystemicRisk { t.Error("Expected IsSystemicRisk to be false") } // Should have Art. 51, 53, 50 (generative) hasArt51 := false hasArt53 := false hasArt50 := false for _, a := range result.GPAIResult.ApplicableArticles { if a == "Art. 51" { hasArt51 = true } if a == "Art. 53" { hasArt53 = true } if a == "Art. 50" { hasArt50 = true } } if !hasArt51 { t.Error("Expected Art. 51 in applicable articles") } if !hasArt53 { t.Error("Expected Art. 53 in applicable articles") } if !hasArt50 { t.Error("Expected Art. 50 in applicable articles (generative AI)") } } func TestEvaluateDecisionTree_GPAI_SystemicRisk(t *testing.T) { // Q8=Yes, Q10=Yes → GPAI systemic risk req := &DecisionTreeEvalRequest{ SystemName: "GPT-5", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q8: {QuestionID: Q8, Value: true}, Q9: {QuestionID: Q9, Value: true}, Q10: {QuestionID: Q10, Value: true}, Q11: {QuestionID: Q11, Value: true}, Q12: {QuestionID: Q12, Value: true}, }, } result := EvaluateDecisionTree(req) if !result.GPAIResult.IsGPAI { t.Error("Expected IsGPAI to be true") } if result.GPAIResult.Category != GPAICategorySystemic { t.Errorf("Expected category 'systemic', got '%s'", result.GPAIResult.Category) } if !result.GPAIResult.IsSystemicRisk { t.Error("Expected IsSystemicRisk to be true") } // Should have Art. 55 hasArt55 := false for _, a := range result.GPAIResult.ApplicableArticles { if a == "Art. 55" { hasArt55 = true } } if !hasArt55 { t.Error("Expected Art. 55 in applicable articles (systemic risk)") } } func TestEvaluateDecisionTree_Combined_HighRiskAndGPAI(t *testing.T) { // Q1=Yes, Q4=Yes (high risk) + Q8=Yes, Q9=Yes (GPAI standard) req := &DecisionTreeEvalRequest{ SystemName: "HR Screening with LLM", SystemDescription: "LLM-based applicant screening system", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q2: {QuestionID: Q2, Value: false}, Q3: {QuestionID: Q3, Value: false}, Q4: {QuestionID: Q4, Value: true}, Q5: {QuestionID: Q5, Value: false}, Q6: {QuestionID: Q6, Value: false}, Q7: {QuestionID: Q7, Value: true}, Q8: {QuestionID: Q8, Value: true}, Q9: {QuestionID: Q9, Value: true}, Q10: {QuestionID: Q10, Value: false}, Q11: {QuestionID: Q11, Value: false}, Q12: {QuestionID: Q12, Value: false}, }, } result := EvaluateDecisionTree(req) // Both axes should be triggered if result.HighRiskResult != AIActHighRisk { t.Errorf("Expected high_risk, got %s", result.HighRiskResult) } if !result.GPAIResult.IsGPAI { t.Error("Expected GPAI to be true") } if result.GPAIResult.Category != GPAICategoryStandard { t.Errorf("Expected GPAI category 'standard', got '%s'", result.GPAIResult.Category) } // Combined obligations should include both axes if len(result.CombinedObligations) < 5 { t.Errorf("Expected at least 5 combined obligations, got %d", len(result.CombinedObligations)) } // Should have articles from both axes if len(result.ApplicableArticles) < 3 { t.Errorf("Expected at least 3 applicable articles, got %d", len(result.ApplicableArticles)) } // Check system name preserved if result.SystemName != "HR Screening with LLM" { t.Errorf("Expected system name preserved, got '%s'", result.SystemName) } if result.SystemDescription != "LLM-based applicant screening system" { t.Errorf("Expected description preserved, got '%s'", result.SystemDescription) } } func TestEvaluateDecisionTree_GPAI_MarketPenetration(t *testing.T) { // Q8=Yes, Q10=No, Q12=Yes → GPAI standard with market penetration warning req := &DecisionTreeEvalRequest{ SystemName: "Popular Chatbot", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q8: {QuestionID: Q8, Value: true}, Q9: {QuestionID: Q9, Value: true}, Q10: {QuestionID: Q10, Value: false}, Q11: {QuestionID: Q11, Value: true}, Q12: {QuestionID: Q12, Value: true}, }, } result := EvaluateDecisionTree(req) if result.GPAIResult.Category != GPAICategoryStandard { t.Errorf("Expected category 'standard' (not systemic because Q10=No), got '%s'", result.GPAIResult.Category) } // Should have Art. 51 Abs. 3 warning hasArt51_3 := false for _, a := range result.GPAIResult.ApplicableArticles { if a == "Art. 51 Abs. 3" { hasArt51_3 = true } } if !hasArt51_3 { t.Error("Expected Art. 51 Abs. 3 in applicable articles for high market penetration") } } func TestEvaluateDecisionTree_NoGPAI(t *testing.T) { // Q8=No → No GPAI classification req := &DecisionTreeEvalRequest{ SystemName: "Traditional ML", Answers: map[string]DecisionTreeAnswer{ Q1: {QuestionID: Q1, Value: true}, Q8: {QuestionID: Q8, Value: false}, }, } result := EvaluateDecisionTree(req) if result.GPAIResult.IsGPAI { t.Error("Expected IsGPAI to be false") } if result.GPAIResult.Category != GPAICategoryNone { t.Errorf("Expected category 'none', got '%s'", result.GPAIResult.Category) } if len(result.GPAIResult.Obligations) != 0 { t.Errorf("Expected 0 GPAI obligations, got %d", len(result.GPAIResult.Obligations)) } } func TestAnswerIsYes(t *testing.T) { tests := []struct { name string answers map[string]DecisionTreeAnswer qID string expected bool }{ {"yes answer", map[string]DecisionTreeAnswer{"Q1": {Value: true}}, "Q1", true}, {"no answer", map[string]DecisionTreeAnswer{"Q1": {Value: false}}, "Q1", false}, {"missing answer", map[string]DecisionTreeAnswer{}, "Q1", false}, {"different question", map[string]DecisionTreeAnswer{"Q2": {Value: true}}, "Q1", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := answerIsYes(tt.answers, tt.qID) if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } }