import { execFileSync } from 'node:child_process' import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import pg from 'pg' import { loadEnvFromRepoRoot } from '../dist/loadEnv.js' import { resolveTestDatabaseUrl } from '../dist/testDatabase.js' /** * Setzt die Testdatenbank neu auf und bringt sie auf den Migrationsstand. * Wird von `pnpm test` vorgeschaltet. * * **Neu aufsetzen, nicht nur anlegen.** Verlaufseinträge, Akten und * Protokollzeilen sind nach Zusicherung 2 der README nicht löschbar — was * ein Testlauf anlegt, bleibt für immer stehen. Nach einigen Dutzend Läufen * standen 2487 Akten in der Datenbank, und Tests, die eine auf zehn Einträge * begrenzte Liste prüfen, fanden ihre eigene Akte nicht mehr darin. Solche * Fehlschläge sehen aus wie ein Fehler im Code und sind keiner; das kostet mehr * Zeit als das Neuaufsetzen. * * `KEEP_TEST_DB=1` behält den Bestand. Für das schnelle Nacheinander beim * Arbeiten an einer einzelnen Testdatei. * * Läuft nur in der Entwicklung und in der CI, nie im Betrieb. */ loadEnvFromRepoRoot() const testUrl = resolveTestDatabaseUrl() const dbName = new URL(testUrl).pathname.replace(/^\//, '') const maintenance = new URL(testUrl) maintenance.pathname = '/postgres' const client = new pg.Client({ connectionString: maintenance.toString() }) await client.connect() // Bezeichner lassen sich nicht als Parameter binden, deshalb quotiert. Der Name // kommt aus resolveTestDatabaseUrl und endet immer auf _test. const quoted = `"${dbName.replace(/"/g, '""')}"` if (!dbName.endsWith('_test')) { throw new Error(`Sicherung: ${dbName} endet nicht auf _test, wird nicht angefasst`) } try { const { rowCount } = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [dbName]) const behalten = process.env.KEEP_TEST_DB === '1' if (rowCount > 0 && !behalten) { // Offene Verbindungen verhindern das Verwerfen — etwa ein Prisma Studio // oder ein abgebrochener Testlauf. await client.query( `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()`, [dbName], ) await client.query(`DROP DATABASE ${quoted}`) console.log(`[test-db] ${dbName} verworfen`) } if (rowCount === 0 || !behalten) { await client.query(`CREATE DATABASE ${quoted}`) console.log(`[test-db] ${dbName} angelegt`) } else { console.log(`[test-db] ${dbName} behalten (KEEP_TEST_DB=1)`) } } finally { await client.end() } const require = createRequire(import.meta.url) const prismaCli = require.resolve('prisma/build/index.js') const testEnv = { ...process.env, DATABASE_URL: testUrl, DIRECT_DATABASE_URL: testUrl } execFileSync(process.execPath, [prismaCli, 'migrate', 'deploy'], { stdio: 'inherit', env: testEnv, }) // Der Auslieferungszustand gehört auch in die Testdatenbank: Freigabestufen und // Änderungsrichtlinie werden von Tests vorausgesetzt. Der Seed ist idempotent. const tsxCli = require.resolve('tsx/cli') execFileSync(process.execPath, [tsxCli, 'prisma/seed.ts'], { stdio: 'inherit', cwd: fileURLToPath(new URL('..', import.meta.url)), env: testEnv, }) // Die Änderungsrichtlinie ist eine einzige Zeile für die ganze Anwendung. Der // Seed lässt sie bewusst in Ruhe, damit er die Einstellung des Leiters nicht // überschreibt — in der Testdatenbank soll sie aber bei jedem Lauf im // Auslieferungszustand starten, sonst hängt das Ergebnis davon ab, was der // vorige Lauf hinterlassen hat. const reset = new pg.Client({ connectionString: testUrl }) await reset.connect() try { await reset.query( `UPDATE "AppSetting" SET "entryEditPolicy" = 'GRACE_WINDOW', "entryEditGraceMinutes" = 15, "entryEditMarkGrace" = false WHERE id = 1`, ) } finally { await reset.end() } console.log(`[test-db] ${dbName} ist auf Stand`)