Files
breakpilot-lehrer/school-service/internal/services/timetable_constraints_more_test.go
T
Benjamin Admin 73636f76a2
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 30s
CI / test-go-edu-search (push) Successful in 30s
CI / test-python-klausur (push) Failing after 3m6s
CI / test-python-agent-core (push) Successful in 20s
CI / test-nodejs-website (push) Successful in 21s
Stundenplan Phase 3b: 3 more Stammdaten managers, first constraint editor, full test coverage
Frontend additions in studio-v2:
  - LehrerManager / FaecherManager / RaeumeManager — same CRUD pattern as
    Klassen, with entity-specific form fields and table columns.
  - regeln/TeacherUnavailableDayEditor — first constraint editor, joins
    against teachersApi to render a readable name in the dropdown and
    list. Falls back to a guidance banner when no teachers exist yet.
  - page.tsx wires up the new tabs; data-testid attributes added across
    managers so the Playwright suite can target them deterministically.

Tests:
  - school-service: timetable_constraints_more_test.go fills the
    remaining 9 constraint DTOs (TeacherMaxHoursDay/Week,
    TeacherExcludedSubject/Room, SubjectMinDayGap,
    SubjectContiguousWhenRepeated, SubjectDoubleLesson, ClassNoGaps,
    RoomRequiresType). 66 subtests total, all green.
  - studio-v2: e2e/stundenplan.spec.ts covers the page shell, tab
    navigation, Klassen CRUD with mocked backend, constraint editor's
    empty-teacher fallback, sidebar entry. All school-service calls
    intercepted via page.route() so the suite is hermetic.

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

189 lines
6.4 KiB
Go

package services
import (
"testing"
"github.com/breakpilot/school-service/internal/models"
)
// Additional validator tests covering the 9 constraint DTOs not exercised in
// timetable_constraints_test.go. Each entry probes both the happy path and
// the boundary that the binding tags are supposed to reject.
const (
uidTeacher = "00000000-0000-0000-0000-0000000000a1"
uidSubject = "00000000-0000-0000-0000-0000000000a2"
uidClass = "00000000-0000-0000-0000-0000000000a3"
uidRoom = "00000000-0000-0000-0000-0000000000a4"
)
func TestCreateTeacherMaxHoursDayRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTeacherMaxHoursDayRequest
wantErr bool
}{
{"valid", models.CreateTeacherMaxHoursDayRequest{TeacherID: uidTeacher, MaxHours: 6, IsHard: false, Weight: 50}, false},
{"missing teacher", models.CreateTeacherMaxHoursDayRequest{MaxHours: 6}, true},
{"hours below 1", models.CreateTeacherMaxHoursDayRequest{TeacherID: uidTeacher, MaxHours: 0}, true},
{"hours above 12", models.CreateTeacherMaxHoursDayRequest{TeacherID: uidTeacher, MaxHours: 13}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateTeacherMaxHoursWeekRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTeacherMaxHoursWeekRequest
wantErr bool
}{
{"valid", models.CreateTeacherMaxHoursWeekRequest{TeacherID: uidTeacher, MaxHours: 28, IsHard: true, Weight: 100}, false},
{"hours below 1", models.CreateTeacherMaxHoursWeekRequest{TeacherID: uidTeacher, MaxHours: 0}, true},
{"hours above 40", models.CreateTeacherMaxHoursWeekRequest{TeacherID: uidTeacher, MaxHours: 41}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateTeacherExcludedSubjectRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTeacherExcludedSubjectRequest
wantErr bool
}{
{"valid", models.CreateTeacherExcludedSubjectRequest{TeacherID: uidTeacher, SubjectID: uidSubject, IsHard: true, Weight: 100}, false},
{"missing subject", models.CreateTeacherExcludedSubjectRequest{TeacherID: uidTeacher}, true},
{"non-uuid subject", models.CreateTeacherExcludedSubjectRequest{TeacherID: uidTeacher, SubjectID: "nope"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateTeacherExcludedRoomRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateTeacherExcludedRoomRequest
wantErr bool
}{
{"valid", models.CreateTeacherExcludedRoomRequest{TeacherID: uidTeacher, RoomID: uidRoom, IsHard: true, Weight: 100}, false},
{"missing room", models.CreateTeacherExcludedRoomRequest{TeacherID: uidTeacher}, true},
{"non-uuid room", models.CreateTeacherExcludedRoomRequest{TeacherID: uidTeacher, RoomID: "nope"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateSubjectMinDayGapRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateSubjectMinDayGapRequest
wantErr bool
}{
{"valid", models.CreateSubjectMinDayGapRequest{SubjectID: uidSubject, MinGapDays: 1, IsHard: false, Weight: 70}, false},
{"below 1", models.CreateSubjectMinDayGapRequest{SubjectID: uidSubject, MinGapDays: 0}, true},
{"above 4", models.CreateSubjectMinDayGapRequest{SubjectID: uidSubject, MinGapDays: 5}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateSubjectContiguousWhenRepeatedRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateSubjectContiguousWhenRepeatedRequest
wantErr bool
}{
{"valid", models.CreateSubjectContiguousWhenRepeatedRequest{SubjectID: uidSubject, IsHard: true, Weight: 100}, false},
{"missing subject", models.CreateSubjectContiguousWhenRepeatedRequest{}, true},
{"weight above 100", models.CreateSubjectContiguousWhenRepeatedRequest{SubjectID: uidSubject, Weight: 200}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateSubjectDoubleLessonRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateSubjectDoubleLessonRequest
wantErr bool
}{
{"valid", models.CreateSubjectDoubleLessonRequest{SubjectID: uidSubject, IsHard: false, Weight: 60}, false},
{"missing subject", models.CreateSubjectDoubleLessonRequest{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateClassNoGapsRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateClassNoGapsRequest
wantErr bool
}{
{"valid", models.CreateClassNoGapsRequest{ClassID: uidClass, IsHard: false, Weight: 80}, false},
{"missing class", models.CreateClassNoGapsRequest{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}
func TestCreateRoomRequiresTypeRequest_Validation(t *testing.T) {
tests := []struct {
name string
req models.CreateRoomRequiresTypeRequest
wantErr bool
}{
{"valid", models.CreateRoomRequiresTypeRequest{SubjectID: uidSubject, RoomType: "Sporthalle", IsHard: true, Weight: 100}, false},
{"missing room type", models.CreateRoomRequiresTypeRequest{SubjectID: uidSubject}, true},
{"non-uuid subject", models.CreateRoomRequiresTypeRequest{SubjectID: "x", RoomType: "y"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (validate.Struct(tt.req) != nil) != tt.wantErr {
t.Errorf("unexpected validation outcome")
}
})
}
}