Files
breakpilot-lehrer/school-service/internal/services/timetable_constraints_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

141 lines
5.1 KiB
Go

package services
import (
"testing"
"github.com/breakpilot/school-service/internal/models"
)
// These tests exercise the request DTO binding tags (the same the Gin layer
// uses). They don't hit the database — DB-level checks live in integration
// tests against a real Postgres.
func TestCreateTeacherUnavailableDayRequest_Validation(t *testing.T) {
uid := "00000000-0000-0000-0000-000000000001"
tests := []struct {
name string
req models.CreateTeacherUnavailableDayRequest
wantErr bool
}{
{"valid monday", models.CreateTeacherUnavailableDayRequest{TeacherID: uid, DayOfWeek: 1, IsHard: true, Weight: 100, Active: true}, false},
{"day too low", models.CreateTeacherUnavailableDayRequest{TeacherID: uid, DayOfWeek: 0}, true},
{"day too high", models.CreateTeacherUnavailableDayRequest{TeacherID: uid, DayOfWeek: 8}, true},
{"non-uuid teacher", models.CreateTeacherUnavailableDayRequest{TeacherID: "not-a-uuid", DayOfWeek: 1}, true},
{"weight above 100", models.CreateTeacherUnavailableDayRequest{TeacherID: uid, DayOfWeek: 1, Weight: 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 TestCreateTeacherUnavailableWindowRequest_Validation(t *testing.T) {
uid := "00000000-0000-0000-0000-000000000001"
tests := []struct {
name string
req models.CreateTeacherUnavailableWindowRequest
wantErr bool
}{
{"valid", models.CreateTeacherUnavailableWindowRequest{TeacherID: uid, DayOfWeek: 2, StartTime: "13:00", EndTime: "17:00"}, false},
{"missing times", models.CreateTeacherUnavailableWindowRequest{TeacherID: uid, DayOfWeek: 2}, true},
{"day too high", models.CreateTeacherUnavailableWindowRequest{TeacherID: uid, DayOfWeek: 8, StartTime: "13:00", EndTime: "17:00"}, 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 TestCreateSubjectMaxConsecutiveRequest_Validation(t *testing.T) {
uid := "00000000-0000-0000-0000-000000000002"
tests := []struct {
name string
req models.CreateSubjectMaxConsecutiveRequest
wantErr bool
}{
{"valid 2 in a row", models.CreateSubjectMaxConsecutiveRequest{SubjectID: uid, MaxConsecutive: 2, IsHard: true, Weight: 100}, false},
{"below 1", models.CreateSubjectMaxConsecutiveRequest{SubjectID: uid, MaxConsecutive: 0}, true},
{"above 5", models.CreateSubjectMaxConsecutiveRequest{SubjectID: uid, MaxConsecutive: 6}, true},
{"non-uuid subject", models.CreateSubjectMaxConsecutiveRequest{SubjectID: "x", MaxConsecutive: 2}, 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 TestCreateSubjectPreferredPeriodRequest_Validation(t *testing.T) {
uid := "00000000-0000-0000-0000-000000000002"
tests := []struct {
name string
req models.CreateSubjectPreferredPeriodRequest
wantErr bool
}{
{"valid morning", models.CreateSubjectPreferredPeriodRequest{SubjectID: uid, PeriodFrom: 1, PeriodTo: 4, IsHard: false, Weight: 40}, false},
{"from missing", models.CreateSubjectPreferredPeriodRequest{SubjectID: uid, PeriodTo: 4}, true},
{"to too high", models.CreateSubjectPreferredPeriodRequest{SubjectID: uid, PeriodFrom: 1, PeriodTo: 13}, 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 TestCreateClassMaxHoursDayRequest_Validation(t *testing.T) {
uid := "00000000-0000-0000-0000-000000000003"
tests := []struct {
name string
req models.CreateClassMaxHoursDayRequest
wantErr bool
}{
{"valid", models.CreateClassMaxHoursDayRequest{ClassID: uid, MaxHours: 6, IsHard: true, Weight: 100}, false},
{"hours below 1", models.CreateClassMaxHoursDayRequest{ClassID: uid, MaxHours: 0}, true},
{"hours above 12", models.CreateClassMaxHoursDayRequest{ClassID: uid, MaxHours: 13}, 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 TestCreateRoomUnavailableRequest_Validation(t *testing.T) {
uid := "00000000-0000-0000-0000-000000000004"
tests := []struct {
name string
req models.CreateRoomUnavailableRequest
wantErr bool
}{
{"valid", models.CreateRoomUnavailableRequest{RoomID: uid, DayOfWeek: 3, PeriodIndex: 4, IsHard: true, Weight: 100}, false},
{"missing day", models.CreateRoomUnavailableRequest{RoomID: uid, PeriodIndex: 4}, true},
{"period too high", models.CreateRoomUnavailableRequest{RoomID: uid, DayOfWeek: 3, PeriodIndex: 13}, 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)
}
})
}
}