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:
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user