Files
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

452 lines
9.1 KiB
Go

package services
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestExamService_ValidateExamInput(t *testing.T) {
tests := []struct {
name string
title string
examType string
durationMinutes int
maxPoints float64
wantErr bool
}{
{
name: "valid klassenarbeit",
title: "Mathematik Klassenarbeit Nr. 1",
examType: "klassenarbeit",
durationMinutes: 45,
maxPoints: 50,
wantErr: false,
},
{
name: "valid test",
title: "Vokabeltest Englisch",
examType: "test",
durationMinutes: 20,
maxPoints: 20,
wantErr: false,
},
{
name: "valid klausur",
title: "Oberstufen-Klausur Deutsch",
examType: "klausur",
durationMinutes: 180,
maxPoints: 100,
wantErr: false,
},
{
name: "empty title",
title: "",
examType: "klassenarbeit",
durationMinutes: 45,
maxPoints: 50,
wantErr: true,
},
{
name: "invalid exam type",
title: "Test",
examType: "invalid_type",
durationMinutes: 45,
maxPoints: 50,
wantErr: true,
},
{
name: "negative duration",
title: "Test",
examType: "test",
durationMinutes: -10,
maxPoints: 50,
wantErr: true,
},
{
name: "zero max points",
title: "Test",
examType: "test",
durationMinutes: 45,
maxPoints: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateExamInput(tt.title, tt.examType, tt.durationMinutes, tt.maxPoints)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestExamService_ValidateExamResult(t *testing.T) {
tests := []struct {
name string
pointsAchieved float64
maxPoints float64
wantErr bool
}{
{
name: "valid result - full points",
pointsAchieved: 50,
maxPoints: 50,
wantErr: false,
},
{
name: "valid result - partial points",
pointsAchieved: 35.5,
maxPoints: 50,
wantErr: false,
},
{
name: "valid result - zero points",
pointsAchieved: 0,
maxPoints: 50,
wantErr: false,
},
{
name: "invalid result - negative points",
pointsAchieved: -5,
maxPoints: 50,
wantErr: true,
},
{
name: "invalid result - exceeds max",
pointsAchieved: 55,
maxPoints: 50,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateExamResult(tt.pointsAchieved, tt.maxPoints)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestExamService_CalculateGrade(t *testing.T) {
tests := []struct {
name string
pointsAchieved float64
maxPoints float64
expectedGrade float64
}{
{
name: "100% - Grade 1",
pointsAchieved: 50,
maxPoints: 50,
expectedGrade: 1.0,
},
{
name: "92% - Grade 1",
pointsAchieved: 46,
maxPoints: 50,
expectedGrade: 1.0,
},
{
name: "85% - Grade 2",
pointsAchieved: 42.5,
maxPoints: 50,
expectedGrade: 2.0,
},
{
name: "70% - Grade 3",
pointsAchieved: 35,
maxPoints: 50,
expectedGrade: 3.0,
},
{
name: "55% - Grade 4",
pointsAchieved: 27.5,
maxPoints: 50,
expectedGrade: 4.0,
},
{
name: "40% - Grade 5",
pointsAchieved: 20,
maxPoints: 50,
expectedGrade: 5.0,
},
{
name: "20% - Grade 6",
pointsAchieved: 10,
maxPoints: 50,
expectedGrade: 6.0,
},
{
name: "0% - Grade 6",
pointsAchieved: 0,
maxPoints: 50,
expectedGrade: 6.0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
grade := calculateGrade(tt.pointsAchieved, tt.maxPoints)
assert.Equal(t, tt.expectedGrade, grade)
})
}
}
func TestExamService_CalculatePercentage(t *testing.T) {
tests := []struct {
name string
pointsAchieved float64
maxPoints float64
expectedPercentage float64
}{
{
name: "100%",
pointsAchieved: 50,
maxPoints: 50,
expectedPercentage: 100.0,
},
{
name: "50%",
pointsAchieved: 25,
maxPoints: 50,
expectedPercentage: 50.0,
},
{
name: "0%",
pointsAchieved: 0,
maxPoints: 50,
expectedPercentage: 0.0,
},
{
name: "33.33%",
pointsAchieved: 10,
maxPoints: 30,
expectedPercentage: 33.33,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
percentage := calculatePercentage(tt.pointsAchieved, tt.maxPoints)
assert.InDelta(t, tt.expectedPercentage, percentage, 0.01)
})
}
}
func TestExamService_DetermineNeedsRewrite(t *testing.T) {
tests := []struct {
name string
grade float64
needsRewrite bool
}{
{
name: "Grade 1 - no rewrite",
grade: 1.0,
needsRewrite: false,
},
{
name: "Grade 4 - no rewrite",
grade: 4.0,
needsRewrite: false,
},
{
name: "Grade 5 - needs rewrite",
grade: 5.0,
needsRewrite: true,
},
{
name: "Grade 6 - needs rewrite",
grade: 6.0,
needsRewrite: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := determineNeedsRewrite(tt.grade)
assert.Equal(t, tt.needsRewrite, result)
})
}
}
// Validation helper functions
func validateExamInput(title, examType string, durationMinutes int, maxPoints float64) error {
if title == "" {
return assert.AnError
}
validTypes := map[string]bool{
"klassenarbeit": true,
"test": true,
"klausur": true,
}
if !validTypes[examType] {
return assert.AnError
}
if durationMinutes <= 0 {
return assert.AnError
}
if maxPoints <= 0 {
return assert.AnError
}
return nil
}
func validateExamResult(pointsAchieved, maxPoints float64) error {
if pointsAchieved < 0 {
return assert.AnError
}
if pointsAchieved > maxPoints {
return assert.AnError
}
return nil
}
func calculateGrade(pointsAchieved, maxPoints float64) float64 {
percentage := (pointsAchieved / maxPoints) * 100
switch {
case percentage >= 92:
return 1.0
case percentage >= 81:
return 2.0
case percentage >= 67:
return 3.0
case percentage >= 50:
return 4.0
case percentage >= 30:
return 5.0
default:
return 6.0
}
}
func calculatePercentage(pointsAchieved, maxPoints float64) float64 {
if maxPoints == 0 {
return 0
}
result := (pointsAchieved / maxPoints) * 100
// Round to 2 decimal places
return float64(int(result*100)) / 100
}
func determineNeedsRewrite(grade float64) bool {
return grade >= 5.0
}
func TestExamService_ExamDateValidation(t *testing.T) {
tests := []struct {
name string
examDate time.Time
wantErr bool
}{
{
name: "future date - valid",
examDate: time.Now().AddDate(0, 0, 7),
wantErr: false,
},
{
name: "today - valid",
examDate: time.Now(),
wantErr: false,
},
{
name: "past date - valid for recording",
examDate: time.Now().AddDate(0, 0, -7),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateExamDate(tt.examDate)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func validateExamDate(date time.Time) error {
// Exam dates are always valid as we need to record past exams too
return nil
}
func TestExamService_ExamStatusTransition(t *testing.T) {
tests := []struct {
name string
currentStatus string
newStatus string
valid bool
}{
{
name: "draft to active",
currentStatus: "draft",
newStatus: "active",
valid: true,
},
{
name: "active to archived",
currentStatus: "active",
newStatus: "archived",
valid: true,
},
{
name: "draft to archived",
currentStatus: "draft",
newStatus: "archived",
valid: true,
},
{
name: "archived to active - invalid",
currentStatus: "archived",
newStatus: "active",
valid: false,
},
{
name: "archived to draft - invalid",
currentStatus: "archived",
newStatus: "draft",
valid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isValidStatusTransition(tt.currentStatus, tt.newStatus)
assert.Equal(t, tt.valid, result)
})
}
}
func isValidStatusTransition(current, new string) bool {
transitions := map[string][]string{
"draft": {"active", "archived"},
"active": {"archived"},
"archived": {},
}
allowed, exists := transitions[current]
if !exists {
return false
}
for _, s := range allowed {
if s == new {
return true
}
}
return false
}