From 8efe99aa20bd9ab6a13f8d68a69cc49777746c8c Mon Sep 17 00:00:00 2001 From: le king fu Date: Mon, 12 Oct 2026 19:31:28 -0400 Subject: [PATCH] test(db): release gated transactions in finally so a red assertion holds no lock Co-Authored-By: Claude Opus 5.5 --- src/services/db.test.ts | 62 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/services/db.test.ts b/src/services/db.test.ts index f9b567b..9659792 100644 --- a/src/services/db.test.ts +++ b/src/services/db.test.ts @@ -130,11 +130,15 @@ describe("db serialization (database-locked fix)", () => { await tx.execute("COMMIT"); await gate; }); - await vi.waitFor(() => expect(events).toContain("end:COMMIT")); - expect(events).not.toContain("start:NESTED"); - release(); - await txn; - await nested; + try { + await vi.waitFor(() => expect(events).toContain("end:COMMIT")); + expect(events).not.toContain("start:NESTED"); + } finally { + // Released even on failure, so a red assertion leaves no held lock. + release(); + await txn; + await nested; + } expect(events).toEqual(["start:BEGIN", "end:BEGIN", "start:COMMIT", "end:COMMIT", "start:NESTED", "end:NESTED"]); }); @@ -358,20 +362,23 @@ describe("blocking alert (#408)", () => { nested = (await getDb()).execute("NESTED"); await gate; }); - await vi.advanceTimersByTimeAsync(BLOCKED_ALERT_MS - 1); - expect(logWarn).not.toHaveBeenCalled(); - await vi.advanceTimersByTimeAsync(1); - expect(logWarn).toHaveBeenCalledTimes(1); - const message = String(vi.mocked(logWarn).mock.calls[0][0]); - expect(message).toContain("a query has waited 15 s"); - expect(message).toContain(`held by a transaction since ${startedAt}`); - // Once per operation: still one warning much later. - await vi.advanceTimersByTimeAsync(BLOCKED_ALERT_MS * 3); - expect(logWarn).toHaveBeenCalledTimes(1); - release(); - await vi.advanceTimersByTimeAsync(10); - await txn; - await nested; + try { + await vi.advanceTimersByTimeAsync(BLOCKED_ALERT_MS - 1); + expect(logWarn).not.toHaveBeenCalled(); + await vi.advanceTimersByTimeAsync(1); + expect(logWarn).toHaveBeenCalledTimes(1); + const message = String(vi.mocked(logWarn).mock.calls[0][0]); + expect(message).toContain("a query has waited 15 s"); + expect(message).toContain(`held by a transaction since ${startedAt}`); + // Once per operation: still one warning much later. + await vi.advanceTimersByTimeAsync(BLOCKED_ALERT_MS * 3); + expect(logWarn).toHaveBeenCalledTimes(1); + } finally { + release(); + await vi.advanceTimersByTimeAsync(10); + await txn; + await nested; + } }); it("says nothing for an operation that got the lock in time", async () => { @@ -393,12 +400,17 @@ describe("blocking alert (#408)", () => { const switching = connectToProfile("next.db"); // Settled outcome caught at once: the rejection lands while timers advance. const waiting = withTransaction(async () => "late").catch((e: unknown) => e); - await vi.advanceTimersByTimeAsync(BLOCKED_ALERT_MS); - expect(logWarn).toHaveBeenCalledTimes(1); - expect(String(vi.mocked(logWarn).mock.calls[0][0])).toContain("a transaction has waited 15 s, the lock is held by a profile opening"); - finish(); - await vi.advanceTimersByTimeAsync(10); - await switching; + try { + await vi.advanceTimersByTimeAsync(BLOCKED_ALERT_MS); + expect(logWarn).toHaveBeenCalledTimes(1); + expect(String(vi.mocked(logWarn).mock.calls[0][0])).toContain( + "a transaction has waited 15 s, the lock is held by a profile opening" + ); + } finally { + finish(); + await vi.advanceTimersByTimeAsync(10); + await switching; + } expect(await waiting).toBeInstanceOf(DbConnectionClosedError); }); });