lilith-platform.live/codebase/@features/api/scripts/test-template.ts
Natalie 6a155be0c9 perf(ci): template DB clone, parallel test workers, split CI/deploy queues
- Build one migrated template per run-tests invocation; per-file DBs clone
  via CREATE DATABASE TEMPLATE (~seconds) instead of replaying 148 migrations
- Run up to 4 test workers on CI (QUINN_TEST_WORKERS); sweep orphans once
- QUINN_CI_FAST smoke subset (12 files) on push/PR; full suite on dispatch
- ci-${{ ref }} concurrency separate from deploy-${{ ref }}; cancel stale runs
- Cache Playwright browsers on quinn.www deploy workflow
2026-06-24 03:59:48 -04:00

90 lines
No EOL
3.3 KiB
TypeScript

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 */ });
}
}