This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/consent-service/internal/services/attendance_service_test.go
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

389 lines
9.0 KiB
Go

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
}