From 83c54a6b7968efee3ed7f215609cfc932e407da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 11 Sep 2026 13:43:19 +0200 Subject: [PATCH] test(integration): wait for the ledger row, not for fifty milliseconds The delivery ledger is written without awaiting the result, so the row appears some time after the dispatch returns. The test slept a flat fifty milliseconds and then read, which holds on a warm machine and loses on a loaded runner: the assertion reported an empty ledger as though the dispatch had not recorded the skip. It polls for the rows now, up to five seconds, and fails with a message naming how many it found. The claim is that the row is written, which is what the code promises; that it is written within fifty milliseconds is not. --- ...notification-global-channel-switch.test.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/integration/notification-global-channel-switch.test.ts b/tests/integration/notification-global-channel-switch.test.ts index 43b01fae7..ef66389b1 100644 --- a/tests/integration/notification-global-channel-switch.test.ts +++ b/tests/integration/notification-global-channel-switch.test.ts @@ -59,9 +59,28 @@ async function dispatch() { }); } -/** The ledger write is fire-and-forget; let its microtask land. */ -async function settleLedger(): Promise { - await new Promise((resolve) => setTimeout(resolve, 50)); +/** + * The ledger write is fire-and-forget, so the row appears some time after the + * dispatch returns. This used to wait a flat 50 ms, which is enough on a warm + * machine and not enough on a cold container: the suite failed here once with + * an empty ledger while nothing about the dispatch had changed. It waits for + * the rows instead, and gives up loudly rather than letting the assertion + * below report the absence as a product defect. + */ +async function settleLedger(expected: number): Promise { + const deadline = Date.now() + 5_000; + for (;;) { + const seen = await getPrismaClient().pushAttempt.count({ + where: { userId: TEST_USER_ID }, + }); + if (seen >= expected) return; + if (Date.now() > deadline) { + throw new Error( + `the delivery ledger still holds ${seen} of ${expected} rows after five seconds`, + ); + } + await new Promise((resolve) => setTimeout(resolve, 25)); + } } beforeEach(async () => { @@ -82,7 +101,7 @@ describe("instance-wide notification channel switch", () => { await setNtfyGlobal(false); const outcome = await dispatch(); - await settleLedger(); + await settleLedger(1); expect(ntfySendMock).not.toHaveBeenCalled(); expect(outcome).toEqual({