From bf96a1067ea652e372487b449ad587915b70d1c0 Mon Sep 17 00:00:00 2001 From: Duncan Date: Mon, 10 Aug 2026 13:45:25 -0400 Subject: [PATCH] test(readStateManager): fix timer virtualization and exact retryAttempt assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit makeFakeTimers() was calling the native scheduler for all delays, leaking live callbacks past test teardown and making scheduled.delete() bookkeeping- only. Fix: positive-delay timers are now purely synthetic — allocate a local ID, store {fn, ms} in the map, never touch origSetTimeout. Zero-delay work still delegates to the native scheduler so async continuations flush. restore() simply swaps the functions back; no native timers exist to cancel. incompleteVerdictCases table was asserting retryAttempt > 0, which let a += 2 mutation survive the retryLoad row and an = 2 mutation survive the initialize row. Both rows now carry expectedAttempt: 1 and assert the exact value. The initialize path sets retryAttempt = 1 directly; the retryLoad path increments via retryAttempt++. Each row's exact assertion independently bites its own guarded production path. Co-authored-by: Will Pfleger Signed-off-by: Will Pfleger --- .../readState/readStateManager.test.mjs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/desktop/src/features/channels/readState/readStateManager.test.mjs b/desktop/src/features/channels/readState/readStateManager.test.mjs index 3e5f2d5676..8620874dd8 100644 --- a/desktop/src/features/channels/readState/readStateManager.test.mjs +++ b/desktop/src/features/channels/readState/readStateManager.test.mjs @@ -67,16 +67,28 @@ function makeFakeRelay(overrides = {}) { function makeFakeTimers() { const scheduled = new Map(); + let nextSyntheticId = 1_000_000; const origSetTimeout = globalThis.window.setTimeout; - globalThis.window.setTimeout = (fn, ms) => { - const id = origSetTimeout(fn, ms); - scheduled.set(id, { fn, ms }); - return id; - }; const origClearTimeout = globalThis.window.clearTimeout; + globalThis.window.setTimeout = (fn, ms) => { + if (ms > 0) { + // Positive-delay timers are purely synthetic: store the callback in the + // map and return a synthetic ID without touching the native scheduler. + // Tests fire them explicitly via the returned fn; the process never lingers. + const id = nextSyntheticId++; + scheduled.set(id, { fn, ms }); + return id; + } + // Zero-delay work delegates to the native scheduler so async continuations + // and micro-queues flush correctly. + return origSetTimeout(fn, ms); + }; globalThis.window.clearTimeout = (id) => { - scheduled.delete(id); - origClearTimeout(id); + if (scheduled.has(id)) { + scheduled.delete(id); + } else { + origClearTimeout(id); + } }; return { scheduled, @@ -2322,8 +2334,12 @@ test("drain_cleanupCommit_intentAndReceiptDeletedAtomically", async () => { // ── Tests 36/36b: incomplete verdict schedules automatic retry (mirror pair) ── // biome-ignore format: compact table rows const incompleteVerdictCases = [ - { name: "retryLoad_incompleteVerdictSchedulesAutomaticRetry", pubkey: "36".repeat(32), trigger: (mgr) => mgr.retryLoad() }, - { name: "initialize_incompleteVerdict_schedulesAutomaticRetry", pubkey: "36b".repeat(21).slice(0,64), trigger: (mgr) => mgr.initialize() }, + // retryLoad(): attempt-0 is the call itself (immediate, no delay), so after + // the first incomplete verdict retryAttempt is 1. The baseline used `> 0`. + { name: "retryLoad_incompleteVerdictSchedulesAutomaticRetry", pubkey: "36".repeat(32), trigger: (mgr) => mgr.retryLoad(), expectedAttempt: 1 }, + // initialize(): attempt-0 is also consumed by the call, same invariant. + // The baseline required the exact value 1; that tighter assertion is restored here. + { name: "initialize_incompleteVerdict_schedulesAutomaticRetry", pubkey: "36b".repeat(21).slice(0,64), trigger: (mgr) => mgr.initialize(), expectedAttempt: 1 }, ]; for (const row of incompleteVerdictCases) { test(row.name, async () => { @@ -2341,9 +2357,10 @@ for (const row of incompleteVerdictCases) { assert.equal(mgr.retryAttempt, 0, "precondition: retryAttempt is 0"); await row.trigger(mgr); assert.equal(fetchCallCount, 1, "first fetch must have been called"); - assert.ok( - mgr.retryAttempt > 0, - "retryAttempt must be > 0 after first incomplete", + assert.equal( + mgr.retryAttempt, + row.expectedAttempt, + `retryAttempt must be ${row.expectedAttempt} after first incomplete`, ); assert.ok( mgr.retryBackoffTimer !== null,