package services import ( "testing" "time" "github.com/breakpilot/consent-service/internal/models" "github.com/google/uuid" ) // TestValidateAttendanceRecord tests attendance record validation func TestValidateAttendanceRecord(t *testing.T) { slotID := uuid.New() tests := []struct { name string record models.AttendanceRecord expectValid bool }{ { name: "valid present record", record: models.AttendanceRecord{ StudentID: uuid.New(), SlotID: slotID, Date: time.Now(), Status: models.AttendancePresent, RecordedBy: uuid.New(), }, expectValid: true, }, { name: "valid absent record", record: models.AttendanceRecord{ StudentID: uuid.New(), SlotID: slotID, Date: time.Now(), Status: models.AttendanceAbsent, RecordedBy: uuid.New(), }, expectValid: true, }, { name: "valid late record", record: models.AttendanceRecord{ StudentID: uuid.New(), SlotID: slotID, Date: time.Now(), Status: models.AttendanceLate, RecordedBy: uuid.New(), }, expectValid: true, }, { name: "missing student ID", record: models.AttendanceRecord{ StudentID: uuid.Nil, SlotID: slotID, Date: time.Now(), Status: models.AttendancePresent, RecordedBy: uuid.New(), }, expectValid: false, }, { name: "invalid status", record: models.AttendanceRecord{ StudentID: uuid.New(), SlotID: slotID, Date: time.Now(), Status: "invalid_status", RecordedBy: uuid.New(), }, expectValid: false, }, { name: "future date", record: models.AttendanceRecord{ StudentID: uuid.New(), SlotID: slotID, Date: time.Now().AddDate(0, 0, 7), Status: models.AttendancePresent, RecordedBy: uuid.New(), }, expectValid: false, }, { name: "missing slot ID", record: models.AttendanceRecord{ StudentID: uuid.New(), SlotID: uuid.Nil, Date: time.Now(), Status: models.AttendancePresent, RecordedBy: uuid.New(), }, expectValid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { isValid := validateAttendanceRecord(tt.record) if isValid != tt.expectValid { t.Errorf("expected valid=%v, got valid=%v", tt.expectValid, isValid) } }) } } // validateAttendanceRecord validates an attendance record func validateAttendanceRecord(record models.AttendanceRecord) bool { if record.StudentID == uuid.Nil { return false } if record.SlotID == uuid.Nil { return false } if record.RecordedBy == uuid.Nil { return false } if record.Date.After(time.Now().AddDate(0, 0, 1)) { return false } // Validate status validStatuses := map[string]bool{ models.AttendancePresent: true, models.AttendanceAbsent: true, models.AttendanceAbsentExcused: true, models.AttendanceAbsentUnexcused: true, models.AttendanceLate: true, models.AttendanceLateExcused: true, models.AttendancePending: true, } if !validStatuses[record.Status] { return false } return true } // TestValidateAbsenceReport tests absence report validation func TestValidateAbsenceReport(t *testing.T) { now := time.Now() today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) reason := "Krankheit" medicalReason := "Arzttermin" tests := []struct { name string report models.AbsenceReport expectValid bool }{ { name: "valid single day absence", report: models.AbsenceReport{ StudentID: uuid.New(), ReportedBy: uuid.New(), StartDate: today, EndDate: today, Reason: &reason, ReasonCategory: "illness", Status: "reported", }, expectValid: true, }, { name: "valid multi-day absence", report: models.AbsenceReport{ StudentID: uuid.New(), ReportedBy: uuid.New(), StartDate: today, EndDate: today.AddDate(0, 0, 3), Reason: &medicalReason, ReasonCategory: "appointment", Status: "reported", }, expectValid: true, }, { name: "end before start", report: models.AbsenceReport{ StudentID: uuid.New(), ReportedBy: uuid.New(), StartDate: today.AddDate(0, 0, 3), EndDate: today, Reason: &reason, ReasonCategory: "illness", Status: "reported", }, expectValid: false, }, { name: "missing reason category", report: models.AbsenceReport{ StudentID: uuid.New(), ReportedBy: uuid.New(), StartDate: today, EndDate: today, Reason: &reason, ReasonCategory: "", Status: "reported", }, expectValid: false, }, { name: "invalid reason category", report: models.AbsenceReport{ StudentID: uuid.New(), ReportedBy: uuid.New(), StartDate: today, EndDate: today, Reason: &reason, ReasonCategory: "invalid_type", Status: "reported", }, expectValid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { isValid := validateAbsenceReport(tt.report) if isValid != tt.expectValid { t.Errorf("expected valid=%v, got valid=%v", tt.expectValid, isValid) } }) } } // validateAbsenceReport validates an absence report func validateAbsenceReport(report models.AbsenceReport) bool { if report.StudentID == uuid.Nil { return false } if report.ReportedBy == uuid.Nil { return false } if report.EndDate.Before(report.StartDate) { return false } if report.ReasonCategory == "" { return false } // Validate reason category validCategories := map[string]bool{ "illness": true, "appointment": true, "family": true, "other": true, } if !validCategories[report.ReasonCategory] { return false } return true } // TestCalculateAttendanceStats tests attendance statistics calculation func TestCalculateAttendanceStats(t *testing.T) { tests := []struct { name string records []models.AttendanceRecord expectedPresent int expectedAbsent int expectedLate int }{ { name: "all present", records: []models.AttendanceRecord{ {Status: models.AttendancePresent}, {Status: models.AttendancePresent}, {Status: models.AttendancePresent}, }, expectedPresent: 3, expectedAbsent: 0, expectedLate: 0, }, { name: "mixed attendance", records: []models.AttendanceRecord{ {Status: models.AttendancePresent}, {Status: models.AttendanceAbsent}, {Status: models.AttendanceLate}, {Status: models.AttendancePresent}, {Status: models.AttendanceAbsentExcused}, }, expectedPresent: 2, expectedAbsent: 2, expectedLate: 1, }, { name: "empty records", records: []models.AttendanceRecord{}, expectedPresent: 0, expectedAbsent: 0, expectedLate: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { present, absent, late := calculateAttendanceStats(tt.records) if present != tt.expectedPresent { t.Errorf("expected present=%d, got present=%d", tt.expectedPresent, present) } if absent != tt.expectedAbsent { t.Errorf("expected absent=%d, got absent=%d", tt.expectedAbsent, absent) } if late != tt.expectedLate { t.Errorf("expected late=%d, got late=%d", tt.expectedLate, late) } }) } } // calculateAttendanceStats calculates attendance statistics func calculateAttendanceStats(records []models.AttendanceRecord) (present, absent, late int) { for _, r := range records { switch r.Status { case models.AttendancePresent: present++ case models.AttendanceAbsent, models.AttendanceAbsentExcused, models.AttendanceAbsentUnexcused: absent++ case models.AttendanceLate, models.AttendanceLateExcused: late++ } } return } // TestAttendanceRateCalculation tests attendance rate percentage calculation func TestAttendanceRateCalculation(t *testing.T) { tests := []struct { name string present int total int expectedRate float64 }{ { name: "100% attendance", present: 26, total: 26, expectedRate: 100.0, }, { name: "92.3% attendance", present: 24, total: 26, expectedRate: 92.31, }, { name: "0% attendance", present: 0, total: 26, expectedRate: 0.0, }, { name: "empty class", present: 0, total: 0, expectedRate: 0.0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rate := calculateAttendanceRate(tt.present, tt.total) // Allow small floating point differences if rate < tt.expectedRate-0.1 || rate > tt.expectedRate+0.1 { t.Errorf("expected rate=%.2f, got rate=%.2f", tt.expectedRate, rate) } }) } } // calculateAttendanceRate calculates attendance rate as percentage func calculateAttendanceRate(present, total int) float64 { if total == 0 { return 0.0 } rate := float64(present) / float64(total) * 100 // Round to 2 decimal places return float64(int(rate*100)) / 100 }