Merge branch 'main' into phase/029-handbuch

This commit is contained in:
2026-10-12 16:49:27 +02:00
21 changed files with 2263 additions and 16 deletions
+52
View File
@@ -0,0 +1,52 @@
import { readFile } from "node:fs/promises";
import { expect, test } from "@playwright/test";
import { anmelden } from "./hilfen";
/**
* Audit-Log (#027) gegen den gebauten Stand: Liste, Filter, Aufklappen, Kettenpruefung und
* CSV-Export. Die Anmeldung selbst erzeugt den ersten Eintrag – ohne SSH-Testziele lauffaehig.
*/
test("Audit-Log zeigt, filtert, prüft und exportiert", async ({ page }) => {
await anmelden(page);
await page.goto("/protokoll");
await expect(page.getByRole("heading", { name: "Audit-Log" })).toBeVisible();
const tabelle = page.getByRole("table", { name: "Protokolleinträge" });
await expect(tabelle.getByRole("row").nth(1)).toBeVisible();
// Filter nach Aktionsgruppe: nur noch Anmeldungen.
await page.getByLabel("Aktion").selectOption({ label: "Anmeldung" });
await page.getByRole("button", { name: "Filtern" }).click();
await expect(page).toHaveURL(/gruppe=anmeldung/);
const zeilen = tabelle.locator("tbody > tr");
await expect(zeilen.first()).toBeVisible();
const aktionen = await zeilen.locator("td:nth-child(2)").allInnerTexts();
expect(aktionen.length).toBeGreaterThan(0);
for (const aktion of aktionen) expect(aktion).toMatch(/Anmeld|TOTP/);
// Zeile aufklappen: Hash und Vorgaenger-Hash erscheinen.
await tabelle
.getByRole("button", { name: /Einzelheiten zu Eintrag/ })
.first()
.click();
await expect(tabelle.locator('[data-feld="hash"]').first()).toHaveText(/^[0-9a-f]{64}$/);
// Kettenpruefung auf Knopfdruck.
await page.getByRole("button", { name: "Kette prüfen" }).click();
await expect(page.getByText("Hash-Kette unversehrt")).toBeVisible({ timeout: 30_000 });
// CSV mit denselben Filtern; der Export landet selbst im Protokoll.
const [download] = await Promise.all([
page.waitForEvent("download"),
page.getByRole("link", { name: "CSV exportieren" }).click(),
]);
expect(download.suggestedFilename()).toMatch(/^sshkv-protokoll-\d{4}-\d{2}-\d{2}\.csv$/);
const text = (await readFile((await download.path())!, "utf8")).replace(/^/, "");
expect(text.split("\r\n")[0]).toBe(
'"id";"zeitpunkt";"akteur";"aktion";"ergebnis";"host";"zielbenutzer";"fingerprint";"diff";"details";"vorgaengerHash";"hash"',
);
expect(text).toContain("anmeldung.");
await page.goto("/protokoll?gruppe=protokoll");
await expect(tabelle.getByText("Protokoll exportiert (CSV)").first()).toBeVisible();
});