Files
FireAdministration/scripts/testdaten.ts
T

151 lines
5.3 KiB
TypeScript

import "dotenv/config";
import { spawnSync } from "node:child_process";
import { PrismaClient } from "@prisma/client";
/**
* Die Testwehr zurücksetzen.
*
* npm run db:test-reset
*
* Legt einen eigenen Mandanten für die E2E-Tests an — und setzt ihn bei jedem
* Aufruf auf den Ausgangsstand zurück.
*
* **Warum es das gibt.** Bis Phase 8 schrieben die Playwright-Tests in die
* Beispielwehr `ff-musterhausen`, also in genau den Bestand, den die Anleitung
* zeigt. Nach fünf Phasen standen dort 94 Atemschutz-Überwachungen statt
* einer, 49 Geräte statt 45 und Termine mit Namen wie „E2E-Absage-m1x8kq".
* Zweimal fielen Tests aus, weil ein gesuchter Eintrag von Seite 1 gerutscht
* war — und bei jedem Testlauf unterschieden sich alle 72 Bildschirmfotos.
*
* **Warum Löschen und nicht Aufräumen.** Ein Lauf, der abbricht, lässt beim
* Aufräumen einen Bestand zurück, den niemand erwartet. Ein vollständiger
* Neuaufbau ist der einzige Zustand, den man kennt — und er dauert
* Sekunden, weil der Seed ohnehin schon alles kann.
*
* **Die Beispielwehr wird nicht angefasst.** Weder gelesen noch geschrieben:
* gelöscht wird ausschließlich der Mandant mit der Testkennung.
*/
const SLUG = process.env.E2E_SLUG ?? "ff-teststelle";
const NAME = process.env.E2E_NAME ?? "Freiwillige Feuerwehr Teststelle";
const KURZNAME = process.env.E2E_KURZNAME ?? "FF Teststelle";
const EMAIL = process.env.E2E_ADMIN_EMAIL ?? "e2e@teststelle.de";
const PASSWORT = process.env.E2E_ADMIN_PASSWORT ?? "Feuerwehr2026";
/** Die Beispielwehr. Sie darf unter keinen Umständen getroffen werden. */
const GESCHUETZT = process.env.SEED_SLUG ?? "ff-musterhausen";
const prisma = new PrismaClient({
datasources: {
db: { url: process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL ?? "" },
},
});
function log(nachricht: string): void {
console.info(`[testdaten] ${nachricht}`);
}
async function main(): Promise<void> {
if (SLUG === GESCHUETZT) {
throw new Error(
`Die Testkennung "${SLUG}" ist dieselbe wie die der Beispielwehr. ` +
"Das würde den Bestand löschen, den die Anleitung zeigt — abgebrochen.",
);
}
/**
* Auch die Nachbarwehr aus Phase 10 und das Konto der Kreisebene.
*
* Beide gehören zur Testwehr, liegen aber nicht in ihr: die Nachbarwehr ist
* eine eigene Organisation, das Konto gehört gar keiner. Blieben sie beim
* Zurücksetzen stehen, sähe der Seed die Nachbarwehr, hielte alles für
* erledigt und legte die Mitgliedschaft in der neuen Testwehr nie wieder an
* — der Wehrwähler zeigte danach nur noch die betreute Wehr.
*/
const NACHBAR_SLUG = `${SLUG}-nachbar`;
const KBI_EMAIL = process.env.SEED_KBI_EMAIL ?? `kbi@${SLUG}.de`;
const vorhanden = await prisma.organisation.findFirst({
where: { slug: { in: [SLUG, NACHBAR_SLUG] } },
select: { id: true, name: true },
});
if (vorhanden) {
log(`Entferne die bisherige Testwehr "${vorhanden.name}" ...`);
// Das Audit-Log ist append-only; für das Abräumen einer ganzen Wehr wird
// die Ausnahme gesetzt, die auch der Aufbewahrungslauf des Workers
// benutzt. Alles Übrige hängt am Fremdschlüssel und geht mit.
await prisma.$transaction(async (tx) => {
await tx.$executeRaw`SELECT set_config('app.audit_bereinigung', 'an', true)`;
// Die Trigger, die Protokollzeilen unveränderlich machen, stehen dem
// Löschen im Weg. Im Betrieb passiert das nie — hier wird eine ganze
// Wehr entfernt, und zwar eine, die es nur für Tests gibt.
for (const tabelle of [
"druckmessung",
"atemschutz_ereignis",
"ki_vorgang",
"kiosk_vorgang",
]) {
await tx.$executeRawUnsafe(`ALTER TABLE "${tabelle}" DISABLE TRIGGER USER`);
}
await tx.organisation.deleteMany({ where: { slug: { in: [SLUG, NACHBAR_SLUG] } } });
await tx.benutzer.deleteMany({ where: { email: KBI_EMAIL } });
for (const tabelle of [
"druckmessung",
"atemschutz_ereignis",
"ki_vorgang",
"kiosk_vorgang",
]) {
await tx.$executeRawUnsafe(`ALTER TABLE "${tabelle}" ENABLE TRIGGER USER`);
}
});
}
log(`Lege "${NAME}" neu an ...`);
// Der Seed kann das alles schon — er bekommt nur eine andere Kennung.
//
// Über die Shell aufgerufen, weil `npx` auf Windows eine Batchdatei ist:
// ohne `shell: true` findet spawnSync sie nicht und liefert stillschweigend
// den Status null statt einer Fehlermeldung.
const ergebnis = spawnSync("npx tsx prisma/seed.ts", {
stdio: "inherit",
shell: true,
env: {
...process.env,
SEED_SLUG: SLUG,
SEED_NAME: NAME,
SEED_KURZNAME: KURZNAME,
SEED_ADMIN_EMAIL: EMAIL,
SEED_ADMIN_PASSWORT: PASSWORT,
},
});
if (ergebnis.status !== 0) {
throw new Error(
`Der Seed ist mit Code ${ergebnis.status ?? "unbekannt"} abgebrochen.` +
(ergebnis.error ? ` (${ergebnis.error.message})` : ""),
);
}
log("");
log("Testwehr steht. Die E2E-Tests melden sich damit an:");
log(` E-Mail: ${EMAIL}`);
log(` Passwort: ${PASSWORT}`);
log("");
log(`Die Beispielwehr "${GESCHUETZT}" wurde nicht angefasst.`);
}
main()
.catch((fehler) => {
console.error("[testdaten] Abbruch:", fehler instanceof Error ? fehler.message : fehler);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});