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> = { 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())); }); });