Files
Laufzettel/tests/e2e/verzeichnis.spec.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

88 lines
3.9 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 { expect, test, type Page } from "@playwright/test";
import "dotenv/config";
import { prisma } from "@/lib/db";
/**
* Admin → Verzeichnis durch die Oberflaeche (#016).
*
* Ohne LDAP-Container: geprueft wird, was auch ohne Verzeichnis gilt — dass die
* Attributzuordnung bedienbar ist, dass eine krumme Eingabe abgewiesen wird und
* dass eine gute wirklich in der Datenbank landet. Was der Abgleich **tut**,
* steht in `tests/abgleich.test.ts` gegen einen echten Server.
*
* Voraussetzung: ein **gebauter** Stand laeuft (`pnpm build && pnpm start`).
* Im Entwicklungsmodus haengt React sich hier nicht an, und dann tut kein Knopf
* etwas (siehe CLAUDE.md, „Oberflaeche pruefen“).
*/
async function anmelden(seite: Page) {
const email = process.env["SEED_ADMIN_EMAIL"];
const passwort = process.env["SEED_ADMIN_PASSWORD"];
if (!email || !passwort) throw new Error("SEED_ADMIN_EMAIL und SEED_ADMIN_PASSWORD fehlen.");
await seite.goto("/anmelden");
await seite.fill('input[name="email"]', email);
await seite.fill('input[name="passwort"]', passwort);
await Promise.all([seite.waitForURL("**/"), seite.click('button[type="submit"]')]);
}
test.describe("Verzeichnis", () => {
test("macht die Attributzuordnung bedienbar und prüft sie", async ({ page }) => {
test.slow();
const quelle = await prisma.verzeichnisquelle.findFirst({ orderBy: { name: "asc" } });
test.skip(quelle === null, "Keine Verzeichnisquelle im Bestand.");
if (!quelle) return;
const vorher = quelle.attributZuordnung;
await anmelden(page);
await page.goto("/admin/verzeichnis");
await page.locator("summary", { hasText: "Einstellungen bearbeiten" }).first().click();
await page.locator("summary", { hasText: "Attributzuordnung anpassen" }).first().click();
const kennung = page.locator('input[name="attr.externeId"]').first();
await expect(kennung).toBeVisible();
await expect(kennung).toHaveValue("objectGUID");
// Eine Klammer darf nicht in eine LDAP-Anfrage wandern.
const titel = page.locator('input[name="attr.titel"]').first();
await titel.fill("(objectClass=*)");
await page.getByRole("button", { name: "Speichern" }).first().click();
await expect(page.locator('[role="alert"]').first()).toContainText("kein Attributname");
// Und eine gute Eingabe steht danach wirklich in der Datenbank.
await titel.fill("personalTitle");
await page.getByRole("button", { name: "Speichern" }).first().click();
await expect(page.locator('[role="status"]').last()).toContainText("gespeichert");
const nachher = await prisma.verzeichnisquelle.findUniqueOrThrow({ where: { id: quelle.id } });
expect((nachher.attributZuordnung as Record<string, string>)["titel"]).toBe("personalTitle");
// Neu geladen steht sie auch im Formular – sonst waere sie nur gespeichert,
// aber nicht bearbeitbar.
await page.reload();
await page.locator("summary", { hasText: "Einstellungen bearbeiten" }).first().click();
await page.locator("summary", { hasText: "Attributzuordnung anpassen" }).first().click();
await expect(page.locator('input[name="attr.titel"]').first()).toHaveValue("personalTitle");
await prisma.verzeichnisquelle.update({
where: { id: quelle.id },
data: { attributZuordnung: vorher === null ? {} : vorher },
});
});
test("zeigt das Abgleich-Log und trägt am Handy ohne horizontales Scrollen", async ({ page }) => {
await anmelden(page);
await page.goto("/admin/verzeichnis");
const log = page.locator("summary", { hasText: "Abgleich-Log" }).first();
if ((await log.count()) > 0) {
await log.click();
await expect(page.locator("details", { has: log })).toBeVisible();
}
const masse = await page.evaluate(() => ({
scroll: document.documentElement.scrollWidth,
sichtbar: document.documentElement.clientWidth,
}));
expect(masse.scroll).toBeLessThanOrEqual(masse.sichtbar);
});
});