Gift-card rolling expiry (setting-driven, was dead config): - GetGiftCardExpiryMonths(): single source of truth (business_settings gift_card_expiry_months, fallback 24) shared by payment handlers and the CleanupExpiredGiftCards job (was hardcoded 24). - expiry_date now maintained on ALL 9 gift-card write sites (buy, topup, transfer, redeem, terminal payment, refund credit, till) so the refund-time guard at refunds.go actually fires. Schema default 12->24 + migration note; test-DB seed aligned. Stale "expiry_date IS NULL" test rewritten; new expired-card-rejected regression test. Frontend SvelteDate purge (docs' stated convention, wide): - All 180+ raw `new SvelteDate(...)` uses across routes/components replaced with parseWallClockDate (backend UTC ISO) or new Date (wall-clock constructors). SvelteDate imports removed. timeSlots.ts getDayWithOrdinal fixed. Zero SvelteDate references remain; svelte-check clean. Strict timezone/DST testing + QA fixes: - 8 new hermetic boundary tests: clock.DST transitions (both 2026 folds), closing-hours GMT vs BST, booking date-window midnight, refund-tier elapsed-time independence, deposit-window UTC-instant, scheduling LondonDateString midnight, today AT TIME ZONE window + UTC round-trip. - today.go summary date labels fixed to London wall-clock (were showing the previous UTC day during BST) + regression test. - pgx ScanLocation fixed to UTC via AfterConnect (was host-local -> JSON offsets depended on deployment TZ, contradicting the documented UTC invariant) + regression test. Registered as a new *Type to avoid a data race on the shared type map (caught by -race). Admin Business Settings (setting now functional => legal floor): - gift_card_expiry_months validation floor raised 1 -> 12 months (CMA/ Consumer Rights Act 2015 unfair-contract-term guidance) in endpoint + UI, with rolling-expiry semantics shown in both display and edit form. - 3 new expiry validation tests; 2 pre-existing message assertions updated. Full suite 25/25 + race clean via run-tests.sh lockfile; svelte-check 0 errors/warnings; production build succeeds.
57 lines
2.3 KiB
Go
57 lines
2.3 KiB
Go
//go:build test
|
|
|
|
package scheduling
|
|
|
|
// Strict timezone/DST/midnight-boundary tests for the scheduling package's
|
|
// London-date derivation. All assertions use fixed time.Date instants.
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"crussell/clock"
|
|
)
|
|
|
|
// TestScheduling_DefaultHours_DST_Midnight proves ApplyScheduledDefaultHours's
|
|
// "today" date (derived via LondonDateString) is the LONDON calendar date, not
|
|
// the UTC date. During BST, London dates begin at 23:00 UTC the previous day:
|
|
// both 2026-06-14 23:30 UTC (= 2026-06-15 00:30 BST) and 2026-06-15 00:30 UTC
|
|
// (= 2026-06-15 01:30 BST) fall on London date 2026-06-15 — a naive UTC date
|
|
// would report 2026-06-14 for the first instant and misdate the effective day
|
|
// of a staged default-hours change.
|
|
func TestScheduling_DefaultHours_DST_Midnight(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
utc time.Time
|
|
want string
|
|
}{
|
|
{"00:30 BST window", time.Date(2026, 6, 14, 23, 30, 0, 0, time.UTC), "2026-06-15"},
|
|
{"01:30 BST same day", time.Date(2026, 6, 15, 0, 30, 0, 0, time.UTC), "2026-06-15"},
|
|
{"GMT midnight is UTC midnight", time.Date(2026, 1, 14, 23, 30, 0, 0, time.UTC), "2026-01-14"},
|
|
{"GMT next day", time.Date(2026, 1, 15, 0, 30, 0, 0, time.UTC), "2026-01-15"},
|
|
{"autumn BST->GMT transition day", time.Date(2026, 10, 25, 0, 30, 0, 0, time.UTC), "2026-10-25"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := LondonDateString(tc.utc); got != tc.want {
|
|
t.Errorf("LondonDateString(%s UTC) = %q, expected London date %q", tc.utc.Format(time.RFC3339), got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Direct proof of the DST window: the UTC date of the first instant differs
|
|
// from its London date — exactly the drift the helper must absorb.
|
|
boundary := time.Date(2026, 6, 14, 23, 30, 0, 0, time.UTC)
|
|
if boundary.Format("2006-01-02") == LondonDateString(boundary) {
|
|
t.Fatal("test setup invariant: 2026-06-14 23:30 UTC must straddle the London midnight boundary")
|
|
}
|
|
|
|
// The helper must be consistent with clock.London's own wall-clock for the
|
|
// same instant (the production seam ApplyScheduledDefaultHours now uses).
|
|
nowish := time.Date(2026, 6, 14, 23, 45, 0, 0, time.UTC)
|
|
if LondonDateString(nowish) != nowish.In(clock.London).Format("2006-01-02") {
|
|
t.Errorf("LondonDateString disagrees with the direct London conversion for %s", nowish.Format(time.RFC3339))
|
|
}
|
|
}
|