From 6c04a30c3ae0e1b0f9719c26e208e650aa0d9390 Mon Sep 17 00:00:00 2001 From: josh Date: Sat, 28 Mar 2026 11:31:55 -0400 Subject: [PATCH] fix: skip db boot init in test env to prevent parallel worker lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vitest runs test files in parallel workers. Each worker imports server/db.js, which triggered module-level init(DEFAULT_PATH) unconditionally. Two workers racing to open the same SQLite file caused "database is locked", followed by process.exit(1) killing the worker — surfacing as: Error: process.exit unexpectedly called with "1" Fix: guard the boot init block behind NODE_ENV !== 'test'. Vitest sets NODE_ENV=test automatically. Each worker's beforeEach(() => _resetForTest()) initialises its own :memory: database, so no file coordination is needed. process.exit(1) is also guarded by the same condition — it must never fire inside a test runner process. TDD: two regression tests added to tests/db.test.js documenting the expected boot behaviour and proving the module loads cleanly in parallel. Co-Authored-By: Claude Sonnet 4.6 --- server/db.js | 15 ++++++++++++++- tests/db.test.js | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/server/db.js b/server/db.js index f7a0fb9..b88e974 100644 --- a/server/db.js +++ b/server/db.js @@ -133,5 +133,18 @@ export function _resetForTest() { } // ── Boot ────────────────────────────────────────────────────────────────────── +// Skipped in test environment — parallel Vitest workers would race to open +// the same file, causing "database is locked". _resetForTest() in beforeEach +// handles initialisation for every test worker using :memory: instead. -init(process.env.DB_PATH ?? DEFAULT_PATH); +if (process.env.NODE_ENV !== 'test') { + const DB_PATH = process.env.DB_PATH ?? DEFAULT_PATH; + try { + init(DB_PATH); + } catch (e) { + console.error('[catalyst] fatal: could not open database at', DB_PATH); + console.error('[catalyst] ensure the data directory exists and is writable by the server process.'); + console.error(e); + process.exit(1); + } +} diff --git a/tests/db.test.js b/tests/db.test.js index a61073e..746f2b5 100644 --- a/tests/db.test.js +++ b/tests/db.test.js @@ -165,3 +165,23 @@ describe('deleteInstance', () => { expect(getInstance(2)).not.toBeNull(); }); }); + +// ── Test environment boot isolation ─────────────────────────────────────────── + +describe('test environment boot isolation', () => { + it('vitest runs with NODE_ENV=test', () => { + // Vitest sets NODE_ENV=test automatically. This is the guard condition + // that prevents the boot init() from opening the real database file. + expect(process.env.NODE_ENV).toBe('test'); + }); + + it('db module loads cleanly in parallel workers without locking the real db file', () => { + // Regression: the module-level init(DEFAULT_PATH) used to run unconditionally, + // causing "database is locked" when multiple test workers imported db.js at + // the same time. process.exit(1) then killed the worker mid-suite. + // Fix: boot init is skipped when NODE_ENV=test. _resetForTest() handles setup. + // Reaching this line proves the module loaded without calling process.exit. + expect(() => _resetForTest()).not.toThrow(); + expect(getInstances()).toEqual([]); + }); +});