Laufzettel läuft jetzt in zwei Betriebsmodi, aber nicht in zwei Codepfaden. Jede kundenbezogene Zeile trägt ohnehin schon eine Kunden-Zuordnung; im Modus INTERN ist das die eine Organisation, die in der Oberfläche nirgends erscheint. Der Wechsel ist deshalb eine Datenänderung und keine Migration — dieselbe Zeile bleibt, nichts hängt um. src/server/mandant.ts ist die eine Stelle, an der die Mandantenregel steht: kundeErmitteln verwirft im Modus INTERN eine mitgeschickte kundeId, verlangt im Modus DIENSTLEISTUNG eine Wahl und prüft, ob der Benutzer den Kunden überhaupt sehen darf. Die Sichtbarkeit steckte zunächst in einer zweiten Funktion daneben, an die jeder künftige Aufrufer selbst hätte denken müssen; da #009 diese Signatur übernimmt, ist sie jetzt eingebaut. Der unabhängige Review fand drei Blocker, alle behoben. Die Lesemodelle wandten die Mandantensicht nicht an. kundenListe gab jedem alle Kunden zurück, und sichtbareKundenIds hatte in der Produktion keinen einzigen Aufrufer — ein gebundener Techniker hätte Namen, Notizen und Bestandszahlen fremder Kunden gesehen. Die Funktionen verlangen jetzt den Benutzer und filtern selbst, wie CLAUDE.md es verlangt. Mein Kommentar "Alles in einer Transaktion" war falsch: der Protokolleintrag stand dahinter. Schlug er fehl, war der Modus gewechselt und der Vorgang stand nirgends. Und Prüfung und Vollzug lagen auseinander, sodass zwei gleichzeitige Wechsel beide durchliefen und ein zwischendurch angelegter Kunde zu Modus INTERN bei zwei Kunden führte. Beides reproduziert, beides geschlossen: alles in einer Transaktion, mit pg_advisory_xact_lock als erster Anweisung. Dazu sechs kleinere Befunde: ASCII-Faltung in Texten, die Menschen lesen (die Regel gilt für Bezeichner, nicht für Fließtext — steht jetzt so in CLAUDE.md); eine Erfolgsmeldung, die nach dem Wechsel unsichtbar blieb; eine Zusage im Dialog, die der Code nicht durchsetzt; fehlende Fehlerbehandlung beim Archivieren; ein Handy-Screenshot, der das Navigationsmenü statt des Dialogs zeigte. Der Wechsel wurde nicht nur im Service geprüft, sondern per Playwright durch die Oberfläche gefahren: anmelden, Dialog aufklappen, bestätigen, und danach steht die Navigation auf Kunden. 336 Tests. Closes #8 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
430 lines
17 KiB
TypeScript
430 lines
17 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 };
|
||
|
||
function anspruecke(ueberschreiben: Partial<OidcAnspruecke> = {}): OidcAnspruecke {
|
||
return {
|
||
sub: "anbieter-sub-1",
|
||
email: "maria.huber@firma.example",
|
||
emailBestaetigt: true,
|
||
name: "Maria Huber",
|
||
gruppen: [],
|
||
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: eine entzogene Gruppe entzieht die Rolle.
|
||
const entzogen = await benutzerAusOidcUebernehmen(anspruecke({ gruppen: [] }));
|
||
expect(entzogen.rolle).toBe("LESER");
|
||
});
|
||
|
||
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", () => {
|
||
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-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-kunde-MUS"] }));
|
||
await benutzerAusOidcUebernehmen(anspruecke({ gruppen: ["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-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);
|
||
});
|
||
});
|
||
});
|