feat(bausteine): Abschnitte, die mehrere Vorlagen teilen
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

"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
This commit is contained in:
Florian Wilde
2026-10-04 19:06:31 +02:00
parent b688e2ebc8
commit 0b5138d8bb
24 changed files with 2862 additions and 46 deletions
+552
View File
@@ -0,0 +1,552 @@
import { beforeEach, describe, expect, it } from "vitest";
import { VersionStatus } from "@/generated/prisma/enums";
import {
BausteinFehler,
bausteinAnlegen,
bausteinArchivieren,
bausteinBearbeiten,
bausteinEntwurfHolenOderErzeugen,
bausteinEntwurfSpeichern,
bausteinEntwurfVerwerfen,
bausteinVersionVeroeffentlichen,
} from "@/server/services/bausteine";
import {
bausteinHolen,
bausteinListe,
bausteinVerwendungen,
einsetzbareBausteine,
} from "@/server/queries/bausteine";
import {
VorlagenFehler,
entwurfHolenOderErzeugen,
entwurfSpeichern,
versionVeroeffentlichen,
vorlageAnlegen,
} from "@/server/services/vorlagen";
import { vorlageExportieren, vorlageImportieren } from "@/server/services/vorlagen-austausch";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
/**
* Wiederverwendbare Bausteine (#030).
*
* Die eine Zusage, die alles traegt: **eine Baustein-Aenderung fasst nichts
* Veroeffentlichtes an.** Ein Entwurf verweist, eine veroeffentlichte Version
* traegt Kopien — und ein Lauf sieht deshalb weiterhin konkrete Schritte.
*/
async function grundbestand() {
const admin = await testPrisma.benutzer.create({
data: { email: "admin@firma.example", name: "Admin", rolle: "ADMIN" },
});
return { adminId: admin.id };
}
/** Ein veroeffentlichter Baustein mit den uebergebenen Schritttiteln. */
async function bausteinMitSchritten(
name: string,
titel: string,
schritte: readonly string[],
adminId: string,
): Promise<{ bausteinId: string; versionId: string }> {
const { bausteinId, versionId } = await bausteinAnlegen({ name }, adminId);
await bausteinEntwurfSpeichern(versionId, {
titel,
schritte: schritte.map((schritt) => ({ titel: schritt })),
});
await bausteinVersionVeroeffentlichen(versionId, null, adminId);
return { bausteinId, versionId };
}
/** Eine Vorlage mit einem Baustein-Abschnitt und einem eigenen, veroeffentlicht. */
async function vorlageMitBaustein(
name: string,
bausteinVersionId: string,
adminId: string,
): Promise<{ vorlageId: string; versionId: string }> {
const { vorlage } = await vorlageAnlegen({ name }, adminId);
const entwurf = await entwurfHolenOderErzeugen(vorlage.id, adminId);
const versionId = entwurf.id;
await entwurfSpeichern(versionId, {
variablen: [],
abschnitte: [
{ titel: "Eigener Abschnitt", schritte: [{ titel: "Eigener Schritt" }] },
{ titel: "wird ersetzt", bausteinVersionId, schritte: [] },
],
});
await versionVeroeffentlichen(versionId, null, adminId);
return { vorlageId: vorlage.id, versionId };
}
async function schritteVon(versionId: string): Promise<readonly string[]> {
const abschnitte = await testPrisma.vorlagenAbschnitt.findMany({
where: { versionId },
orderBy: { position: "asc" },
include: { schritte: { orderBy: { position: "asc" } } },
});
return abschnitte.flatMap((abschnitt) => [
`# ${abschnitt.titel}`,
...abschnitt.schritte.map((schritt) => schritt.titel),
]);
}
describe.runIf(datenbankVerfuegbar)("Bausteine", () => {
beforeEach(async () => {
await datenbankLeeren();
});
describe("Pflegen", () => {
it("legt einen Baustein mit Entwurf in Version 1 an", async () => {
const { adminId } = await grundbestand();
const { bausteinId, versionId } = await bausteinAnlegen({ name: "Datensicherung prüfen" }, adminId);
const ansicht = await bausteinHolen(bausteinId);
expect(ansicht?.name).toBe("Datensicherung prüfen");
expect(ansicht?.versionen).toHaveLength(1);
expect(ansicht?.versionen[0]).toMatchObject({
id: versionId,
version: 1,
status: VersionStatus.ENTWURF,
// Der Titel beginnt als Name – in den meisten Fällen stimmt das.
titel: "Datensicherung prüfen",
});
});
it("hält Namen eindeutig, mit einem Satz statt eines Datenbankfehlers", async () => {
const { adminId } = await grundbestand();
await bausteinAnlegen({ name: "Datensicherung prüfen" }, adminId);
await expect(bausteinAnlegen({ name: "Datensicherung prüfen" }, adminId)).rejects.toThrow(
/gibt schon einen Baustein/,
);
});
it("lässt je Baustein höchstens einen Entwurf zu – auch an der Service-Schicht vorbei", async () => {
const { adminId } = await grundbestand();
const { bausteinId } = await bausteinAnlegen({ name: "Einmal" }, adminId);
await expect(
testPrisma.bausteinVersion.create({
data: { bausteinId, version: 2, status: VersionStatus.ENTWURF, titel: "zweiter" },
}),
).rejects.toThrow(/BausteinVersion_ein_entwurf/);
});
it("weist einen Baustein ohne Schritte beim Veröffentlichen ab", async () => {
const { adminId } = await grundbestand();
const { versionId } = await bausteinAnlegen({ name: "Leer" }, adminId);
await expect(bausteinVersionVeroeffentlichen(versionId, null, adminId)).rejects.toThrow(
/ohne Schritte/,
);
});
it("macht aus einer veröffentlichten Version einen Entwurf als tiefe Kopie", async () => {
const { adminId } = await grundbestand();
const { bausteinId } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft", "Rücksicherung getestet"],
adminId,
);
const entwurfId = await bausteinEntwurfHolenOderErzeugen(bausteinId, adminId);
const ansicht = await bausteinHolen(bausteinId);
expect(ansicht?.versionen).toHaveLength(2);
const entwurf = ansicht?.versionen.find((version) => version.id === entwurfId);
expect(entwurf?.version).toBe(2);
expect(entwurf?.schritte.map((schritt) => schritt.titel)).toEqual([
"Sicherung läuft",
"Rücksicherung getestet",
]);
// Zweiter Aufruf gibt denselben Entwurf, nicht einen dritten.
expect(await bausteinEntwurfHolenOderErzeugen(bausteinId, adminId)).toBe(entwurfId);
});
it("lässt eine veröffentlichte Version nicht ändern – der Trigger hält das fest", async () => {
const { adminId } = await grundbestand();
const { versionId } = await bausteinMitSchritten("Fest", "Fest", ["Eins"], adminId);
await expect(
bausteinEntwurfSpeichern(versionId, { titel: "anders", schritte: [{ titel: "Zwei" }] }),
).rejects.toThrow(/unveränderlich/);
// Und auch an der Service-Schicht vorbei nicht.
await expect(
testPrisma.bausteinSchritt.create({ data: { versionId, titel: "Geschmuggelt" } }),
).rejects.toThrow(/unveraenderlich/);
});
it("lässt eine veröffentlichte Version nicht zum Entwurf zurückmachen", async () => {
const { adminId } = await grundbestand();
const { versionId } = await bausteinMitSchritten("Einbahn", "Einbahn", ["Eins"], adminId);
await expect(
testPrisma.bausteinVersion.update({
where: { id: versionId },
data: { status: VersionStatus.ENTWURF },
}),
).rejects.toThrow(/nicht wieder zum Entwurf/);
});
it("verwirft einen Entwurf nur, wenn es noch eine veröffentlichte Version gibt", async () => {
const { adminId } = await grundbestand();
const { bausteinId, versionId } = await bausteinAnlegen({ name: "Einzig" }, adminId);
await expect(bausteinEntwurfVerwerfen(versionId, adminId)).rejects.toThrow(/einzige Version/);
await bausteinEntwurfSpeichern(versionId, { titel: "Einzig", schritte: [{ titel: "Eins" }] });
await bausteinVersionVeroeffentlichen(versionId, null, adminId);
const zweiter = await bausteinEntwurfHolenOderErzeugen(bausteinId, adminId);
await bausteinEntwurfVerwerfen(zweiter, adminId);
expect((await bausteinHolen(bausteinId))?.versionen).toHaveLength(1);
});
it("benennt um und protokolliert", async () => {
const { adminId } = await grundbestand();
const { bausteinId } = await bausteinAnlegen({ name: "Alt" }, adminId);
await bausteinBearbeiten(bausteinId, { name: "Neu", beschreibung: "Erklärung" }, adminId);
expect((await bausteinHolen(bausteinId))?.name).toBe("Neu");
const eintraege = await testPrisma.protokoll.findMany({
where: { entitaet: "Baustein", entitaetId: bausteinId },
orderBy: { erstelltAm: "asc" },
select: { aktion: true },
});
expect(eintraege.map((eintrag) => eintrag.aktion)).toEqual([
"Baustein angelegt",
"Name und Beschreibung geändert",
]);
});
});
describe("Einsetzen und Auflösen", () => {
it("setzt die Schritte des Bausteins beim Veröffentlichen ein", async () => {
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft", "Rücksicherung getestet"],
adminId,
);
const { versionId } = await vorlageMitBaustein("PC Neuinstallation", bausteinVersionId, adminId);
expect(await schritteVon(versionId)).toEqual([
"# Eigener Abschnitt",
"Eigener Schritt",
// Titel und Schritte kommen aus dem Baustein, nicht aus dem Entwurf.
"# Datensicherung",
"Sicherung läuft",
"Rücksicherung getestet",
]);
});
it("lässt den Verweis als Herkunft stehen", async () => {
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Herkunft",
"Herkunft",
["Eins"],
adminId,
);
const { versionId } = await vorlageMitBaustein("Mit Herkunft", bausteinVersionId, adminId);
const abschnitt = await testPrisma.vorlagenAbschnitt.findFirstOrThrow({
where: { versionId, bausteinVersionId: { not: null } },
include: { bausteinVersion: { include: { baustein: true } } },
});
expect(abschnitt.bausteinVersion?.baustein.name).toBe("Herkunft");
});
it("weist einen Verweis auf einen Entwurf ab", async () => {
// Einen Entwurf einzusetzen hieße, beim Veröffentlichen etwas zu kopieren,
// das sich danach noch ändert.
const { adminId } = await grundbestand();
const { versionId: entwurfId } = await bausteinAnlegen({ name: "Noch Entwurf" }, adminId);
const { vorlage } = await vorlageAnlegen({ name: "Will einen Entwurf" }, adminId);
const versionId = (await entwurfHolenOderErzeugen(vorlage.id, adminId)).id;
await expect(
entwurfSpeichern(versionId, {
variablen: [],
abschnitte: [{ titel: "x", bausteinVersionId: entwurfId, schritte: [] }],
}),
).rejects.toThrow(/veroeffentlichte Baustein-Version/);
});
it("weist das Veröffentlichen ab, wenn der Baustein archiviert wurde", async () => {
const { adminId } = await grundbestand();
const { bausteinId, versionId: bausteinVersionId } = await bausteinMitSchritten(
"Weggelegt",
"Weggelegt",
["Eins"],
adminId,
);
const { vorlage } = await vorlageAnlegen({ name: "Mit weggelegtem" }, adminId);
const versionId = (await entwurfHolenOderErzeugen(vorlage.id, adminId)).id;
await entwurfSpeichern(versionId, {
variablen: [],
abschnitte: [{ titel: "x", bausteinVersionId, schritte: [] }],
});
// Archivieren geht hier nicht: der Baustein steht in einem Entwurf.
await expect(bausteinArchivieren(bausteinId, true, adminId)).rejects.toThrow(/Entwurf/);
// An der Service-Schicht vorbei archiviert – dann greift die Prüfung beim
// Veröffentlichen.
await testPrisma.baustein.update({ where: { id: bausteinId }, data: { archiviert: true } });
await expect(versionVeroeffentlichen(versionId, null, adminId)).rejects.toThrow(/archiviert/);
});
it("hält im Protokoll fest, welcher Baustein in welcher Fassung eingesetzt wurde", async () => {
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Eins"],
adminId,
);
const { versionId } = await vorlageMitBaustein("Mit Protokoll", bausteinVersionId, adminId);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { entitaet: "VorlagenVersion", entitaetId: versionId, aktion: "Version veröffentlicht" },
});
expect(eintrag.nachher).toMatchObject({
bausteine: [{ name: "Datensicherung prüfen", version: 1 }],
});
});
});
describe("Unveränderlichkeit über Baustein-Änderungen hinweg", () => {
it("ändert keine veröffentlichte Vorlagenversion, wenn der Baustein neu veröffentlicht wird", async () => {
const { adminId } = await grundbestand();
const { bausteinId, versionId: ersteBausteinVersion } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft"],
adminId,
);
const { versionId } = await vorlageMitBaustein("PC Neuinstallation", ersteBausteinVersion, adminId);
const vorher = await schritteVon(versionId);
// Zweite Fassung des Bausteins, inhaltlich ganz anders.
const zweiterEntwurf = await bausteinEntwurfHolenOderErzeugen(bausteinId, adminId);
await bausteinEntwurfSpeichern(zweiterEntwurf, {
titel: "Datensicherung, neu gedacht",
schritte: [{ titel: "Ganz anders" }, { titel: "Und noch einer" }],
});
await bausteinVersionVeroeffentlichen(zweiterEntwurf, "Umgebaut", adminId);
expect(await schritteVon(versionId)).toEqual(vorher);
});
it("lässt einen laufenden Lauf unberührt", async () => {
const { adminId } = await grundbestand();
const kunde = await testPrisma.kunde.create({
data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true },
});
const { bausteinId, versionId: ersteBausteinVersion } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft"],
adminId,
);
const { versionId } = await vorlageMitBaustein("PC Neuinstallation", ersteBausteinVersion, adminId);
const lauf = await testPrisma.lauf.create({
data: { vorlagenVersionId: versionId, titel: "Ein Lauf", kundeId: kunde.id },
});
const zweiterEntwurf = await bausteinEntwurfHolenOderErzeugen(bausteinId, adminId);
await bausteinEntwurfSpeichern(zweiterEntwurf, {
titel: "Anders",
schritte: [{ titel: "Ganz anders" }],
});
await bausteinVersionVeroeffentlichen(zweiterEntwurf, null, adminId);
const nachher = await testPrisma.lauf.findUniqueOrThrow({
where: { id: lauf.id },
include: {
vorlagenVersion: {
include: { abschnitte: { include: { schritte: true }, orderBy: { position: "asc" } } },
},
},
});
const titel = nachher.vorlagenVersion.abschnitte.flatMap((abschnitt) =>
abschnitt.schritte.map((schritt) => schritt.titel),
);
expect(titel).toContain("Sicherung läuft");
expect(titel).not.toContain("Ganz anders");
});
it("nimmt die neue Fassung in die nächste veröffentlichte Vorlagenversion", async () => {
const { adminId } = await grundbestand();
const { bausteinId, versionId: ersteBausteinVersion } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft"],
adminId,
);
const { vorlageId } = await vorlageMitBaustein("PC Neuinstallation", ersteBausteinVersion, adminId);
const zweiterEntwurf = await bausteinEntwurfHolenOderErzeugen(bausteinId, adminId);
await bausteinEntwurfSpeichern(zweiterEntwurf, {
titel: "Datensicherung",
schritte: [{ titel: "Sicherung läuft" }, { titel: "Rücksicherung getestet" }],
});
await bausteinVersionVeroeffentlichen(zweiterEntwurf, null, adminId);
const neueFassung = await testPrisma.bausteinVersion.findFirstOrThrow({
where: { bausteinId, status: VersionStatus.VEROEFFENTLICHT },
orderBy: { version: "desc" },
});
// Neue Vorlagenversion, die auf die neue Fassung zeigt.
const zweiteVorlagenVersion = (await entwurfHolenOderErzeugen(vorlageId, adminId)).id;
await entwurfSpeichern(zweiteVorlagenVersion, {
variablen: [],
abschnitte: [
{ titel: "Eigener Abschnitt", schritte: [{ titel: "Eigener Schritt" }] },
{ titel: "x", bausteinVersionId: neueFassung.id, schritte: [] },
],
});
await versionVeroeffentlichen(zweiteVorlagenVersion, null, adminId);
expect(await schritteVon(zweiteVorlagenVersion)).toEqual([
"# Eigener Abschnitt",
"Eigener Schritt",
"# Datensicherung",
"Sicherung läuft",
"Rücksicherung getestet",
]);
});
});
describe("Löschen und Verwendungen", () => {
it("lässt eine Baustein-Version nicht löschen, auf die eine Vorlage zeigt", async () => {
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Benutzt",
"Benutzt",
["Eins"],
adminId,
);
await vorlageMitBaustein("Benutzt ihn", bausteinVersionId, adminId);
await expect(testPrisma.bausteinVersion.delete({ where: { id: bausteinVersionId } })).rejects.toThrow();
});
it("nennt, wo ein Baustein steckt – mit Vorlage, Version und Fassung", async () => {
const { adminId } = await grundbestand();
const { bausteinId, versionId: bausteinVersionId } = await bausteinMitSchritten(
"Überall",
"Überall",
["Eins"],
adminId,
);
await vorlageMitBaustein("Vorlage A", bausteinVersionId, adminId);
await vorlageMitBaustein("Vorlage B", bausteinVersionId, adminId);
const verwendungen = await bausteinVerwendungen(bausteinId);
expect(verwendungen).toHaveLength(2);
expect(verwendungen.map((eintrag) => eintrag.vorlageName)).toEqual(["Vorlage A", "Vorlage B"]);
expect(verwendungen[0]).toMatchObject({
vorlagenVersion: 1,
status: VersionStatus.VEROEFFENTLICHT,
bausteinVersion: 1,
});
});
it("zählt in der Liste Verwendungen und Schritte", async () => {
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Gezählt",
"Gezählt",
["Eins", "Zwei"],
adminId,
);
await vorlageMitBaustein("Benutzt ihn", bausteinVersionId, adminId);
const liste = await bausteinListe();
expect(liste).toHaveLength(1);
expect(liste[0]).toMatchObject({
name: "Gezählt",
veroeffentlichteVersion: 1,
hatEntwurf: false,
anzahlSchritte: 2,
verwendungen: 1,
});
});
it("bietet zum Einsetzen nur veröffentlichte Fassungen nicht archivierter Bausteine", async () => {
const { adminId } = await grundbestand();
await bausteinMitSchritten("Brauchbar", "Brauchbar", ["Eins"], adminId);
await bausteinAnlegen({ name: "Nur Entwurf" }, adminId);
const { bausteinId } = await bausteinMitSchritten("Weggelegt", "Weggelegt", ["Eins"], adminId);
await bausteinArchivieren(bausteinId, true, adminId);
const einsetzbar = await einsetzbareBausteine();
expect(einsetzbar.map((eintrag) => eintrag.name)).toEqual(["Brauchbar"]);
expect(einsetzbar[0]).toMatchObject({ version: 1, anzahlSchritte: 1, titel: "Brauchbar" });
});
});
describe("Austausch", () => {
it("exportiert die aufgelösten Schritte und keinen Verweis", async () => {
// Eine Kennung aus dieser Installation bedeutet in einer anderen nichts.
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft"],
adminId,
);
const { versionId } = await vorlageMitBaustein("Zum Ausfuhren", bausteinVersionId, adminId);
const austausch = await vorlageExportieren(versionId);
expect(JSON.stringify(austausch)).not.toContain(bausteinVersionId);
const abschnitte = austausch.struktur.abschnitte.map((abschnitt) => ({
titel: abschnitt.titel,
schritte: abschnitt.schritte.map((schritt) => schritt.titel),
}));
expect(abschnitte).toEqual([
{ titel: "Eigener Abschnitt", schritte: ["Eigener Schritt"] },
{ titel: "Datensicherung", schritte: ["Sicherung läuft"] },
]);
});
it("übernimmt beim Import keinen mitgeschickten Verweis", async () => {
const { adminId } = await grundbestand();
const { versionId: bausteinVersionId } = await bausteinMitSchritten(
"Datensicherung prüfen",
"Datensicherung",
["Sicherung läuft"],
adminId,
);
const { versionId } = await vorlageMitBaustein("Quelle", bausteinVersionId, adminId);
const austausch = await vorlageExportieren(versionId);
const { versionId: kopie } = await vorlageImportieren(
{
...austausch,
name: "Kopie",
struktur: {
...austausch.struktur,
abschnitte: austausch.struktur.abschnitte.map((abschnitt) => ({
...abschnitt,
bausteinVersionId,
})),
},
},
adminId,
);
const abschnitte = await testPrisma.vorlagenAbschnitt.findMany({ where: { versionId: kopie } });
expect(abschnitte.every((abschnitt) => abschnitt.bausteinVersionId === null)).toBe(true);
// Die Schritte sind trotzdem alle da – sie stehen in der Datei.
expect(await schritteVon(kopie)).toEqual([
"# Eigener Abschnitt",
"Eigener Schritt",
"# Datensicherung",
"Sicherung läuft",
]);
});
});
it("wirft eigene Fehlerklassen, damit die Actions sie unterscheiden können", async () => {
const { adminId } = await grundbestand();
await expect(bausteinAnlegen({ name: " " }, adminId)).rejects.toThrow(BausteinFehler);
await expect(versionVeroeffentlichen("gibt-es-nicht", null, adminId)).rejects.toThrow(VorlagenFehler);
});
});