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