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