Files
Laufzettel/tests/datenbankregeln.test.ts
T
Florian Wilde 0b5138d8bb
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(bausteine): Abschnitte, die mehrere Vorlagen teilen
"Datensicherung pruefen" steht in vier der sieben mitgelieferten Vorlagen,
wortgleich. Wer den Ablauf aendert, aendert ihn viermal -- oder dreimal und
vergisst den vierten.

Entschieden wie im Issue vorgeschlagen: ein Entwurf **verweist** auf eine
veroeffentlichte Baustein-Fassung, und beim Veroeffentlichen der Vorlage setzt
der Dienst deren Schritte als tiefe Kopie ein. Danach sieht ein Lauf weiterhin
konkrete Schritte, und Schrittergebnis, Druckansicht und Archiv muessen nichts
von Bausteinen wissen. Die Alternative -- Laeufe verweisen zur Laufzeit --
haette die Unveraenderlichkeit an einer zweiten Stelle durchsetzen muessen.

Eine neue Fassung aendert damit keine veroeffentlichte Vorlagenversion und
keinen laufenden Lauf. Sie wirkt in der naechsten veroeffentlichten Version;
die Oberflaeche sagt das an zwei Stellen, weil man sonst Durchschlag erwartet.

Vier Garantien liegen in der Datenbank, nicht nur im Dienst: hoechstens ein
Entwurf je Baustein, eine veroeffentlichte Fassung ist unveraenderlich, der Weg
zurueck zum Entwurf ist versperrt, und ein Abschnitt kann nur auf eine
veroeffentlichte Fassung verweisen.

Der Titel kommt aus dem Baustein, nicht aus dem Abschnitt der Vorlage: sonst
muesste er in jeder Vorlage einzeln gepflegt werden, und zwei Vorlagen zeigten
denselben Baustein unter verschiedenen Namen.

Ein Export traegt die aufgeloesten Schritte, keinen Verweis -- eine Kennung aus
dieser Installation bedeutet in einer anderen nichts.

Das Durchklicken des gebauten Stands fand einen Fehler, den die Suite nicht
gesehen haette: die Platzhalter-Schritte eines Baustein-Abschnitts haben keinen
Titel, wurden aber mitgeschickt, und SchrittSchema wies den ganzen Entwurf ab.
Behoben, mit Test und Gegenprobe. Der ganze Weg danach am gebauten Stand
nachgemessen: Baustein anlegen, veroeffentlichen, einsetzen, Vorlage
veroeffentlichen -- die Schritte stehen drin, mit Herkunft und
Protokolleintrag.

Closes #50
2026-10-04 19:06:31 +02:00

106 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { datenbankVerfuegbar, testPrisma } from "./helpers/db";
/**
* Wächter über die Datenbankobjekte, die Prisma nicht kennt (#009).
*
* Zusammengesetzte Fremdschlüssel, Trigramm-Indizes und Trigger lassen sich im
* Prisma-Schema nicht ausdrücken. Deshalb erzeugt `prisma migrate dev` bei
* **jeder** künftigen Migration Löschbefehle für sie — sie sind aus Prismas
* Sicht Abweichungen von der Vorgabe. Wer die Löschbefehle einmal übersieht,
* entfernt stillschweigend die Mandantengrenze, die Archivsuche oder den Schutz
* der Unveränderlichkeit.
*
* Dieser Test macht das laut. Er prüft nicht, dass die Regeln *wirken* — das tun
* `tests/datenmodell.test.ts` und `tests/vorlagen.test.ts` —, sondern nur, dass
* die Objekte überhaupt noch da sind.
*/
async function namen(abfrage: string): Promise<string[]> {
const zeilen = await testPrisma.$queryRawUnsafe<{ name: string }[]>(abfrage);
return zeilen.map((zeile) => zeile.name).sort();
}
describe.runIf(datenbankVerfuegbar)("Handgeschriebene Datenbankobjekte", () => {
it("hat die zusammengesetzten Fremdschlüssel der Mandantengrenze", async () => {
// Ohne sie kann ein Lauf ein Gerät eines fremden Kunden ziehen.
expect(
await namen(`SELECT conname AS name FROM pg_constraint WHERE conname LIKE '%gleicher_kunde'`),
).toEqual([
"Geraet_besitzer_gleicher_kunde",
"Lauf_abteilung_gleicher_kunde",
"Lauf_geraet_gleicher_kunde",
"Lauf_mitarbeiter_gleicher_kunde",
"Mitarbeiter_vorgesetzter_gleicher_kunde",
]);
});
it("hat die Trigramm-Indizes für die Archivsuche", async () => {
// Ohne sie wird jede Archivsuche zum vollständigen Durchlauf der Tabelle.
expect(
await namen(
`SELECT indexname AS name FROM pg_indexes WHERE schemaname='public' AND indexdef LIKE '%gin_trgm_ops%'`,
),
).toEqual([
"Kommentar_rumpf_trgm_idx",
"Lauf_titel_trgm_idx",
"Schrittergebnis_notiz_trgm_idx",
"Schrittergebnis_wert_trgm_idx",
]);
});
it("hat die Trigger, die eine veröffentlichte Version schützen", async () => {
expect(await namen(`SELECT tgname AS name FROM pg_trigger WHERE NOT tgisinternal`)).toEqual([
"BausteinSchritt_unveraenderlich",
"BausteinVersion_status_einbahn",
"VorlagenAbschnitt_baustein_veroeffentlicht",
"VorlagenAbschnitt_unveraenderlich",
"VorlagenSchritt_unveraenderlich",
"VorlagenVariable_unveraenderlich",
"VorlagenVersion_status_einbahn",
]);
});
it("hat die partiellen Eindeutigkeitsindizes", async () => {
expect(
await namen(
`SELECT indexname AS name FROM pg_indexes WHERE schemaname='public' AND indexdef LIKE '%WHERE%'`,
),
).toEqual([
"BausteinVersion_ein_entwurf",
"Kunde_eigene_organisation_einmalig",
"Vorlage_seedSchluessel_einmalig",
"VorlagenVersion_ein_entwurf",
]);
});
it("hat alle Prüfbedingungen", async () => {
const vorhanden = await namen(
`SELECT conname AS name FROM pg_constraint WHERE contype='c' AND connamespace='public'::regnamespace`,
);
// Namentlich aufgezählt, damit auch das Verschwinden einer einzelnen auffällt.
expect(vorhanden).toEqual([
"Einstellung_einzelne_zeile",
"Geraet_registriert_braucht_namen",
"Geraet_tagcode_form",
"Kunde_kuerzel_form",
"Lauf_abgeschlossen_mit_zeitpunkt",
"Lauf_archiviert_mit_zeitpunkt",
"Lauf_begonnen_mit_zeitpunkt",
"Lauf_mitarbeiter_eindeutig",
"Mitarbeiter_vorgesetzter_nicht_selbst",
"Schrittergebnis_begruendung_noetig",
"TagCodeVergabe_form",
"Verzeichnisquelle_bindpasswort_verschluesselt",
"VorlagenVersion_veroeffentlicht_mit_zeitpunkt",
]);
});
it("hat die Erweiterung für die Trigramm-Suche", async () => {
const zeilen = await testPrisma.$queryRaw<{ extname: string }[]>`
SELECT extname FROM pg_extension WHERE extname = 'pg_trgm'
`;
expect(zeilen).toHaveLength(1);
});
});