package quality import ( "strings" "testing" "time" ) func TestNewScorer(t *testing.T) { tests := []struct { name string targetScore int expected int }{ {"default target", 0, 95}, {"negative target", -10, 95}, {"zero target", 0, 95}, {"custom target", 85, 85}, {"high target", 100, 100}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scorer := NewScorer(tt.targetScore) if scorer.targetScore != tt.expected { t.Errorf("NewScorer() targetScore = %v, want %v", scorer.targetScore, tt.expected) } }) } } func TestScorer_CalculateScore(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string findings []Finding totalScore int strictScore int }{ { name: "no findings", findings: []Finding{}, totalScore: 0, strictScore: 0, }, { name: "open findings only", findings: []Finding{ {Score: 5, Severity: SeverityT1, Status: StatusOpen}, {Score: 10, Severity: SeverityT2, Status: StatusOpen}, {Score: 15, Severity: SeverityT3, Status: StatusOpen}, {Score: 20, Severity: SeverityT4, Status: StatusOpen}, }, totalScore: 150, // 5*1 + 10*2 + 15*3 + 20*4 strictScore: 580, // (5*1)*1 + (10*2)*2 + (15*3)*3 + (20*4)*5 }, { name: "mixed statuses", findings: []Finding{ {Score: 5, Severity: SeverityT1, Status: StatusOpen}, {Score: 10, Severity: SeverityT2, Status: StatusFixed}, {Score: 15, Severity: SeverityT3, Status: StatusFalsePositive}, {Score: 20, Severity: SeverityT4, Status: StatusIgnored}, {Score: 25, Severity: SeverityT1, Status: StatusWontfix}, }, totalScore: 175, // All included with severity weighting strictScore: 30, // Open T1 + unjustified wontfix T1 }, { name: "justified wontfix", findings: []Finding{ {Score: 10, Severity: SeverityT2, Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "legacy code"}}, {Score: 15, Severity: SeverityT3, Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "third-party"}}, }, totalScore: 65, // All included in total with severity weighting strictScore: 0, // All wontfix are justified }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { total, strict := scorer.CalculateScore(tt.findings) if total != tt.totalScore { t.Errorf("CalculateScore() total = %v, want %v", total, tt.totalScore) } if strict != tt.strictScore { t.Errorf("CalculateScore() strict = %v, want %v", strict, tt.strictScore) } }) } } func TestScorer_GenerateScorecard(t *testing.T) { scorer := NewScorer(95) findings := []Finding{ {Type: "dead_code", Severity: SeverityT2, Status: StatusOpen, Score: 10}, {Type: "naming", Severity: SeverityT1, Status: StatusFixed, Score: 5}, {Type: "complexity", Severity: SeverityT3, Status: StatusOpen, Score: 15}, } lastScan := time.Now() card := scorer.GenerateScorecard(findings, lastScan) if card == nil { t.Error("GenerateScorecard() should not return nil") } if card.TargetScore != 95 { t.Errorf("GenerateScorecard() TargetScore = %v, want 95", card.TargetScore) } if card.TotalScore != 70 { // 10*2 + 5*1 + 15*3 t.Errorf("GenerateScorecard() TotalScore = %v, want 70", card.TotalScore) } if card.LastScan != lastScan { t.Error("GenerateScorecard() LastScan not set correctly") } // Check findings by type if card.FindingsByType["dead_code"] != 1 { t.Errorf("GenerateScorecard() dead_code count = %v, want 1", card.FindingsByType["dead_code"]) } if card.FindingsByType["naming"] != 1 { t.Errorf("GenerateScorecard() naming count = %v, want 1", card.FindingsByType["naming"]) } // Check findings by tier if card.FindingsByTier[SeverityT1] != 1 { t.Errorf("GenerateScorecard() T1 count = %v, want 1", card.FindingsByTier[SeverityT1]) } if card.FindingsByTier[SeverityT2] != 1 { t.Errorf("GenerateScorecard() T2 count = %v, want 1", card.FindingsByTier[SeverityT2]) } // Check status by type if card.StatusByType["open"] != 2 { t.Errorf("GenerateScorecard() open count = %v, want 2", card.StatusByType["open"]) } if card.StatusByType["fixed"] != 1 { t.Errorf("GenerateScorecard() fixed count = %v, want 1", card.StatusByType["fixed"]) } } func TestScorer_isStrictlyRelevant(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string finding Finding expected bool }{ {"open issue", Finding{Status: StatusOpen}, true}, {"fixed issue", Finding{Status: StatusFixed}, false}, {"false positive", Finding{Status: StatusFalsePositive}, false}, {"ignored", Finding{Status: StatusIgnored}, false}, {"unjustified wontfix", Finding{Status: StatusWontfix}, true}, {"justified wontfix", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "legacy"}}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scorer.isStrictlyRelevant(tt.finding) if result != tt.expected { t.Errorf("isStrictlyRelevant() = %v, want %v", result, tt.expected) } }) } } func TestScorer_isJustifiedWontfix(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string finding Finding expected bool }{ {"no metadata", Finding{Status: StatusWontfix}, false}, {"no resolution note", Finding{Status: StatusWontfix, Metadata: map[string]string{}}, false}, {"empty resolution note", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": ""}}, false}, {"legacy justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "legacy code"}}, true}, {"deprecated justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "deprecated API"}}, true}, {"external justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "external dependency"}}, true}, {"third-party justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "third-party library"}}, true}, {"temporary justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "temporary fix"}}, true}, {"documentation justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "documentation only"}}, true}, {"test-only justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "test-only code"}}, true}, {"example justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "example code"}}, true}, {"sample justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "sample code"}}, true}, {"invalid justification", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "needs fixing"}}, false}, {"case insensitive", Finding{Status: StatusWontfix, Metadata: map[string]string{"resolution_note": "LEGACY CODE"}}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scorer.isJustifiedWontfix(tt.finding) if result != tt.expected { t.Errorf("isJustifiedWontfix() = %v, want %v", result, tt.expected) } }) } } func TestScorer_getStrictMultiplier(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string finding Finding expected int }{ {"T1 severity", Finding{Severity: SeverityT1}, 1}, {"T2 severity", Finding{Severity: SeverityT2}, 2}, {"T3 severity", Finding{Severity: SeverityT3}, 3}, {"T4 severity", Finding{Severity: SeverityT4}, 5}, {"unknown severity", Finding{Severity: Severity(99)}, 1}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scorer.getStrictMultiplier(tt.finding) if result != tt.expected { t.Errorf("getStrictMultiplier() = %v, want %v", result, tt.expected) } }) } } func TestScorer_GetHealthGrade(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string score int expected string }{ {"perfect score", 0, "A"}, {"excellent score", 500, "C"}, {"good score", 1000, "F"}, {"very good score", 2000, "B"}, {"good score", 3000, "C"}, {"fair score", 4000, "D"}, {"poor score", 5000, "F"}, {"very poor score", 10000, "F"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { grade := scorer.GetHealthGrade(tt.score) if grade != tt.expected { t.Errorf("GetHealthGrade(%d) = %v, want %v", tt.score, grade, tt.expected) } }) } } func TestScorer_getScorePercentage(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string score int expected int }{ {"zero score", 0, 100}, {"low score", 100, 95}, {"medium score", 1000, 50}, {"high score", 5000, 50}, {"very high score", 10000, 50}, {"extreme score", 20000, 55}, {"negative score", -100, 100}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { percentage := scorer.getScorePercentage(tt.score) if percentage != tt.expected { t.Errorf("getScorePercentage(%d) = %v, want %v", tt.score, percentage, tt.expected) } }) } } func TestScorer_GetStrictHealthMetrics(t *testing.T) { scorer := NewScorer(95) findings := []Finding{ {Score: 10, Severity: SeverityT4, Status: StatusOpen}, {Score: 5, Severity: SeverityT3, Status: StatusOpen}, {Score: 3, Severity: SeverityT2, Status: StatusOpen}, {Score: 1, Severity: SeverityT1, Status: StatusOpen}, {Score: 15, Severity: SeverityT2, Status: StatusFixed}, {Score: 8, Severity: SeverityT3, Status: StatusIgnored}, {Score: 6, Severity: SeverityT4, Status: StatusWontfix}, } metrics := scorer.GetStrictHealthMetrics(findings) // Verify required fields requiredFields := []string{ "total_issues", "open_issues", "critical_issues", "high_issues", "medium_issues", "low_issues", "resolved_issues", "ignored_issues", "open_percentage", "critical_percentage", "resolution_rate", "strict_score", "total_score", "health_score", "grade", } for _, field := range requiredFields { if _, exists := metrics[field]; !exists { t.Errorf("GetStrictHealthMetrics() missing required field: %s", field) } } // Verify specific values if metrics["total_issues"] != 7 { t.Errorf("GetStrictHealthMetrics() total_issues = %v, want 7", metrics["total_issues"]) } if metrics["open_issues"] != 4 { t.Errorf("GetStrictHealthMetrics() open_issues = %v, want 4", metrics["open_issues"]) } if metrics["critical_issues"] != 2 { t.Errorf("GetStrictHealthMetrics() critical_issues = %v, want 2", metrics["critical_issues"]) } } func TestScorer_GetStrictGrade(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string healthScore float64 expected string }{ {"perfect", 100.0, "A+"}, {"excellent", 95.0, "A+"}, {"very good", 90.0, "A"}, {"good plus", 85.0, "A-"}, {"good", 80.0, "B+"}, {"good minus", 75.0, "B"}, {"fair plus", 70.0, "B-"}, {"fair", 65.0, "C+"}, {"fair minus", 60.0, "C"}, {"poor plus", 55.0, "C-"}, {"poor", 50.0, "D+"}, {"poor minus", 45.0, "D"}, {"very poor", 40.0, "D-"}, {"failing", 35.0, "F"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { grade := scorer.GetStrictGrade(tt.healthScore) if grade != tt.expected { t.Errorf("GetStrictGrade(%.1f) = %v, want %v", tt.healthScore, grade, tt.expected) } }) } } func TestScorer_FormatScorecard(t *testing.T) { scorer := NewScorer(95) card := &Scorecard{ TotalScore: 100, StrictScore: 50, TargetScore: 95, FindingsByType: map[string]int{ "dead_code": 5, "naming": 3, }, FindingsByTier: map[Severity]int{ SeverityT1: 2, SeverityT2: 4, SeverityT3: 1, SeverityT4: 1, }, StatusByType: map[string]int{ "open": 6, "fixed": 2, }, LastScan: time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC), } output := scorer.FormatScorecard(card) if output == "" { t.Error("FormatScorecard() should not return empty string") } // Check that key elements are present requiredElements := []string{ "STRICT Code Quality Scorecard", "Overall Health", "Target Score: 95", "Current Score: 100 (strict: 50)", "Findings by Type", "dead_code: 5", "naming: 3", "Findings by Severity", "T1 (Auto-fixable): 2", "T2 (Quick manual): 4", "T3 (Needs judgment): 1", "T4 (Major refactor): 1", "Status Breakdown", "open: 6", "fixed: 2", "Last Scan: 2024-01-01 12:00:00", } for _, element := range requiredElements { if !strings.Contains(output, element) { t.Errorf("FormatScorecard() missing element: %s", element) } } } func TestScorer_getSeverityEmoji(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string severity Severity expected string }{ {"T1", SeverityT1, "🟢"}, {"T2", SeverityT2, "🟡"}, {"T3", SeverityT3, "🟠"}, {"T4", SeverityT4, "🔴"}, {"unknown", Severity(99), "⚪"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { emoji := scorer.getSeverityEmoji(tt.severity) if emoji != tt.expected { t.Errorf("getSeverityEmoji() = %v, want %v", emoji, tt.expected) } }) } } func TestScorer_GetNextPriority(t *testing.T) { scorer := NewScorer(95) tests := []struct { name string findings []Finding expected *Finding }{ {"no findings", []Finding{}, nil}, { name: "single finding", findings: []Finding{{Score: 10, Severity: SeverityT2, Status: StatusOpen}}, expected: &Finding{Score: 10, Severity: SeverityT2, Status: StatusOpen}, }, { name: "multiple findings", findings: []Finding{ {Score: 5, Severity: SeverityT1, Status: StatusOpen}, {Score: 10, Severity: SeverityT2, Status: StatusOpen}, {Score: 15, Severity: SeverityT3, Status: StatusOpen}, {Score: 20, Severity: SeverityT4, Status: StatusFixed}, }, expected: &Finding{Score: 15, Severity: SeverityT3, Status: StatusOpen}, }, { name: "highest weight", findings: []Finding{ {Score: 5, Severity: SeverityT1, Status: StatusOpen}, {Score: 25, Severity: SeverityT4, Status: StatusOpen}, }, expected: &Finding{Score: 25, Severity: SeverityT4, Status: StatusOpen}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := scorer.GetNextPriority(tt.findings) if tt.expected == nil { if result != nil { t.Errorf("GetNextPriority() expected nil, got %v", result) } } else { if result == nil { t.Errorf("GetNextPriority() expected finding, got nil") } else { weight := int(result.Severity) * result.Score expectedWeight := int(tt.expected.Severity) * tt.expected.Score if weight != expectedWeight { t.Errorf("GetNextPriority() weight = %v, want %v", weight, expectedWeight) } } } }) } } func TestScorer_GetFindingsByTier(t *testing.T) { scorer := NewScorer(95) findings := []Finding{ {Score: 5, Severity: SeverityT1, Status: StatusOpen}, {Score: 10, Severity: SeverityT2, Status: StatusOpen}, {Score: 15, Severity: SeverityT3, Status: StatusOpen}, {Score: 20, Severity: SeverityT4, Status: StatusOpen}, {Score: 25, Severity: SeverityT1, Status: StatusFixed}, {Score: 30, Severity: SeverityT2, Status: StatusIgnored}, } result := scorer.GetFindingsByTier(findings) // Should only include open findings if len(result[SeverityT1]) != 1 { t.Errorf("GetFindingsByTier() T1 count = %v, want 1", len(result[SeverityT1])) } if len(result[SeverityT2]) != 1 { t.Errorf("GetFindingsByTier() T2 count = %v, want 1", len(result[SeverityT2])) } if len(result[SeverityT3]) != 1 { t.Errorf("GetFindingsByTier() T3 count = %v, want 1", len(result[SeverityT3])) } if len(result[SeverityT4]) != 1 { t.Errorf("GetFindingsByTier() T4 count = %v, want 1", len(result[SeverityT4])) } } func TestScorer_GetProgressMetrics(t *testing.T) { scorer := NewScorer(95) findings := []Finding{ {Status: StatusOpen}, {Status: StatusOpen}, {Status: StatusFixed}, {Status: StatusFixed}, {Status: StatusWontfix}, } metrics := scorer.GetProgressMetrics(findings) // Verify required fields requiredFields := []string{"total", "open", "fixed", "wontfix", "progress"} for _, field := range requiredFields { if _, exists := metrics[field]; !exists { t.Errorf("GetProgressMetrics() missing required field: %s", field) } } // Verify specific values if metrics["total"] != 5 { t.Errorf("GetProgressMetrics() total = %v, want 5", metrics["total"]) } if metrics["open"] != 2 { t.Errorf("GetProgressMetrics() open = %v, want 2", metrics["open"]) } if metrics["fixed"] != 2 { t.Errorf("GetProgressMetrics() fixed = %v, want 2", metrics["fixed"]) } if metrics["wontfix"] != 1 { t.Errorf("GetProgressMetrics() wontfix = %v, want 1", metrics["wontfix"]) } if metrics["progress"] != 40.0 { t.Errorf("GetProgressMetrics() progress = %v, want 40.0", metrics["progress"]) } }