Files
Laufzettel/tests/abteilungsvorschlaege.test.ts
T
Florian WildeandClaude Opus 5 16059e7846
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
feat(verzeichnis): LDAP-Abgleich, Zeitplan, CSV-Import und Log
Quelle anlegen, Verbindung testen, nach Zeitplan oder auf Knopfdruck
abgleichen. Gelesen wird seitenweise mit searchPaginated, objectGUID
kommt als Puffer und wird gemischt-endian zur GUID umsortiert. Wer im
Verzeichnis verschwindet, wird hier inaktiv und nie geloescht.

Drei Regeln verhindern, dass eine kaputte Eingabe wie "alle sind
gegangen" aussieht: laesst sich kein einziger Eintrag zuordnen, wird
niemand deaktiviert; uebersprungene Eintraege werden am dn erkannt und
ausgenommen; eine von Hand angelegte Person wird uebernommen statt
verdoppelt.

Der Zeitplan laeuft ueber node-cron im selben Prozess, gestartet aus
src/instrumentation.ts, und nimmt keine eigene Sperre - die liegt in der
Datenbank, damit zwei Instanzen sich einig sind.

Closes #16

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 02:21:08 +02:00

149 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it } from "vitest";
import { Betriebsmodus, Herkunft } from "@/generated/prisma/enums";
import { abteilungAnlegen } from "@/server/services/abteilungen";
import { ungepflegteAbteilungen } from "@/server/queries/abteilungen";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
/**
* Abteilungsvorschläge (#016).
*
* Die Liste, aus der ein Administrator mit einem Klick echte `Abteilung`-Einträge
* macht. Jeder Eintrag ist damit ein Versprechen: der Klick muss gelingen.
* Deshalb steht hier nichts, was `abteilungAnlegen` danach abweisen würde — keine
* bereits (auch archiviert) gepflegte Abteilung und kein archivierter Kunde.
*/
async function grundbestand(modus: Betriebsmodus = Betriebsmodus.INTERN) {
const eigene = await testPrisma.kunde.create({
data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true },
});
await testPrisma.einstellung.create({ data: { id: "global", modus } });
const benutzer = await testPrisma.benutzer.create({
data: { email: "admin@firma.example", name: "Administrator", rolle: "ADMIN" },
});
return { eigene, admin: { id: benutzer.id, rolle: benutzer.rolle } };
}
async function person(
kundeId: string,
abteilung: string,
weiteres: { aktiv?: boolean; herkunft?: Herkunft } = {},
) {
const nummer = await testPrisma.mitarbeiter.count();
return testPrisma.mitarbeiter.create({
data: {
kundeId,
vorname: "Person",
nachname: `Nummer ${nummer}`,
anzeigename: `Person Nummer ${nummer}`,
abteilung,
...weiteres,
},
});
}
describe.runIf(datenbankVerfuegbar)("Abteilungsvorschläge", () => {
beforeEach(async () => {
await datenbankLeeren();
delete process.env["OIDC_CUSTOMER_GROUP_PREFIX"];
});
it("zeigt nur Namen, zu denen es noch keine Abteilung gibt", async () => {
const { admin, eigene } = await grundbestand();
await abteilungAnlegen({ name: "Buchhaltung", zielOu: "OU=Buchhaltung" }, admin);
await person(eigene.id, "Buchhaltung");
await person(eigene.id, "Vertrieb", { herkunft: Herkunft.ABGLEICH });
await person(eigene.id, "Vertrieb");
expect(await ungepflegteAbteilungen(admin)).toEqual([
{ name: "Vertrieb", anzahl: 2, kundeId: eigene.id },
]);
});
it("zählt deaktivierte Personen nicht mit", async () => {
// Eine Abteilung, in der nur noch Ausgeschiedene stehen, braucht keine
// Ziel-OU mehr — und ein Vorschlag, den niemand mehr braucht, verdeckt die
// übrigen.
const { admin, eigene } = await grundbestand();
await person(eigene.id, "Altlast", { aktiv: false });
await person(eigene.id, "Vertrieb");
expect((await ungepflegteAbteilungen(admin)).map((eintrag) => eintrag.name)).toEqual(["Vertrieb"]);
});
it("schlägt nicht vor, was es archiviert schon gibt", async () => {
// Der Name ist je Kunde eindeutig: `abteilungAnlegen` würde diesen Klick mit
// „gibt es schon“ abweisen. Ein Knopf, der das tut, ist keiner.
const { admin, eigene } = await grundbestand();
const abteilung = await abteilungAnlegen({ name: "Vertrieb" }, admin);
await testPrisma.abteilung.update({ where: { id: abteilung.id }, data: { archiviert: true } });
await person(eigene.id, "Vertrieb");
expect(await ungepflegteAbteilungen(admin)).toEqual([]);
await expect(abteilungAnlegen({ name: "Vertrieb" }, admin)).rejects.toThrow(/gibt es schon/);
});
it("übernimmt einen Vorschlag – und zwar nur den Namen", async () => {
const { admin, eigene } = await grundbestand();
await person(eigene.id, "Vertrieb");
const offen = await ungepflegteAbteilungen(admin);
expect(offen).toHaveLength(1);
const abteilung = await abteilungAnlegen({ name: offen[0]!.name, kundeId: offen[0]!.kundeId }, admin);
expect(abteilung).toMatchObject({ name: "Vertrieb", zielOu: null, standardGruppen: [] });
expect(await ungepflegteAbteilungen(admin)).toEqual([]);
});
describe("Mandantengrenze", () => {
async function zweiKunden() {
const { admin, eigene } = await grundbestand(Betriebsmodus.DIENSTLEISTUNG);
const meiner = await testPrisma.kunde.create({ data: { name: "Meiner", kuerzel: "MEIN" } });
const fremder = await testPrisma.kunde.create({ data: { name: "Fremder", kuerzel: "FREMD" } });
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-";
return { admin, eigene, meiner, fremder, techniker: { id: techniker.id, rolle: techniker.rolle } };
}
it("zeigt nur die Vorschläge der sichtbaren Kunden", async () => {
const { meiner, fremder, techniker } = await zweiKunden();
await person(meiner.id, "Vertrieb");
await person(fremder.id, "Vertrieb");
await person(fremder.id, "Technik");
expect(await ungepflegteAbteilungen(techniker)).toEqual([
{ name: "Vertrieb", anzahl: 1, kundeId: meiner.id },
]);
});
it("hält Kunden auseinander, die denselben Namen benutzen", async () => {
// „Vertrieb“ ist bei einem Kunden gepflegt und beim anderen nicht. Ohne die
// Gruppierung nach Kunde und Name verschwände der offene Fall — genau das
// stand hier, bis der Review von #011 es gefunden hat.
const { admin, meiner, fremder } = await zweiKunden();
await testPrisma.abteilung.create({ data: { kundeId: meiner.id, name: "Vertrieb" } });
await person(meiner.id, "Vertrieb");
await person(fremder.id, "Vertrieb");
expect(await ungepflegteAbteilungen(admin)).toEqual([
{ name: "Vertrieb", anzahl: 1, kundeId: fremder.id },
]);
});
it("schlägt nichts für einen archivierten Kunden vor", async () => {
// `kundeErmitteln` weist einen archivierten Kunden ab; der Klick auf den
// Vorschlag ergäbe nur eine Fehlermeldung.
const { admin, fremder } = await zweiKunden();
await person(fremder.id, "Vertrieb");
await testPrisma.kunde.update({ where: { id: fremder.id }, data: { archiviert: true } });
expect(await ungepflegteAbteilungen(admin)).toEqual([]);
await expect(abteilungAnlegen({ name: "Vertrieb", kundeId: fremder.id }, admin)).rejects.toThrow(
/archiviert/,
);
});
});
});