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>
65 lines
2.4 KiB
TypeScript
65 lines
2.4 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
|
|
/**
|
|
* Rollen-/Wehr-Scoping (Definition of Done #3, Plan WS11 Aufgabe 6 / Verifikation
|
|
* 6).
|
|
*
|
|
* NICHT in der Sandbox ausführbar (kein Server/DB) — deferred. Wird über
|
|
* `npm run test:e2e` gegen einen geseedeten Server mit Storage-States aus dem
|
|
* Auth-Setup ausgeführt.
|
|
*
|
|
* Garantien:
|
|
* - wehr_read kann NICHT schreiben (Status-Änderung -> 403/forbidden).
|
|
* - wehr_admin A kann Wehr B NICHT ändern (fremdes Asset -> 403/404,
|
|
* Datensatz bleibt unverändert).
|
|
* - eigene Ressource: wehr_admin A kann Status setzen (-> 200, status='wartung').
|
|
*
|
|
* Storage-States kommen aus tests/e2e/fixtures/auth.setup.ts. Feste Asset-UUIDs
|
|
* stammen aus dem deterministischen Seed (global-setup.ts).
|
|
*/
|
|
|
|
const VEHICLE_A = process.env.E2E_VEHICLE_A_ID ?? "";
|
|
const VEHICLE_B = process.env.E2E_VEHICLE_B_ID ?? "";
|
|
|
|
test.describe("wehr_read darf nicht schreiben", () => {
|
|
test.skip(!process.env.E2E_WEHR_READ_STATE, "benötigt wehr_read-Fixture");
|
|
test.use({ storageState: process.env.E2E_WEHR_READ_STATE });
|
|
|
|
test("Aufruf der Verwaltungs-Schreibseite -> 403", async ({ page }) => {
|
|
const res = await page.goto("/verwaltung/fahrzeuge/neu");
|
|
expect(res?.status()).toBe(403);
|
|
});
|
|
});
|
|
|
|
test.describe("wehr_admin A darf Wehr B nicht ändern", () => {
|
|
test.skip(
|
|
!process.env.E2E_WEHR_ADMIN_STATE || !VEHICLE_B,
|
|
"benötigt wehr_admin-A-Fixture + Wehr-B-Fahrzeug-ID",
|
|
);
|
|
test.use({ storageState: process.env.E2E_WEHR_ADMIN_STATE });
|
|
|
|
test("fremdes Fahrzeug (Wehr B) -> 404, unverändert", async ({ page }) => {
|
|
const res = await page.goto(`/verwaltung/fahrzeuge/${VEHICLE_B}`);
|
|
expect(res?.status()).toBe(404);
|
|
});
|
|
});
|
|
|
|
test.describe("wehr_admin A darf eigenes Fahrzeug ändern", () => {
|
|
test.skip(
|
|
!process.env.E2E_WEHR_ADMIN_STATE || !VEHICLE_A,
|
|
"benötigt wehr_admin-A-Fixture + Wehr-A-Fahrzeug-ID",
|
|
);
|
|
test.use({ storageState: process.env.E2E_WEHR_ADMIN_STATE });
|
|
|
|
test("eigenes Fahrzeug ist erreichbar (200) und editierbar", async ({
|
|
page,
|
|
}) => {
|
|
const res = await page.goto(`/verwaltung/fahrzeuge/${VEHICLE_A}`);
|
|
expect(res?.status()).toBeLessThan(400);
|
|
// Status auf 'wartung' setzen (Verifikation 6: eigenes -> 200).
|
|
await page.getByLabel(/Status/i).selectOption("wartung");
|
|
await page.getByRole("button", { name: /Speichern/i }).click();
|
|
await expect(page.getByText(/gespeichert|wartung/i)).toBeVisible();
|
|
});
|
|
});
|