Files
Laufzettel/tests/rollen.test.ts
T
florian.wilde f85bc87324
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(stammdaten): Mitarbeiter, Abteilungen und Geraete (#25)
2026-09-25 10:52:44 +00:00

145 lines
6.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { Rolle } from "@/generated/prisma/enums";
import { RECHTE, ROLLEN_RANG, ROLLEN_TEXT, darf, staerksteRolle, type Recht } from "@/server/auth/rollen";
const ALLE_ROLLEN = Object.values(Rolle);
const ALLE_RECHTE = Object.keys(RECHTE) as Recht[];
/**
* Die vollstaendige Rechtematrix, absichtlich ausgeschrieben statt aus `RECHTE` berechnet:
* nur so faellt auf, wenn jemand einem `LESER` versehentlich Schreibrechte gibt (#007). Ein
* Test, der die Tabelle nur gegen sich selbst haelt, wuerde jede Aenderung mitmachen.
*/
const MATRIX: Record<Recht, Record<Rolle, boolean>> = {
lesen: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: true },
laeufeBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false },
geraeteBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false },
mitarbeiterBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: true, LESER: false },
vorlagenBearbeiten: { ADMIN: true, REDAKTEUR: true, TECHNIKER: false, LESER: false },
archivVerwalten: { ADMIN: true, REDAKTEUR: false, TECHNIKER: false, LESER: false },
verwaltung: { ADMIN: true, REDAKTEUR: false, TECHNIKER: false, LESER: false },
};
describe("Rechtematrix", () => {
const faelle = ALLE_RECHTE.flatMap((recht) =>
ALLE_ROLLEN.map((rolle) => [recht, rolle, MATRIX[recht][rolle]] as const),
);
it("prueft jede Rolle gegen jedes Recht", () => {
// Haelt fest, dass die Tabelle unten wirklich vollstaendig ist: vier Rollen, sieben Rechte.
expect(ALLE_ROLLEN).toHaveLength(4);
expect(ALLE_RECHTE).toHaveLength(7);
expect(faelle).toHaveLength(28);
});
it.each(faelle)("%s: %s -> %s", (recht, rolle, erwartet) => {
expect(darf(rolle, recht)).toBe(erwartet);
});
});
describe("Rollen im Einzelnen", () => {
it("laesst LESER nur lesen", () => {
expect(darf("LESER", "lesen")).toBe(true);
for (const recht of ALLE_RECHTE.filter((r) => r !== "lesen")) {
expect(darf("LESER", recht), `LESER darf ${recht} nicht`).toBe(false);
}
});
it("gibt TECHNIKER Laeufe und Geraete, aber keine Vorlagen und keine Verwaltung", () => {
expect(darf("TECHNIKER", "laeufeBearbeiten")).toBe(true);
expect(darf("TECHNIKER", "geraeteBearbeiten")).toBe(true);
expect(darf("TECHNIKER", "vorlagenBearbeiten")).toBe(false);
expect(darf("TECHNIKER", "verwaltung")).toBe(false);
expect(darf("TECHNIKER", "archivVerwalten")).toBe(false);
});
it("gibt REDAKTEUR alles von TECHNIKER plus Vorlagen, aber keine Verwaltung", () => {
for (const recht of ALLE_RECHTE.filter((r) => darf("TECHNIKER", r))) {
expect(darf("REDAKTEUR", recht), `REDAKTEUR darf ${recht} wie ein TECHNIKER`).toBe(true);
}
expect(darf("REDAKTEUR", "vorlagenBearbeiten")).toBe(true);
expect(darf("REDAKTEUR", "verwaltung")).toBe(false);
expect(darf("REDAKTEUR", "archivVerwalten")).toBe(false);
});
it("gibt ADMIN alles", () => {
for (const recht of ALLE_RECHTE) {
expect(darf("ADMIN", recht), `ADMIN darf ${recht}`).toBe(true);
}
});
it("behaelt das Zurueckholen aus dem Archiv beim ADMIN", () => {
// Steht als eigener Test da, weil die Zusage "ein archivierter Lauf aendert sich nicht"
// daran haengt.
expect(RECHTE.archivVerwalten).toStrictEqual(["ADMIN"]);
});
});
describe("Aufbau der Tabelle", () => {
it("listet fuer jedes Recht mindestens eine Rolle", () => {
for (const recht of ALLE_RECHTE) {
expect(RECHTE[recht].length, `${recht} ohne Rolle waere fuer niemanden erreichbar`).toBeGreaterThan(0);
}
});
it("nennt in jedem Recht nur bekannte Rollen, jede hoechstens einmal", () => {
for (const recht of ALLE_RECHTE) {
const rollen = RECHTE[recht] as readonly Rolle[];
expect(new Set(rollen).size, `${recht} nennt eine Rolle doppelt`).toBe(rollen.length);
for (const rolle of rollen) {
expect(ALLE_ROLLEN).toContain(rolle);
}
}
});
it("deckt mit ROLLEN_TEXT genau die Rollen des Enums ab", () => {
expect(Object.keys(ROLLEN_TEXT).sort()).toStrictEqual([...ALLE_ROLLEN].sort());
for (const rolle of ALLE_ROLLEN) {
expect(ROLLEN_TEXT[rolle].length).toBeGreaterThan(0);
}
});
it("deckt mit ROLLEN_RANG genau die Rollen des Enums ab", () => {
expect(Object.keys(ROLLEN_RANG).sort()).toStrictEqual([...ALLE_ROLLEN].sort());
});
it("gibt jeder Rolle einen eigenen Rang, ADMIN oben und LESER unten", () => {
const raenge = ALLE_ROLLEN.map((rolle) => ROLLEN_RANG[rolle]);
expect(new Set(raenge).size).toBe(ALLE_ROLLEN.length);
expect(Math.max(...raenge)).toBe(ROLLEN_RANG.ADMIN);
expect(Math.min(...raenge)).toBe(ROLLEN_RANG.LESER);
expect(ROLLEN_RANG.ADMIN).toBeGreaterThan(ROLLEN_RANG.REDAKTEUR);
expect(ROLLEN_RANG.REDAKTEUR).toBeGreaterThan(ROLLEN_RANG.TECHNIKER);
expect(ROLLEN_RANG.TECHNIKER).toBeGreaterThan(ROLLEN_RANG.LESER);
});
});
describe("staerksteRolle", () => {
it("gibt bei leerer Liste LESER zurueck", () => {
// Wer in keiner abgebildeten Gruppe ist, soll hineinsehen duerfen -- nicht scheitern
// und erst recht nicht ADMIN werden.
expect(staerksteRolle([])).toBe("LESER");
});
it.each(ALLE_ROLLEN)("gibt bei nur %s diese Rolle zurueck", (rolle) => {
expect(staerksteRolle([rolle])).toBe(rolle);
});
it.each([
["LESER und TECHNIKER", ["LESER", "TECHNIKER"], "TECHNIKER"],
["TECHNIKER und REDAKTEUR", ["TECHNIKER", "REDAKTEUR"], "REDAKTEUR"],
["REDAKTEUR und ADMIN", ["REDAKTEUR", "ADMIN"], "ADMIN"],
["alle vier", ["LESER", "TECHNIKER", "REDAKTEUR", "ADMIN"], "ADMIN"],
["ADMIN zuerst genannt", ["ADMIN", "LESER"], "ADMIN"],
["LESER zuerst genannt", ["LESER", "ADMIN"], "ADMIN"],
["dieselbe Rolle mehrfach", ["TECHNIKER", "TECHNIKER"], "TECHNIKER"],
] as const)("waehlt aus %s die Rolle %s", (_fall, rollen, erwartet) => {
expect(staerksteRolle(rollen)).toBe(erwartet);
});
it("ist von der Reihenfolge unabhaengig", () => {
const rollen: Rolle[] = ["TECHNIKER", "ADMIN", "LESER", "REDAKTEUR"];
expect(staerksteRolle(rollen)).toBe(staerksteRolle([...rollen].reverse()));
});
});