Files
Laufzettel/tests/schnellstart.test.ts
T
florian.wilde f5936327a0
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(schnellstart): Kacheln je Geraetetyp in der Oberflaeche pflegen (#44)
2026-10-04 15:41:47 +00:00

265 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string> {
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<readonly string[]> {
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();
});
});