mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
feat(classes): weekly recurring schedules with lazy session materialization
class_schedules rules generate concrete session rows for a rolling 28-day horizon — no cron; materialize runs idempotently on dashboard and public reads via a partial unique index on (schedule_id, starts_at). Rule delete stops future sessions; existing sessions keep bookings and overrides. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
7b9080eb1c
commit
ad60167a31
15 files changed
+1029
-7
No files matched your search
@@ -797,3 +797,92 @@ func TestUpdateClassTemplate(t *testing.T) {
|
||||
t.Fatalf("expected ErrClassTemplateNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassScheduleMaterializesSessions(t *testing.T) {
|
||||
repo := db.NewMemoryRepository()
|
||||
svc := newCatalogService(repo)
|
||||
ctx := context.Background()
|
||||
|
||||
schedules, err := svc.ListClassSchedules(ctx, owner)
|
||||
if err != nil {
|
||||
t.Fatalf("list schedules: %v", err)
|
||||
}
|
||||
if len(schedules) != 0 {
|
||||
t.Fatalf("expected 0 schedules in fresh repo, got %d", len(schedules))
|
||||
}
|
||||
|
||||
locID := "659f1cc0-a850-46d6-b3b8-cb15d55d8daf"
|
||||
created, err := svc.CreateClassSchedule(ctx, owner, domain.CreateClassScheduleRequest{
|
||||
TemplateID: "d13fe5fd-727f-4d69-bfd8-47f1b92a2cf7",
|
||||
LocationID: &locID,
|
||||
DayOfWeek: 1,
|
||||
StartsLocal: "15:30",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create schedule: %v", err)
|
||||
}
|
||||
if !created.Active || created.StartsLocal != "15:30:00" {
|
||||
t.Fatalf("unexpected created schedule: %+v", created)
|
||||
}
|
||||
|
||||
sessions, err := svc.ListClassSessions(ctx, owner)
|
||||
if err != nil {
|
||||
t.Fatalf("list sessions: %v", err)
|
||||
}
|
||||
var generated int
|
||||
for _, session := range sessions {
|
||||
if session.ScheduleID != nil && *session.ScheduleID == created.ID {
|
||||
generated++
|
||||
if session.StartsAt.Weekday() != time.Monday {
|
||||
t.Fatalf("generated session on wrong weekday: %v", session.StartsAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
if generated < 3 {
|
||||
t.Fatalf("expected >=3 materialized sessions over the horizon, got %d", generated)
|
||||
}
|
||||
|
||||
sessions2, err := svc.ListClassSessions(ctx, owner)
|
||||
if err != nil {
|
||||
t.Fatalf("list sessions 2: %v", err)
|
||||
}
|
||||
var generated2 int
|
||||
for _, session := range sessions2 {
|
||||
if session.ScheduleID != nil && *session.ScheduleID == created.ID {
|
||||
generated2++
|
||||
}
|
||||
}
|
||||
if generated2 != generated {
|
||||
t.Fatalf("materialize not idempotent: %d then %d", generated, generated2)
|
||||
}
|
||||
|
||||
if err := svc.DeleteClassSchedule(ctx, owner, created.ID); err != nil {
|
||||
t.Fatalf("delete schedule: %v", err)
|
||||
}
|
||||
schedules, _ = svc.ListClassSchedules(ctx, owner)
|
||||
if len(schedules) != 0 {
|
||||
t.Fatalf("expected 0 schedules after delete, got %d", len(schedules))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateClassScheduleValidation(t *testing.T) {
|
||||
repo := db.NewMemoryRepository()
|
||||
svc := newCatalogService(repo)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := svc.CreateClassSchedule(ctx, owner, domain.CreateClassScheduleRequest{
|
||||
TemplateID: "d13fe5fd-727f-4d69-bfd8-47f1b92a2cf7", DayOfWeek: 7, StartsLocal: "10:00",
|
||||
}); !errors.Is(err, ErrInvalidInput) {
|
||||
t.Fatalf("expected ErrInvalidInput for bad weekday, got %v", err)
|
||||
}
|
||||
if _, err := svc.CreateClassSchedule(ctx, owner, domain.CreateClassScheduleRequest{
|
||||
TemplateID: "d13fe5fd-727f-4d69-bfd8-47f1b92a2cf7", DayOfWeek: 2, StartsLocal: "25:00",
|
||||
}); !errors.Is(err, ErrInvalidInput) {
|
||||
t.Fatalf("expected ErrInvalidInput for bad time, got %v", err)
|
||||
}
|
||||
if _, err := svc.CreateClassSchedule(ctx, owner, domain.CreateClassScheduleRequest{
|
||||
TemplateID: "00000000-0000-0000-0000-000000000099", DayOfWeek: 2, StartsLocal: "10:00",
|
||||
}); !errors.Is(err, ErrClassTemplateNotFound) {
|
||||
t.Fatalf("expected ErrClassTemplateNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user