package services import ( "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func TestGradebookService_ValidateAttendanceStatus(t *testing.T) { tests := []struct { name string status string wantErr bool }{ { name: "valid - present", status: "present", wantErr: false, }, { name: "valid - absent_excused", status: "absent_excused", wantErr: false, }, { name: "valid - absent_unexcused", status: "absent_unexcused", wantErr: false, }, { name: "valid - late", status: "late", wantErr: false, }, { name: "invalid status", status: "invalid", wantErr: true, }, { name: "empty status", status: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateAttendanceStatus(tt.status) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestGradebookService_ValidateEntryType(t *testing.T) { tests := []struct { name string entryType string wantErr bool }{ { name: "valid - note", entryType: "note", wantErr: false, }, { name: "valid - warning", entryType: "warning", wantErr: false, }, { name: "valid - praise", entryType: "praise", wantErr: false, }, { name: "valid - incident", entryType: "incident", wantErr: false, }, { name: "valid - homework", entryType: "homework", wantErr: false, }, { name: "invalid entry type", entryType: "invalid", wantErr: true, }, { name: "empty entry type", entryType: "", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateEntryType(tt.entryType) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestGradebookService_ValidateAttendanceInput(t *testing.T) { tests := []struct { name string studentID uuid.UUID date time.Time status string periods int wantErr bool }{ { name: "valid attendance", studentID: uuid.New(), date: time.Now(), status: "absent_excused", periods: 2, wantErr: false, }, { name: "zero periods", studentID: uuid.New(), date: time.Now(), status: "absent_excused", periods: 0, wantErr: true, }, { name: "negative periods", studentID: uuid.New(), date: time.Now(), status: "absent_excused", periods: -1, wantErr: true, }, { name: "too many periods", studentID: uuid.New(), date: time.Now(), status: "absent_excused", periods: 15, wantErr: true, }, { name: "nil student ID", studentID: uuid.Nil, date: time.Now(), status: "absent_excused", periods: 2, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateAttendanceInput(tt.studentID, tt.date, tt.status, tt.periods) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestGradebookService_ValidateGradebookEntry(t *testing.T) { tests := []struct { name string classID uuid.UUID entryType string content string wantErr bool }{ { name: "valid entry", classID: uuid.New(), entryType: "note", content: "Today we discussed the French Revolution", wantErr: false, }, { name: "empty content", classID: uuid.New(), entryType: "note", content: "", wantErr: true, }, { name: "invalid entry type", classID: uuid.New(), entryType: "invalid", content: "Some content", wantErr: true, }, { name: "nil class ID", classID: uuid.Nil, entryType: "note", content: "Some content", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateGradebookEntry(tt.classID, tt.entryType, tt.content) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestGradebookService_CalculateAbsenceDays(t *testing.T) { tests := []struct { name string attendances []attendanceRecord expectedTotal int expectedExcused int }{ { name: "no absences", attendances: []attendanceRecord{ {Status: "present", Periods: 6}, {Status: "present", Periods: 6}, }, expectedTotal: 0, expectedExcused: 0, }, { name: "excused absences only", attendances: []attendanceRecord{ {Status: "absent_excused", Periods: 6}, {Status: "absent_excused", Periods: 6}, }, expectedTotal: 2, expectedExcused: 2, }, { name: "unexcused absences only", attendances: []attendanceRecord{ {Status: "absent_unexcused", Periods: 6}, }, expectedTotal: 1, expectedExcused: 0, }, { name: "mixed absences", attendances: []attendanceRecord{ {Status: "absent_excused", Periods: 6}, {Status: "absent_unexcused", Periods: 6}, {Status: "present", Periods: 6}, }, expectedTotal: 2, expectedExcused: 1, }, { name: "late arrivals not counted", attendances: []attendanceRecord{ {Status: "late", Periods: 1}, {Status: "late", Periods: 1}, }, expectedTotal: 0, expectedExcused: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { total, excused := calculateAbsenceDays(tt.attendances) assert.Equal(t, tt.expectedTotal, total) assert.Equal(t, tt.expectedExcused, excused) }) } } func TestGradebookService_DateRangeValidation(t *testing.T) { tests := []struct { name string startDate time.Time endDate time.Time wantErr bool }{ { name: "valid range - same day", startDate: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC), endDate: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC), wantErr: false, }, { name: "valid range - week", startDate: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC), endDate: time.Date(2024, 1, 22, 0, 0, 0, 0, time.UTC), wantErr: false, }, { name: "invalid range - end before start", startDate: time.Date(2024, 1, 22, 0, 0, 0, 0, time.UTC), endDate: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC), wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateDateRange(tt.startDate, tt.endDate) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } // Helper types and functions type attendanceRecord struct { Status string Periods int } func validateAttendanceStatus(status string) error { validStatuses := map[string]bool{ "present": true, "absent_excused": true, "absent_unexcused": true, "late": true, } if !validStatuses[status] { return assert.AnError } return nil } func validateEntryType(entryType string) error { validTypes := map[string]bool{ "note": true, "warning": true, "praise": true, "incident": true, "homework": true, } if !validTypes[entryType] { return assert.AnError } return nil } func validateAttendanceInput(studentID uuid.UUID, date time.Time, status string, periods int) error { if studentID == uuid.Nil { return assert.AnError } if periods <= 0 || periods > 12 { return assert.AnError } return validateAttendanceStatus(status) } func validateGradebookEntry(classID uuid.UUID, entryType, content string) error { if classID == uuid.Nil { return assert.AnError } if content == "" { return assert.AnError } return validateEntryType(entryType) } func calculateAbsenceDays(attendances []attendanceRecord) (total, excused int) { for _, a := range attendances { if a.Status == "absent_excused" { total++ excused++ } else if a.Status == "absent_unexcused" { total++ } } return total, excused } func validateDateRange(start, end time.Time) error { if end.Before(start) { return assert.AnError } return nil } func TestGradebookService_BulkAttendanceValidation(t *testing.T) { tests := []struct { name string studentIDs []uuid.UUID date time.Time status string wantErr bool errCount int }{ { name: "all valid", studentIDs: []uuid.UUID{uuid.New(), uuid.New(), uuid.New()}, date: time.Now(), status: "present", wantErr: false, errCount: 0, }, { name: "empty list", studentIDs: []uuid.UUID{}, date: time.Now(), status: "present", wantErr: true, errCount: 1, }, { name: "contains nil UUID", studentIDs: []uuid.UUID{uuid.New(), uuid.Nil, uuid.New()}, date: time.Now(), status: "present", wantErr: true, errCount: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { errs := validateBulkAttendance(tt.studentIDs, tt.date, tt.status) if tt.wantErr { assert.Len(t, errs, tt.errCount) } else { assert.Empty(t, errs) } }) } } func validateBulkAttendance(studentIDs []uuid.UUID, date time.Time, status string) []error { var errs []error if len(studentIDs) == 0 { errs = append(errs, assert.AnError) return errs } for _, id := range studentIDs { if id == uuid.Nil { errs = append(errs, assert.AnError) } } if err := validateAttendanceStatus(status); err != nil { errs = append(errs, err) } return errs }