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>
This commit is contained in:
465
school-service/internal/services/gradebook_service_test.go
Normal file
465
school-service/internal/services/gradebook_service_test.go
Normal file
@@ -0,0 +1,465 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user