Files
Crussell/backend/handlers/bookings/closing_time_test.go
T
popertots 52caf1b7b5 feat: add getClosingTimeForDate to check staged default hours for bookings
Add getClosingTimeForDate that queries default_hours_scheduled_changes for a pending staged change whose effective_date <= the booking date. Falls back to current working_hours if no staged change applies. Also checks the staged schedule to see if a weekday is closed (returns 00:00).
2026-08-22 00:34:48 +01:00

202 lines
6.0 KiB
Go

//go:build test && dev
package bookings
import (
"testing"
"time"
"crussell/clock"
"crussell/testutils"
"crussell/testutils/fixtures"
)
func TestCheckClosingHours_WithinHours(t *testing.T) {
// Closing at 17:00, booking ends at 16:30 — should pass.
london, err := time.LoadLocation("Europe/London")
if err != nil {
t.Fatalf("failed to load location: %v", err)
}
localEnd := time.Date(2026, 6, 24, 16, 30, 0, 0, london)
if err := checkClosingHours(localEnd, "17:00"); err != nil {
t.Errorf("expected no error for 16:30 end vs 17:00 close, got: %v", err)
}
}
func TestCheckClosingHours_AtClosing(t *testing.T) {
// Closing at 17:00, booking ends exactly at 17:00 — should pass.
london, err := time.LoadLocation("Europe/London")
if err != nil {
t.Fatalf("failed to load location: %v", err)
}
localEnd := time.Date(2026, 6, 24, 17, 0, 0, 0, london)
if err := checkClosingHours(localEnd, "17:00"); err != nil {
t.Errorf("expected no error for 17:00 end vs 17:00 close, got: %v", err)
}
}
func TestCheckClosingHours_PastClosing(t *testing.T) {
// Closing at 17:00, booking ends at 17:01 — should return ErrPastClosing.
london, err := time.LoadLocation("Europe/London")
if err != nil {
t.Fatalf("failed to load location: %v", err)
}
localEnd := time.Date(2026, 6, 24, 17, 1, 0, 0, london)
if err := checkClosingHours(localEnd, "17:00"); !IsPastClosing(err) {
t.Errorf("expected ErrPastClosing for 17:01 end vs 17:00 close, got: %v", err)
}
}
func TestCheckClosingHours_WellPastClosing(t *testing.T) {
// Closing at 17:00, booking ends at 18:00 — should return ErrPastClosing.
london, err := time.LoadLocation("Europe/London")
if err != nil {
t.Fatalf("failed to load location: %v", err)
}
localEnd := time.Date(2026, 6, 24, 18, 0, 0, 0, london)
if err := checkClosingHours(localEnd, "17:00"); !IsPastClosing(err) {
t.Errorf("expected ErrPastClosing for 18:00 end vs 17:00 close, got: %v", err)
}
}
func TestCheckClosingHours_InvalidFormat(t *testing.T) {
// Malformed closing time string — should return a generic error, not ErrPastClosing.
london, err := time.LoadLocation("Europe/London")
if err != nil {
t.Fatalf("failed to load location: %v", err)
}
localEnd := time.Date(2026, 6, 24, 14, 0, 0, 0, london)
tests := []struct {
name string
closeStr string
}{
{"empty string", ""},
{"no colon", "1700"},
{"non-numeric hour", "ab:00"},
{"non-numeric minute", "17:ab"},
{"only hour", "17"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := checkClosingHours(localEnd, tc.closeStr)
if err == nil {
t.Error("expected error for invalid format, got nil")
}
if IsPastClosing(err) {
t.Errorf("expected non-past-closing error for invalid format %q, got ErrPastClosing", tc.closeStr)
}
})
}
}
// IsPastClosing reports whether err indicates the booking extends beyond closing hours.
// Exported helper for tests.
func IsPastClosing(err error) bool {
return err == ErrPastClosing
}
func TestGetClosingTimeForDate_FallsBackToWorkingHours(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
closeStr, err := getClosingTimeForDate(ctx, tx, 1, time.Now())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if closeStr == "" {
t.Error("expected non-empty closing time")
}
}
func TestGetClosingTimeForDate_StagedChangeOpenDay(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin user: %v", err)
}
defer fixtures.DeleteUser(tx, adminID)
hoursJSON := `[{"weekday":0,"startTime":"10:00","endTime":"18:00","isOpen":true}]`
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
_, err = tx.Exec(ctx, `
INSERT INTO default_hours_scheduled_changes (effective_date, created_by, hours)
VALUES ($1, $2, $3)
`, tomorrow, adminID, hoursJSON)
if err != nil {
t.Fatalf("failed to insert staged change: %v", err)
}
bookingDate := time.Now().AddDate(0, 0, 1)
closeStr, err := getClosingTimeForDate(ctx, tx, 0, bookingDate)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if closeStr != "18:00" {
t.Errorf("expected 18:00 from staged change, got %q", closeStr)
}
}
func TestGetClosingTimeForDate_StagedChangeClosedDay(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin user: %v", err)
}
defer fixtures.DeleteUser(tx, adminID)
hoursJSON := `[{"weekday":0,"startTime":"00:00","endTime":"00:00","isOpen":false}]`
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
_, err = tx.Exec(ctx, `
INSERT INTO default_hours_scheduled_changes (effective_date, created_by, hours)
VALUES ($1, $2, $3)
`, tomorrow, adminID, hoursJSON)
if err != nil {
t.Fatalf("failed to insert staged change: %v", err)
}
tomorrowLondon := time.Now().AddDate(0, 0, 1).In(clock.London)
weekday := int((tomorrowLondon.Weekday() + 6) % 7)
closeStr, err := getClosingTimeForDate(ctx, tx, weekday, tomorrowLondon)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if closeStr != "00:00" {
t.Errorf("expected 00:00 for closed day, got %q", closeStr)
}
}
func TestGetClosingTimeForDate_StagedChangeNotYetEffective(t *testing.T) {
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin user: %v", err)
}
defer fixtures.DeleteUser(tx, adminID)
hoursJSON := `[{"weekday":0,"startTime":"10:00","endTime":"18:00","isOpen":true}]`
tomorrow := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
_, err = tx.Exec(ctx, `
INSERT INTO default_hours_scheduled_changes (effective_date, created_by, hours)
VALUES ($1, $2, $3)
`, tomorrow, adminID, hoursJSON)
if err != nil {
t.Fatalf("failed to insert staged change: %v", err)
}
closeStr, err := getClosingTimeForDate(ctx, tx, 1, time.Now())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if closeStr == "" {
t.Error("expected non-empty closing time")
}
}