feat(verzeichnis): LDAP-Abgleich, Zeitplan, CSV-Import und Log
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

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>
This commit is contained in:
Florian Wilde
2026-09-26 02:21:08 +02:00
co-authored by Claude Opus 5
parent ab5777a57e
commit 16059e7846
57 changed files with 6834 additions and 61 deletions
+23 -2
View File
@@ -575,7 +575,7 @@ describe.runIf(datenbankVerfuegbar)("Vorlagen-Versionierung", () => {
const { vorlage } = await veroeffentlichteVorlage(benutzer.id);
const entwurf = await entwurfHolenOderErzeugen(vorlage.id, benutzer.id);
await Promise.allSettled([
const [veroeffentlichung, speichern] = await Promise.allSettled([
versionVeroeffentlichen(entwurf.id, "Veröffentlicht", benutzer.id),
entwurfSpeichern(entwurf.id, {
variablen: [],
@@ -584,9 +584,30 @@ describe.runIf(datenbankVerfuegbar)("Vorlagen-Versionierung", () => {
]);
const danach = await strukturLesen(entwurf.id);
/*
* Welcher der beiden zuerst an die Zeile kommt, entscheidet die Datenbank —
* **beide Ausgaenge** werden hier geprueft. Vorher stand die Erwartung in
* einem `if`: gewann das Speichern das Rennen, lief der Test durch, ohne
* etwas zu belegen, und die eigentliche Zusage waere unbemerkt gefallen
* (Reviewbefund zu #016).
*/
expect(danach.status === "VEROEFFENTLICHT" || danach.status === "ENTWURF").toBe(true);
if (danach.status === "VEROEFFENTLICHT") {
// Wurde veroeffentlicht, darf der nachträgliche Aufbau nicht drinstehen.
expect(veroeffentlichung.status).toBe("fulfilled");
// Veroeffentlicht heisst unveraenderlich: der nachtraegliche Aufbau darf
// nicht drinstehen, und das Speichern muss abgewiesen worden sein.
expect(danach.abschnitte.map((a) => a.titel)).not.toContain("NACHTRAEGLICH");
expect(speichern.status).toBe("rejected");
} else {
// Das Speichern war zuerst da. Dann darf nicht veroeffentlicht worden sein
// – sonst trueege die veroeffentlichte Version genau diesen Abschnitt.
expect(speichern.status).toBe("fulfilled");
expect(veroeffentlichung.status).toBe("rejected");
expect(
await testPrisma.protokoll.count({
where: { entitaetId: entwurf.id, aktion: "Version veröffentlicht" },
}),
).toBe(0);
}
});