lilith-platform.live/codebase/@features/api/scripts/test-template.ts

90 lines
3.3 KiB
TypeScript
Raw Normal View History

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<string> },
): Promise<number> {
const keep = opts?.keep ?? new Set<string>();
const admin = postgres(adminUrl, { max: 1, connect_timeout: 10, onnotice: () => { /* quiet */ } });
let dropped = 0;
try {
const nowMs = Date.now();
const rows = await admin<Array<{ datname: string }>>`
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<string> {
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<void> {
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 */ });
}
}