import { describe, it, expect } from "vitest"; import { windowEndAt } from "./delivery-window.js"; const TZ = "Asia/Kuala_Lumpur"; // UTC+8 (no DST) describe("windowEndAt", () => { it("returns today's end-hour boundary in the given timezone", () => { // Fire at 2026-05-10 10:00 KL == 02:00 UTC. End hour 18 == 18:00 KL == 10:00 UTC. const fireAt = new Date("2026-05-10T02:00:00.000Z"); const out = windowEndAt(TZ, 18, fireAt); expect(out.toISOString()).toBe("2026-05-10T10:00:00.000Z"); }); it("returns a past timestamp when fireAt is already after the end hour", () => { // Fire at 2026-05-10 19:00 KL == 11:00 UTC. End hour 18 → today's 18:00 KL == 10:00 UTC. const fireAt = new Date("2026-05-10T11:00:00.000Z"); const out = windowEndAt(TZ, 18, fireAt); expect(out.toISOString()).toBe("2026-05-10T10:00:00.000Z"); expect(out.getTime()).toBeLessThan(fireAt.getTime()); }); it("respects the timezone (UTC vs UTC+8)", () => { const fireAt = new Date("2026-05-10T02:00:00.000Z"); const inUtc = windowEndAt("UTC", 18, fireAt); expect(inUtc.toISOString()).toBe("2026-05-10T18:00:00.000Z"); const inKl = windowEndAt("Asia/Kuala_Lumpur", 18, fireAt); expect(inKl.toISOString()).toBe("2026-05-10T10:00:00.000Z"); }); it("handles end hour 24 as midnight at the calendar day boundary", () => { // 2026-05-10 in KL ends at 2026-05-11 00:00 KL == 2026-05-10 16:00 UTC. const fireAt = new Date("2026-05-10T02:00:00.000Z"); const out = windowEndAt(TZ, 24, fireAt); expect(out.toISOString()).toBe("2026-05-10T16:00:00.000Z"); }); it("DST transition day stays on the same calendar day", () => { // US/Eastern starts DST on 2026-03-08; 18:00 EDT is real time. // Fire at 2026-03-08 10:00 EST (15:00 UTC). End at 2026-03-08 18:00 EDT (22:00 UTC). const fireAt = new Date("2026-03-08T15:00:00.000Z"); const out = windowEndAt("America/New_York", 18, fireAt); expect(out.toISOString()).toBe("2026-03-08T22:00:00.000Z"); }); it("rejects end hour outside 0..24", () => { const fireAt = new Date("2026-05-10T00:00:00Z"); expect(() => windowEndAt(TZ, -1, fireAt)).toThrow(); expect(() => windowEndAt(TZ, 25, fireAt)).toThrow(); }); });