From 6ee735de897f4507b9c53288775b980cffd554c4 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:14:20 -0700 Subject: [PATCH 1/2] test(digest): pin the digest-hour case to fixed summer and winter instants 15:01 UTC is 08:00 in Los Angeles only under PDT. The test built 15:01 on the current date, so it went red for every PR and the merge queue when daylight saving ended on 2026-11-01, and would have stayed red until March. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- .../dailyDigestService.membership.test.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/backend/__tests__/unit/services/dailyDigestService.membership.test.js b/backend/__tests__/unit/services/dailyDigestService.membership.test.js index b7ab1dca43..c0f77b1c25 100644 --- a/backend/__tests__/unit/services/dailyDigestService.membership.test.js +++ b/backend/__tests__/unit/services/dailyDigestService.membership.test.js @@ -331,19 +331,29 @@ describe('the digest hour', () => { expect(DailyDigestService.dueAt({ timezone }, new Date(at))).toBe(due); }); - it('a scheduled run takes only the people for whom it is the hour', async () => { - const la = await person(new Date(Date.now() - 2 * 86400000)); + // Fixed instants, never today: "15:01 UTC is 08:00 in Los Angeles" holds only + // under PDT, so a test that built 15:01 on the current date went red the day + // daylight saving ended (2026-11-01) and would have stayed red until March. + it.each([ + ['in summer, 15:01 UTC is 08:01 PDT: Los Angeles is due', '2026-10-17T15:01:00Z', true], + ['in winter, 15:01 UTC is 07:01 PST: Los Angeles is not due', '2026-12-15T15:01:00Z', false], + ['in winter, 16:01 UTC is 08:01 PST: Los Angeles is due', '2026-12-15T16:01:00Z', true], + ])('a scheduled run takes only the people for whom it is the hour: %s', async (_label, iso, laDue) => { + const at = new Date(iso); + const twoDaysBefore = new Date(at.getTime() - 2 * 86400000); + const la = await person(twoDaysBefore); await User.updateOne({ _id: la._id }, { $set: { timezone: 'America/Los_Angeles' } }); - const nozone = await person(new Date(Date.now() - 2 * 86400000)); + const nozone = await person(twoDaysBefore); const service = new DailyDigestService(); const generate = jest.spyOn(service, 'generateUserDailyDigest').mockResolvedValue({ _id: 'digest-1' }); - const laEight = new Date(); laEight.setUTCHours(15, 1, 0, 0); - await service.generateAllDailyDigests({ at: laEight }); + await service.generateAllDailyDigests({ at }); const called = generate.mock.calls.map(([id]) => String(id)); - expect(laEight.getUTCHours() === 15 && localHourIsEight(laEight)).toBe(true); - expect(called).toContain(String(la._id)); + expect(localHourIsEight(at)).toBe(laDue); + if (laDue) expect(called).toContain(String(la._id)); + else expect(called).not.toContain(String(la._id)); + // 06:00 UTC is the no-zone hour, and none of these instants is 06:00 UTC. expect(called).not.toContain(String(nozone._id)); }); From f23cb044792ae3eca9d5b98c3c74229a89b3ddb6 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:15:41 -0700 Subject: [PATCH 2/2] test(digest): add the no-zone 06:01 row and drop a stale DST comment Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpQmb6BbxDrAtw45bgN --- .../dailyDigestService.membership.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/__tests__/unit/services/dailyDigestService.membership.test.js b/backend/__tests__/unit/services/dailyDigestService.membership.test.js index c0f77b1c25..8b600cfa5b 100644 --- a/backend/__tests__/unit/services/dailyDigestService.membership.test.js +++ b/backend/__tests__/unit/services/dailyDigestService.membership.test.js @@ -33,7 +33,7 @@ const agentUser = async (username, botMetadata) => { // no zone on file that is 06:00 UTC. Today's 06:00 UTC, so the windows built // from Date.now() in these fixtures still hold. const digestHourUtc = () => { const at = new Date(); at.setUTCHours(6, 0, 0, 0); return at; }; -// 15:01 UTC is 08:01 in Los Angeles under daylight time (Mar-Nov) only. +// Guards the table rows below: which UTC hour is 08:00 there depends on the date. const localHourIsEight = (at) => require('../../../utils/timeZone').localHour('America/Los_Angeles', at) === 8; const podWithSummary = async (owner, name, options = {}) => { @@ -335,10 +335,11 @@ describe('the digest hour', () => { // under PDT, so a test that built 15:01 on the current date went red the day // daylight saving ended (2026-11-01) and would have stayed red until March. it.each([ - ['in summer, 15:01 UTC is 08:01 PDT: Los Angeles is due', '2026-10-17T15:01:00Z', true], - ['in winter, 15:01 UTC is 07:01 PST: Los Angeles is not due', '2026-12-15T15:01:00Z', false], - ['in winter, 16:01 UTC is 08:01 PST: Los Angeles is due', '2026-12-15T16:01:00Z', true], - ])('a scheduled run takes only the people for whom it is the hour: %s', async (_label, iso, laDue) => { + ['in summer, 15:01 UTC is 08:01 PDT: Los Angeles is due', '2026-10-17T15:01:00Z', true, false], + ['in winter, 15:01 UTC is 07:01 PST: Los Angeles is not due', '2026-12-15T15:01:00Z', false, false], + ['in winter, 16:01 UTC is 08:01 PST: Los Angeles is due', '2026-12-15T16:01:00Z', true, false], + ['at 06:01 UTC only the person with no zone is due', '2026-12-15T06:01:00Z', false, true], + ])('a scheduled run takes only the people for whom it is the hour: %s', async (_label, iso, laDue, nozoneDue) => { const at = new Date(iso); const twoDaysBefore = new Date(at.getTime() - 2 * 86400000); const la = await person(twoDaysBefore); @@ -351,10 +352,9 @@ describe('the digest hour', () => { const called = generate.mock.calls.map(([id]) => String(id)); expect(localHourIsEight(at)).toBe(laDue); - if (laDue) expect(called).toContain(String(la._id)); - else expect(called).not.toContain(String(la._id)); - // 06:00 UTC is the no-zone hour, and none of these instants is 06:00 UTC. - expect(called).not.toContain(String(nozone._id)); + expect(called.includes(String(la._id))).toBe(laDue); + // 06:00 UTC is the hour for someone with no zone. + expect(called.includes(String(nozone._id))).toBe(nozoneDue); }); it('nobody gets a second digest within 20 hours, even if their hour comes round again', async () => {