Files
breakpilot-lehrer/school-service/internal/services/gradebook_service_test.go
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

466 lines
9.3 KiB
Go

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
}