Aus einer Vorlage wird hier der PowerShell-Befehl, den ein Techniker kopiert und
auf einem Domaenencontroller ausfuehrt. Entsprechend dicht ist die Abdeckung:
810 Tests, und die Maskierung wird **ausgefuehrt** geprueft, nicht gelesen.
- Platzhalter aus Geraet, Lauf, Mitarbeiter und Abteilung, dazu die Variablen
der Vorlage; ein unbekannter bleibt sichtbar stehen
- `{{ou}}` ist der Eltern-Container des DN — genau der Pfad fuer New-ADUser
- Namensregeln fuer upn, sam, mail und hostname mit Transliteration, `{v}`,
`{nnn}` und `{kundenkuerzel}`; Kundenregeln ueberlagern die globalen feldweise
- Maskierung je Shell, Skript-Kopie je Abschnitt mit Kommentarzeilen
- `{{generiertes_passwort}}`: 20 Zeichen, bei jeder Anzeige neu, nirgends
gespeichert; im Druck `[Passwort – nicht gespeichert]`
Der unabhaengige Review fand drei Blocker. Der schwerste war eine
Codeausfuehrung: PowerShell liest auch `“`, `”` und `„` als Begrenzer einer
Zeichenkette, die Maskierung kannte nur das ASCII-Zeichen. Ein Abteilungsname
aus Word — Word wandelt `"` von selbst um — reichte. Gefunden hat es der Review
nicht durch Lesen, sondern indem er sich PowerShell in einen Container geholt
und die Markierungsdatei hat entstehen lassen. Der Test dagegen hatte sich still
uebersprungen; er nimmt jetzt selbst das Abbild und bricht in CI ab.
Dazu: zwei verschiedene Passwoerter in einer Antwort (Anzeige und Skript wurden
getrennt gefuellt), eine Vorlagenvariable, die `{{techniker}}` und `{{kunde}}`
ueberschreiben konnte, und die tragende Annahme der Maskierung — dass der
Platzhalter in Anfuehrungszeichen steht —, die niemand geprueft hat.
Schliesst #012
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
424 lines
17 KiB
TypeScript
424 lines
17 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
||
import { Betriebsmodus } from "@/generated/prisma/enums";
|
||
import {
|
||
befehlFuellen,
|
||
befehleDesLaufs,
|
||
skriptAusFertigen,
|
||
namenVorschlagen,
|
||
namensregelnFuer,
|
||
skriptFuerAbschnitt,
|
||
} from "@/server/services/befehle";
|
||
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
|
||
|
||
/**
|
||
* Fertige Befehle und Namensvorschlaege (#012).
|
||
*
|
||
* Hier laufen Werte, Ersetzung und Maskierung zusammen. Was hier falsch ist,
|
||
* fuehrt ein Techniker auf einem Domaenencontroller aus — entsprechend steht
|
||
* jede Zusage des Auftrags einzeln als Test.
|
||
*/
|
||
|
||
const WERTE = {
|
||
mitarbeiter: 'Anna "Anni" O\u2019Neill',
|
||
vorname: "Anna",
|
||
ou: "OU=Buchhaltung,DC=firma,DC=de",
|
||
};
|
||
|
||
describe("Einen Befehl füllen", () => {
|
||
it("maskiert den eingesetzten Wert, nicht den Befehl", () => {
|
||
/*
|
||
* Die Reihenfolge ist der Kern: maskiert wird der **Wert**, nicht der
|
||
* fertige Befehl. Sonst maskierte man die Anfuehrungszeichen des Befehls
|
||
* gleich mit, und aus `-Name "…"` wuerde `-Name ""…""`.
|
||
*/
|
||
const fertig = befehlFuellen(
|
||
{ titel: "Konto anlegen", shell: "powershell", befehl: 'New-ADUser -Name "{{mitarbeiter}}"' },
|
||
WERTE,
|
||
);
|
||
/*
|
||
* Das typografische Apostroph wird dabei zum geraden: PowerShell liest
|
||
* `U+2019` als Begrenzer einer `'…'`-Zeichenkette, es laesst sich also nicht
|
||
* als Text durchreichen. Gegen PowerShell 7 nachgemessen — ohne diese
|
||
* Angleichung fuehrt ein Wert mit `”` fremden Code aus.
|
||
*/
|
||
expect(fertig.befehl).toBe(`New-ADUser -Name "Anna ""Anni"" O'Neill"`);
|
||
expect(fertig.fehler).toBeUndefined();
|
||
});
|
||
|
||
it("hält einen Wert mit $() vom Ausführen ab", () => {
|
||
// Akzeptanzkriterium: ein Wert mit `"` und Backtick darf den Befehl nicht
|
||
// brechen. `$(…)` ist der gefaehrlichere Verwandte.
|
||
const fertig = befehlFuellen(
|
||
{ titel: "x", shell: "powershell", befehl: 'Write-Host "{{vorname}}"' },
|
||
{ vorname: 'a$(Remove-Item C:\\ -Recurse)b`c"d' },
|
||
);
|
||
/*
|
||
* Geprueft wird die **Maskierung**, nicht das Wort: `$(Remove-Item…` steht
|
||
* danach noch da, aber mit einem Backtick davor — PowerShell liest es dann
|
||
* als Text und nicht als Befehlsersetzung. Jedes `$` im Ergebnis muss einen
|
||
* Backtick vor sich haben.
|
||
*/
|
||
for (const treffer of fertig.befehl.matchAll(/\$/gu)) {
|
||
const davor = fertig.befehl.charAt((treffer.index ?? 0) - 1);
|
||
expect(davor, `unmaskiertes $ an Stelle ${treffer.index}`).toBe("`");
|
||
}
|
||
expect(fertig.befehl).toContain("`$(Remove-Item");
|
||
expect(fertig.befehl.startsWith('Write-Host "')).toBe(true);
|
||
expect(fertig.befehl.endsWith('"')).toBe(true);
|
||
});
|
||
|
||
it("maskiert den Titel nicht für die Shell", () => {
|
||
// Der Titel wird gelesen, nicht ausgefuehrt. Maskiert stuende er mit
|
||
// Backticks auf dem Bildschirm.
|
||
const fertig = befehlFuellen(
|
||
{ titel: "Konto für {{mitarbeiter}}", shell: "powershell", befehl: "Get-Date" },
|
||
WERTE,
|
||
);
|
||
// Der Titel wird **nicht** maskiert und behaelt deshalb das typografische
|
||
// Apostroph – er wird gelesen, nicht ausgefuehrt.
|
||
expect(fertig.titel).toBe('Konto für Anna "Anni" O\u2019Neill');
|
||
});
|
||
|
||
it("lässt einen unbekannten Platzhalter stehen und meldet ihn", () => {
|
||
const fertig = befehlFuellen({ titel: "x", shell: "bash", befehl: "echo '{{tipfehler}}'" }, WERTE);
|
||
expect(fertig.befehl).toContain("{{tipfehler}}");
|
||
expect(fertig.unbekannt).toEqual(["tipfehler"]);
|
||
});
|
||
|
||
it("weist einen Platzhalter ohne Anführungszeichen ab", () => {
|
||
/*
|
||
* Die tragende Annahme der ganzen Maskierung — und bis zum Review von #012
|
||
* prueffte sie niemand. `Write-Host {{vorname}}` laesst sich nicht sicher
|
||
* fuellen: ein Leerzeichen im Wert zerlegt das Argument, ein `;` beendet den
|
||
* Befehl. Belegt im Review mit einer Markierungsdatei, die entstand.
|
||
*/
|
||
const fertig = befehlFuellen(
|
||
{ titel: "x", shell: "powershell", befehl: "Write-Host {{vorname}}" },
|
||
WERTE,
|
||
);
|
||
expect(fertig.fehler).toMatch(/Anführungszeichen/);
|
||
expect(fertig.befehl).toContain("{{vorname}}");
|
||
});
|
||
|
||
it("erkennt die richtige Anführungszeichenart je Shell", () => {
|
||
// Bash arbeitet mit einfachen, PowerShell und Cmd mit doppelten.
|
||
expect(
|
||
befehlFuellen({ titel: "x", shell: "bash", befehl: "echo '{{vorname}}'" }, WERTE).fehler,
|
||
).toBeUndefined();
|
||
expect(befehlFuellen({ titel: "x", shell: "bash", befehl: 'echo "{{vorname}}"' }, WERTE).fehler).toMatch(
|
||
/Anführungszeichen/,
|
||
);
|
||
expect(
|
||
befehlFuellen({ titel: "x", shell: "powershell", befehl: "Write-Host '{{vorname}}'" }, WERTE).fehler,
|
||
).toMatch(/Anführungszeichen/);
|
||
});
|
||
|
||
it("bricht nicht ab, wenn sich ein Wert für cmd nicht sicher maskieren lässt", () => {
|
||
/*
|
||
* `wertMaskieren` wirft fuer cmd bei einem Anfuehrungszeichen — dort laesst
|
||
* es sich nicht sicher maskieren, und alles nach dem `"` liest cmd.exe als
|
||
* Befehl. Ein ungefangener Wurf risse die ganze Abarbeitungsansicht ab.
|
||
*/
|
||
const fertig = befehlFuellen(
|
||
{ titel: "Freigabe setzen", shell: "cmd", befehl: 'net share X="{{mitarbeiter}}"' },
|
||
WERTE,
|
||
);
|
||
expect(fertig.fehler).toBeTruthy();
|
||
// Der Befehl bleibt mit Platzhaltern stehen: sichtbar, aber nicht kopierbar.
|
||
expect(fertig.befehl).toContain("{{mitarbeiter}}");
|
||
});
|
||
});
|
||
|
||
describe("Skript eines Abschnitts", () => {
|
||
const SCHRITTE = [
|
||
{ titel: "Rechner umbenennen", shell: "powershell" as const, befehl: 'Rename-Computer "{{vorname}}"' },
|
||
{ titel: "Neu starten", shell: "powershell" as const, befehl: "Restart-Computer" },
|
||
];
|
||
|
||
it("liefert die Befehle in Reihenfolge mit Kommentarzeilen", () => {
|
||
// Akzeptanzkriterium.
|
||
const { skript } = skriptFuerAbschnitt(SCHRITTE, WERTE);
|
||
const zeilen = skript.split(/\r?\n/u).filter((zeile) => zeile !== "");
|
||
expect(zeilen).toEqual([
|
||
"# Schritt: Rechner umbenennen",
|
||
'Rename-Computer "Anna"',
|
||
"# Schritt: Neu starten",
|
||
"Restart-Computer",
|
||
]);
|
||
});
|
||
|
||
it("nimmt einen nicht maskierbaren Schritt aus dem Skript und meldet ihn", () => {
|
||
// Mit stehen gebliebenen Platzhaltern waere es ein Skript, das etwas
|
||
// anderes tut, als es liest.
|
||
const ergebnis = skriptFuerAbschnitt(
|
||
[
|
||
{ titel: "Gut", shell: "cmd", befehl: 'echo "{{vorname}}"' },
|
||
{ titel: "Kaputt", shell: "cmd", befehl: 'echo "{{mitarbeiter}}"' },
|
||
],
|
||
WERTE,
|
||
);
|
||
expect(ergebnis.skript).toContain("Anna");
|
||
expect(ergebnis.skript).not.toContain("{{mitarbeiter}}");
|
||
expect(ergebnis.nichtMaskierbar).toEqual(["Kaputt"]);
|
||
});
|
||
|
||
it("meldet, wenn ein Passwort darin steckt", () => {
|
||
const ergebnis = skriptFuerAbschnitt(
|
||
[{ titel: "Konto", shell: "powershell", befehl: '-Password "{{generiertes_passwort}}"' }],
|
||
WERTE,
|
||
);
|
||
expect(ergebnis.mitPasswort).toBe(true);
|
||
expect(ergebnis.skript).not.toContain("{{generiertes_passwort}}");
|
||
});
|
||
});
|
||
|
||
describe("Ein Passwort je Antwort", () => {
|
||
const SCHRITT = {
|
||
titel: "Konto anlegen",
|
||
shell: "powershell" as const,
|
||
befehl: '-AccountPassword "{{generiertes_passwort}}"',
|
||
};
|
||
|
||
it("zeigt im Skript dasselbe Passwort wie im Schritt", () => {
|
||
/*
|
||
* Der Blocker aus dem Review: `befehleDesLaufs` hat jeden Schritt zweimal
|
||
* gefuellt — einmal fuer die Anzeige, einmal fuer das Skript. Damit stand auf
|
||
* dem Bildschirm Passwort X und in der Kopie Passwort Y. Der Techniker legt
|
||
* das Konto mit Y an und nennt dem Benutzer X, und weil nichts gespeichert
|
||
* wird, ist Y danach unwiederbringlich.
|
||
*/
|
||
const gefuellt = befehlFuellen(SCHRITT, {});
|
||
const { skript } = skriptAusFertigen([gefuellt]);
|
||
const ausSchritt = /"(\S{20})"/u.exec(gefuellt.befehl)?.[1];
|
||
const ausSkript = /"(\S{20})"/u.exec(skript)?.[1];
|
||
expect(ausSchritt).toBeTruthy();
|
||
expect(ausSkript).toBe(ausSchritt);
|
||
});
|
||
|
||
it("erzeugt bei einem zweiten Füllen ein anderes Passwort", () => {
|
||
// Die andere Haelfte derselben Regel: **je Anzeige** neu. Zweimal derselbe
|
||
// Wert waere eine stillschweigende Speicherung.
|
||
expect(befehlFuellen(SCHRITT, {}).befehl).not.toBe(befehlFuellen(SCHRITT, {}).befehl);
|
||
});
|
||
});
|
||
|
||
describe.runIf(datenbankVerfuegbar)("Namensregeln mit Kunden-Fallback", () => {
|
||
beforeEach(async () => {
|
||
await datenbankLeeren();
|
||
});
|
||
|
||
async function aufbau(modus: Betriebsmodus) {
|
||
const eigene = await testPrisma.kunde.create({
|
||
data: { name: "Muster GmbH", kuerzel: "MUS", istEigeneOrganisation: true },
|
||
});
|
||
const kunde = await testPrisma.kunde.create({
|
||
data: {
|
||
name: "Bäckerei Reiter",
|
||
kuerzel: "REI",
|
||
namensregeln: { mail: "{vorname}@reiter.de" },
|
||
},
|
||
});
|
||
await testPrisma.einstellung.create({
|
||
data: {
|
||
id: "global",
|
||
modus,
|
||
namensregeln: {
|
||
upn: "{vorname}.{nachname}@firma.de",
|
||
sam: "{v}{nachname}",
|
||
mail: "{vorname}.{nachname}@firma.de",
|
||
hostname: "{kundenkuerzel}-PC-{nnn}",
|
||
},
|
||
},
|
||
});
|
||
const benutzer = await testPrisma.benutzer.create({
|
||
data: { email: "admin@firma.example", name: "Admin", rolle: "ADMIN" },
|
||
});
|
||
return { eigene, kunde, admin: { id: benutzer.id, rolle: benutzer.rolle } };
|
||
}
|
||
|
||
it("überlagert Feld für Feld, nicht als Ganzes", async () => {
|
||
// Ein Kunde mit eigener Mail-Domain soll nicht auch seine Hostname-Regel
|
||
// neu schreiben muessen.
|
||
const { kunde } = await aufbau(Betriebsmodus.DIENSTLEISTUNG);
|
||
const regeln = await namensregelnFuer(kunde.id);
|
||
expect(regeln.mail).toBe("{vorname}@reiter.de");
|
||
expect(regeln.upn).toBe("{vorname}.{nachname}@firma.de");
|
||
expect(regeln.hostname).toBe("{kundenkuerzel}-PC-{nnn}");
|
||
});
|
||
|
||
it("ignoriert Kundenregeln im Modus Intern", async () => {
|
||
// Dort gibt es keine Kundenebene – eine dort hinterlegte Regel waere eine
|
||
// stille Ausnahme, die niemand sieht.
|
||
const { kunde } = await aufbau(Betriebsmodus.INTERN);
|
||
expect((await namensregelnFuer(kunde.id)).mail).toBe("{vorname}.{nachname}@firma.de");
|
||
});
|
||
|
||
it("schlägt Namen vor und macht sie eindeutig", async () => {
|
||
/*
|
||
* Das Akzeptanzkriterium des Meilensteins, Ende zu Ende durch die Datenbank:
|
||
* „Jörg Müller-Lüdenscheid“ ergibt joerg.mueller-luedenscheid@firma.de und
|
||
* jmuellerluedensch.
|
||
*/
|
||
const { eigene, admin } = await aufbau(Betriebsmodus.INTERN);
|
||
const vorschlag = await namenVorschlagen(
|
||
eigene.id,
|
||
{ vorname: "Jörg", nachname: "Müller-Lüdenscheid" },
|
||
admin,
|
||
);
|
||
expect(vorschlag.upn).toBe("joerg.mueller-luedenscheid@firma.de");
|
||
expect(vorschlag.sam).toBe("jmuellerluedensch");
|
||
});
|
||
|
||
it("weicht aus, wenn der Name schon vergeben ist", async () => {
|
||
const { eigene, admin } = await aufbau(Betriebsmodus.INTERN);
|
||
await testPrisma.mitarbeiter.create({
|
||
data: {
|
||
kundeId: eigene.id,
|
||
vorname: "Jörg",
|
||
nachname: "Müller-Lüdenscheid",
|
||
anzeigename: "Jörg Müller-Lüdenscheid",
|
||
sam: "jmuellerluedensch",
|
||
},
|
||
});
|
||
const vorschlag = await namenVorschlagen(
|
||
eigene.id,
|
||
{ vorname: "Jörg", nachname: "Müller-Lüdenscheid" },
|
||
admin,
|
||
);
|
||
expect(vorschlag.sam).not.toBe("jmuellerluedensch");
|
||
expect(vorschlag.sam?.length).toBeLessThanOrEqual(20);
|
||
});
|
||
|
||
it("zählt Hostnamen hoch, die es schon gibt", async () => {
|
||
const { eigene, admin } = await aufbau(Betriebsmodus.DIENSTLEISTUNG);
|
||
await testPrisma.geraet.create({
|
||
data: { kundeId: eigene.id, tagCode: "LZ-7K3F9Q", hostname: "MUS-PC-001" },
|
||
});
|
||
const vorschlag = await namenVorschlagen(eigene.id, { kundenkuerzel: "MUS" }, admin);
|
||
expect(vorschlag.hostname).toBe("MUS-PC-002");
|
||
});
|
||
|
||
it("schlägt nichts vor, wo keine Regel steht", async () => {
|
||
// Ein erfundener Anmeldename ist schlimmer als gar keiner.
|
||
const eigene = await testPrisma.kunde.create({
|
||
data: { name: "Ohne Regeln", kuerzel: "OHN", istEigeneOrganisation: true },
|
||
});
|
||
await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.INTERN } });
|
||
const benutzer = await testPrisma.benutzer.create({
|
||
data: { email: "a@firma.example", name: "Admin", rolle: "ADMIN" },
|
||
});
|
||
const admin = { id: benutzer.id, rolle: benutzer.rolle };
|
||
expect(await namenVorschlagen(eigene.id, { vorname: "Anna", nachname: "Meier" }, admin)).toEqual({});
|
||
});
|
||
});
|
||
|
||
describe.runIf(datenbankVerfuegbar)("Befehle eines Laufs", () => {
|
||
beforeEach(async () => {
|
||
await datenbankLeeren();
|
||
});
|
||
|
||
it("liefert die Abschnitte der Version, an der der Lauf hängt", async () => {
|
||
const eigene = await testPrisma.kunde.create({
|
||
data: { name: "Muster", kuerzel: "MUS", istEigeneOrganisation: true },
|
||
});
|
||
await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.INTERN } });
|
||
|
||
const vorlage = await testPrisma.vorlage.create({ data: { name: "PC" } });
|
||
const version = await testPrisma.vorlagenVersion.create({
|
||
data: { vorlageId: vorlage.id, version: 1, status: "ENTWURF" },
|
||
});
|
||
const abschnitt = await testPrisma.vorlagenAbschnitt.create({
|
||
data: { versionId: version.id, titel: "Installation", position: 0 },
|
||
});
|
||
await testPrisma.vorlagenSchritt.createMany({
|
||
data: [
|
||
{
|
||
abschnittId: abschnitt.id,
|
||
titel: "Umbenennen",
|
||
typ: "BEFEHL",
|
||
position: 0,
|
||
konfig: { shell: "powershell", befehl: 'Rename-Computer -NewName "{{hostname}}"' },
|
||
},
|
||
{ abschnittId: abschnitt.id, titel: "Haken", typ: "HAKEN", position: 1, konfig: {} },
|
||
],
|
||
});
|
||
await testPrisma.vorlagenVersion.update({
|
||
where: { id: version.id },
|
||
data: { status: "VEROEFFENTLICHT", veroeffentlichtAm: new Date() },
|
||
});
|
||
|
||
const geraet = await testPrisma.geraet.create({
|
||
data: { kundeId: eigene.id, tagCode: "LZ-7K3F9Q", hostname: "PC-001", registriertAm: new Date() },
|
||
});
|
||
const lauf = await testPrisma.lauf.create({
|
||
data: {
|
||
vorlagenVersionId: version.id,
|
||
titel: "PC-001 neu aufsetzen",
|
||
kundeId: eigene.id,
|
||
geraetId: geraet.id,
|
||
},
|
||
});
|
||
|
||
const abschnitte = await befehleDesLaufs({ laufId: lauf.id });
|
||
expect(abschnitte).toHaveLength(1);
|
||
expect(abschnitte[0]?.abschnitt).toBe("Installation");
|
||
// Nur BEFEHL-Schritte – ein Haken gehoert in kein Skript.
|
||
expect(abschnitte[0]?.schritte.map((schritt) => schritt.titel)).toEqual(["Umbenennen"]);
|
||
expect(abschnitte[0]?.schritte[0]?.befehl).toBe('Rename-Computer -NewName "PC-001"');
|
||
expect(abschnitte[0]?.skript).toContain("# Schritt: Umbenennen");
|
||
});
|
||
|
||
it("schreibt beim Füllen nichts in die Datenbank", async () => {
|
||
/*
|
||
* Die zweite Haelfte der Passwort-Zusage: es landet **nirgends**. Geprueft
|
||
* wird, was heute pruefbar ist — der Weg vom Lauf zum fertigen Befehl
|
||
* schreibt keine Zeile, auch keinen Protokolleintrag. `Schrittergebnis` und
|
||
* `Kommentar` gibt es erst mit #013; dort gehoert derselbe Beweis noch
|
||
* einmal hin.
|
||
*/
|
||
const eigene = await testPrisma.kunde.create({
|
||
data: { name: "Muster", kuerzel: "MUS", istEigeneOrganisation: true },
|
||
});
|
||
await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.INTERN } });
|
||
const vorlage = await testPrisma.vorlage.create({ data: { name: "Onboarding" } });
|
||
const version = await testPrisma.vorlagenVersion.create({
|
||
data: { vorlageId: vorlage.id, version: 1, status: "ENTWURF" },
|
||
});
|
||
const abschnitt = await testPrisma.vorlagenAbschnitt.create({
|
||
data: { versionId: version.id, titel: "Konto", position: 0 },
|
||
});
|
||
await testPrisma.vorlagenSchritt.create({
|
||
data: {
|
||
abschnittId: abschnitt.id,
|
||
titel: "Konto anlegen",
|
||
typ: "BEFEHL",
|
||
position: 0,
|
||
konfig: { shell: "powershell", befehl: '-AccountPassword "{{generiertes_passwort}}"' },
|
||
},
|
||
});
|
||
await testPrisma.vorlagenVersion.update({
|
||
where: { id: version.id },
|
||
data: { status: "VEROEFFENTLICHT", veroeffentlichtAm: new Date() },
|
||
});
|
||
const lauf = await testPrisma.lauf.create({
|
||
data: { vorlagenVersionId: version.id, titel: "Onboarding", kundeId: eigene.id },
|
||
});
|
||
|
||
const vorher = await testPrisma.protokoll.count();
|
||
const abschnitte = await befehleDesLaufs({ laufId: lauf.id });
|
||
const passwort = /"(\S{20})"/u.exec(abschnitte[0]?.schritte[0]?.befehl ?? "")?.[1];
|
||
expect(passwort).toBeTruthy();
|
||
|
||
expect(await testPrisma.protokoll.count()).toBe(vorher);
|
||
// Und das Passwort steht in keiner Zeile, die wir schreiben koennten.
|
||
const protokolle = JSON.stringify(await testPrisma.protokoll.findMany());
|
||
const laeufe = JSON.stringify(await testPrisma.lauf.findMany());
|
||
expect(protokolle).not.toContain(passwort);
|
||
expect(laeufe).not.toContain(passwort);
|
||
});
|
||
|
||
it("liefert nichts für einen Lauf, den es nicht gibt", async () => {
|
||
expect(await befehleDesLaufs({ laufId: "gibtsnicht" })).toEqual([]);
|
||
});
|
||
});
|