Workstream 11: Tests & Sicherheitshärtung (Phase 7)
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>
This commit is contained in:
55
tests/e2e/search-eta.spec.ts
Normal file
55
tests/e2e/search-eta.spec.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Suche & Eintreffzeit-Sortierung (Definition of Done #6, Plan WS11 Aufgabe 7 /
|
||||
* Verifikation 7).
|
||||
*
|
||||
* NICHT in der Sandbox ausführbar (kein Server/DB/OSRM) — deferred. Wird über
|
||||
* `npm run test:e2e` gegen einen geseedeten Server mit authentifizierter
|
||||
* Session ausgeführt.
|
||||
*
|
||||
* Garantien:
|
||||
* - Dynamische Filter (UND-verknüpft) liefern korrekte Teilmengen.
|
||||
* - Treffer sind nach ETA aufsteigend sortiert.
|
||||
* - OSRM-Ausfall (E2E_FORCE_HAVERSINE=1) -> sichtbarer "Luftlinie"-Fallback.
|
||||
*/
|
||||
test.skip(
|
||||
!process.env.E2E_AUTH_STATE && !process.env.E2E_WEHR_READ_STATE,
|
||||
"benötigt authentifizierte Session (Auth-Setup)",
|
||||
);
|
||||
test.use({
|
||||
storageState:
|
||||
process.env.E2E_AUTH_STATE ?? process.env.E2E_WEHR_READ_STATE ?? undefined,
|
||||
});
|
||||
|
||||
test("Filter grenzt Treffer ein (UND-Verknüpfung)", async ({ page }) => {
|
||||
await page.goto("/fahrzeuge");
|
||||
const before = await page.getByText(/Treffer/).first().innerText();
|
||||
await page.getByLabel("Nur einsatzbereit").click();
|
||||
await expect(page).toHaveURL(/bereit=1/);
|
||||
const after = await page.getByText(/Treffer/).first().innerText();
|
||||
// Teilmenge: Anzahl sinkt nicht und Filter ist in der URL aktiv.
|
||||
expect(after).not.toBe(before);
|
||||
});
|
||||
|
||||
test("Treffer mit Standort sind aufsteigend nach ETA sortiert", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/fahrzeuge?ort=St.+P%C3%B6lten");
|
||||
await page.waitForLoadState("networkidle");
|
||||
const etaTexts = await page
|
||||
.locator("[data-testid='eta-minutes']")
|
||||
.allInnerTexts();
|
||||
const minutes = etaTexts.map((t) => parseInt(t.replace(/\D/g, ""), 10));
|
||||
const sorted = [...minutes].sort((a, b) => a - b);
|
||||
expect(minutes).toEqual(sorted);
|
||||
});
|
||||
|
||||
test("OSRM-Ausfall zeigt Luftlinie-Fallback", async ({ page }) => {
|
||||
test.skip(
|
||||
process.env.E2E_FORCE_HAVERSINE !== "1",
|
||||
"nur mit E2E_FORCE_HAVERSINE=1",
|
||||
);
|
||||
await page.goto("/fahrzeuge?ort=St.+P%C3%B6lten");
|
||||
await expect(page.getByText(/Luftlinie/i).first()).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user