Beweist die Auth-Gating-Garantie und härtet das System ab (Definition of Done #1, #2, #3, #7, #8): - Routen-Manifest (tests/e2e/routes.manifest.ts) als einzige Quelle der Wahrheit; anonyme Seite -> Redirect /login, anonyme API -> 401. - Kritische auth-gating.spec.ts: genau ein Fall je Manifest-Eintrag, ohne Daten-Leak. - Driftschutz (routes.manifest.spec.ts + tests/unit/routes-manifest.test.ts): keine ungetestete neue Route unter src/app/**. - Default-Deny-Beweis für Server Actions (server-actions-guard.spec.ts + tests/unit/server-actions-guard.test.ts): jede "use server"-Funktion ruft als erste Anweisung einen Guard; Login-Actions per Allowlist ausgenommen. - Wiederverwendbare reine Scanner unter tests/support (route-scan, guard-scan) — offline lauffähig, in Vitest und Playwright geteilt. - rbac-scoping, search-eta, login-ratelimit, security-headers Specs (gegen geseedeten Server; in der Sandbox deferred, per test.skip abgesichert). - global-setup (Migration + Seed) und auth.setup (Login je Konto -> storageState); Playwright-Projekte setup -> chromium verdrahtet. - src/lib/security/headers.test.ts: statischer Beleg für CSP, HSTS, X-Frame-Options DENY, nosniff, Permissions-Policy. - vitest.config.ts: Coverage-Schwellen (>=90 %) für src/lib/search + src/lib/geo. - package.json: Scripts test:unit, test:coverage, test:e2e, test:e2e:gating. - docs/reference/sicherheitshaertung-checkliste.md: jeder Härtungspunkt mit Test/Befehl und Negativ-Probe. Offline verifiziert: tsc --noEmit (0), vitest run (229 passed / 7 db-skipped), drizzle-kit check (ok), next build (exit 0), next lint (0 Fehler), playwright --list (98 Tests, 15 Dateien). DB-/Server-/Browser-abhängige E2E-Läufe sind deferred (kein Postgres/Server in der Sandbox). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
import { test as setup, expect } from "@playwright/test";
|
|
import path from "node:path";
|
|
|
|
/**
|
|
* Auth-Setup (Plan WS11 Aufgabe 3): echter Credentials-Login je Konto ->
|
|
* storageState. Wird als Playwright-Projekt "setup" ausgeführt; die übrigen
|
|
* Projekte hängen davon ab und laden den jeweiligen storageState.
|
|
*
|
|
* NICHT in der Sandbox ausführbar (kein Server/DB) — deferred.
|
|
*
|
|
* Erzeugt vier Storage-States passend zu den vier Seed-Konten:
|
|
* - platform_admin
|
|
* - wehr_admin (Wehr A)
|
|
* - wehr_admin (Wehr B)
|
|
* - wehr_read (Wehr A)
|
|
*/
|
|
export const AUTH_DIR = path.join(process.cwd(), "tests/e2e/.auth");
|
|
|
|
interface Account {
|
|
email: string;
|
|
file: string;
|
|
envVar: string;
|
|
}
|
|
|
|
const PASSWORD = process.env.E2E_TEST_PASSWORD ?? "Test-Passwort-1234";
|
|
|
|
const ACCOUNTS: Account[] = [
|
|
{
|
|
email: "platform-admin@example.test",
|
|
file: "platform-admin.json",
|
|
envVar: "E2E_PLATFORM_ADMIN_STATE",
|
|
},
|
|
{
|
|
email: "wehr-admin-a@example.test",
|
|
file: "wehr-admin-a.json",
|
|
envVar: "E2E_WEHR_ADMIN_STATE",
|
|
},
|
|
{
|
|
email: "wehr-admin-b@example.test",
|
|
file: "wehr-admin-b.json",
|
|
envVar: "E2E_WEHR_ADMIN_B_STATE",
|
|
},
|
|
{
|
|
email: "wehr-read-a@example.test",
|
|
file: "wehr-read-a.json",
|
|
envVar: "E2E_WEHR_READ_STATE",
|
|
},
|
|
];
|
|
|
|
for (const account of ACCOUNTS) {
|
|
setup(`Login ${account.email}`, async ({ page }) => {
|
|
await page.goto("/login");
|
|
await page.getByLabel(/E-Mail/i).fill(account.email);
|
|
await page.getByLabel(/Passwort/i).fill(PASSWORD);
|
|
await page.getByRole("button", { name: /Anmelden/i }).click();
|
|
// Erfolgreicher Login verlässt /login.
|
|
await page.waitForURL((url) => !url.pathname.startsWith("/login"));
|
|
await expect(page).not.toHaveURL(/\/login/);
|
|
await page
|
|
.context()
|
|
.storageState({ path: path.join(AUTH_DIR, account.file) });
|
|
});
|
|
}
|