import { beforeEach, describe, expect, it } from "vitest"; import { Betriebsmodus, GeraeteTyp, VersionStatus } from "@/generated/prisma/enums"; import { RECHTE } from "@/server/auth/rollen"; import { schnellstartPflege } from "@/server/queries/schnellstart"; import { SchnellstartFehler, schnellstartSetzen } from "@/server/services/schnellstart"; import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db"; /** * Schnellstart je Geraetetyp pflegen (#022). * * Dass die Kacheln auf der Geraeteseite richtig erscheinen, prueft * `tests/geraeteseite.test.ts` — auch den Fall, dass eine archivierte Vorlage von * selbst verschwindet. Hier geht es um das Pflegen: die Reihenfolge, die * Abweisungen, das Protokoll, und die Marke, die den Seed heraushaelt. */ async function grundbestand() { const kunde = await testPrisma.kunde.create({ data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true }, }); await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.INTERN } }); const admin = await testPrisma.benutzer.create({ data: { email: "admin@firma.example", name: "Admin", rolle: "ADMIN" }, }); return { kunde, adminId: admin.id }; } /** Eine Vorlage mit veroeffentlichter Version 1 – nur die ist zuweisbar. */ async function vorlageAnlegen( name: string, optionen: { archiviert?: boolean; nurEntwurf?: boolean; kundeId?: string } = {}, ): Promise { const vorlage = await testPrisma.vorlage.create({ data: { name, archiviert: optionen.archiviert ?? false, ...(optionen.kundeId ? { kundeId: optionen.kundeId } : {}), }, }); const version = await testPrisma.vorlagenVersion.create({ data: { vorlageId: vorlage.id, version: 1, status: VersionStatus.ENTWURF }, }); if (!optionen.nurEntwurf) { await testPrisma.vorlagenVersion.update({ where: { id: version.id }, data: { status: VersionStatus.VEROEFFENTLICHT, veroeffentlichtAm: new Date() }, }); } return vorlage.id; } async function listeVon(typ: GeraeteTyp): Promise { const { typen } = await schnellstartPflege(); return typen.find((eintrag) => eintrag.typ === typ)?.zugewiesen.map((e) => e.name) ?? []; } describe("Recht auf den Schnellstart", () => { it("liegt bei verwaltung, also nur bei ADMIN", () => { // Die Zuordnung gilt global und bestimmt, was jeder Techniker an jedem Gerät // zuerst sieht. TECHNIKER und LESER dürfen das nicht ändern. expect(RECHTE.verwaltung).toEqual(["ADMIN"]); }); }); describe.runIf(datenbankVerfuegbar)("Schnellstart pflegen", () => { beforeEach(async () => { await datenbankLeeren(); }); it("setzt die Liste in der übergebenen Reihenfolge", async () => { const { adminId } = await grundbestand(); const erste = await vorlageAnlegen("Erster Griff"); const zweite = await vorlageAnlegen("Zweiter Griff"); await schnellstartSetzen(GeraeteTyp.PC, [zweite, erste], adminId); expect(await listeVon(GeraeteTyp.PC)).toEqual(["Zweiter Griff", "Erster Griff"]); // Umsortieren heißt ersetzen, nicht ergänzen. await schnellstartSetzen(GeraeteTyp.PC, [erste, zweite], adminId); expect(await listeVon(GeraeteTyp.PC)).toEqual(["Erster Griff", "Zweiter Griff"]); expect(await testPrisma.geraetetypVorlage.count({ where: { geraeteTyp: GeraeteTyp.PC } })).toBe(2); }); it("sortiert auch um, wenn alte Zeilen dieselbe Position tragen", async () => { /* * Der Fall, den erst das Durchklicken des gebauten Stands zeigte. Altdaten * tragen mehrere Zeilen auf `position` 0 — der Seed zaehlt je Vorgabe von 0, * und ein Beispielskript legte eine zweite dazu. Bei gleicher Position ist * die Reihenfolge in Postgres nicht festgelegt, und der Vergleich * "unveraendert" hielt eine echte Umsortierung deshalb fuer einen Leerlauf: * die Marke wurde gesetzt, geschrieben wurde nichts. */ const { adminId } = await grundbestand(); const a = await vorlageAnlegen("Alpha"); const b = await vorlageAnlegen("Beta"); await testPrisma.geraetetypVorlage.createMany({ data: [ { geraeteTyp: GeraeteTyp.PC, vorlageId: a, position: 0 }, { geraeteTyp: GeraeteTyp.PC, vorlageId: b, position: 0 }, ], }); // Genau die Reihenfolge schicken, die das Lesemodell zeigt – ein Mensch, der // nichts umsortiert, aber auf Speichern drückt. const gezeigt = await listeVon(GeraeteTyp.PC); const nachName = new Map([ ["Alpha", a], ["Beta", b], ]); const umgekehrt = [...gezeigt].reverse().map((name) => nachName.get(name) ?? ""); await schnellstartSetzen(GeraeteTyp.PC, umgekehrt, adminId); expect(await listeVon(GeraeteTyp.PC)).toEqual([...gezeigt].reverse()); // Und die Positionen stehen danach aufeinanderfolgend, nicht beide auf 0. const zeilen = await testPrisma.geraetetypVorlage.findMany({ where: { geraeteTyp: GeraeteTyp.PC }, orderBy: { position: "asc" }, select: { position: true }, }); expect(zeilen.map((zeile) => zeile.position)).toEqual([0, 1]); }); it("zieht beim Speichern alte, gleiche Positionen gerade", async () => { // Ohne Aenderung gespeichert: die Liste bleibt, die Positionen werden // aufeinanderfolgend. Aufraeumen, kein Fehlerfall (#022). const { adminId } = await grundbestand(); const a = await vorlageAnlegen("Alpha"); const b = await vorlageAnlegen("Beta"); await testPrisma.geraetetypVorlage.createMany({ data: [ { geraeteTyp: GeraeteTyp.PC, vorlageId: a, position: 0 }, { geraeteTyp: GeraeteTyp.PC, vorlageId: b, position: 0 }, ], }); const gezeigt = await listeVon(GeraeteTyp.PC); const nachName = new Map([ ["Alpha", a], ["Beta", b], ]); await schnellstartSetzen( GeraeteTyp.PC, gezeigt.map((name) => nachName.get(name) ?? ""), adminId, ); expect(await listeVon(GeraeteTyp.PC)).toEqual(gezeigt); const zeilen = await testPrisma.geraetetypVorlage.findMany({ where: { geraeteTyp: GeraeteTyp.PC }, orderBy: { position: "asc" }, select: { position: true }, }); expect(zeilen.map((zeile) => zeile.position)).toEqual([0, 1]); }); it("nimmt eine leere Liste als Angabe und entfernt alles", async () => { const { adminId } = await grundbestand(); const vorlage = await vorlageAnlegen("Einziger Griff"); await schnellstartSetzen(GeraeteTyp.PC, [vorlage], adminId); await schnellstartSetzen(GeraeteTyp.PC, [], adminId); expect(await listeVon(GeraeteTyp.PC)).toEqual([]); }); it("lässt andere Gerätetypen unberührt", async () => { const { adminId } = await grundbestand(); const vorlage = await vorlageAnlegen("Für beide"); await schnellstartSetzen(GeraeteTyp.PC, [vorlage], adminId); await schnellstartSetzen(GeraeteTyp.SERVER, [vorlage], adminId); await schnellstartSetzen(GeraeteTyp.PC, [], adminId); expect(await listeVon(GeraeteTyp.SERVER)).toEqual(["Für beide"]); }); it("weist dieselbe Vorlage zweimal in einer Liste ab", async () => { const { adminId } = await grundbestand(); const vorlage = await vorlageAnlegen("Einmal reicht"); await expect(schnellstartSetzen(GeraeteTyp.PC, [vorlage, vorlage], adminId)).rejects.toThrow( SchnellstartFehler, ); }); it("weist eine archivierte Vorlage ab", async () => { const { adminId } = await grundbestand(); const weggelegt = await vorlageAnlegen("Weggelegt", { archiviert: true }); await expect(schnellstartSetzen(GeraeteTyp.PC, [weggelegt], adminId)).rejects.toThrow(/archiviert/); }); it("weist eine Vorlage ohne veröffentlichte Version ab", async () => { // Eine Kachel, die zu einem Entwurf führt, kann keinen Lauf starten. const { adminId } = await grundbestand(); const entwurf = await vorlageAnlegen("Noch Entwurf", { nurEntwurf: true }); await expect(schnellstartSetzen(GeraeteTyp.PC, [entwurf], adminId)).rejects.toThrow( /veröffentlichte Version/, ); }); it("bietet zur Auswahl nur veröffentlichte, nicht archivierte Vorlagen", async () => { const { kunde } = await grundbestand(); await vorlageAnlegen("Brauchbar"); await vorlageAnlegen("Weggelegt", { archiviert: true }); await vorlageAnlegen("Noch Entwurf", { nurEntwurf: true }); await vorlageAnlegen("Nur für einen Kunden", { kundeId: kunde.id }); const { auswahl } = await schnellstartPflege(); expect(auswahl.map((eintrag) => eintrag.name).sort()).toEqual(["Brauchbar", "Nur für einen Kunden"]); // Eine kundengebundene Vorlage ist zuweisbar und wird als solche gezeigt; // dass sie nur bei ihren Geräten erscheint, entscheidet das Lesen. expect(auswahl.find((eintrag) => eintrag.name === "Nur für einen Kunden")?.kundeName).toBe( "Eigene Organisation", ); }); it("nennt jeden Gerätetyp, auch die ohne Zuordnung", async () => { await grundbestand(); const { typen } = await schnellstartPflege(); expect(typen.map((eintrag) => eintrag.typ)).toEqual(Object.values(GeraeteTyp)); }); it("schreibt die Änderung mit Namen ins Protokoll, nicht mit Kennungen", async () => { const { adminId } = await grundbestand(); const erste = await vorlageAnlegen("Erster Griff"); const zweite = await vorlageAnlegen("Zweiter Griff"); await schnellstartSetzen(GeraeteTyp.PC, [erste], adminId); await schnellstartSetzen(GeraeteTyp.PC, [erste, zweite], adminId); const eintraege = await testPrisma.protokoll.findMany({ where: { entitaet: "GeraetetypVorlage" }, orderBy: { erstelltAm: "asc" }, }); expect(eintraege).toHaveLength(2); expect(eintraege[1]?.aktion).toBe("Schnellstart für PC geändert"); expect(eintraege[1]?.vorher).toEqual({ vorlagen: ["Erster Griff"] }); expect(eintraege[1]?.nachher).toEqual({ vorlagen: ["Erster Griff", "Zweiter Griff"] }); // Ein Protokolleintrag muss auch lesbar sein, wenn die Vorlage später // anders heißt – deshalb Namen und keine Kennungen. expect(JSON.stringify(eintraege[1]?.nachher)).not.toContain(erste); }); it("schreibt keinen Protokolleintrag, wenn sich die Liste nicht ändert", async () => { const { adminId } = await grundbestand(); const vorlage = await vorlageAnlegen("Unverändert"); await schnellstartSetzen(GeraeteTyp.PC, [vorlage], adminId); await schnellstartSetzen(GeraeteTyp.PC, [vorlage], adminId); expect(await testPrisma.protokoll.count({ where: { entitaet: "GeraetetypVorlage" } })).toBe(1); }); it("setzt die Marke, die den Seed heraushält – auch bei unveränderter Liste", async () => { const { adminId } = await grundbestand(); const vorlage = await vorlageAnlegen("Einziger Griff"); expect( (await testPrisma.einstellung.findUniqueOrThrow({ where: { id: "global" } })).schnellstartGepflegt, ).toBe(false); // Ein Speichern, das nichts ändert, ist trotzdem die Aussage „ich pflege das // selbst“ — sonst legte der Seed seine Vorgaben weiter nach. await schnellstartSetzen(GeraeteTyp.PC, [], adminId); expect( (await testPrisma.einstellung.findUniqueOrThrow({ where: { id: "global" } })).schnellstartGepflegt, ).toBe(true); expect(vorlage).toBeTruthy(); }); });