import { beforeEach, describe, expect, it } from "vitest"; import { prisma } from "@/lib/db"; import { VorlagenFehler, type VorlageEingabe } from "@/lib/vorlagen"; import { vorlagenListe } from "@/server/queries/vorlagen"; import { vorlageAnlegen, vorlageArchivieren, vorlageBearbeiten } from "@/server/services/vorlagen"; import { datenbankLeeren } from "./hilfen/db"; /** Vorlagen-Service (#019) gegen die Testdatenbank: Eindeutigkeit, Protokoll, Schutz der eingebauten. */ let akteur: { id: string; name: string }; beforeEach(async () => { await datenbankLeeren(); const benutzer = await prisma.benutzer.create({ data: { name: "Ada Admin", rolle: "ADMIN" } }); akteur = { id: benutzer.id, name: benutzer.name }; }); const BASIS: VorlageEingabe = { name: "Monitoring", beschreibung: " Zugang für Prometheus ", schluesselTyp: "ED25519", kategorie: "DIENST", standardLaufzeitTage: 30, zielbenutzer: "monitor", zielbenutzerAnlegen: true, sudoModus: "BEFEHLE", sudoBefehle: ["/usr/bin/systemctl status nginx"], authorizedKeysOptionen: 'from="{{quellHostIp}}",no-pty', sshConfigAlias: "mon-{{zielHost}}", knownHostsEintragen: true, privatNachAusrollenLoeschen: false, }; async function fehlerVon(versuch: Promise): Promise { const fehler = await versuch.then( () => undefined, (f: unknown) => f, ); expect(fehler).toBeInstanceOf(VorlagenFehler); return fehler as VorlagenFehler; } describe("Vorlage anlegen", () => { it("speichert bereinigt, laesst Platzhalter stehen und protokolliert", async () => { const id = await vorlageAnlegen(BASIS, akteur); const v = await prisma.vorlage.findUniqueOrThrow({ where: { id } }); expect(v.eingebaut).toBe(false); expect(v.beschreibung).toBe("Zugang für Prometheus"); expect(v.sudoBefehle).toEqual(["/usr/bin/systemctl status nginx"]); expect(v.authorizedKeysOptionen).toBe('from="{{quellHostIp}}",no-pty'); expect(v.sshConfigAlias).toBe("mon-{{zielHost}}"); const eintrag = await prisma.protokoll.findFirstOrThrow({ where: { aktion: "vorlage.angelegt" } }); expect(eintrag.akteurId).toBe(akteur.id); expect(eintrag.details).toMatchObject({ name: "Monitoring" }); expect((await vorlagenListe()).map((l) => l.name)).toEqual(["Monitoring"]); }); it("verwirft sudo-Befehle, wenn der Modus nicht BEFEHLE ist", async () => { const id = await vorlageAnlegen({ ...BASIS, sudoModus: "KEINER" }, akteur); expect((await prisma.vorlage.findUniqueOrThrow({ where: { id } })).sudoBefehle).toEqual([]); }); it("meldet einen doppelten Namen am Feld name", async () => { await vorlageAnlegen(BASIS, akteur); const fehler = await fehlerVon(vorlageAnlegen({ ...BASIS, zielbenutzer: "anders" }, akteur)); expect(fehler.feld).toBe("name"); expect(await prisma.vorlage.count()).toBe(1); }); it("meldet einen unbekannten Platzhalter am Feld der Optionen", async () => { const fehler = await fehlerVon( vorlageAnlegen({ ...BASIS, authorizedKeysOptionen: 'from="{{quellIp}}"' }, akteur), ); expect(fehler.feld).toBe("authorizedKeysOptionen"); expect(fehler.message).toContain("{{quellIp}}"); expect(await prisma.vorlage.count()).toBe(0); }); }); describe("Vorlage bearbeiten", () => { it("protokolliert genau die geaenderten Felder", async () => { const id = await vorlageAnlegen(BASIS, akteur); await vorlageBearbeiten( id, { ...BASIS, standardLaufzeitTage: 60, sudoBefehle: ["/usr/bin/systemctl status nginx", "/usr/bin/id"] }, akteur, ); const v = await prisma.vorlage.findUniqueOrThrow({ where: { id } }); expect(v.standardLaufzeitTage).toBe(60); const eintrag = await prisma.protokoll.findFirstOrThrow({ where: { aktion: "vorlage.geaendert" } }); const details = eintrag.details as { name: string; geaendert: string[] }; expect(details.name).toBe("Monitoring"); expect(details.geaendert.sort()).toEqual(["standardLaufzeitTage", "sudoBefehle"]); }); it("meldet beim Umbenennen auf einen vergebenen Namen das Feld name", async () => { await vorlageAnlegen(BASIS, akteur); const id = await vorlageAnlegen({ ...BASIS, name: "Zweite" }, akteur); const fehler = await fehlerVon(vorlageBearbeiten(id, BASIS, akteur)); expect(fehler.feld).toBe("name"); }); it("verweigert archivierte Vorlagen", async () => { const id = await vorlageAnlegen(BASIS, akteur); await vorlageArchivieren(id, akteur); await fehlerVon(vorlageBearbeiten(id, BASIS, akteur)); }); }); describe("Vorlage archivieren", () => { it("archiviert eigene Vorlagen und blendet sie aus der Liste aus", async () => { const id = await vorlageAnlegen(BASIS, akteur); await vorlageArchivieren(id, akteur); expect((await prisma.vorlage.findUniqueOrThrow({ where: { id } })).archiviertAm).not.toBeNull(); expect(await vorlagenListe()).toEqual([]); expect(await prisma.protokoll.count({ where: { aktion: "vorlage.archiviert" } })).toBe(1); }); it("laesst eingebaute Vorlagen nicht archivieren, aber bearbeiten", async () => { const eingebaut = await prisma.vorlage.create({ data: { name: "Deployment-Zugang", eingebaut: true, zielbenutzer: "deploy" }, }); await fehlerVon(vorlageArchivieren(eingebaut.id, akteur)); expect((await prisma.vorlage.findUniqueOrThrow({ where: { id: eingebaut.id } })).archiviertAm).toBeNull(); await vorlageBearbeiten(eingebaut.id, { ...BASIS, name: "Deployment-Zugang" }, akteur); const v = await prisma.vorlage.findUniqueOrThrow({ where: { id: eingebaut.id } }); expect(v.eingebaut).toBe(true); expect(v.zielbenutzer).toBe("monitor"); }); });