fix(detail): validate route-param UUID and align e2e storageState

Detailseiten & Kontakt (WS8) – zwei BLOCKING-Befunde behoben:

- UUID-Validierung an der Grenze (Querschnittsstandard 4): Die drei
  Detailseiten (fahrzeuge/geraete/wehren [id]) gaben den Route-Param `id`
  ungeprüft an die DB (Postgres `uuid`). Ein malformter Pfad wie
  /fahrzeuge/abc erzeugte `invalid input syntax for type uuid` -> 500.
  Jetzt: uuidSchema.safeParse(id) -> notFound() (deutsche 404) vor dem
  Query-Aufruf.

- e2e-Harness: detail-auth.spec.ts nutzte storageState
  "tests/e2e/.auth/wehr-read.json" (existiert nicht, ENOENT -> ganzer
  'Eingeloggt'-Block errort). Auf Projektkonvention umgestellt:
  storageState: process.env.E2E_WEHR_READ_STATE ?? { cookies, origins }
  + test.skip ohne Fixture (analog verwaltung-scoping.spec.ts).
  Zusätzlich nicht-UUID-Fall (/fahrzeuge/abc -> deutsche 404) abgesichert.

Verifiziert (offline): tsc --noEmit OK, vitest detail-Unit-Tests OK,
next build "Compiled successfully" + Typecheck OK. Build-Page-Data-Phase
und e2e deferred (kein Postgres/Server in der Sandbox).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthias Hochmeister
2026-06-09 11:50:01 +02:00
parent 44050c7278
commit 6975679c4e
4 changed files with 35 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
import { notFound } from "next/navigation";
import { requireSession } from "@/lib/auth/guards";
import { getFahrzeugDetail } from "@/lib/detail/queries";
import { uuidSchema } from "@/lib/validation/common";
import { DetailHeader } from "@/components/detail/DetailHeader";
import { EckdatenGrid } from "@/components/detail/EckdatenGrid";
import { BeladungListe } from "@/components/detail/BeladungListe";
@@ -15,7 +16,11 @@ export default async function FahrzeugDetailPage({
// Default-deny in der Tiefe (Querschnittsstandard 1): Guard als erste Zeile.
await requireSession();
const { id } = await params;
const v = await getFahrzeugDetail(id);
// Route-Param ist Nutzereingabe an der Grenze (Querschnittsstandard 4):
// nicht-UUID -> saubere deutsche 404 statt Postgres `invalid input syntax`.
const parsed = uuidSchema.safeParse(id);
if (!parsed.success) notFound();
const v = await getFahrzeugDetail(parsed.data);
if (!v) notFound();
return (

View File

@@ -2,6 +2,7 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { requireSession } from "@/lib/auth/guards";
import { getGeraetDetail } from "@/lib/detail/queries";
import { uuidSchema } from "@/lib/validation/common";
import { DetailHeader } from "@/components/detail/DetailHeader";
import { EckdatenGrid } from "@/components/detail/EckdatenGrid";
import { WehrCard } from "@/components/kontakt/WehrCard";
@@ -15,7 +16,11 @@ export default async function GeraetDetailPage({
// Default-deny in der Tiefe (Querschnittsstandard 1): Guard als erste Zeile.
await requireSession();
const { id } = await params;
const g = await getGeraetDetail(id);
// Route-Param ist Nutzereingabe an der Grenze (Querschnittsstandard 4):
// nicht-UUID -> saubere deutsche 404 statt Postgres `invalid input syntax`.
const parsed = uuidSchema.safeParse(id);
if (!parsed.success) notFound();
const g = await getGeraetDetail(parsed.data);
if (!g) notFound();
return (

View File

@@ -2,6 +2,7 @@ import Link from "next/link";
import { notFound } from "next/navigation";
import { requireSession } from "@/lib/auth/guards";
import { getWehrDetail } from "@/lib/detail/queries";
import { uuidSchema } from "@/lib/validation/common";
import { DetailHeader } from "@/components/detail/DetailHeader";
import { KontaktButton } from "@/components/kontakt/KontaktButton";
import { StatusBadge } from "@/components/ui/badge";
@@ -15,7 +16,11 @@ export default async function WehrDetailPage({
// Default-deny in der Tiefe (Querschnittsstandard 1): Guard als erste Zeile.
await requireSession();
const { id } = await params;
const w = await getWehrDetail(id);
// Route-Param ist Nutzereingabe an der Grenze (Querschnittsstandard 4):
// nicht-UUID -> saubere deutsche 404 statt Postgres `invalid input syntax`.
const parsed = uuidSchema.safeParse(id);
if (!parsed.success) notFound();
const w = await getWehrDetail(parsed.data);
if (!w) notFound();
const adresse = [w.strasse, [w.plz, w.ort].filter(Boolean).join(" ")]