diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2c1e3..d7b40a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/). ### Hinzugefügt +- Mitgelieferte Vorlagen tragen einen **stabilen Schlüssel** (`seedSchluessel`). Der Seed erkennt + seine sieben Vorlagen daran statt am Namen — wer eine umbenannte, bekam vorher beim nächsten Lauf + eine zweite, samt doppeltem Schnellstart auf der Geräteseite. Eine bestehende Installation bekommt + den Schlüssel beim ersten Lauf danach nachgetragen, ohne Dubletten. Eine über den Import + eingespielte Kopie bekommt **keinen** Schlüssel: sie soll nicht den Platz des Originals einnehmen + (#021) - `pnpm pruefe:doku` vergleicht die Schranken-Befehle in `CLAUDE.md` und `README.md` und verlangt von beiden den LDAP-Container. Genau diese Abweichung war unbemerkt entstanden (#040) - Fünf Tests für den Healthcheck, darunter einer, der belegt, dass die Begründung ins Protokoll diff --git a/docs/issues/021-stabiler-schluessel-vorlagen.md b/docs/issues/021-stabiler-schluessel-vorlagen.md index 83606c3..bc79028 100644 --- a/docs/issues/021-stabiler-schluessel-vorlagen.md +++ b/docs/issues/021-stabiler-schluessel-vorlagen.md @@ -1,11 +1,11 @@ # #021 Stabiler Schlüssel für Seed-Vorlagen -**Status:** offen +**Status:** erledigt **Im Tracker:** [#32](https://git.wilde-server.de/florian.wilde/Laufzettel/issues/32) — die Zählung dort schließt Pull Requests ein und läuft deshalb auseinander **Meilenstein:** v0.2.0 **Labels:** phase-2, backend -**Geschätzt:** 2 h **Tatsächlich:** – +**Geschätzt:** 2 h **Tatsächlich:** 55 min ## Ziel @@ -15,17 +15,19 @@ Geräteseite doppelt. Aufgefallen im Review zu [#017](017-seed-sieben-vorlagen.m ## Aufgaben -- [ ] Ein optionales Feld `schluessel` im [Austauschformat](../vorlagen-format.md) — gedacht für +- [x] Ein optionales Feld `seedSchluessel` im [Austauschformat](../vorlagen-format.md) — **nicht** + `schluessel` wie hier geplant: so heißt im Format schon das Feld einer Variablen, und zwei + Dinge gleichen Namens in derselben Datei wären eine Lesefalle — gedacht für mitgelieferte Vorlagen, nicht für die Oberfläche; beim Import **nicht** übernommen, damit eine eingespielte Kopie nicht den Platz des Originals einnimmt -- [ ] `Vorlage.seedSchluessel` mit partiellem eindeutigen Index (nur wo gesetzt) -- [ ] Der Seed sucht über den Schlüssel und fällt auf den Namen zurück, solange noch keiner gesetzt +- [x] `Vorlage.seedSchluessel` mit partiellem eindeutigen Index (nur wo gesetzt) +- [x] Der Seed sucht über den Schlüssel und fällt auf den Namen zurück, solange noch keiner gesetzt ist — eine bestehende Installation darf beim ersten Lauf danach keine Dubletten bekommen -- [ ] Dokumentieren, was der Schlüssel **nicht** tut: er macht eine Vorlage nicht unveränderlich und +- [x] Dokumentieren, was der Schlüssel **nicht** tut: er macht eine Vorlage nicht unveränderlich und erlaubt dem Seed nicht, eine bearbeitete Vorlage zu überschreiben ## Akzeptanzkriterien -- [ ] Test: umbenannte Seed-Vorlage, zweiter Seed-Lauf legt keine zweite an -- [ ] Test: zweiter Lauf erzeugt keine zweite `GeraetetypVorlage`-Zeile -- [ ] Test: eine über den Import eingespielte Kopie bekommt **keinen** Schlüssel +- [x] Test: umbenannte Seed-Vorlage, zweiter Seed-Lauf legt keine zweite an +- [x] Test: zweiter Lauf erzeugt keine zweite `GeraetetypVorlage`-Zeile +- [x] Test: eine über den Import eingespielte Kopie bekommt **keinen** Schlüssel diff --git a/docs/issues/027-e2e-reste-vorlagen.md b/docs/issues/027-e2e-reste-vorlagen.md new file mode 100644 index 0000000..ab43c57 --- /dev/null +++ b/docs/issues/027-e2e-reste-vorlagen.md @@ -0,0 +1,35 @@ +# #027 Die Editor-Abläufe lassen ihre Vorlagen stehen + +**Status:** offen +**Im Tracker:** – +**Meilenstein:** v0.2.0 +**Labels:** phase-2, test +**Geschätzt:** 1 h **Tatsächlich:** – + +## Ziel + +In der Entwicklungsdatenbank stehen über hundert Vorlagen mit Namen wie +`ZZ E2E Editor 1790324192596 C`. Die Ende-zu-Ende-Abläufe legen sie an und räumen sie nicht weg; bei +jedem Lauf kommen drei dazu. + +Aufgefallen beim Nachsehen zu [#021](021-stabiler-schluessel-vorlagen.md), als eine Abfrage über +`Vorlage` statt sieben Zeilen über hundert lieferte. + +Das ist kein Schaden an den Tests — das `ZZ`-Präfix sortiert sie ans Ende, und jeder Ablauf legt +seine eigenen an. Es ist ein Schaden an allem, was **danach** in dieselbe Datenbank sieht: die +Vorlagenliste in der Oberfläche, Screenshots, und jede Abfrage von Hand. Bei einem Fehler, der +„0 Treffer“ oder „zu viele Treffer“ heißt, kostet das Zeit. + +## Aufgaben + +- [ ] Die Abläufe unter `tests/e2e/`, die Vorlagen anlegen, räumen sie in einem Nachlauf wieder weg — + über ihr Präfix, nicht über gemerkte Kennungen (ein abgebrochener Lauf merkt sich nichts) +- [ ] Dasselbe für Geräte, Personen und Läufe prüfen, die die Abläufe anlegen +- [ ] Einmalig aufräumen: die vorhandenen Reste aus der Entwicklungsdatenbank entfernen +- [ ] Prüfen, ob ein Nachlauf das überhaupt leisten kann — ein Ablauf, der mitten im Lauf scheitert, + darf den Nachlauf nicht überspringen + +## Akzeptanzkriterien + +- [ ] Nach `pnpm test:e2e` steht in `Vorlage` keine Zeile mit `ZZ E2E` +- [ ] Ein abgebrochener Lauf lässt ebenfalls nichts stehen diff --git a/docs/issues/README.md b/docs/issues/README.md index 1107a8b..a376e6a 100644 --- a/docs/issues/README.md +++ b/docs/issues/README.md @@ -25,10 +25,11 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac | #018 | [M13 – E2E-Tests, Anleitung, PDF](018-e2e-anleitung-pdf.md) | erledigt | v0.1.0 | 14 h | 2 h 15 min | | #019 | [M14 – Betriebswege und Release v0.1.0](019-betrieb-release.md) | erledigt | v0.1.0 | 10 h | 5 h 25 min | | #020 | [Vorgesetzter auch für von Hand angelegte Personen](020-vorgesetzter-manuell.md) | offen | v0.2.0 | 3 h | – | -| #021 | [Stabiler Schlüssel für Seed-Vorlagen](021-stabiler-schluessel-vorlagen.md) | offen | v0.2.0 | 2 h | – | +| #021 | [Stabiler Schlüssel für Seed-Vorlagen](021-stabiler-schluessel-vorlagen.md) | erledigt | v0.2.0 | 2 h | 55 min | | #022 | [Schnellstart je Gerätetyp pflegen](022-schnellstart-pflegen.md) | offen | v0.2.0 | 3 h | – | | #023 | [Protokoll ansehen](023-protokoll-ansicht.md) | offen | v0.2.0 | 4 h | – | | #024 | [Proxmox-Einzeiler auf einem echten PVE-Host gegenprüfen](024-proxmox-echtlauf.md) | offen | v0.2.0 | 2 h | – | | #025 | [Healthcheck gibt die rohe Datenbankfehlermeldung heraus](025-gesundheit-fehlermeldung.md) | erledigt | v0.2.0 | 1 h | 25 min | | #026 | [Zwei Konventionen präzisieren, die strenger klingen als sie gemeint sind](026-konventionen-praezisieren.md) | erledigt | v0.2.0 | 1 h | 20 min | -| | **Summe** | | | **152 h** | **36 h 05 min** | +| #027 | [Die Editor-Abläufe lassen ihre Vorlagen stehen](027-e2e-reste-vorlagen.md) | offen | v0.2.0 | 1 h | – | +| | **Summe** | | | **153 h** | **37 h 00 min** | diff --git a/docs/vorlagen-format.md b/docs/vorlagen-format.md index 85e77d5..76f9672 100644 --- a/docs/vorlagen-format.md +++ b/docs/vorlagen-format.md @@ -104,6 +104,7 @@ Platzhaltern. **JSON kennt keine Kommentare** — die Zeilen mit `//` müssen vo | -------------- | -------- | ------------------------------------------------------------------------------- | | `format` | Pflicht | Fassung des Austauschformats. Derzeit `1`; ein anderer Wert wird abgewiesen. | | `name` | Pflicht | Name der Vorlage, nicht leer. Namen sind **nicht** eindeutig. | +| `seedSchluessel` | optional | **Nur für mitgelieferte Vorlagen.** Siehe unten — beim Import verfällt das Feld. | | `beschreibung` | optional | Ein bis zwei Sätze, was die Vorlage abdeckt. | | `kategorie` | optional | Freitext, gruppiert die Vorlagenliste (etwa `Onboarding`). | | `schlagworte` | optional | Liste nicht leerer Texte; Standard `[]`. | @@ -230,3 +231,33 @@ Das Feld `format` ist die Fassung des Formats, nicht die der Anwendung. Es gilt: der die Hälfte versteht und die andere Hälfte still verwirft, wäre schlimmer als ein Fehlschlag. Die Zahl steht als `FORMAT_VERSION` in `src/lib/vorlagen-format.ts` an genau einer Stelle. + +## `seedSchluessel` — nur für mitgelieferte Vorlagen + +Die sieben Vorlagen, die Laufzettel mitbringt, tragen in ihrer Datei ein zusätzliches Feld: + +```json +{ "format": 1, "name": "PC Neuinstallation", "seedSchluessel": "pc-neuinstallation" } +``` + +Daran erkennt der Seed sie wieder. Vorher ging das über den **Namen** — und wer eine mitgelieferte +Vorlage umbenannte, bekam beim nächsten Seed-Lauf eine zweite, samt doppeltem Schnellstart auf der +Geräteseite (#021). + +**Was der Schlüssel nicht tut:** + +- Er macht eine Vorlage **nicht unveränderlich.** Umbenennen, umbauen, archivieren — alles bleibt + erlaubt. Der Seed erkennt sie danach wieder und lässt sie in Ruhe. +- Er erlaubt dem Seed **nicht**, eine bearbeitete Vorlage zu überschreiben. Gibt es die Vorlage, legt + er nichts an und ändert nichts. Das war vorher so und bleibt so. +- Er ist **keine Kennung über Installationen hinweg.** Zwei Installationen haben für dieselbe + mitgelieferte Vorlage denselben Schlüssel, aber verschiedene `id`s — der Schlüssel sagt nur + „diese Zeile stammt aus dem Seed“. + +**Beim Import verfällt das Feld.** Wer eine mitgelieferte Vorlage exportiert und wieder einspielt, +bekommt eine gewöhnliche Vorlage ohne Schlüssel. Das ist Absicht: die Kopie soll nicht den Platz des +Originals einnehmen, sonst hielte der Seed sie dafür und würde das Original ein zweites Mal anlegen. +Der Export schreibt das Feld deshalb auch nicht — ein Feld, das beim Einlesen verfällt, verwirrt nur. + +Eigene Vorlagen brauchen den Schlüssel nicht und sollten ihn nicht bekommen. Er ist eindeutig, wo er +gesetzt ist; eine zweite Vorlage mit demselben Schlüssel weist die Datenbank ab. diff --git a/prisma/migrations/20261004120000_seed_schluessel/migration.sql b/prisma/migrations/20261004120000_seed_schluessel/migration.sql new file mode 100644 index 0000000..d5cd18d --- /dev/null +++ b/prisma/migrations/20261004120000_seed_schluessel/migration.sql @@ -0,0 +1,24 @@ +-- --------------------------------------------------------------------------- +-- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die +-- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes +-- (Archivsuche), die partiellen eindeutigen Indizes und die Trigger auf +-- veroeffentlichten Versionen: es kennt sie nicht, weil sie sich im Schema nicht +-- ausdruecken lassen. Diese Loeschbefehle sind bewusst gestrichen. +-- +-- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind – +-- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf. +-- --------------------------------------------------------------------------- + +-- Stabiler Schluessel fuer mitgelieferte Vorlagen (#021). +-- +-- Bisher erkannte der Seed seine sieben Vorlagen am Namen. Wurde eine +-- umbenannt, legte der naechste Lauf sie erneut an, und der Schnellstart stand +-- auf der Geraeteseite doppelt. Der Schluessel ueberlebt das Umbenennen. +ALTER TABLE "Vorlage" + ADD COLUMN "seedSchluessel" TEXT; + +-- Eindeutig nur dort, wo gesetzt: eigene Vorlagen tragen keinen Schluessel, und +-- ein gewoehnlicher eindeutiger Index wuerde an der zweiten davon scheitern. +CREATE UNIQUE INDEX "Vorlage_seedSchluessel_einmalig" + ON "Vorlage" ("seedSchluessel") + WHERE "seedSchluessel" IS NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9c2fc83..f2f1f73 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -368,17 +368,24 @@ model GeraetetypVorlage { // ---- Vorlagen -------------------------------------------------------------- model Vorlage { - id String @id @default(cuid()) - name String - beschreibung String? - kategorie String? - schlagworte String[] @default([]) + id String @id @default(cuid()) + name String + beschreibung String? + kategorie String? + schlagworte String[] @default([]) /// Nur im Modus DIENSTLEISTUNG: kundenspezifische Vorlage. Leer heisst /// "gilt fuer alle". - kundeId String? - archiviert Boolean @default(false) - erstelltAm DateTime @default(now()) - geaendertAm DateTime @default(now()) @updatedAt + kundeId String? + archiviert Boolean @default(false) + /// Kennung einer **mitgelieferten** Vorlage. Der Seed setzt sie und erkennt + /// seine sieben Vorlagen daran statt am Namen, der sich aendern darf (#021). + /// Eindeutig, wo gesetzt — erzwungen durch den partiellen eindeutigen Index + /// `Vorlage_seedSchluessel_einmalig`, den Prisma nicht ausdruecken kann. + /// Der Import setzt sie absichtlich **nicht**: eine eingespielte Kopie soll + /// nicht den Platz des Originals einnehmen. + seedSchluessel String? + erstelltAm DateTime @default(now()) + geaendertAm DateTime @default(now()) @updatedAt kunde Kunde? @relation(fields: [kundeId], references: [id], onDelete: Restrict) versionen VorlagenVersion[] diff --git a/prisma/seed-daten/vorlagen.ts b/prisma/seed-daten/vorlagen.ts index 7c3eee9..0cd6534 100644 --- a/prisma/seed-daten/vorlagen.ts +++ b/prisma/seed-daten/vorlagen.ts @@ -68,11 +68,61 @@ export type GesaeteVorlage = { readonly neu: boolean; }; +/** + * Sucht die mitgelieferte Vorlage – zuerst am Schluessel, dann am Namen. + * + * Der Schluessel ueberlebt ein Umbenennen, der Name nicht: genau daran legte der + * Seed eine umbenannte Vorlage ein zweites Mal an, und der Schnellstart stand + * auf der Geraeteseite doppelt (#021). Der Rueckfall auf den Namen bleibt, weil + * eine Installation von vor diesem Feld noch keinen Schluessel hat — ohne ihn + * bekaeme sie beim ersten Lauf danach genau die Dubletten, die das Feld + * verhindern soll. Wird sie so gefunden, traegt der Seed den Schluessel nach. + * + * Auch eine **archivierte** Vorlage gilt als vorhanden: eine zweite gleichen + * Namens danebenzustellen waere keine Hilfe, sondern eine Dublette in jeder + * Liste. Wer die Beispielvorlage zurueck will, holt sie aus dem Archiv. + */ +async function mitgelieferteFinden(db: PrismaClient, dokument: Austausch) { + const mitVersion = { + versionen: { + where: { status: VersionStatus.VEROEFFENTLICHT }, + orderBy: { version: "desc" as const }, + take: 1, + }, + }; + + if (dokument.seedSchluessel !== undefined) { + const amSchluessel = await db.vorlage.findFirst({ + where: { seedSchluessel: dokument.seedSchluessel }, + include: mitVersion, + }); + if (amSchluessel) return amSchluessel; + } + + const amNamen = await db.vorlage.findFirst({ + // Nur eine Zeile **ohne** Schluessel darf hier zugeordnet werden. Sonst + // koennte eine fremde Vorlage, die zufaellig so heisst wie die mitgelieferte, + // deren Schluessel bekommen - und der naechste Lauf wuerde sie fuer das + // Original halten. + where: { name: dokument.name, kundeId: null, seedSchluessel: null }, + include: mitVersion, + }); + if (amNamen && dokument.seedSchluessel !== undefined) { + await db.vorlage.update({ + where: { id: amNamen.id }, + data: { seedSchluessel: dokument.seedSchluessel }, + }); + console.log(` Vorlage „${amNamen.name}“: Schlüssel ${dokument.seedSchluessel} nachgetragen`); + } + return amNamen; +} + /** * Legt die Vorlagen als veroeffentlichte Version 1 an. * - * Idempotent ueber den **Namen**: gibt es die Vorlage schon, bleibt sie - * unangetastet — auch dann, wenn jemand sie inzwischen bearbeitet hat. Ein + * Idempotent ueber den **Schluessel** (Rueckfall: Name, siehe + * `mitgelieferteFinden`): gibt es die Vorlage schon, bleibt sie unangetastet — + * auch dann, wenn jemand sie inzwischen bearbeitet oder umbenannt hat. Ein * zweiter Seed-Lauf darf die Arbeit eines Redakteurs nicht zurueckdrehen, und * eine veroeffentlichte Version wird ohnehin nie veraendert. */ @@ -82,19 +132,7 @@ export async function vorlagenSaeen(db: PrismaClient): Promise { await namen( `SELECT indexname AS name FROM pg_indexes WHERE schemaname='public' AND indexdef LIKE '%WHERE%'`, ), - ).toEqual(["Kunde_eigene_organisation_einmalig", "VorlagenVersion_ein_entwurf"]); + ).toEqual([ + "Kunde_eigene_organisation_einmalig", + "Vorlage_seedSchluessel_einmalig", + "VorlagenVersion_ein_entwurf", + ]); }); it("hat alle Prüfbedingungen", async () => { diff --git a/tests/datenmodell.test.ts b/tests/datenmodell.test.ts index 3ba1657..57a988b 100644 --- a/tests/datenmodell.test.ts +++ b/tests/datenmodell.test.ts @@ -321,6 +321,27 @@ describe.runIf(datenbankVerfuegbar)("Datenmodell: Regeln in der Datenbank", () = }); }); + describe("Vorlage", () => { + it("haelt den Seed-Schluessel eindeutig, wo er gesetzt ist", async () => { + await testPrisma.vorlage.create({ + data: { name: "PC Neuinstallation", seedSchluessel: "pc-neuinstallation" }, + }); + await expect( + testPrisma.vorlage.create({ + data: { name: "Anders benannt", seedSchluessel: "pc-neuinstallation" }, + }), + ).rejects.toThrow(/Vorlage_seedSchluessel_einmalig/); + }); + + it("laesst beliebig viele Vorlagen ohne Seed-Schluessel zu", async () => { + // Der Index ist partiell, sonst waere die zweite eigene Vorlage eine + // Dublette von null (#021). + await testPrisma.vorlage.create({ data: { name: "Eigene A" } }); + await testPrisma.vorlage.create({ data: { name: "Eigene B" } }); + expect(await testPrisma.vorlage.count({ where: { seedSchluessel: null } })).toBe(2); + }); + }); + describe("Vorlagen-Version", () => { it("haelt die Versionsnummer je Vorlage eindeutig", async () => { const vorlage = await testPrisma.vorlage.create({ data: { name: "PC Neuinstallation" } }); diff --git a/tests/seed.test.ts b/tests/seed.test.ts index 7b27cc2..096aef2 100644 --- a/tests/seed.test.ts +++ b/tests/seed.test.ts @@ -342,6 +342,59 @@ describe.runIf(datenbankVerfuegbar)("Seed gegen die Datenbank", () => { expect(await testPrisma.vorlagenVersion.count({ where: { vorlageId: erste.vorlageId } })).toBe(2); }); + it("erkennt eine umbenannte Vorlage am Schluessel und legt keine zweite an", async () => { + // Der Fall, fuer den das Feld da ist (#021). Vorher erkannte der Seed seine + // Vorlagen am Namen: wer eine umbenannte, bekam beim naechsten Lauf eine + // zweite - und der Schnellstart stand auf der Geraeteseite doppelt. + const [erste] = await vorlagenSaeen(testPrisma); + if (!erste) throw new Error("Der Seed hat keine Vorlage angelegt."); + await testPrisma.vorlage.update({ + where: { id: erste.vorlageId }, + data: { name: "Von Hand umbenannt" }, + }); + + const zweit = await vorlagenSaeen(testPrisma); + expect(zweit.every((eintrag) => !eintrag.neu)).toBe(true); + expect(await testPrisma.vorlage.count()).toBe(7); + // Und der Name bleibt der, den jemand gewaehlt hat. + const nachher = await testPrisma.vorlage.findUniqueOrThrow({ where: { id: erste.vorlageId } }); + expect(nachher.name).toBe("Von Hand umbenannt"); + }); + + it("legt zu einer umbenannten Vorlage keine zweite Schnellstart-Zeile an", async () => { + const gesaet = await vorlagenSaeen(testPrisma); + await typVorgabenSaeen(testPrisma, gesaet); + const vorher = await testPrisma.geraetetypVorlage.count(); + + // „PC Neuinstallation“ steht im Schnellstart fuer PC und NOTEBOOK - genau die + // Zeilen, die sich vorher verdoppelt haben. + const pc = gesaet.find((eintrag) => eintrag.name === "PC Neuinstallation"); + if (!pc) throw new Error("Die PC-Vorlage fehlt im Seed."); + await testPrisma.vorlage.update({ + where: { id: pc.vorlageId }, + data: { name: "Arbeitsplatz neu aufsetzen" }, + }); + + const nochmal = await vorlagenSaeen(testPrisma); + expect(await typVorgabenSaeen(testPrisma, nochmal)).toBe(0); + expect(await testPrisma.geraetetypVorlage.count()).toBe(vorher); + }); + + it("traegt den Schluessel bei einer Installation nach, die ihn noch nicht hat", async () => { + // Eine Installation von vor diesem Feld: die Zeilen stehen, der Schluessel + // fehlt. Ohne den Rueckfall auf den Namen bekaeme sie beim ersten Lauf + // danach genau die Dubletten, die das Feld verhindern soll. + const gesaet = await vorlagenSaeen(testPrisma); + await testPrisma.vorlage.updateMany({ data: { seedSchluessel: null } }); + expect(await testPrisma.vorlage.count({ where: { seedSchluessel: null } })).toBe(7); + + const zweit = await vorlagenSaeen(testPrisma); + expect(zweit.every((eintrag) => !eintrag.neu)).toBe(true); + expect(await testPrisma.vorlage.count()).toBe(7); + expect(await testPrisma.vorlage.count({ where: { seedSchluessel: null } })).toBe(0); + expect(zweit.map((e) => e.vorlageId).sort()).toEqual(gesaet.map((e) => e.vorlageId).sort()); + }); + it("verknuepft die Schnellstart-Vorgaben mit den richtigen Vorlagen", async () => { const gesaet = await vorlagenSaeen(testPrisma); const angelegt = await typVorgabenSaeen(testPrisma, gesaet); diff --git a/tests/vorlagen-austausch.test.ts b/tests/vorlagen-austausch.test.ts index 54ce389..19a3831 100644 --- a/tests/vorlagen-austausch.test.ts +++ b/tests/vorlagen-austausch.test.ts @@ -354,6 +354,24 @@ describe.runIf(datenbankVerfuegbar)("Vorlagen-Austausch", () => { expect(await testPrisma.vorlagenVersion.count({ where: { vorlageId } })).toBe(1); }); + it("gibt einer eingespielten Kopie keinen Seed-Schluessel", async () => { + // Sonst hielte der Seed die Kopie fuer das Original und legte das Original + // ein zweites Mal an (#021). Geprueft wird mit einem Dokument, das den + // Schluessel ausdruecklich mitbringt - ein Angreifer oder ein Versehen + // koennte ihn ja hineinschreiben. + const redakteur = await redakteurAnlegen(); + const quelle = await beispielVersionAnlegen(); + const austausch = await vorlageExportieren(quelle); + + const { vorlageId } = await vorlageImportieren( + { ...austausch, seedSchluessel: "pc-neuinstallation" }, + redakteur.id, + ); + + const kopie = await testPrisma.vorlage.findUniqueOrThrow({ where: { id: vorlageId } }); + expect(kopie.seedSchluessel).toBeNull(); + }); + it("leitet die Positionen aus der Reihenfolge der Listen ab", async () => { const redakteur = await redakteurAnlegen(); const { versionId } = await vorlageImportieren( @@ -437,6 +455,19 @@ describe.runIf(datenbankVerfuegbar)("Vorlagen-Austausch", () => { }); }); + describe("Seed-Schluessel", () => { + it("steht in keinem Export – auch nicht bei einer mitgelieferten Vorlage", async () => { + // Ein Feld, das beim Einlesen verfaellt, gehoert nicht in die Datei: wer es + // dort sieht, haelt es fuer uebertragbar. + const quelle = await beispielVersionAnlegen(); + await testPrisma.vorlage.updateMany({ data: { seedSchluessel: "pc-neuinstallation" } }); + + const austausch = await vorlageExportieren(quelle); + expect(austausch).not.toHaveProperty("seedSchluessel"); + expect(JSON.stringify(austausch)).not.toContain("pc-neuinstallation"); + }); + }); + describe("Rundlauf", () => { it("ergibt ein inhaltsgleiches Dokument – bis auf die Version", async () => { const redakteur = await redakteurAnlegen();