feat(stammdaten): Mitarbeiter, Abteilungen und Geraete (#25)
This commit was merged in pull request #25.
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user