fix(test-infra): openInboundDb honors in-memory test DB

initTestSessionDb() creates an in-memory inbound singleton, but
openInboundDb() always opened the hardcoded /workspace/inbound.db
path. Every test that exercised getPendingMessages — directly, or via
test fixtures that load data through it (e.g. poll-loop.test.ts:29
loads formatter test rows via getPendingMessages) — failed with
SQLITE_CANTOPEN under `bun test` outside a real container.

Baseline on main: 34 pass, 25 fail across 6 files. After this fix:
59 pass, 0 fail.

In test mode, openInboundDb returns the in-memory singleton. The
singleton's .close() is no-op'd in initTestSessionDb so caller
try/finally cleanup doesn't tear down the shared DB; closeSessionDb
invokes the saved original close to do the real teardown.

Production behavior is unchanged — _inboundIsTest only flips inside
initTestSessionDb, which is never called outside the test runner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mike Nolet
2026-05-02 08:45:23 +02:00
parent 1b08b58fcd
commit ceb0b9cf5f

View File

@@ -27,6 +27,13 @@ const DEFAULT_HEARTBEAT_PATH = '/workspace/.heartbeat';
let _inbound: Database | null = null;
let _outbound: Database | null = null;
let _heartbeatPath: string = DEFAULT_HEARTBEAT_PATH;
// True when initTestSessionDb() set _inbound to an in-memory DB. Used by
// openInboundDb() so tests don't try to open the missing /workspace path.
let _inboundIsTest = false;
// Saved real close() for the in-memory inbound singleton. We no-op the
// public .close() during tests so caller try/finally doesn't tear down
// the shared DB; closeSessionDb() invokes this to do the real teardown.
let _inboundOriginalClose: (() => void) | null = null;
/**
* Avoid all cached db reads; open inbound.db read-only with mmap and page cache disabled.
@@ -42,6 +49,12 @@ let _heartbeatPath: string = DEFAULT_HEARTBEAT_PATH;
* Cost is microseconds per query, so safe for universal use.
*/
export function openInboundDb(): Database {
// In test mode the inbound DB is an in-memory singleton — there is no
// file at DEFAULT_INBOUND_PATH. Return the singleton directly; its
// .close() was no-op'd in initTestSessionDb so caller try/finally
// cleanup doesn't tear down the shared DB.
if (_inboundIsTest && _inbound) return _inbound;
const db = new Database(DEFAULT_INBOUND_PATH, { readonly: true });
db.exec('PRAGMA busy_timeout = 5000');
db.exec('PRAGMA mmap_size = 0');
@@ -171,6 +184,12 @@ export function clearStaleProcessingAcks(): void {
/** For tests — creates in-memory DBs with the session schemas. */
export function initTestSessionDb(): { inbound: Database; outbound: Database } {
_inbound = new Database(':memory:');
_inboundIsTest = true;
// No-op .close() so callers using openInboundDb()'s try/finally pattern
// don't tear down our shared singleton. closeSessionDb() does the real
// teardown via the saved original.
_inboundOriginalClose = _inbound.close.bind(_inbound);
_inbound.close = () => {};
_inbound.exec('PRAGMA foreign_keys = ON');
_inbound.exec(`
CREATE TABLE messages_in (
@@ -244,8 +263,14 @@ export function initTestSessionDb(): { inbound: Database; outbound: Database } {
}
export function closeSessionDb(): void {
_inbound?.close();
if (_inboundOriginalClose) {
_inboundOriginalClose();
_inboundOriginalClose = null;
} else {
_inbound?.close();
}
_inbound = null;
_inboundIsTest = false;
_outbound?.close();
_outbound = null;
}