import { describe, expect, it } from "vitest"; import { RolleSchluessel } from "@prisma/client"; import { ALLE_RECHTE, KeinZugriffFehler, ROLLEN, ROLLEN_BEZEICHNUNG, ROLLEN_RECHTE, alsRolle, darfPersonSehen, gehoertZurOrganisation, hatAlleRechte, hatEinesDerRechte, hatRecht, rechteVon, verlangeRecht, type Sitzung, } from "@/lib/authz"; /** * Beispieltest für Phase 0. * * Geprüft wird das, was am teuersten schiefgeht: dass der Gerätewart nicht in * die Kasse kommt, dass der Administrator wirklich alles darf, und dass das * Terminal nur das Nötigste kann. */ function sitzung( rollen: Sitzung["rollen"], organisationId = "org-1", ): Sitzung & { personId: string } { return { personId: "person-1", organisationId, name: "Testperson", rollen, anmeldeArt: "LOKAL", }; } /** * Eine Sitzung der Kreisaufsicht: kein Konto in dieser Wehr, keine Akte. * * Seit Phase 10 gibt es diesen Zustand, und er ist der einzige, in dem * `personId` fehlt. */ function aufsicht(organisationId = "org-2"): Sitzung { return { personId: null, organisationId, name: "Kreisbrandinspektion", rollen: ["AUFSICHT"], anmeldeArt: "LOKAL", nurLesend: true, }; } describe("Rollenkatalog", () => { it("hat für jede Rolle eine deutsche Bezeichnung", () => { for (const rolle of ROLLEN) { expect(ROLLEN_BEZEICHNUNG[rolle]).toBeTruthy(); } }); it("vergibt in ROLLEN_RECHTE nur Rechte, die es im Katalog gibt", () => { for (const [rolle, rechte] of Object.entries(ROLLEN_RECHTE)) { for (const recht of rechte) { expect(ALLE_RECHTE, `${rolle} verweist auf unbekanntes Recht ${recht}`).toContain( recht, ); } } }); }); describe("hatRecht", () => { it("gibt dem Administrator ausnahmslos alles", () => { const admin = sitzung(["SUPERADMIN"]); for (const recht of ALLE_RECHTE) { expect(hatRecht(admin, recht)).toBe(true); } }); it("lässt den Gerätewart Geräte bearbeiten", () => { expect(hatRecht(sitzung(["GERAETEWART"]), "geraet.bearbeiten")).toBe(true); expect(hatRecht(sitzung(["GERAETEWART"]), "pruefung.durchfuehren")).toBe(true); }); it("lässt den Gerätewart NICHT an die Kasse", () => { expect(hatRecht(sitzung(["GERAETEWART"]), "kasse.bearbeiten")).toBe(false); expect(hatRecht(sitzung(["GERAETEWART"]), "kasse.abschluss")).toBe(false); }); it("lässt den Kassier nicht an Geräte und den Atemschutz", () => { expect(hatRecht(sitzung(["KASSIER"]), "geraet.bearbeiten")).toBe(false); expect(hatRecht(sitzung(["KASSIER"]), "atemschutz.ueberwachung")).toBe(false); }); it("gibt dem einfachen Mitglied nur Lese- und eigene Rechte", () => { const mitglied = sitzung(["MITGLIED"]); expect(hatRecht(mitglied, "person.eigene")).toBe(true); expect(hatRecht(mitglied, "geraet.lesen")).toBe(true); expect(hatRecht(mitglied, "person.bearbeiten")).toBe(false); expect(hatRecht(mitglied, "geraet.bearbeiten")).toBe(false); }); it("beschränkt das Terminal auf Anmelden und Erfassen", () => { const kiosk = sitzung(["KIOSK"]); expect(hatRecht(kiosk, "kiosk.anmelden")).toBe(true); expect(hatRecht(kiosk, "kiosk.erfassen")).toBe(true); expect(hatRecht(kiosk, "person.bearbeiten")).toBe(false); expect(hatRecht(kiosk, "system.einstellungen")).toBe(false); expect(hatRecht(kiosk, "kasse.lesen")).toBe(false); }); it("verweigert ohne Sitzung alles", () => { for (const recht of ALLE_RECHTE) { expect(hatRecht(null, recht)).toBe(false); } }); it("addiert die Rechte mehrerer Rollen", () => { const doppelrolle = sitzung(["GERAETEWART", "KASSIER"]); expect(hatRecht(doppelrolle, "geraet.bearbeiten")).toBe(true); expect(hatRecht(doppelrolle, "kasse.bearbeiten")).toBe(true); }); }); describe("rechteVon", () => { it("liefert für den Administrator den vollständigen Katalog", () => { expect(rechteVon(["SUPERADMIN"]).size).toBe(ALLE_RECHTE.length); }); it("liefert für das Terminal deutlich weniger als für den Kommandanten", () => { expect(rechteVon(["KIOSK"]).size).toBeLessThan(rechteVon(["KOMMANDANT"]).size); }); }); describe("hatEinesDerRechte / hatAlleRechte", () => { const geraetewart = sitzung(["GERAETEWART"]); it("genügt ein Treffer bei hatEinesDerRechte", () => { expect(hatEinesDerRechte(geraetewart, ["kasse.bearbeiten", "geraet.bearbeiten"])).toBe( true, ); }); it("verlangt hatAlleRechte jeden einzelnen Treffer", () => { expect(hatAlleRechte(geraetewart, ["kasse.bearbeiten", "geraet.bearbeiten"])).toBe(false); expect(hatAlleRechte(geraetewart, ["geraet.lesen", "geraet.bearbeiten"])).toBe(true); }); }); describe("verlangeRecht", () => { it("lässt durch, wenn das Recht vorliegt", () => { expect(() => verlangeRecht(sitzung(["KOMMANDANT"]), "person.bearbeiten")).not.toThrow(); }); it("wirft KeinZugriffFehler, wenn das Recht fehlt", () => { expect(() => verlangeRecht(sitzung(["MITGLIED"]), "person.bearbeiten")).toThrow( KeinZugriffFehler, ); }); it("nennt im Fehler das fehlende Recht", () => { try { verlangeRecht(sitzung(["MITGLIED"]), "kasse.abschluss"); expect.unreachable("hätte werfen müssen"); } catch (fehler) { expect(fehler).toBeInstanceOf(KeinZugriffFehler); expect((fehler as Error).message).toContain("kasse.abschluss"); } }); }); describe("Mandantentrennung", () => { it("erkennt Datensätze der eigenen Wehr", () => { expect(gehoertZurOrganisation(sitzung(["MITGLIED"], "org-1"), { organisationId: "org-1" })).toBe( true, ); }); it("weist Datensätze einer fremden Wehr ab — auch dem Administrator", () => { expect( gehoertZurOrganisation(sitzung(["SUPERADMIN"], "org-1"), { organisationId: "org-2" }), ).toBe(false); }); it("weist null-Datensätze ab", () => { expect(gehoertZurOrganisation(sitzung(["KOMMANDANT"]), null)).toBe(false); }); }); describe("darfPersonSehen", () => { it("lässt jeden die eigenen Daten sehen, auch ohne person.lesen", () => { const kiosk = sitzung(["KIOSK"]); expect(hatRecht(kiosk, "person.lesen")).toBe(false); expect(darfPersonSehen(kiosk, kiosk.personId)).toBe(true); }); it("verlangt für fremde Personen das Recht person.lesen", () => { expect(darfPersonSehen(sitzung(["KIOSK"]), "andere-person")).toBe(false); expect(darfPersonSehen(sitzung(["MITGLIED"]), "andere-person")).toBe(true); }); }); describe("alsRolle", () => { it("erkennt Rollen unabhängig von Groß- und Kleinschreibung", () => { expect(alsRolle("kommandant")).toBe("KOMMANDANT"); expect(alsRolle(" GeraeteWart ")).toBe("GERAETEWART"); }); it("gibt bei Unsinn null zurück", () => { expect(alsRolle("oberbrandmeister")).toBeNull(); expect(alsRolle("")).toBeNull(); expect(alsRolle(null)).toBeNull(); }); }); describe("Kreisaufsicht", () => { /** * Der wichtigste Test dieser Datei. * * Die Kreisebene sieht Wehren, denen sie nicht angehört. Bekäme sie dort * versehentlich ein Schreibrecht, wäre das kein Bedienfehler, sondern ein * Loch in der Mandantentrennung — und zwar eines, das niemandem auffällt, * weil in der Oberfläche alles normal aussieht. * * Die Zusicherung ist deshalb bewusst allgemein formuliert: nicht "diese * zwölf Rechte", sondern "kein einziges, das nicht lesen heißt". So gilt * sie auch für Module, die es noch gar nicht gibt. */ it("hat ausschließlich Leserechte — auch für künftige Module", () => { const rechte = [...rechteVon(["AUFSICHT"])]; expect(rechte.length).toBeGreaterThan(0); expect(rechte.filter((recht) => !recht.endsWith(".lesen"))).toEqual([]); }); it("sieht die Kasse nicht", () => { // Sie gehört dem Verein oder der Kommune, nicht der Kreisaufsicht — und // wäre nur deshalb dabei, weil das Recht zufällig auf ".lesen" endet. expect(hatRecht(aufsicht(), "kasse.lesen")).toBe(false); }); it("sieht weder Audit-Log noch KI-Protokoll", () => { // Wer wann was getan hat, bleibt innerhalb der Wehr. expect(hatRecht(aufsicht(), "system.auditlog")).toBe(false); expect(hatRecht(aufsicht(), "ki.protokoll")).toBe(false); }); it("kann keine Personendaten exportieren", () => { // Das Auskunftsrecht nach DSGVO erfüllt die Wehr, die die Daten führt. expect(hatRecht(aufsicht(), "person.export")).toBe(false); }); it("sieht den Atemschutz — dafür gibt es sie", () => { expect(hatRecht(aufsicht(), "atemschutz.lesen")).toBe(true); expect(hatRecht(aufsicht(), "atemschutz.bearbeiten")).toBe(false); expect(hatRecht(aufsicht(), "atemschutz.ueberwachung")).toBe(false); }); it("hat in dieser Wehr keine eigene Akte", () => { // Daran hängt, dass sie in keiner Mitgliederliste auftaucht und als // Urheber keiner Änderung eingetragen werden kann. expect(aufsicht().personId).toBeNull(); expect(aufsicht().nurLesend).toBe(true); }); it("sieht fremde Personen nur über person.lesen, nie über die eigene Akte", () => { // `darfPersonSehen` lässt jeden die eigenen Daten sehen. Ohne Akte darf // dieser Weg nicht versehentlich greifen: sonst entschiede ein Vergleich // von null mit null darüber, wer wen sieht. const kreis = aufsicht(); expect(darfPersonSehen(kreis, "irgendeine-person")).toBe(true); expect(hatRecht(kreis, "person.lesen")).toBe(true); }); }); describe("Rollenkatalog und Mandantentrennung", () => { it("führt AUFSICHT nicht als vergebbare Rolle in der Datenbank", () => { // Sie ergibt sich aus der Mitgliedschaftsart, nicht aus einer Zuweisung. // Wäre sie vergebbar, könnte jemand innerhalb einer Wehr Rechte an einer // anderen verteilen. expect(RolleSchluessel).not.toHaveProperty("AUFSICHT"); }); });