Die README beschrieb das Vorgehen statt der Anwendung. Sie erklärt jetzt, was das System kann, wie es aufgebaut ist und wie man es lokal und im Betrieb fährt. Dazu tragen die acht Zusicherungen dort ihre Nummern - Berechtigungsprüfung in der Abfrage, kein DELETE im Verlauf, Protokollpflicht ab Stufe 3, Magic Bytes statt Dateiendungen, Skelette statt Spinner, Existenz als Information, keine Geheimnisse im Repository, keine Einmal-Container. Achtzehn Dateien verwiesen für ebendiese Regeln auf eine Datei, die nicht mehr mitverfolgt wird; sie zeigen jetzt auf die README. Arbeitsunterlagen bleiben auf der Platte, werden aber nicht mehr verfolgt. Die Ausschlussregel steht in .git/info/exclude statt in .gitignore, damit sie nicht selbst im Repository steht.
108 lines
3.8 KiB
JavaScript
108 lines
3.8 KiB
JavaScript
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`)
|