feat(quick-260907-e8k-02): Layout-Gate vor jedes Modulverzeichnis, generische Route umgestellt

Legt je ein layout.tsx fuer cert-manager, dkv-fleet, domaincheck und
tender-radar an, das ModuleAccessGate mit dem fest eingetragenen
Verzeichnis-Slug umschliesst. Ein Layout im App Router deckt alle
verschachtelten Unterrouten automatisch mit ab — my-sources und
settings unter tender-radar sowie settings und vehicles unter
dkv-fleet schliessen sich ohne eigene Datei (WINDOWS #10, PERM-04).

Die generische Route [category]/[moduleSlug]/page.tsx nutzt jetzt
ebenfalls ModuleAccessGate statt des bisherigen Inline-403-Markups —
das 403-Markup existiert damit nur noch einmal im Code
(module-access-denied.tsx).

module-layouts.test.tsx deckt zwei Threats ab: falscher Slug in einem
der vier Layouts (T-e8k-03) und ein kuenftig hinzugefuegtes
Modulverzeichnis ohne Layout (T-e8k-04, liest das Verzeichnis per
node:fs aus). module-access.test.tsx ist auf die Weitergabe an das
Gate umgeschrieben, die 403-vs-Rendern-Entscheidung ist bereits durch
module-access-gate.test.tsx abgedeckt.

Volle Web-Testsuite (213 Tests), Typpruefung und Produktionsbau sind
gruen. Browser-Gegenprobe folgt durch den Orchestrator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
2026-09-07 10:27:47 +02:00
parent 4a23e13731
commit 69b641861c
7 changed files with 118 additions and 100 deletions
@@ -0,0 +1,56 @@
import { existsSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import CertManagerLayout from './cert-manager/layout';
import DkvFleetLayout from './dkv-fleet/layout';
import DomaincheckLayout from './domaincheck/layout';
import TenderRadarLayout from './tender-radar/layout';
/**
* Covers T-e8k-01, T-e8k-03, and T-e8k-04 (WINDOWS #10, PERM-04):
*
* - Each module-owned layout wraps children in ModuleAccessGate with the
* slug that exactly matches its directory name — a copy-paste mistake
* between the four near-identical files would flip the wrong module's
* gate (T-e8k-03).
* - Every non-dynamic module directory has a layout.tsx — a future module
* directory added without one would run past the gate again exactly
* like the four routes this plan fixes (T-e8k-04).
*/
const modulesDir = dirname(fileURLToPath(import.meta.url));
const placeholderChild = <div data-testid="placeholder-child">child</div>;
describe('module layouts — ModuleAccessGate slug wiring (T-e8k-01, T-e8k-03)', () => {
it.each([
['cert-manager', CertManagerLayout],
['dkv-fleet', DkvFleetLayout],
['domaincheck', DomaincheckLayout],
['tender-radar', TenderRadarLayout],
] as const)('%s/layout.tsx passes moduleSlug="%s" and forwards children', (expectedSlug, Layout) => {
const element = Layout({ children: placeholderChild });
expect(element.props.moduleSlug).toBe(expectedSlug);
expect(element.props.children).toBe(placeholderChild);
});
});
describe('module directory coverage — every module has a layout (T-e8k-04)', () => {
it('requires a layout.tsx in every non-dynamic module directory', () => {
const entries = readdirSync(modulesDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.filter((entry) => !entry.name.startsWith('['))
.map((entry) => entry.name);
expect(entries.length).toBeGreaterThan(0);
for (const dirName of entries) {
const hasLayout =
existsSync(join(modulesDir, dirName, 'layout.tsx')) ||
existsSync(join(modulesDir, dirName, 'layout.ts'));
expect(hasLayout, `${dirName}/ is missing a layout.tsx`).toBe(true);
}
});
});