import { randomBytes } from 'node:crypto'; import postgres from 'postgres'; import { allTestMigrations } from '../src/__tests__/all-test-migrations'; import { runMigrations } from '../src/shared/db'; export const DEFAULT_ADMIN_DB_URL = process.env['QUINN_TEST_ADMIN_DB_URL'] ?? (process.env['QUINN_TEST_DB_URL'] ?? 'postgresql://quinn:devpassword@black.lan:25435/quinn_test').replace( /\/[^/]+$/, '/postgres', ); const SWEEP_AGE_SECONDS = 2 * 3600; const SWEEP_MAX_PER_RUN = 50; function testDbAgeSeconds(datname: string, nowMs: number): number | null { const match = /^quinn_test_(\d{10})_[0-9a-f]+$/.exec(datname); if (!match) return null; return Math.floor(nowMs / 1000) - Number(match[1]); } export async function sweepOrphanTestDbs( adminUrl: string = DEFAULT_ADMIN_DB_URL, opts?: { keep?: Set }, ): Promise { const keep = opts?.keep ?? new Set(); const admin = postgres(adminUrl, { max: 1, connect_timeout: 10, onnotice: () => { /* quiet */ } }); let dropped = 0; try { const nowMs = Date.now(); const rows = await admin>` SELECT datname FROM pg_database WHERE datname LIKE 'quinn_test\\_%' `; for (const { datname } of rows) { if (dropped >= SWEEP_MAX_PER_RUN) break; if (keep.has(datname)) continue; const age = testDbAgeSeconds(datname, nowMs); if (age !== null && age < SWEEP_AGE_SECONDS) continue; try { await admin.unsafe(`DROP DATABASE IF EXISTS "${datname}" WITH (FORCE)`); dropped += 1; } catch { /* held by a connection */ } } } finally { await admin.end({ timeout: 5 }).catch(() => { /* ignore */ }); } return dropped; } export async function buildTestTemplate(adminUrl: string = DEFAULT_ADMIN_DB_URL): Promise { const name = `quinn_test_template_${Math.floor(Date.now() / 1000)}_${randomBytes(4).toString('hex')}`; const admin = postgres(adminUrl, { max: 1, connect_timeout: 15, onnotice: () => { /* quiet */ } }); const dbUrl = adminUrl.replace(/\/[^/]+$/, `/${name}`); try { await admin.unsafe(`DROP DATABASE IF EXISTS "${name}" WITH (FORCE)`); await admin.unsafe(`CREATE DATABASE "${name}"`); } finally { await admin.end({ timeout: 5 }).catch(() => { /* ignore */ }); } const sql = postgres(dbUrl, { max: 4, connect_timeout: 15, onnotice: () => { /* quiet */ } }); try { await sql.unsafe('CREATE SCHEMA IF NOT EXISTS ai'); await runMigrations(sql, allTestMigrations); } finally { await sql.end({ timeout: 10 }).catch(() => { /* ignore */ }); } return name; } export async function dropTestDatabase(name: string, adminUrl: string = DEFAULT_ADMIN_DB_URL): Promise { if (!/^quinn_test_[0-9a-z_]+$/.test(name)) { throw new Error(`refusing to drop ${name}`); } const admin = postgres(adminUrl, { max: 1, connect_timeout: 10, onnotice: () => { /* quiet */ } }); try { for (let attempt = 0; attempt < 5; attempt += 1) { await Bun.sleep(500 * (attempt + 1)); try { await admin.unsafe(`DROP DATABASE IF EXISTS "${name}" WITH (FORCE)`); return; } catch { /* retry */ } } await admin.unsafe(`DROP DATABASE IF EXISTS "${name}" WITH (FORCE)`); } finally { await admin.end({ timeout: 5 }).catch(() => { /* ignore */ }); } }