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

440 lines
8.9 KiB
Go

package services
import (
"context"
"testing"
"time"
"github.com/breakpilot/school-service/internal/models"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// MockPool implements a mock for pgxpool.Pool for testing
// In production tests, use a real test database or testcontainers
func TestClassService_CreateSchoolYear(t *testing.T) {
tests := []struct {
name string
teacherID uuid.UUID
yearName string
startDate time.Time
endDate time.Time
wantErr bool
}{
{
name: "valid school year",
teacherID: uuid.New(),
yearName: "2024/2025",
startDate: time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC),
endDate: time.Date(2025, 7, 31, 0, 0, 0, 0, time.UTC),
wantErr: false,
},
{
name: "empty year name",
teacherID: uuid.New(),
yearName: "",
startDate: time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC),
endDate: time.Date(2025, 7, 31, 0, 0, 0, 0, time.UTC),
wantErr: true,
},
{
name: "end date before start date",
teacherID: uuid.New(),
yearName: "2024/2025",
startDate: time.Date(2025, 8, 1, 0, 0, 0, 0, time.UTC),
endDate: time.Date(2024, 7, 31, 0, 0, 0, 0, time.UTC),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Validate input
err := validateSchoolYearInput(tt.yearName, tt.startDate, tt.endDate)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestClassService_CreateClass(t *testing.T) {
tests := []struct {
name string
teacherID uuid.UUID
schoolYearID uuid.UUID
className string
gradeLevel int
schoolType string
federalState string
wantErr bool
}{
{
name: "valid class",
teacherID: uuid.New(),
schoolYearID: uuid.New(),
className: "7a",
gradeLevel: 7,
schoolType: "gymnasium",
federalState: "niedersachsen",
wantErr: false,
},
{
name: "empty class name",
teacherID: uuid.New(),
schoolYearID: uuid.New(),
className: "",
gradeLevel: 7,
schoolType: "gymnasium",
federalState: "niedersachsen",
wantErr: true,
},
{
name: "invalid grade level - too low",
teacherID: uuid.New(),
schoolYearID: uuid.New(),
className: "0a",
gradeLevel: 0,
schoolType: "gymnasium",
federalState: "niedersachsen",
wantErr: true,
},
{
name: "invalid grade level - too high",
teacherID: uuid.New(),
schoolYearID: uuid.New(),
className: "14a",
gradeLevel: 14,
schoolType: "gymnasium",
federalState: "niedersachsen",
wantErr: true,
},
{
name: "valid grundschule",
teacherID: uuid.New(),
schoolYearID: uuid.New(),
className: "3b",
gradeLevel: 3,
schoolType: "grundschule",
federalState: "bayern",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateClassInput(tt.className, tt.gradeLevel, tt.schoolType)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestClassService_CreateStudent(t *testing.T) {
tests := []struct {
name string
classID uuid.UUID
firstName string
lastName string
wantErr bool
}{
{
name: "valid student",
classID: uuid.New(),
firstName: "Max",
lastName: "Mustermann",
wantErr: false,
},
{
name: "empty first name",
classID: uuid.New(),
firstName: "",
lastName: "Mustermann",
wantErr: true,
},
{
name: "empty last name",
classID: uuid.New(),
firstName: "Max",
lastName: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateStudentInput(tt.firstName, tt.lastName)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestClassService_CreateSubject(t *testing.T) {
tests := []struct {
name string
teacherID uuid.UUID
subjectName string
shortName string
isMainSubject bool
wantErr bool
}{
{
name: "valid subject - Mathematik",
teacherID: uuid.New(),
subjectName: "Mathematik",
shortName: "Ma",
isMainSubject: true,
wantErr: false,
},
{
name: "valid subject - Sport",
teacherID: uuid.New(),
subjectName: "Sport",
shortName: "Sp",
isMainSubject: false,
wantErr: false,
},
{
name: "empty subject name",
teacherID: uuid.New(),
subjectName: "",
shortName: "Ma",
isMainSubject: true,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateSubjectInput(tt.subjectName)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestParseCSVStudents(t *testing.T) {
tests := []struct {
name string
csvData string
expected int
wantErr bool
}{
{
name: "valid CSV with header",
csvData: "Vorname,Nachname\nMax,Mustermann\nAnna,Schmidt",
expected: 2,
wantErr: false,
},
{
name: "valid CSV without header",
csvData: "Max,Mustermann\nAnna,Schmidt",
expected: 2,
wantErr: false,
},
{
name: "empty CSV",
csvData: "",
expected: 0,
wantErr: true,
},
{
name: "CSV with only header",
csvData: "Vorname,Nachname",
expected: 0,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
students, err := parseCSVStudents(tt.csvData)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Len(t, students, tt.expected)
}
})
}
}
// Validation helper functions (these should be in the actual service)
func validateSchoolYearInput(name string, start, end time.Time) error {
if name == "" {
return assert.AnError
}
if end.Before(start) {
return assert.AnError
}
return nil
}
func validateClassInput(name string, gradeLevel int, schoolType string) error {
if name == "" {
return assert.AnError
}
if gradeLevel < 1 || gradeLevel > 13 {
return assert.AnError
}
return nil
}
func validateStudentInput(firstName, lastName string) error {
if firstName == "" || lastName == "" {
return assert.AnError
}
return nil
}
func validateSubjectInput(name string) error {
if name == "" {
return assert.AnError
}
return nil
}
type csvStudent struct {
FirstName string
LastName string
}
func parseCSVStudents(csvData string) ([]csvStudent, error) {
if csvData == "" {
return nil, assert.AnError
}
lines := splitLines(csvData)
if len(lines) == 0 {
return nil, assert.AnError
}
var students []csvStudent
startIdx := 0
// Check if first line is header
firstLine := lines[0]
if isHeader(firstLine) {
startIdx = 1
}
for i := startIdx; i < len(lines); i++ {
line := lines[i]
if line == "" {
continue
}
parts := splitCSV(line)
if len(parts) >= 2 {
students = append(students, csvStudent{
FirstName: parts[0],
LastName: parts[1],
})
}
}
return students, nil
}
func splitLines(s string) []string {
var lines []string
current := ""
for _, c := range s {
if c == '\n' {
lines = append(lines, current)
current = ""
} else {
current += string(c)
}
}
if current != "" {
lines = append(lines, current)
}
return lines
}
func splitCSV(s string) []string {
var parts []string
current := ""
for _, c := range s {
if c == ',' {
parts = append(parts, current)
current = ""
} else {
current += string(c)
}
}
if current != "" {
parts = append(parts, current)
}
return parts
}
func isHeader(line string) bool {
lower := ""
for _, c := range line {
if c >= 'A' && c <= 'Z' {
lower += string(c + 32)
} else {
lower += string(c)
}
}
return contains(lower, "vorname") || contains(lower, "nachname") || contains(lower, "first") || contains(lower, "last")
}
func contains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
// Integration test helper - requires real database
func setupTestDB(t *testing.T) *pgxpool.Pool {
t.Helper()
// Skip if no test database available
t.Skip("Integration test requires database connection")
return nil
}
func TestClassService_Integration(t *testing.T) {
pool := setupTestDB(t)
if pool == nil {
return
}
defer pool.Close()
service := NewClassService(pool)
ctx := context.Background()
teacherID := uuid.New()
// Test CreateSchoolYear
t.Run("CreateSchoolYear_Integration", func(t *testing.T) {
req := &models.CreateSchoolYearRequest{
Name: "2024/2025",
StartDate: "2024-08-01",
EndDate: "2025-07-31",
}
year, err := service.CreateSchoolYear(ctx, teacherID.String(), req)
require.NoError(t, err)
assert.NotEqual(t, uuid.Nil, year.ID)
assert.Equal(t, "2024/2025", year.Name)
})
}