feat(verzeichnis): LDAP-Abgleich, Zeitplan, CSV-Import und Log (#30)
This commit was merged in pull request #30.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
/**
|
||||
* Der Zeitplan des Verzeichnis-Abgleichs (#016).
|
||||
*
|
||||
* Geprueft wird der **Durchgang**, nicht die Uhr: dass `node-cron` jede Minute
|
||||
* ausloest, ist Sache der Bibliothek; dass ein Durchgang die faelligen Quellen
|
||||
* holt, jede unter der Sperre abgleicht und an einem Fehler nicht abbricht, ist
|
||||
* unsere.
|
||||
*
|
||||
* Die Dienste darunter werden ersetzt. Das ist hier richtig und nicht faul: was
|
||||
* der Abgleich **tut**, prueft `tests/abgleich.test.ts` gegen einen echten
|
||||
* LDAP-Container. Hier geht es allein um die Ablaufsteuerung — und die laesst
|
||||
* sich mit echten Diensten nur pruefen, indem man eine Minute wartet und ein
|
||||
* Verzeichnis danebenstellt.
|
||||
*/
|
||||
|
||||
const abgleichAusfuehren = vi.fn();
|
||||
const faelligeQuellen = vi.fn();
|
||||
/** Nur da, um zu belegen, dass der Zeitplan sie **nicht** nimmt. */
|
||||
const mitAbgleichSperre = vi.fn();
|
||||
|
||||
/** Dieselbe Klasse wie im Dienst – `instanceof` entscheidet ueber das Verhalten. */
|
||||
class AbgleichLaeuftBereitsFehler extends Error {}
|
||||
|
||||
vi.mock("@/server/services/abgleich", () => ({
|
||||
AbgleichLaeuftBereitsFehler,
|
||||
abgleichAusfuehren: (...args: unknown[]) => abgleichAusfuehren(...args),
|
||||
faelligeQuellen: (...args: unknown[]) => faelligeQuellen(...args),
|
||||
mitAbgleichSperre: (...args: unknown[]) => mitAbgleichSperre(...args),
|
||||
}));
|
||||
|
||||
const { abgleichDurchgang, zeitplanAnhalten, zeitplanLaeuft, zeitplanStarten } =
|
||||
await import("@/server/zeitplan/abgleich-plan");
|
||||
|
||||
afterEach(() => {
|
||||
zeitplanAnhalten();
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("Ein Durchgang des Zeitplans", () => {
|
||||
it("gleicht jede fällige Quelle ab", async () => {
|
||||
faelligeQuellen.mockResolvedValue([
|
||||
{ id: "q1", name: "Firma AD" },
|
||||
{ id: "q2", name: "Zweigstelle" },
|
||||
]);
|
||||
abgleichAusfuehren.mockResolvedValue({ status: "ERFOLG" });
|
||||
|
||||
await abgleichDurchgang();
|
||||
|
||||
expect(abgleichAusfuehren).toHaveBeenCalledTimes(2);
|
||||
expect(abgleichAusfuehren.mock.calls.map((aufruf) => aufruf[0])).toEqual(["q1", "q2"]);
|
||||
/*
|
||||
* **Ohne eigene Sperre.** Der Dienst nimmt sie selbst, in der Datenbank und
|
||||
* damit ueber Instanzen hinweg. Eine zweite hier traege einen anderen
|
||||
* Schluessel, liefe an der ersten vorbei — und ein von Hand angestossener
|
||||
* Abgleich saehe fuer den Zeitplan wie ein unerwarteter Fehler aus.
|
||||
*/
|
||||
expect(mitAbgleichSperre).not.toHaveBeenCalled();
|
||||
/*
|
||||
* Der handelnde „Benutzer“ ist der Systemlauf: ADMIN, damit die Rechtepruefung
|
||||
* des Dienstes greift, und ohne Id, weil kein Mensch es war. Der Dienst macht
|
||||
* daraus im Protokoll `benutzerId: null`.
|
||||
*/
|
||||
const zweitesArgument = abgleichAusfuehren.mock.calls[0]?.[1];
|
||||
expect(zweitesArgument).toEqual({ id: "", rolle: "ADMIN" });
|
||||
});
|
||||
|
||||
it("überspringt still, was gerade schon abgeglichen wird", async () => {
|
||||
/*
|
||||
* Die belegte Sperre meldet der Dienst als eigene Ausnahme. Im Normalbetrieb
|
||||
* mit zwei Instanzen — oder waehrend jemand „Jetzt abgleichen“ drueckt —
|
||||
* waere das jede Minute eine Zeile im Protokoll, deshalb weder Fehler noch
|
||||
* Ausgabe.
|
||||
*/
|
||||
faelligeQuellen.mockResolvedValue([
|
||||
{ id: "q1", name: "Firma AD" },
|
||||
{ id: "q2", name: "Zweigstelle" },
|
||||
]);
|
||||
abgleichAusfuehren.mockImplementation(async (id: string) => {
|
||||
if (id === "q1") throw new AbgleichLaeuftBereitsFehler("läuft schon");
|
||||
return { status: "ERFOLG" };
|
||||
});
|
||||
const fehlerAusgabe = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
await abgleichDurchgang();
|
||||
|
||||
// Und die naechste Quelle kommt trotzdem dran.
|
||||
expect(abgleichAusfuehren).toHaveBeenCalledTimes(2);
|
||||
expect(fehlerAusgabe).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("macht nach einem unerwarteten Fehler mit der nächsten Quelle weiter", async () => {
|
||||
/*
|
||||
* Der Fall, der den Zeitplan sonst still stillegt: eine Quelle wirft etwas,
|
||||
* das der Dienst nicht abgefangen hat. Ohne diese Schleife stuende danach
|
||||
* der Abgleich **aller** Quellen — bis jemand den Prozess neu startet.
|
||||
*/
|
||||
faelligeQuellen.mockResolvedValue([
|
||||
{ id: "kaputt", name: "Antwortet nicht" },
|
||||
{ id: "q2", name: "Zweigstelle" },
|
||||
]);
|
||||
abgleichAusfuehren.mockImplementation(async (id: string) => {
|
||||
if (id === "kaputt") throw new Error("Netz weg");
|
||||
return { status: "ERFOLG" };
|
||||
});
|
||||
const fehlerAusgabe = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
await abgleichDurchgang();
|
||||
|
||||
expect(abgleichAusfuehren).toHaveBeenCalledTimes(2);
|
||||
expect(abgleichAusfuehren.mock.calls[1]?.[0]).toBe("q2");
|
||||
expect(fehlerAusgabe).toHaveBeenCalledOnce();
|
||||
// Der Name der Quelle gehört in die Meldung, sonst sucht jemand im Dunkeln.
|
||||
expect(String(fehlerAusgabe.mock.calls[0]?.[0])).toContain("Antwortet nicht");
|
||||
});
|
||||
|
||||
it("tut nichts, wenn keine Quelle fällig ist", async () => {
|
||||
faelligeQuellen.mockResolvedValue([]);
|
||||
await abgleichDurchgang();
|
||||
expect(abgleichAusfuehren).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Der Zeitplan selbst", () => {
|
||||
it("startet genau einmal", () => {
|
||||
// Next laedt Module im Entwicklungsmodus mehrfach; ein zweiter Zeitplan
|
||||
// liefe unbemerkt mit und verdoppelte jeden Durchgang.
|
||||
expect(zeitplanLaeuft()).toBe(false);
|
||||
zeitplanStarten();
|
||||
zeitplanStarten();
|
||||
expect(zeitplanLaeuft()).toBe(true);
|
||||
|
||||
zeitplanAnhalten();
|
||||
expect(zeitplanLaeuft()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user