package services import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestExamService_ValidateExamInput(t *testing.T) { tests := []struct { name string title string examType string durationMinutes int maxPoints float64 wantErr bool }{ { name: "valid klassenarbeit", title: "Mathematik Klassenarbeit Nr. 1", examType: "klassenarbeit", durationMinutes: 45, maxPoints: 50, wantErr: false, }, { name: "valid test", title: "Vokabeltest Englisch", examType: "test", durationMinutes: 20, maxPoints: 20, wantErr: false, }, { name: "valid klausur", title: "Oberstufen-Klausur Deutsch", examType: "klausur", durationMinutes: 180, maxPoints: 100, wantErr: false, }, { name: "empty title", title: "", examType: "klassenarbeit", durationMinutes: 45, maxPoints: 50, wantErr: true, }, { name: "invalid exam type", title: "Test", examType: "invalid_type", durationMinutes: 45, maxPoints: 50, wantErr: true, }, { name: "negative duration", title: "Test", examType: "test", durationMinutes: -10, maxPoints: 50, wantErr: true, }, { name: "zero max points", title: "Test", examType: "test", durationMinutes: 45, maxPoints: 0, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateExamInput(tt.title, tt.examType, tt.durationMinutes, tt.maxPoints) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestExamService_ValidateExamResult(t *testing.T) { tests := []struct { name string pointsAchieved float64 maxPoints float64 wantErr bool }{ { name: "valid result - full points", pointsAchieved: 50, maxPoints: 50, wantErr: false, }, { name: "valid result - partial points", pointsAchieved: 35.5, maxPoints: 50, wantErr: false, }, { name: "valid result - zero points", pointsAchieved: 0, maxPoints: 50, wantErr: false, }, { name: "invalid result - negative points", pointsAchieved: -5, maxPoints: 50, wantErr: true, }, { name: "invalid result - exceeds max", pointsAchieved: 55, maxPoints: 50, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateExamResult(tt.pointsAchieved, tt.maxPoints) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestExamService_CalculateGrade(t *testing.T) { tests := []struct { name string pointsAchieved float64 maxPoints float64 expectedGrade float64 }{ { name: "100% - Grade 1", pointsAchieved: 50, maxPoints: 50, expectedGrade: 1.0, }, { name: "92% - Grade 1", pointsAchieved: 46, maxPoints: 50, expectedGrade: 1.0, }, { name: "85% - Grade 2", pointsAchieved: 42.5, maxPoints: 50, expectedGrade: 2.0, }, { name: "70% - Grade 3", pointsAchieved: 35, maxPoints: 50, expectedGrade: 3.0, }, { name: "55% - Grade 4", pointsAchieved: 27.5, maxPoints: 50, expectedGrade: 4.0, }, { name: "40% - Grade 5", pointsAchieved: 20, maxPoints: 50, expectedGrade: 5.0, }, { name: "20% - Grade 6", pointsAchieved: 10, maxPoints: 50, expectedGrade: 6.0, }, { name: "0% - Grade 6", pointsAchieved: 0, maxPoints: 50, expectedGrade: 6.0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { grade := calculateGrade(tt.pointsAchieved, tt.maxPoints) assert.Equal(t, tt.expectedGrade, grade) }) } } func TestExamService_CalculatePercentage(t *testing.T) { tests := []struct { name string pointsAchieved float64 maxPoints float64 expectedPercentage float64 }{ { name: "100%", pointsAchieved: 50, maxPoints: 50, expectedPercentage: 100.0, }, { name: "50%", pointsAchieved: 25, maxPoints: 50, expectedPercentage: 50.0, }, { name: "0%", pointsAchieved: 0, maxPoints: 50, expectedPercentage: 0.0, }, { name: "33.33%", pointsAchieved: 10, maxPoints: 30, expectedPercentage: 33.33, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { percentage := calculatePercentage(tt.pointsAchieved, tt.maxPoints) assert.InDelta(t, tt.expectedPercentage, percentage, 0.01) }) } } func TestExamService_DetermineNeedsRewrite(t *testing.T) { tests := []struct { name string grade float64 needsRewrite bool }{ { name: "Grade 1 - no rewrite", grade: 1.0, needsRewrite: false, }, { name: "Grade 4 - no rewrite", grade: 4.0, needsRewrite: false, }, { name: "Grade 5 - needs rewrite", grade: 5.0, needsRewrite: true, }, { name: "Grade 6 - needs rewrite", grade: 6.0, needsRewrite: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := determineNeedsRewrite(tt.grade) assert.Equal(t, tt.needsRewrite, result) }) } } // Validation helper functions func validateExamInput(title, examType string, durationMinutes int, maxPoints float64) error { if title == "" { return assert.AnError } validTypes := map[string]bool{ "klassenarbeit": true, "test": true, "klausur": true, } if !validTypes[examType] { return assert.AnError } if durationMinutes <= 0 { return assert.AnError } if maxPoints <= 0 { return assert.AnError } return nil } func validateExamResult(pointsAchieved, maxPoints float64) error { if pointsAchieved < 0 { return assert.AnError } if pointsAchieved > maxPoints { return assert.AnError } return nil } func calculateGrade(pointsAchieved, maxPoints float64) float64 { percentage := (pointsAchieved / maxPoints) * 100 switch { case percentage >= 92: return 1.0 case percentage >= 81: return 2.0 case percentage >= 67: return 3.0 case percentage >= 50: return 4.0 case percentage >= 30: return 5.0 default: return 6.0 } } func calculatePercentage(pointsAchieved, maxPoints float64) float64 { if maxPoints == 0 { return 0 } result := (pointsAchieved / maxPoints) * 100 // Round to 2 decimal places return float64(int(result*100)) / 100 } func determineNeedsRewrite(grade float64) bool { return grade >= 5.0 } func TestExamService_ExamDateValidation(t *testing.T) { tests := []struct { name string examDate time.Time wantErr bool }{ { name: "future date - valid", examDate: time.Now().AddDate(0, 0, 7), wantErr: false, }, { name: "today - valid", examDate: time.Now(), wantErr: false, }, { name: "past date - valid for recording", examDate: time.Now().AddDate(0, 0, -7), wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateExamDate(tt.examDate) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func validateExamDate(date time.Time) error { // Exam dates are always valid as we need to record past exams too return nil } func TestExamService_ExamStatusTransition(t *testing.T) { tests := []struct { name string currentStatus string newStatus string valid bool }{ { name: "draft to active", currentStatus: "draft", newStatus: "active", valid: true, }, { name: "active to archived", currentStatus: "active", newStatus: "archived", valid: true, }, { name: "draft to archived", currentStatus: "draft", newStatus: "archived", valid: true, }, { name: "archived to active - invalid", currentStatus: "archived", newStatus: "active", valid: false, }, { name: "archived to draft - invalid", currentStatus: "archived", newStatus: "draft", valid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isValidStatusTransition(tt.currentStatus, tt.newStatus) assert.Equal(t, tt.valid, result) }) } } func isValidStatusTransition(current, new string) bool { transitions := map[string][]string{ "draft": {"active", "archived"}, "active": {"archived"}, "archived": {}, } allowed, exists := transitions[current] if !exists { return false } for _, s := range allowed { if s == new { return true } } return false }