test(18-03): add failing stored-TZ all-day tests for scheduler + outbox
- reminderScheduler: new describe block with mockThreeQueries helper that
extends mockTwoQueries to mock getHouseholdTimezone app_config SELECT
(select({value}).from(appConfig).where(...).limit(1) chain)
- reminderScheduler: D-05 test expects dispatch at 14:00 UTC (Chicago CDT)
when stored zone is America/Chicago; fails RED (code still reads process.env.TZ=America/New_York)
- reminderScheduler: D-05 NOT-fire test expects no dispatch at 13:00 UTC (NY time)
when stored zone overrides to Chicago; fails RED (code fires at NY time)
- outboxWorker: new describe block with wireMockChainWithTz that extends
mockFromFn to handle app_config table via where().limit() chain
- outboxWorker: D-05 create-branch test expects VALARM TRIGGER 20260619T140000Z
(Chicago CDT); fails RED (code emits 20260619T130000Z using UTC fallback)
- outboxWorker: D-05 update-branch test same assertion, also fails RED
- Existing process.env.TZ-pinned all-day tests untouched; all 72 pass
This commit is contained in:
@@ -1094,3 +1094,109 @@ describe('runOutboxDrain — WR-02: reminderLeadMinutes max(10080) in outboxPayl
|
||||
expect(setArg?.status).not.toBe('failed');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Plan 18-03: stored household_timezone drives all-day alert instant ───────
|
||||
//
|
||||
// D-05: when getHouseholdTimezone(db) returns 'America/Chicago', the CREATE and
|
||||
// UPDATE all-day branches must compute the VALARM TRIGGER using Chicago
|
||||
// local time, not process.env.TZ.
|
||||
// D-06: existing tests that do not mock app_config continue to fall back to the
|
||||
// current process.env.TZ / Intl behavior (backward-compat path).
|
||||
//
|
||||
// These tests FAIL before the rewire (GREEN) because the code still reads
|
||||
// process.env.TZ at both sites.
|
||||
|
||||
describe('runOutboxDrain — Plan 18-03: stored household_timezone drives all-day alert (D-05)', () => {
|
||||
// Helper: wire the full db mock chain including app_config for getHouseholdTimezone.
|
||||
// The app_config SELECT chain: select({value}).from(appConfig).where(...).limit(1)
|
||||
// → from(app_config) → { where: fn → limitFn } → limitFn() → [{value}]
|
||||
function wireMockChainWithTz(storedTz: string | null) {
|
||||
wireMockChain(); // base wiring for outbox/credentials/events
|
||||
|
||||
// Extend mockFromFn to also handle app_config table with where().limit() chain
|
||||
const baseMockFromFn = mockFromFn.getMockImplementation();
|
||||
mockFromFn.mockImplementation((table: unknown) => {
|
||||
const tableName = (table as Record<symbol, string>)[Symbol.for('drizzle:Name')] ?? '';
|
||||
if (tableName === 'app_config') {
|
||||
const tzRows = storedTz ? [{ value: storedTz }] : [];
|
||||
const limitResolve = vi.fn().mockResolvedValue(tzRows);
|
||||
// .where() must return an object with .limit(), not the fn itself
|
||||
return { where: vi.fn().mockReturnValue({ limit: limitResolve }) };
|
||||
}
|
||||
return baseMockFromFn ? baseMockFromFn(table) : { where: mockWherePending };
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockPendingRows = [];
|
||||
wireMockChain();
|
||||
});
|
||||
|
||||
// Event date: 2026-06-20 (summer). Lead = 1440 min = 1 day → alert date = 2026-06-19.
|
||||
// America/Chicago CDT (UTC-5): 9 AM CDT on 2026-06-19 = 2026-06-19T14:00:00Z.
|
||||
// process.env.TZ is unset (UTC in test environment): 9 AM UTC on 2026-06-19 = 2026-06-19T09:00:00Z.
|
||||
// When stored zone drives computation: TRIGGER must contain the Chicago time (14:00 UTC).
|
||||
|
||||
it('D-05 create branch: stored America/Chicago produces 14:00 UTC trigger for 2026-06-20 all-day', async () => {
|
||||
wireMockChainWithTz('America/Chicago');
|
||||
|
||||
const { createCalendarEvent } = await import('../../src/broker/write.js');
|
||||
let capturedIcsString: unknown = null;
|
||||
vi.mocked(createCalendarEvent).mockImplementation(async (_client, _cal, _uid, icsString) => {
|
||||
capturedIcsString = icsString;
|
||||
return makeResponse(201);
|
||||
});
|
||||
|
||||
// all-day CREATE payload with 1440-min lead (1 day before)
|
||||
const payload = JSON.stringify({
|
||||
title: 'Stored-TZ Birthday',
|
||||
allDay: true,
|
||||
start: '2026-06-20',
|
||||
end: '2026-06-20',
|
||||
reminderLeadMinutes: 1440,
|
||||
});
|
||||
mockPendingRows = [makeRow({ payload })];
|
||||
|
||||
await runOutboxDrain();
|
||||
|
||||
expect(typeof capturedIcsString).toBe('string');
|
||||
expect(capturedIcsString as string).toContain('BEGIN:VALARM');
|
||||
// With stored 'America/Chicago' (CDT, UTC-5): alert date = 2026-06-19, 9 AM CDT = 14:00 UTC.
|
||||
// The absolute TRIGGER value must be 20260619T140000Z (Chicago time).
|
||||
expect(capturedIcsString as string).toContain('20260619T140000Z');
|
||||
});
|
||||
|
||||
it('D-05 update branch: stored America/Chicago produces 14:00 UTC trigger for 2026-06-20 all-day', async () => {
|
||||
wireMockChainWithTz('America/Chicago');
|
||||
|
||||
const { updateCalendarEvent } = await import('../../src/broker/write.js');
|
||||
let capturedIcsString: unknown = null;
|
||||
vi.mocked(updateCalendarEvent).mockImplementation(
|
||||
async (_client, _calObjectUrl, icsString, _etag) => {
|
||||
capturedIcsString = icsString;
|
||||
return makeResponse(204);
|
||||
},
|
||||
);
|
||||
|
||||
// all-day UPDATE payload with 1440-min lead — must also read stored TZ
|
||||
const payload = JSON.stringify({
|
||||
title: 'Stored-TZ Update',
|
||||
allDay: true,
|
||||
start: '2026-06-20',
|
||||
end: '2026-06-20',
|
||||
reminderLeadMinutes: 1440,
|
||||
});
|
||||
mockPendingRows = [makeRow({ operation: 'update', calendarObjectUrl: 'https://example.com/event.ics', etag: 'W/"abc"', payload })];
|
||||
|
||||
// Provide etag so WR-02 re-read path resolves (no rawVevent → falls through to all-day branch)
|
||||
mockWhereCalEvents.mockResolvedValue([{ etag: 'W/"abc"' }]);
|
||||
|
||||
await runOutboxDrain();
|
||||
|
||||
expect(typeof capturedIcsString).toBe('string');
|
||||
expect(capturedIcsString as string).toContain('BEGIN:VALARM');
|
||||
// Same expectation: alert date = 2026-06-19, 9 AM CDT = 14:00 UTC.
|
||||
expect(capturedIcsString as string).toContain('20260619T140000Z');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user