From ec3b5d5185c5401a75d74191394aab848092594a Mon Sep 17 00:00:00 2001 From: le king fu Date: Thu, 8 Oct 2026 21:05:14 -0400 Subject: [PATCH] test(data): pin each restore's notification after the DB lock ADR 0022 wants a transactional write to notify once `withTransaction` has returned, never from its callback. Only the categorization test checked the lock; the restore tests checked that the notification followed the COMMIT, which a notification sent from inside the callback also satisfies. Each restore mode now asserts the lock was released. Review of #396 (PR #402). Co-Authored-By: Claude Opus 5.5 --- src/services/dataExportService.test.ts | 42 +++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/services/dataExportService.test.ts b/src/services/dataExportService.test.ts index 1702c35..676f190 100644 --- a/src/services/dataExportService.test.ts +++ b/src/services/dataExportService.test.ts @@ -31,7 +31,7 @@ vi.mock("./db", () => { }; }); -import { getDb } from "./db"; +import { getDb, withTransaction } from "./db"; import { getExportImportSources, getExportImportTemplates, @@ -803,6 +803,46 @@ describe("a committed restore is announced on the data channel (#396)", () => { expect(seen[0][seen[0].length - 1]).toBe("COMMIT"); }); + // ADR 0022: a write notifies once `withTransaction` has returned, never from + // its callback, where the DB lock is still held. + it.each([ + [ + "transactions with categories", + () => + importTransactionsWithCategories( + envelopeData({ transactions: [transactionFixture(1)] }), + "backup.json" + ), + ], + [ + "transactions only", + () => importTransactionsOnly(envelopeData({ transactions: [transactionFixture(1)] }), "backup.json"), + ], + [ + "categories only", + () => importCategoriesOnly({ categories: CATEGORY_FIXTURES, suppliers: [], keywords: [] }), + ], + ] as const)("%s: notifies once the DB lock is released", async (_name, restore) => { + let inLock = false; + const locks: boolean[] = []; + const stop = subscribeDataChanged(() => locks.push(inLock)); + vi.mocked(withTransaction).mockImplementation(async (fn) => { + inLock = true; + try { + return await fn((await getDb()) as never); + } finally { + inLock = false; + } + }); + try { + await restore(); + } finally { + stop(); + vi.mocked(withTransaction).mockImplementation(async (fn) => fn((await getDb()) as never)); + } + expect(locks).toEqual([false]); + }); + it("a restore rolled back announces nothing", async () => { let txInserts = 0; wire(