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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user