mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-03 20:13:03 +00:00
updage
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
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: 100, // 5*1 + 10*2 + 15*3 + 20*4
|
||||
strictScore: 230, // 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: 75, // All included in total
|
||||
strictScore: 5, // Only open T1 (unjustified wontfix excluded)
|
||||
},
|
||||
{
|
||||
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: 25, // All included in total
|
||||
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 != 40 { // 10*2 + 5*1 + 15*3
|
||||
t.Errorf("GenerateScorecard() TotalScore = %v, want 40", 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, "B"},
|
||||
{"good score", 1000, "C"},
|
||||
{"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, 90},
|
||||
{"high score", 5000, 75},
|
||||
{"very high score", 10000, 50},
|
||||
{"extreme score", 20000, 0},
|
||||
{"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"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user