265 lines
11 KiB
TypeScript
265 lines
11 KiB
TypeScript
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();
|
||
});
|
||
});
|