Files
breakpilot-lehrer/school-service/internal/services/timetable_service_test.go
T
Benjamin Admin e958f88a2d Add timetable scheduler Phases 1 + 2 to school-service
Phase 1 — Stammdaten (7 tables):
  tt_class, tt_period, tt_room, tt_subject, tt_teacher,
  tt_curriculum, tt_assignment with CRUD endpoints.

Phase 2 — Constraints (15 typed tables):
  Teacher (6): unavailable_day, unavailable_window, max_hours_day,
    max_hours_week, excluded_subject, excluded_room
  Subject (5): min_day_gap, max_consecutive, contiguous_when_repeated,
    preferred_period, double_lesson
  Class (2): max_hours_day, no_gaps
  Room (2): requires_type, unavailable

Each constraint row carries is_hard / weight / active / note /
created_by_user_id; ownership enforced via WHERE EXISTS against the
parent tt_teacher/tt_class/tt_subject/tt_room row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:12:23 +02:00

110 lines
4.2 KiB
Go

package services
import (
"testing"
"github.com/go-playground/validator/v10"
"github.com/breakpilot/school-service/internal/models"
)
// validate is a singleton used to exercise the same struct tags Gin uses for
// request validation. The DB tests live in integration tests against a real
// database; this test pins the contract for the request DTOs.
var validate = func() *validator.Validate {
v := validator.New()
v.SetTagName("binding")
return v
}()
func TestNewTimetableService_Constructs(t *testing.T) {
s := NewTimetableService(nil)
if s == nil {
t.Fatal("expected non-nil service")
}
}
func TestCreateTimetableClassRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTimetableClassRequest
wantErr bool
}{
{"valid", models.CreateTimetableClassRequest{Name: "5a", GradeLevel: 5, StudentCount: 24}, false},
{"missing name", models.CreateTimetableClassRequest{GradeLevel: 5}, true},
{"grade too low", models.CreateTimetableClassRequest{Name: "0a", GradeLevel: 0}, true},
{"grade too high", models.CreateTimetableClassRequest{Name: "14a", GradeLevel: 14}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.Struct(tt.req)
if (err != nil) != tt.wantErr {
t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr)
}
})
}
}
func TestCreateTimetablePeriodRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTimetablePeriodRequest
wantErr bool
}{
{"valid monday first", models.CreateTimetablePeriodRequest{DayOfWeek: 1, PeriodIndex: 1, StartTime: "08:00", EndTime: "08:45"}, false},
{"day too low", models.CreateTimetablePeriodRequest{DayOfWeek: 0, PeriodIndex: 1, StartTime: "08:00", EndTime: "08:45"}, true},
{"day too high", models.CreateTimetablePeriodRequest{DayOfWeek: 8, PeriodIndex: 1, StartTime: "08:00", EndTime: "08:45"}, true},
{"missing times", models.CreateTimetablePeriodRequest{DayOfWeek: 1, PeriodIndex: 1}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.Struct(tt.req)
if (err != nil) != tt.wantErr {
t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr)
}
})
}
}
func TestCreateTimetableTeacherRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTimetableTeacherRequest
wantErr bool
}{
{"valid full-time", models.CreateTimetableTeacherRequest{FirstName: "Anna", LastName: "Schmidt", ShortCode: "SCH", EmploymentPercentage: 100, MaxHoursWeek: 28}, false},
{"valid part-time", models.CreateTimetableTeacherRequest{FirstName: "Bea", LastName: "Mueller", ShortCode: "MUE", EmploymentPercentage: 50, MaxHoursWeek: 14}, false},
{"missing names", models.CreateTimetableTeacherRequest{ShortCode: "XX"}, true},
{"employment above 100", models.CreateTimetableTeacherRequest{FirstName: "X", LastName: "Y", ShortCode: "Z", EmploymentPercentage: 150}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.Struct(tt.req)
if (err != nil) != tt.wantErr {
t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr)
}
})
}
}
func TestCreateTimetableCurriculumRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTimetableCurriculumRequest
wantErr bool
}{
{"valid", models.CreateTimetableCurriculumRequest{ClassID: "00000000-0000-0000-0000-000000000001", SubjectID: "00000000-0000-0000-0000-000000000002", WeeklyHours: 4}, false},
{"non-uuid class", models.CreateTimetableCurriculumRequest{ClassID: "not-a-uuid", SubjectID: "00000000-0000-0000-0000-000000000002", WeeklyHours: 4}, true},
{"hours below 1", models.CreateTimetableCurriculumRequest{ClassID: "00000000-0000-0000-0000-000000000001", SubjectID: "00000000-0000-0000-0000-000000000002", WeeklyHours: 0}, true},
{"hours above 10", models.CreateTimetableCurriculumRequest{ClassID: "00000000-0000-0000-0000-000000000001", SubjectID: "00000000-0000-0000-0000-000000000002", WeeklyHours: 11}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.Struct(tt.req)
if (err != nil) != tt.wantErr {
t.Errorf("got err=%v, wantErr=%v", err, tt.wantErr)
}
})
}
}