import { beforeEach, describe, expect, it } from "vitest"; import { Betriebsmodus } from "@/generated/prisma/enums"; import { AbteilungsFehler, abteilungAnlegen, abteilungArchivieren, abteilungBearbeiten, } from "@/server/services/abteilungen"; import { abteilungsListe, ungepflegteAbteilungen } from "@/server/queries/abteilungen"; import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db"; /** * Abteilungen (#011). * * An einer Abteilung haengen Ziel-OU und Standardgruppen — das, was die * spaeteren PowerShell-Befehle in ein Verzeichnis schreiben (#012). Deshalb wird * hier genauer geprueft als bei einer blossen Beschriftung. */ async function grundbestand() { const eigene = await testPrisma.kunde.create({ data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true }, }); await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.INTERN } }); const benutzer = await testPrisma.benutzer.create({ data: { email: "admin@firma.example", name: "Admin", rolle: "ADMIN" }, }); return { eigene, admin: { id: benutzer.id, rolle: benutzer.rolle } }; } describe.runIf(datenbankVerfuegbar)("Abteilungen", () => { beforeEach(async () => { await datenbankLeeren(); }); describe("Anlegen", () => { it("legt Name, Ziel-OU, Gruppen und Mail-Domain an", async () => { const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen( { name: " Buchhaltung ", zielOu: "OU=Buchhaltung,OU=Benutzer,DC=firma,DC=de", standardGruppen: ["G-Buchhaltung", "G-Alle"], mailDomain: "firma.de", }, admin, ); expect(abteilung.name).toBe("Buchhaltung"); expect(abteilung.standardGruppen).toEqual(["G-Buchhaltung", "G-Alle"]); expect(abteilung.mailDomain).toBe("firma.de"); }); it("weist denselben Namen ein zweites Mal ab, mit lesbarer Meldung", async () => { const { admin } = await grundbestand(); await abteilungAnlegen({ name: "Buchhaltung" }, admin); await expect(abteilungAnlegen({ name: "Buchhaltung" }, admin)).rejects.toThrow(/gibt es schon/); }); it("räumt leere und doppelte Gruppen weg, behält die Reihenfolge", async () => { // Die Reihenfolge landet spaeter im Add-ADGroupMember-Befehl; sie ist // keine Menge, sondern eine Liste. const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen( { name: "Vertrieb", standardGruppen: ["G-Alle", " ", "G-Vertrieb", "G-Alle", ""] }, admin, ); expect(abteilung.standardGruppen).toEqual(["G-Alle", "G-Vertrieb"]); }); it("weist eine Mail-Adresse als Domain ab", async () => { const { admin } = await grundbestand(); await expect(abteilungAnlegen({ name: "Vertrieb", mailDomain: "max@firma.de" }, admin)).rejects.toThrow( /Mail-Domain/, ); await expect(abteilungAnlegen({ name: "Einkauf", mailDomain: "firma" }, admin)).rejects.toThrow( AbteilungsFehler, ); }); it("nimmt eine Domain mit führendem @ an und normalisiert sie", async () => { const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen({ name: "IT", mailDomain: "@Firma.DE" }, admin); expect(abteilung.mailDomain).toBe("firma.de"); }); it("weist einen leeren Namen ab", async () => { const { admin } = await grundbestand(); await expect(abteilungAnlegen({ name: " " }, admin)).rejects.toThrow(AbteilungsFehler); expect(await testPrisma.abteilung.count()).toBe(0); }); }); describe("Bearbeiten", () => { it("schreibt nichts, wenn sich nichts ändert", async () => { const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen({ name: "Buchhaltung" }, admin); const vorher = await testPrisma.protokoll.count(); await abteilungBearbeiten(abteilung.id, { name: "Buchhaltung" }, admin); expect(await testPrisma.protokoll.count()).toBe(vorher); }); it("erkennt eine geänderte Gruppenreihenfolge als Änderung", async () => { const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen( { name: "Buchhaltung", standardGruppen: ["G-Alle", "G-Buchhaltung"] }, admin, ); const nachher = await abteilungBearbeiten( abteilung.id, { standardGruppen: ["G-Buchhaltung", "G-Alle"] }, admin, ); expect(nachher.standardGruppen).toEqual(["G-Buchhaltung", "G-Alle"]); expect(await testPrisma.protokoll.count({ where: { aktion: "Standardgruppen geändert" } })).toBe(1); }); it("weist einen Namen ab, den es schon gibt", async () => { const { admin } = await grundbestand(); await abteilungAnlegen({ name: "Buchhaltung" }, admin); const vertrieb = await abteilungAnlegen({ name: "Vertrieb" }, admin); await expect(abteilungBearbeiten(vertrieb.id, { name: "Buchhaltung" }, admin)).rejects.toThrow( /gibt es schon/, ); }); }); describe("Archivieren statt löschen", () => { it("archiviert und gibt wieder frei", async () => { const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen({ name: "Buchhaltung" }, admin); expect((await abteilungArchivieren(abteilung.id, true, admin)).archiviert).toBe(true); expect((await abteilungArchivieren(abteilung.id, false, admin)).archiviert).toBe(false); }); it("bietet gar keinen Löschweg an", async () => { const dienst = await import("@/server/services/abteilungen"); expect(Object.keys(dienst).filter((name) => /loesch|entfern|delete/iu.test(name))).toEqual([]); }); it("blendet archivierte auf Wunsch aus", async () => { const { admin } = await grundbestand(); const abteilung = await abteilungAnlegen({ name: "Buchhaltung" }, admin); await abteilungArchivieren(abteilung.id, true, admin); expect(await abteilungsListe(admin, false)).toEqual([]); expect(await abteilungsListe(admin, true)).toHaveLength(1); }); }); describe("Zuordnung über den Namen", () => { it("zählt nur aktive Personen mit passendem Abteilungsnamen", async () => { const { admin, eigene } = await grundbestand(); await abteilungAnlegen({ name: "Buchhaltung" }, admin); for (const [name, aktiv] of [ ["Maria", true], ["Jens", true], ["Ex", false], ] as const) { await testPrisma.mitarbeiter.create({ data: { kundeId: eigene.id, vorname: name, nachname: "X", anzeigename: name, abteilung: "Buchhaltung", aktiv, }, }); } const zeile = (await abteilungsListe(admin))[0]!; expect(zeile.anzahlMitarbeiter).toBe(2); }); it("meldet Abteilungsnamen, die es bei Personen gibt, aber nicht gepflegt sind", async () => { // Das ist der Vorschlag nach einem Verzeichnisabgleich: der Name steht // schon in den Daten, es fehlen nur Ziel-OU und Gruppen. const { admin, eigene } = await grundbestand(); await abteilungAnlegen({ name: "Buchhaltung" }, admin); for (const abteilung of ["Buchhaltung", "Vertrieb", "Vertrieb"]) { await testPrisma.mitarbeiter.create({ data: { kundeId: eigene.id, vorname: "P", nachname: "X", anzeigename: "P X", abteilung }, }); } expect(await ungepflegteAbteilungen(admin)).toEqual([ { name: "Vertrieb", anzahl: 2, kundeId: eigene.id }, ]); }); }); describe("Mandantensicht", () => { async function zweiKunden() { const eigene = await testPrisma.kunde.create({ data: { name: "Eigene", kuerzel: "EIGEN", istEigeneOrganisation: true }, }); const meiner = await testPrisma.kunde.create({ data: { name: "Meiner", kuerzel: "MEIN" } }); const fremder = await testPrisma.kunde.create({ data: { name: "Fremder", kuerzel: "FREMD" } }); await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.DIENSTLEISTUNG }, }); const techniker = await testPrisma.benutzer.create({ data: { email: "t@firma.example", name: "Techniker", rolle: "TECHNIKER" }, }); await testPrisma.benutzerKunde.create({ data: { benutzerId: techniker.id, kundeId: meiner.id } }); process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = "lz-kunde-"; await testPrisma.abteilung.create({ data: { kundeId: meiner.id, name: "Meine Abteilung" } }); const fremde = await testPrisma.abteilung.create({ data: { kundeId: fremder.id, name: "Fremde Abteilung", zielOu: "OU=Geheim" }, }); return { eigene, techniker: { id: techniker.id, rolle: techniker.rolle }, fremde }; } it("zeigt einem gebundenen Techniker keine fremde Abteilung", async () => { const { techniker } = await zweiKunden(); expect((await abteilungsListe(techniker)).map((z) => z.name)).toEqual(["Meine Abteilung"]); }); it("lässt eine fremde Abteilung nicht bearbeiten", async () => { const { techniker, fremde } = await zweiKunden(); await expect(abteilungBearbeiten(fremde.id, { zielOu: "OU=Anders" }, techniker)).rejects.toThrow( /Berechtigung/, ); }); it("hält bei ungepflegten Abteilungen die Kunden auseinander", async () => { /* * Der Blocker aus dem Review: gruppiert wurde nach Namen allein. Damit galt * „Vertrieb“ als gepflegt, sobald **irgendein** Kunde eine Abteilung dieses * Namens hatte — und beim Kunden ohne sie blieben `{{ou}}` und * `{{gruppen}}` im Lauf leer, ohne dass die Seite es je sagte. */ const { eigene } = await zweiKunden(); const admin = await testPrisma.benutzer.create({ data: { email: "a@firma.example", name: "Admin", rolle: "ADMIN" }, }); const handelnder = { id: admin.id, rolle: admin.rolle }; const meiner = await testPrisma.kunde.findFirstOrThrow({ where: { kuerzel: "MEIN" } }); const fremder = await testPrisma.kunde.findFirstOrThrow({ where: { kuerzel: "FREMD" } }); // Nur der eine Kunde pflegt „Vertrieb“. await testPrisma.abteilung.create({ data: { kundeId: meiner.id, name: "Vertrieb" } }); for (const kundeId of [meiner.id, fremder.id, fremder.id]) { await testPrisma.mitarbeiter.create({ data: { kundeId, vorname: "P", nachname: "X", anzeigename: "P X", abteilung: "Vertrieb" }, }); } const offen = await ungepflegteAbteilungen(handelnder); expect(offen).toEqual([{ name: "Vertrieb", anzahl: 2, kundeId: fremder.id }]); expect(offen.some((eintrag) => eintrag.kundeId === meiner.id)).toBe(false); expect(eigene.id).toBeTruthy(); }); it("lässt eine fremde Abteilung nicht archivieren", async () => { const { techniker, fremde } = await zweiKunden(); await expect(abteilungArchivieren(fremde.id, true, techniker)).rejects.toThrow(/Berechtigung/); }); it("erlaubt denselben Abteilungsnamen bei zwei Kunden", async () => { // Der Eindeutigkeitsindex geht ueber (kundeId, name) – „Buchhaltung“ gibt // es bei jedem Kunden genau einmal, und das ist richtig so. const { eigene } = await zweiKunden(); const zweiter = await testPrisma.kunde.findFirstOrThrow({ where: { kuerzel: "MEIN" } }); await testPrisma.abteilung.create({ data: { kundeId: eigene.id, name: "Buchhaltung" } }); await expect( testPrisma.abteilung.create({ data: { kundeId: zweiter.id, name: "Buchhaltung" } }), ).resolves.toBeTruthy(); }); }); });