„Was ist eigentlich mit meinem Notebook passiert?“ musste bisher ein Techniker nachsehen. Ein Mitarbeiter kann die Arbeiten an **seinen** Geraeten jetzt selbst ansehen — mit der fuenften Rolle MITARBEITER, zugeordnet beim Anmelden ueber den UPN. Was er sieht, sind Titel, Zeitraum und Status. Keine Schritte, keine Notizen, keine Kommentare, keine Anhaenge: in einem Lauf stehen Arbeitsnotizen ueber Menschen und Befehle mit Platzhaltern fuer Zugangsdaten. Eine Selbstauskunft ist keine Akteneinsicht, und das haelt nicht die Seite fest, sondern das Lesemodell — eine Seite, die filtert, ist eine Seite, die es vergessen kann. `selbstauskunft` ist ein **Ersatz** fuer `lesen` und kein Zusatz; auch ein ADMIN hat es nicht. Ein zweiter Weg zu denselben Daten waere eine zweite Stelle, an der jemand die Einschraenkung auf die eigene Person vergisst. Die Datenbank verlangt das sogar: ein CHECK haelt fest, dass Rolle und Zuordnung zusammengehoeren, in beide Richtungen. Ein mehrdeutiger UPN wird abgewiesen und protokolliert. Zwei Kunden koennen dieselbe Person fuehren; ein findFirst entschiede nach Sortierung darueber, wessen Geraete jemand sieht. **Verhaltensaenderung:** wer sich anmeldet und in keiner Rollengruppe steht, bekommt nicht mehr automatisch LESER — also nicht mehr Lesezugriff auf alle Laeufe des Hauses. Das greift nur mit konfigurierten OIDC_GROUP_*; ohne sie bleibt alles wie vorher, denn ohne abgebildete Gruppen laesst sich Techniker nicht von Mitarbeiter unterscheiden. Nebenbei behoben: `staerksteRolle` benutzte LESER als Startwert und damit als Untergrenze — mit einer Rolle darunter lieferte sie mehr Rechte als uebergeben. Gefunden von einem Test, der seit #007 unveraendert dasteht. Closes #55 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
471 lines
19 KiB
TypeScript
471 lines
19 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||
import {
|
||
AnmeldeFehler,
|
||
aktivSetzen,
|
||
benutzerAusOidcUebernehmen,
|
||
lokalAnmelden,
|
||
lokalenBenutzerAnlegen,
|
||
lokalerLoginAktiv,
|
||
passwortHashen,
|
||
rolleSetzen,
|
||
} from "@/server/auth/benutzer";
|
||
import type { OidcAnspruecke } from "@/server/auth/oidc";
|
||
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
|
||
|
||
/**
|
||
* Anmeldung und Benutzerpflege gegen die echte Datenbank (#007).
|
||
*
|
||
* Hier haengen mehrere Akzeptanzkriterien dran: JIT-Provisioning, dass die Rolle
|
||
* bei jeder Anmeldung neu gesetzt wird, dass ein deaktiviertes Konto abgewiesen
|
||
* wird und dass Passwoerter nur als Hash in der Datenbank stehen.
|
||
*/
|
||
|
||
const UMGEBUNG = { ...process.env };
|
||
|
||
/**
|
||
* Standard-Anspruechge **mit** einer Rollengruppe.
|
||
*
|
||
* Seit #035 ist „angemeldet, aber in keiner Rollengruppe“ kein stilles `LESER`
|
||
* mehr, sondern fuehrt in die Selbstauskunft — und ohne passende Person im
|
||
* Verzeichnis wird die Anmeldung abgewiesen. Die Tests hier pruefen
|
||
* JIT-Provisioning, Protokoll und Kundenbindung; sie sollen nicht nebenbei an
|
||
* dieser Weiche haengen. Wer die Weiche selbst prueft, findet das in
|
||
* `tests/selbstauskunft.test.ts`.
|
||
*/
|
||
function anspruecke(ueberschreiben: Partial<OidcAnspruecke> = {}): OidcAnspruecke {
|
||
return {
|
||
sub: "anbieter-sub-1",
|
||
email: "maria.huber@firma.example",
|
||
emailBestaetigt: true,
|
||
name: "Maria Huber",
|
||
gruppen: ["lz-techniker"],
|
||
// Der UPN haengt an der Selbstauskunft (#035); im AD ist er oft nicht die
|
||
// Mailadresse, deshalb hier bewusst ein anderer Wert.
|
||
upn: "mhuber@firma.local",
|
||
ziel: "/",
|
||
...ueberschreiben,
|
||
};
|
||
}
|
||
|
||
describe.runIf(datenbankVerfuegbar)("Anmeldung und Benutzerpflege", () => {
|
||
beforeEach(async () => {
|
||
await datenbankLeeren();
|
||
process.env["AUTH_LOCAL_ENABLED"] = "true";
|
||
process.env["OIDC_GROUP_ADMIN"] = "lz-admin";
|
||
process.env["OIDC_GROUP_EDITOR"] = "lz-redakteur";
|
||
process.env["OIDC_GROUP_TECHNICIAN"] = "lz-techniker";
|
||
process.env["OIDC_GROUP_VIEWER"] = "lz-leser";
|
||
delete process.env["OIDC_CUSTOMER_GROUP_PREFIX"];
|
||
});
|
||
|
||
afterEach(() => {
|
||
process.env = { ...UMGEBUNG };
|
||
});
|
||
|
||
describe("JIT-Provisioning", () => {
|
||
it("legt einen unbekannten Benutzer beim ersten Login an", async () => {
|
||
const benutzer = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-techniker"] }));
|
||
|
||
expect(benutzer.email).toBe("maria.huber@firma.example");
|
||
expect(benutzer.name).toBe("Maria Huber");
|
||
expect(benutzer.rolle).toBe("TECHNIKER");
|
||
expect(benutzer.aktiv).toBe(true);
|
||
expect(benutzer.passwortHash).toBeNull();
|
||
expect(await testPrisma.benutzer.count()).toBe(1);
|
||
});
|
||
|
||
it("haelt den Vorgang im Protokoll fest", async () => {
|
||
const benutzer = await benutzerAusOidcUebernehmen(anspruecke());
|
||
const eintraege = await testPrisma.protokoll.findMany({
|
||
where: { entitaet: "Benutzer", entitaetId: benutzer.id },
|
||
});
|
||
expect(eintraege).toHaveLength(1);
|
||
expect(eintraege[0]?.aktion).toContain("angelegt");
|
||
});
|
||
|
||
it("legt beim zweiten Login kein zweites Konto an", async () => {
|
||
await benutzerAusOidcUebernehmen(anspruecke());
|
||
await benutzerAusOidcUebernehmen(anspruecke());
|
||
expect(await testPrisma.benutzer.count()).toBe(1);
|
||
});
|
||
|
||
it("erkennt den Benutzer an der Kennung wieder, auch wenn die Adresse wechselt", async () => {
|
||
// Die Adresse eines Kontos kann sich aendern, die Kennung beim Anbieter nicht.
|
||
const erst = await benutzerAusOidcUebernehmen(anspruecke());
|
||
const dann = await benutzerAusOidcUebernehmen(
|
||
anspruecke({ email: "m.huber@firma.example", name: "Maria Huber-Schmitt" }),
|
||
);
|
||
expect(dann.id).toBe(erst.id);
|
||
expect(dann.email).toBe("m.huber@firma.example");
|
||
expect(dann.name).toBe("Maria Huber-Schmitt");
|
||
expect(await testPrisma.benutzer.count()).toBe(1);
|
||
});
|
||
|
||
it("verknuepft nicht ueber eine unbestaetigte Adresse", async () => {
|
||
// Sonst waere ein Anbieter mit frei waehlbarer Adresse der Weg, den
|
||
// lokalen Notfall-Administrator zu uebernehmen.
|
||
await testPrisma.benutzer.create({
|
||
data: {
|
||
email: "maria.huber@firma.example",
|
||
name: "Maria Huber",
|
||
rolle: "ADMIN",
|
||
passwortHash: await passwortHashen("ein-langes-passwort"),
|
||
},
|
||
});
|
||
await expect(benutzerAusOidcUebernehmen(anspruecke({ emailBestaetigt: false }))).rejects.toBeInstanceOf(
|
||
AnmeldeFehler,
|
||
);
|
||
// Das bestehende Konto bleibt unangetastet.
|
||
const unveraendert = await testPrisma.benutzer.findUniqueOrThrow({
|
||
where: { email: "maria.huber@firma.example" },
|
||
});
|
||
expect(unveraendert.oidcSub).toBeNull();
|
||
expect(unveraendert.rolle).toBe("ADMIN");
|
||
});
|
||
|
||
it("uebernimmt ein bestehendes lokales Konto mit derselben Adresse", async () => {
|
||
const lokal = await testPrisma.benutzer.create({
|
||
data: {
|
||
email: "maria.huber@firma.example",
|
||
name: "Maria Huber",
|
||
rolle: "LESER",
|
||
passwortHash: await passwortHashen("ein-langes-passwort"),
|
||
},
|
||
});
|
||
const nachOidc = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-admin"] }));
|
||
|
||
expect(nachOidc.id).toBe(lokal.id);
|
||
expect(nachOidc.oidcSub).toBe("anbieter-sub-1");
|
||
// Der Notfallzugang bleibt erhalten.
|
||
expect(nachOidc.passwortHash).not.toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("Rolle aus den Gruppen", () => {
|
||
it("setzt die Rolle bei jeder Anmeldung neu", async () => {
|
||
const erst = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-techniker"] }));
|
||
expect(erst.rolle).toBe("TECHNIKER");
|
||
|
||
const befoerdert = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-admin"] }));
|
||
expect(befoerdert.rolle).toBe("ADMIN");
|
||
|
||
/*
|
||
* Und wieder zurueck. **Seit #035 ist das kein `LESER` mehr:** wem die
|
||
* Rollengruppe entzogen wird und wer zu keiner Person im Verzeichnis
|
||
* passt, kommt nicht mehr herein.
|
||
*
|
||
* Das ist strenger als vorher und Absicht. Vorher behielt ein
|
||
* ausgeschiedener Techniker Lesezugriff auf **alle** Laeufe des Hauses,
|
||
* solange niemand sein Konto deaktivierte — und genau das vergisst man.
|
||
* Der Notfallzugang dagegen bleibt: `AUTH_LOCAL_ENABLED` haengt nicht an
|
||
* den Gruppen.
|
||
*/
|
||
await expect(benutzerAusOidcUebernehmen(anspruecke({ gruppen: [] }))).rejects.toThrow(AnmeldeFehler);
|
||
|
||
// Die Rolle in der Datenbank bleibt dabei, was sie war – abgewiesen heisst
|
||
// abgewiesen, nicht herabgestuft.
|
||
const unveraendert = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: erst.id } });
|
||
expect(unveraendert.rolle).toBe("ADMIN");
|
||
});
|
||
|
||
it("protokolliert jede Rollenaenderung aus den Gruppen", async () => {
|
||
const benutzer = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-techniker"] }));
|
||
await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-admin"] }));
|
||
|
||
const aenderungen = await testPrisma.protokoll.findMany({
|
||
where: { entitaetId: benutzer.id, aktion: { contains: "Rolle" } },
|
||
});
|
||
expect(aenderungen).toHaveLength(1);
|
||
expect(aenderungen[0]?.vorher).toEqual({ rolle: "TECHNIKER" });
|
||
expect(aenderungen[0]?.nachher).toEqual({ rolle: "ADMIN" });
|
||
});
|
||
|
||
it("weist ein deaktiviertes Konto ab, auch bei gueltiger Anmeldung beim Anbieter", async () => {
|
||
const benutzer = await benutzerAusOidcUebernehmen(anspruecke());
|
||
await testPrisma.benutzer.update({ where: { id: benutzer.id }, data: { aktiv: false } });
|
||
|
||
await expect(benutzerAusOidcUebernehmen(anspruecke())).rejects.toBeInstanceOf(AnmeldeFehler);
|
||
});
|
||
|
||
it("schreibt nichts, bevor ein deaktiviertes Konto abgewiesen wird", async () => {
|
||
const benutzer = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-admin"] }));
|
||
await testPrisma.benutzer.update({
|
||
where: { id: benutzer.id },
|
||
data: { aktiv: false, letzteAnmeldungAm: null },
|
||
});
|
||
|
||
await expect(benutzerAusOidcUebernehmen(anspruecke())).rejects.toBeInstanceOf(AnmeldeFehler);
|
||
|
||
const danach = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: benutzer.id } });
|
||
expect(danach.letzteAnmeldungAm).toBeNull();
|
||
expect(danach.rolle).toBe("ADMIN");
|
||
});
|
||
|
||
it("laesst die Rolle unangetastet, wenn keine Gruppen-Zuordnung eingerichtet ist", async () => {
|
||
// Sonst waere jede OIDC-Anmeldung eine stille Herabstufung auf LESER – bei
|
||
// einem einzigen Administrator koennte danach niemand mehr etwas richten.
|
||
const admin = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-admin"] }));
|
||
expect(admin.rolle).toBe("ADMIN");
|
||
|
||
for (const name of [
|
||
"OIDC_GROUP_ADMIN",
|
||
"OIDC_GROUP_EDITOR",
|
||
"OIDC_GROUP_TECHNICIAN",
|
||
"OIDC_GROUP_VIEWER",
|
||
]) {
|
||
delete process.env[name];
|
||
}
|
||
|
||
const nochmal = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: [] }));
|
||
expect(nochmal.rolle).toBe("ADMIN");
|
||
});
|
||
});
|
||
|
||
describe("Kundenbindung", () => {
|
||
/*
|
||
* **Rollengruppe und Kundengruppe sind zwei Dinge** (#035). Diese Tests gaben
|
||
* vorher nur `lz-kunde-MUS` mit und bekamen dadurch stillschweigend `LESER`.
|
||
* Seit #035 fuehrt „keine Rollengruppe“ in die Selbstauskunft — eine
|
||
* Kundengruppe allein sagt eben nichts darueber, was jemand duerfen soll.
|
||
* Wer lesen soll, gehoert in die Leser-Gruppe; dafuer gibt es
|
||
* `OIDC_GROUP_VIEWER`.
|
||
*/
|
||
it("bindet den Benutzer an die Kunden aus seinen Gruppen", async () => {
|
||
process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = "lz-kunde-";
|
||
const mus = await testPrisma.kunde.create({ data: { name: "Musterfirma", kuerzel: "MUS" } });
|
||
await testPrisma.kunde.create({ data: { name: "Baeckerei Reiter", kuerzel: "REI" } });
|
||
|
||
const benutzer = await benutzerAusOidcUebernehmen(
|
||
anspruecke({ gruppen: ["lz-leser", "lz-kunde-MUS"] }),
|
||
);
|
||
const bindungen = await testPrisma.benutzerKunde.findMany({
|
||
where: { benutzerId: benutzer.id },
|
||
});
|
||
expect(bindungen).toHaveLength(1);
|
||
expect(bindungen[0]?.kundeId).toBe(mus.id);
|
||
});
|
||
|
||
it("schreibt die Bindung bei jeder Anmeldung neu", async () => {
|
||
process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = "lz-kunde-";
|
||
await testPrisma.kunde.create({ data: { name: "Musterfirma", kuerzel: "MUS" } });
|
||
const rei = await testPrisma.kunde.create({
|
||
data: { name: "Baeckerei Reiter", kuerzel: "REI" },
|
||
});
|
||
|
||
const benutzer = await benutzerAusOidcUebernehmen(
|
||
anspruecke({ gruppen: ["lz-leser", "lz-kunde-MUS"] }),
|
||
);
|
||
await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["lz-leser", "lz-kunde-REI"] }));
|
||
|
||
const bindungen = await testPrisma.benutzerKunde.findMany({
|
||
where: { benutzerId: benutzer.id },
|
||
});
|
||
expect(bindungen).toHaveLength(1);
|
||
expect(bindungen[0]?.kundeId).toBe(rei.id);
|
||
});
|
||
|
||
it("bindet ohne Praefix niemanden ein", async () => {
|
||
await testPrisma.kunde.create({ data: { name: "Musterfirma", kuerzel: "MUS" } });
|
||
const benutzer = await benutzerAusOidcUebernehmen(
|
||
anspruecke({ gruppen: ["lz-leser", "lz-kunde-MUS"] }),
|
||
);
|
||
expect(await testPrisma.benutzerKunde.count({ where: { benutzerId: benutzer.id } })).toBe(0);
|
||
});
|
||
});
|
||
|
||
describe("Lokale Anmeldung", () => {
|
||
const PASSWORT = "ein-hinreichend-langes-passwort";
|
||
|
||
async function kontoAnlegen(email = "admin@firma.example") {
|
||
return testPrisma.benutzer.create({
|
||
data: {
|
||
email,
|
||
name: "Administrator",
|
||
rolle: "ADMIN",
|
||
passwortHash: await passwortHashen(PASSWORT),
|
||
},
|
||
});
|
||
}
|
||
|
||
it("laesst ein richtiges Passwort durch", async () => {
|
||
const konto = await kontoAnlegen();
|
||
const angemeldet = await lokalAnmelden("admin@firma.example", PASSWORT);
|
||
expect(angemeldet.id).toBe(konto.id);
|
||
});
|
||
|
||
it("merkt sich den Zeitpunkt der Anmeldung", async () => {
|
||
await kontoAnlegen();
|
||
const angemeldet = await lokalAnmelden("admin@firma.example", PASSWORT);
|
||
expect(angemeldet.letzteAnmeldungAm).toBeNull();
|
||
|
||
const frisch = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: angemeldet.id } });
|
||
expect(frisch.letzteAnmeldungAm).not.toBeNull();
|
||
});
|
||
|
||
it("nimmt die Adresse unabhaengig von Gross- und Kleinschreibung", async () => {
|
||
await kontoAnlegen();
|
||
await expect(lokalAnmelden(" Admin@Firma.Example ", PASSWORT)).resolves.toBeTruthy();
|
||
});
|
||
|
||
it("weist ein falsches Passwort ab", async () => {
|
||
await kontoAnlegen();
|
||
await expect(lokalAnmelden("admin@firma.example", "falsch")).rejects.toBeInstanceOf(AnmeldeFehler);
|
||
});
|
||
|
||
it("verraet nicht, ob es die Adresse gibt", async () => {
|
||
await kontoAnlegen();
|
||
const beiFalschemPasswort = await lokalAnmelden("admin@firma.example", "falsch").catch(
|
||
(f: Error) => f.message,
|
||
);
|
||
const beiUnbekannterAdresse = await lokalAnmelden("gibtsnicht@firma.example", "egal").catch(
|
||
(f: Error) => f.message,
|
||
);
|
||
expect(beiFalschemPasswort).toBe(beiUnbekannterAdresse);
|
||
});
|
||
|
||
it("weist ein Konto ohne Passwort ab, auch mit leerem Passwort", async () => {
|
||
await testPrisma.benutzer.create({
|
||
data: { email: "nur-oidc@firma.example", name: "Nur OIDC", oidcSub: "sub-x" },
|
||
});
|
||
await expect(lokalAnmelden("nur-oidc@firma.example", "")).rejects.toBeInstanceOf(AnmeldeFehler);
|
||
});
|
||
|
||
it("weist ein deaktiviertes Konto ab", async () => {
|
||
const konto = await kontoAnlegen();
|
||
await testPrisma.benutzer.update({ where: { id: konto.id }, data: { aktiv: false } });
|
||
await expect(lokalAnmelden("admin@firma.example", PASSWORT)).rejects.toThrow(/deaktiviert/);
|
||
});
|
||
|
||
it("ist abgeschaltet, solange AUTH_LOCAL_ENABLED nicht true ist", async () => {
|
||
await kontoAnlegen();
|
||
for (const wert of ["false", "", "1", "ja"]) {
|
||
process.env["AUTH_LOCAL_ENABLED"] = wert;
|
||
expect(lokalerLoginAktiv()).toBe(false);
|
||
await expect(lokalAnmelden("admin@firma.example", PASSWORT)).rejects.toThrow(/abgeschaltet/);
|
||
}
|
||
});
|
||
|
||
it("speichert das Passwort nur als argon2-Hash", async () => {
|
||
const konto = await kontoAnlegen();
|
||
expect(konto.passwortHash).toMatch(/^\$argon2/);
|
||
expect(konto.passwortHash).not.toContain(PASSWORT);
|
||
});
|
||
});
|
||
|
||
describe("Verwaltung", () => {
|
||
async function adminAnlegen() {
|
||
return testPrisma.benutzer.create({
|
||
data: { email: "admin@firma.example", name: "Administrator", rolle: "ADMIN" },
|
||
});
|
||
}
|
||
|
||
it("legt ein lokales Konto an und protokolliert es", async () => {
|
||
const admin = await adminAnlegen();
|
||
const neu = await lokalenBenutzerAnlegen(
|
||
{
|
||
email: " Neu@Firma.Example ",
|
||
name: " Neue Kollegin ",
|
||
rolle: "TECHNIKER",
|
||
passwort: "ein-langes-passwort",
|
||
},
|
||
admin.id,
|
||
);
|
||
|
||
expect(neu.email).toBe("neu@firma.example");
|
||
expect(neu.name).toBe("Neue Kollegin");
|
||
expect(neu.passwortHash).toMatch(/^\$argon2/);
|
||
|
||
const protokoll = await testPrisma.protokoll.findFirst({
|
||
where: { entitaetId: neu.id, aktion: "lokales Konto angelegt" },
|
||
});
|
||
expect(protokoll?.benutzerId).toBe(admin.id);
|
||
});
|
||
|
||
it("weist eine doppelte Adresse ab", async () => {
|
||
const admin = await adminAnlegen();
|
||
const eingabe = {
|
||
email: "doppelt@firma.example",
|
||
name: "Doppelt",
|
||
rolle: "LESER" as const,
|
||
passwort: "ein-langes-passwort",
|
||
};
|
||
await lokalenBenutzerAnlegen(eingabe, admin.id);
|
||
await expect(lokalenBenutzerAnlegen(eingabe, admin.id)).rejects.toBeInstanceOf(AnmeldeFehler);
|
||
});
|
||
|
||
it("protokolliert eine Rollenaenderung mit Vorher und Nachher", async () => {
|
||
const admin = await adminAnlegen();
|
||
const ziel = await testPrisma.benutzer.create({
|
||
data: { email: "ziel@firma.example", name: "Ziel", rolle: "LESER" },
|
||
});
|
||
|
||
await rolleSetzen(ziel.id, "REDAKTEUR", admin.id);
|
||
|
||
const frisch = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: ziel.id } });
|
||
expect(frisch.rolle).toBe("REDAKTEUR");
|
||
const protokoll = await testPrisma.protokoll.findFirst({
|
||
where: { entitaetId: ziel.id, aktion: "Rolle geändert" },
|
||
});
|
||
expect(protokoll?.vorher).toEqual({ rolle: "LESER" });
|
||
expect(protokoll?.nachher).toEqual({ rolle: "REDAKTEUR" });
|
||
});
|
||
|
||
it("macht beim Deaktivieren zugleich alle laufenden Sitzungen ungueltig", async () => {
|
||
// Sonst koennte sich der Betroffene mit einem gueltigen Cookie noch bis zu
|
||
// zwoelf Stunden weiterbewegen.
|
||
const admin = await adminAnlegen();
|
||
const ziel = await testPrisma.benutzer.create({
|
||
data: { email: "ziel@firma.example", name: "Ziel", rolle: "TECHNIKER" },
|
||
});
|
||
|
||
await aktivSetzen(ziel.id, false, admin.id);
|
||
|
||
const frisch = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: ziel.id } });
|
||
expect(frisch.aktiv).toBe(false);
|
||
expect(frisch.sitzungenUngueltigAb).not.toBeNull();
|
||
});
|
||
|
||
it("laesst den letzten aktiven Administrator nicht herabstufen", async () => {
|
||
const admin = await adminAnlegen();
|
||
await expect(rolleSetzen(admin.id, "LESER", admin.id)).rejects.toThrow(/letzte aktive Administrator/);
|
||
const unveraendert = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: admin.id } });
|
||
expect(unveraendert.rolle).toBe("ADMIN");
|
||
});
|
||
|
||
it("laesst den letzten aktiven Administrator nicht deaktivieren", async () => {
|
||
const admin = await adminAnlegen();
|
||
await expect(aktivSetzen(admin.id, false, admin.id)).rejects.toThrow(/letzte aktive Administrator/);
|
||
});
|
||
|
||
it("erlaubt die Aenderung, sobald ein zweiter Administrator da ist", async () => {
|
||
const admin = await adminAnlegen();
|
||
await testPrisma.benutzer.create({
|
||
data: { email: "zweiter@firma.example", name: "Zweiter", rolle: "ADMIN" },
|
||
});
|
||
await rolleSetzen(admin.id, "LESER", admin.id);
|
||
const danach = await testPrisma.benutzer.findUniqueOrThrow({ where: { id: admin.id } });
|
||
expect(danach.rolle).toBe("LESER");
|
||
});
|
||
|
||
it("zaehlt einen deaktivierten Administrator nicht als Rueckweg", async () => {
|
||
const admin = await adminAnlegen();
|
||
await testPrisma.benutzer.create({
|
||
data: { email: "inaktiv@firma.example", name: "Inaktiv", rolle: "ADMIN", aktiv: false },
|
||
});
|
||
await expect(rolleSetzen(admin.id, "LESER", admin.id)).rejects.toThrow(/letzte aktive Administrator/);
|
||
});
|
||
|
||
it("schreibt nichts, wenn sich nichts aendert", async () => {
|
||
const admin = await adminAnlegen();
|
||
const ziel = await testPrisma.benutzer.create({
|
||
data: { email: "ziel@firma.example", name: "Ziel", rolle: "LESER" },
|
||
});
|
||
|
||
await rolleSetzen(ziel.id, "LESER", admin.id);
|
||
await aktivSetzen(ziel.id, true, admin.id);
|
||
|
||
expect(await testPrisma.protokoll.count({ where: { entitaetId: ziel.id } })).toBe(0);
|
||
});
|
||
});
|
||
});
|