import { Client } from 'pg'; async function waitForDb(retries = 20): Promise { const config = { host: process.env.TEST_PGHOST || 'localhost', port: parseInt(process.env.TEST_PGPORT || '55432', 10), user: process.env.TEST_PGUSER || 'redefined_test', password: process.env.TEST_PGPASSWORD || 'redefined_test', database: process.env.TEST_PGDATABASE || 'redefined_test' }; for (let i = 0; i < retries; i++) { const client = new Client(config); try { await client.connect(); await client.end(); return; } catch { await new Promise(r => setTimeout(r, 1000)); } } throw new Error( 'Could not reach the test database on port 55432. Run `npm run db:test:up` before `npm run test:integration`.' ); } /** * Every address the database host resolves to, and which server answered (#154). * * This is the measurement that issue has needed twice and never had. The * schema in a failing run comes back — a suite fails on a missing `orders`, * and a later suite truncating that same table passes — which a database * losing its schema cannot do. More than one server answering to one name can, * and Docker's embedded DNS round-robins every container sharing an alias, so * a leftover service container from an earlier run fits every observation * including the empty `dmesg`. * * **More than one address printed here is the answer outright.** One address, * and this reading is wrong too — the next suspect is a single container being * restarted with a fresh data directory, which the postmaster start time * recorded alongside will show. * * Logged unconditionally rather than only on failure: a passing run's addresses * are the control, and without them a failing run's have nothing to be compared * against. Never throws — a diagnostic that can fail the run it was added to * explain is worse than none. */ async function reportDatabaseIdentity(): Promise { const host = process.env.TEST_PGHOST || 'localhost'; try { const { lookup } = await import('dns/promises'); const addresses = await lookup(host, { all: true }); const rendered = addresses.map((a) => a.address).join(', '); console.info( `[#154] "${host}" resolves to ${addresses.length} address(es): ${rendered}` + (addresses.length > 1 ? ' <-- more than one server can answer; this is the bug' : '') ); } catch (err) { console.info(`[#154] could not resolve "${host}": ${err instanceof Error ? err.message : String(err)}`); } } export default async function globalSetup(): Promise { await waitForDb(); const { migrate, closeDb, assertSchemaPresent, describeBackend } = await import('./testDb'); await reportDatabaseIdentity(); // Recorded before migrating so the run's baseline is in the log even if the // migration is the thing that fails. A suite that later reports a different // postmaster start time is talking to a different instance. console.info(`[#154] ${await describeBackend('globalSetup reached')}`); await migrate(); // Cheap, once, and it establishes the fact the rest of the run depends on: // the schema was here when we started. Without it, a run that never had a // schema and a run that lost one midway are indistinguishable from the // failures they produce. See #154. await assertSchemaPresent('immediately after migrating'); await closeDb(); }