cm_whatsapp_bot_v1/packages/shared/src/delivery-window.test.ts
yiekheng 7039d57a41 feat(db,shared): delivery window columns + windowEndAt helper
Adds two integer columns to the reminders table:
* delivery_window_start_hour (default 6)
* delivery_window_end_hour   (default 18)

Both are documented in the operator's timezone. End hour will gate
the runtime fire-reminder loop in a later phase; this commit just
lands the data model and the pure window-end calculator.

windowEndAt(timezone, endHour, fireAt) lives in @cmbot/shared so
both bot (window enforcement) and web (ETA preview) can import it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:48:36 +08:00

51 lines
2.2 KiB
TypeScript

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();
});
});