Der Seed erkannte seine sieben Vorlagen am Namen. Wer eine umbenannte, bekam beim naechsten Lauf eine zweite -- und der Schnellstart stand auf der Geraeteseite doppelt. Vorlage.seedSchluessel traegt die Kennung, eindeutig wo gesetzt (partieller Index, weil eigene Vorlagen keinen tragen). Das Austauschformat kennt das Feld als seedSchluessel; nicht als schluessel, wie das Issue plante, denn so heisst dort schon das Feld einer Variablen. Der Rueckfall auf den Namen bleibt, sonst bekaeme eine Installation von vor diesem Feld beim ersten Lauf danach genau die Dubletten, die es verhindern soll. Wird sie so gefunden, traegt der Seed den Schluessel nach -- an der Entwicklungsdatenbank gefahren: sieben nachgetragen, nichts neu angelegt. Der Import setzt den Schluessel nicht, auch nicht wenn das Dokument ihn mitbringt, und der Export schreibt ihn nicht: eine eingespielte Kopie soll nicht den Platz des Originals einnehmen. Gegenprobe gefahren: ohne die Suche am Schluessel werden vier Tests rot. Closes #32
100 lines
3.7 KiB
TypeScript
100 lines
3.7 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",
|
|
]);
|
|
});
|
|
|
|
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([
|
|
"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([
|
|
"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",
|
|
"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);
|
|
});
|
|
});
|