diff --git a/apps/backend/internal/bookings/service_test.go b/apps/backend/internal/bookings/service_test.go index 7a68eee..c3d1833 100644 --- a/apps/backend/internal/bookings/service_test.go +++ b/apps/backend/internal/bookings/service_test.go @@ -607,8 +607,40 @@ func TestRescheduleRearmsReminder(t *testing.T) { resp := bookAppointmentSlot(t, repo, service) booking, _ := repo.GetBookingByID(ctx, resp.BookingID) - newStartsAt := booking.StartsAt.Add(48 * time.Hour) - newEndsAt := booking.EndsAt.Add(48 * time.Hour) + // Reschedule into a real availability slot on a later day, mirroring the + // manage-page slot picker — a blind +48h shift can land on a weekend or + // blocked day and fail validation for the wrong reason. + var newStartsAt, newEndsAt time.Time + horizon := time.Now().UTC().Add(25 * time.Hour) + for offset := 1; offset <= 14 && newStartsAt.IsZero(); offset++ { + date := time.Now().UTC().AddDate(0, 0, offset).Format("2006-01-02") + availability, err := service.Availability(ctx, "studio-atelier", AvailabilityFilter{Date: date}) + if err != nil { + t.Fatalf("availability for %s: %v", date, err) + } + for _, slot := range availability.Slots { + if slot.Mode != "appointment" { + continue + } + starts, err := time.Parse(time.RFC3339, slot.StartsAt) + if err != nil { + t.Fatalf("parse slot start: %v", err) + } + ends, err := time.Parse(time.RFC3339, slot.EndsAt) + if err != nil { + t.Fatalf("parse slot end: %v", err) + } + // Stay above the 25h tier so the reminder re-arms at -24h. + if starts.Equal(booking.StartsAt) || !starts.After(horizon) { + continue + } + newStartsAt, newEndsAt = starts, ends + break + } + } + if newStartsAt.IsZero() { + t.Fatal("no alternative appointment slot found within 14 days") + } manageURL, _ := url.Parse(resp.ManageURL) token := manageURL.Query().Get("token") if err := customerService.RescheduleBooking(ctx, resp.Reference, domain.RescheduleBookingRequest{