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) }) }