import { test, expect } from "@playwright/test"; import { ROUTES } from "./routes.manifest"; /** * KRITISCHE Auth-Gating-Suite (Definition of Done #1, oberstes Prinzip). * * Erzeugt GENAU EINEN Testfall pro Manifest-Eintrag (ROUTES.length): * - Seiten -> Redirect auf /login (mit callbackUrl), kein Daten-Leak. * - API -> 401 ohne Fachdaten im Body. * * NICHT in der Sandbox ausführbar (kein Server/DB) — deferred. Wird über * `npm run test:e2e:gating` gegen einen laufenden Server ausgeführt. * * Negativ-Probe (CI): Entfernen eines Layout-Guards oder einer Manifest-Route * muss diese Suite rot machen. */ // Erzwingt anonymen Zustand: keine gespeicherte Session. test.use({ storageState: { cookies: [], origins: [] } }); // Fachbegriffe, die in einem 401/Redirect-Body NIE auftauchen dürfen. const LEAK_TERMS = [ "funkrufname", "wehrfuehrer", "einsatzbereit", "passwort", "kennzeichen", ]; function assertNoLeak(body: string) { const lower = body.toLowerCase(); for (const term of LEAK_TERMS) { expect(lower, `Daten-Leak: Body enthält "${term}"`).not.toContain(term); } } for (const route of ROUTES) { if (route.expectWhenAnon === "redirect") { test(`Seite ${route.path}: anonym -> Redirect auf /login`, async ({ page, }) => { const response = await page.goto(route.path); await expect(page).toHaveURL(/\/login/); // callbackUrl bewahrt das ursprüngliche Ziel. expect(page.url()).toContain("callbackUrl"); const body = await page.content(); assertNoLeak(body); // Kein 500 o. ä. if (response) expect(response.status()).toBeLessThan(500); }); } else { test(`API ${route.path}: anonym -> 401 ohne Daten-Leak`, async ({ request, }) => { // /api/geo/geocode ist POST-only; health ist GET. Beide gehen durch apiAuth(). const res = route.path.includes("geocode") ? await request.post(route.path, { data: { address: "x" } }) : await request.get(route.path); expect(res.status()).toBe(401); assertNoLeak(await res.text()); }); } } test.describe("Öffentliche Routen bleiben anonym erreichbar", () => { test("Login-Seite ist anonym 200", async ({ page }) => { const res = await page.goto("/login"); expect(res?.status()).toBeLessThan(400); await expect(page.getByRole("heading", { name: "Anmelden" })).toBeVisible(); }); test("Container-Health ist anonym 200", async ({ request }) => { const res = await request.get("/api/health"); expect(res.status()).toBe(200); expect(await res.json()).toEqual({ status: "ok" }); }); });