BLOCKING-Befunde aus "Tests & Sicherheitshaertung": 1) Coverage-Pfad war nie ausfuehrbar: @vitest/coverage-v8 fehlte in den devDependencies, obwohl vitest.config coverage.provider "v8" setzt und test:coverage "vitest run --coverage" aufruft. Paket passend zu vitest ^3.2 ergaenzt und installiert. coverage.include zog ganze Verzeichnisse (src/lib/search, src/lib/geo) ein - inkl. DB-/HTTP-gebundener Wrapper (Drizzle gegen Postgres, Nominatim/OSRM), die offline nicht laufen und die globale Schwelle verwaesserten. Scope auf die REINE, offline testbare Logik beschraenkt (perFile-Schwellen), I/O-Wrapper und reine Typ-Module ausgenommen (per Integrations-/E2E-Tests abgesichert). Fehlende Branch-Abdeckung in geo/eintreffzeit.ts mit Tests fuer Einzel-Haversine-Fallback, reine Koordinaten-lose Liste und fehlende OSRM-distances-Zeile geschlossen. npm run test:coverage: Exit 0, Schwellen (Lines/Stmts/Funcs >=90, Branches >=80) erfuellt. 2) Driftschutz zu permissiv: isPublic() in tests/support/route-scan.ts stufte ueber `route.startsWith(p)` jeden reinen String-Praefix als oeffentlich ein (z. B. /loginhelp, /api/healthz, /api/authentication) und liess solche Routen dem Auth-Gating-Driftcheck entkommen. Die redundante dritte Bedingung entfernt; exakter Treffer und echtes Unterpfad-Segment (p + "/") sind korrekt und ausreichend. Negativ- Testfall ergaenzt. Verifiziert (offline): tsc --noEmit (0), vitest run (233 passed, 7 DB-Tests deferred), test:coverage (Exit 0). DB-/Server-/Browser- abhaengige Schritte deferred (kein Postgres/Server im Sandbox). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
PUBLIC_ALLOWLIST,
|
|
filePathToRoute,
|
|
findUndeclaredRoutes,
|
|
discoverAppRoutes,
|
|
} from "../support/route-scan";
|
|
import {
|
|
DECLARED_ROUTE_TEMPLATES,
|
|
ROUTES,
|
|
} from "../e2e/routes.manifest";
|
|
|
|
/**
|
|
* Driftschutz für das Routen-Manifest (Definition of Done #1): jede neue Route
|
|
* unter src/app/** muss im Manifest geführt (oder explizit öffentlich) sein,
|
|
* sonst bleibt sie ungetestet im Auth-Gating. Reine Logik, offline lauffähig.
|
|
*/
|
|
describe("filePathToRoute", () => {
|
|
it("mappt page.tsx einer Route-Group auf den URL-Pfad ohne Gruppe", () => {
|
|
expect(filePathToRoute("src/app/(app)/fahrzeuge/page.tsx")).toBe(
|
|
"/fahrzeuge",
|
|
);
|
|
});
|
|
|
|
it("mappt die Wurzel-Page der (app)-Gruppe auf /", () => {
|
|
expect(filePathToRoute("src/app/(app)/page.tsx")).toBe("/");
|
|
});
|
|
|
|
it("ersetzt dynamische Segmente durch einen Platzhalter", () => {
|
|
expect(filePathToRoute("src/app/(app)/fahrzeuge/[id]/page.tsx")).toBe(
|
|
"/fahrzeuge/[id]",
|
|
);
|
|
});
|
|
|
|
it("mappt route.ts auf den API-Pfad", () => {
|
|
expect(filePathToRoute("src/app/api/health/route.ts")).toBe("/api/health");
|
|
});
|
|
|
|
it("mappt die Root-Page src/app/page.tsx auf /", () => {
|
|
expect(filePathToRoute("src/app/page.tsx")).toBe("/");
|
|
});
|
|
|
|
it("löst catch-all-Segmente auf", () => {
|
|
expect(filePathToRoute("src/app/api/auth/[...nextauth]/route.ts")).toBe(
|
|
"/api/auth/[...nextauth]",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("findUndeclaredRoutes", () => {
|
|
it("flaggt eine Route, die weder im Manifest noch öffentlich ist", () => {
|
|
const declared = new Set(["/fahrzeuge"]);
|
|
const discovered = ["/fahrzeuge", "/leak"];
|
|
expect(findUndeclaredRoutes(discovered, declared)).toEqual(["/leak"]);
|
|
});
|
|
|
|
it("ignoriert Routen mit öffentlichem Präfix", () => {
|
|
const declared = new Set<string>();
|
|
const discovered = ["/login", "/api/health", "/api/auth/[...nextauth]"];
|
|
expect(findUndeclaredRoutes(discovered, declared)).toEqual([]);
|
|
});
|
|
|
|
it("flaggt Routen mit reinem String-Präfix als ungetestet (kein Pfadsegment)", () => {
|
|
const declared = new Set<string>();
|
|
// "/loginhelp" beginnt zwar mit "/login" und "/api/healthz" mit
|
|
// "/api/health", sind aber KEINE Unterpfade -> müssen gegated werden.
|
|
const discovered = ["/loginhelp", "/api/healthz", "/api/authentication"];
|
|
expect(findUndeclaredRoutes(discovered, declared)).toEqual([
|
|
"/loginhelp",
|
|
"/api/healthz",
|
|
"/api/authentication",
|
|
]);
|
|
});
|
|
|
|
it("PUBLIC_ALLOWLIST enthält /api/health und /login", () => {
|
|
expect(PUBLIC_ALLOWLIST).toContain("/api/health");
|
|
expect(PUBLIC_ALLOWLIST).toContain("/login");
|
|
});
|
|
});
|
|
|
|
describe("discoverAppRoutes (echtes Repo)", () => {
|
|
it("findet die bekannten Seiten", () => {
|
|
const routes = discoverAppRoutes();
|
|
expect(routes).toContain("/fahrzeuge");
|
|
expect(routes).toContain("/admin");
|
|
expect(routes).toContain("/api/health");
|
|
});
|
|
});
|
|
|
|
describe("Driftschutz: Manifest deckt alle Routen ab (Definition of Done #1)", () => {
|
|
it("KEINE Route unter src/app/** fehlt im Manifest oder in der Allowlist", () => {
|
|
const discovered = discoverAppRoutes();
|
|
const undeclared = findUndeclaredRoutes(
|
|
discovered,
|
|
DECLARED_ROUTE_TEMPLATES,
|
|
);
|
|
expect(
|
|
undeclared,
|
|
`Ungetestete Routen (im Manifest ergänzen oder als öffentlich markieren):\n${undeclared.join(
|
|
"\n",
|
|
)}`,
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("jeder ROUTES-Eintrag entspricht einer existierenden Route-Vorlage", () => {
|
|
const discovered = new Set(discoverAppRoutes());
|
|
for (const template of DECLARED_ROUTE_TEMPLATES) {
|
|
expect(discovered, `Manifest-Eintrag ${template} fehlt im Dateisystem`).toContain(
|
|
template,
|
|
);
|
|
}
|
|
// Sanity: jede konkrete ROUTES-Zeile ist nicht leer.
|
|
for (const r of ROUTES) expect(r.path.startsWith("/")).toBe(true);
|
|
});
|
|
});
|