#038: Die E2E-Ablaeufe laufen ab jetzt vor jedem Merge nach main, nicht vor jedem Commit. Gemessen: Bau 14 s, Start 7 s, Ablaeufe 256 s — zu den vier Minuten des bestehenden Tores. Neun Minuten vor jedem Commit ist ein Tor, das umgangen wird. Vor jedem Merge waeren es in Phase 2 acht Laeufe gewesen, und jeder der vier Befunde aus #027 waere am Tag seiner Entstehung aufgefallen. Geprueft wurde jeder der 16 Ablaeufe gegen die Frage, ob es ein Vitest-Gegenstueck gibt. Neun Zusagen haengen ausschliesslich dort, und keine laesst sich holen: alle brauchen einen echten Browser. Die Liste steht in CLAUDE.md. Der eigentliche Fund dabei war keine Testluecke, sondern eine Konvention ohne Pruefer: "jede Seite prueft selbst" steht seit #007 in CLAUDE.md, und geprueft hat es bis hierher nur ein Mensch, der nachsieht. Der Review von Phase 2 hat alle 27 Seiten durchgesehen — ein Ergebnis, das fuer genau einen Tag gilt. tests/zugang.test.ts liest sie jetzt, samt jedem Route Handler; die Ausnahmen (Auth, Healthcheck) stehen namentlich mit Begruendung, und ein Test prueft auch die Gegenrichtung. #037: Das Lesemodell der Anhaenge liegt in queries/ und engt selbst ein — vorher hing das daran, dass beide Aufrufer den Lauf vorher pruefen. /meine-geraete wird bei 390 px gemessen, mit einem echten MITARBEITER-Konto aus dem E2E-Vorlauf, dazu ein Test, dass dort kein Link in die Verwaltung angeboten wird. Dabei selbst einen Fehler gemacht, der die neue Regel begruendet: fuer das zweite Konto brauchte `anmelden` eine Variante, die nicht auf / wartet, und ich habe das Warten fuer alle gelockert. Das machte zwei unverwandte Ablaeufe unzuverlaessig — sichtbar nur im vollen Durchgang, einzeln waren beide gruen. Ein gemeinsamer Helfer in einer Testsuite ist geteilter Zustand. Ohne vollen Durchgang haette ich zwei rote Ablaeufe gemergt. 1457 Vitest-Tests (vorher 1454), 115 E2E-Ablaeufe (vorher 113). Offen bleibt nur noch #024, und das verlangt einen echten Proxmox-Host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
80d97467da
commit
75c335df23
@@ -232,6 +232,111 @@ describe("Rechtepruefung an den Einstiegspunkten", () => {
|
||||
expect(befunde).toEqual([]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Alle `page.tsx` unter `(app)`, rekursiv.
|
||||
*
|
||||
* Von Hand aufzuzaehlen waere dasselbe Problem wie bei der Proxy-Allowlist:
|
||||
* eine Liste, die dem Code hinterherlaeuft. Eine neue Seite soll **ohne**
|
||||
* Zutun in diesem Test landen.
|
||||
*/
|
||||
function seitenDateien(ordner: string): string[] {
|
||||
const gefunden: string[] = [];
|
||||
for (const eintrag of readdirSync(ordner, { withFileTypes: true })) {
|
||||
const pfad = join(ordner, eintrag.name);
|
||||
if (eintrag.isDirectory()) gefunden.push(...seitenDateien(pfad));
|
||||
else if (eintrag.name === "page.tsx") gefunden.push(pfad);
|
||||
}
|
||||
return gefunden;
|
||||
}
|
||||
|
||||
it("laesst jede Seite ihr Recht selbst pruefen", () => {
|
||||
/*
|
||||
* **Die Huelle ist das zweite Netz, nicht das erste** (CLAUDE.md). Next
|
||||
* rendert Layouts bei einer Navigation innerhalb desselben Layouts nicht
|
||||
* neu — eine Seite, die sich auf `src/app/(app)/layout.tsx` verlaesst, ist
|
||||
* nach dem ersten Seitenwechsel ungeschuetzt.
|
||||
*
|
||||
* Dass das heute an allen 27 Seiten stimmt, hat der Review von Phase 2 **von
|
||||
* Hand** nachgesehen (Reviewbefund zu #029). Eine Pruefung von Hand ist kein
|
||||
* Regressionstest: sie gilt fuer den Tag, an dem sie lief. Dieser Test gilt
|
||||
* fuer jede kuenftige Seite — und genau dort liegt sein Wert, nicht im
|
||||
* heutigen gruenen Ergebnis.
|
||||
*/
|
||||
const befunde: string[] = [];
|
||||
const seiten = seitenDateien("src/app/(app)");
|
||||
|
||||
for (const datei of seiten) {
|
||||
const quelle = readFileSync(datei, "utf8");
|
||||
const prueft = quelle.includes("sitzungErforderlich(") || quelle.includes("rechtErforderlich(");
|
||||
if (!prueft) befunde.push(`${datei} prueft weder Sitzung noch Recht`);
|
||||
}
|
||||
|
||||
expect(befunde).toEqual([]);
|
||||
// Ohne diese Zeile waere eine leere Liste ein gruener Test.
|
||||
expect(seiten.length).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
it("laesst jeden Route Handler sein Recht selbst pruefen", () => {
|
||||
/*
|
||||
* **Die Huelle deckt Route Handler gar nicht ab**, und der Proxy prueft das
|
||||
* Cookie absichtlich nur auf Vorhandensein (CLAUDE.md). Alles unter
|
||||
* `src/app/api/`, das mehr tut als Auth und Healthcheck, prueft deshalb
|
||||
* selbst.
|
||||
*
|
||||
* Die Ausnahmen stehen **namentlich** darunter, wie bei den oeffentlichen
|
||||
* Aktionen: ein neuer Handler ohne Pruefung soll jemanden zwingen, ihn hier
|
||||
* einzutragen — und damit zu begruenden.
|
||||
*
|
||||
* Dass das noetig ist, hat der Review gezeigt: beim Anhang-Handler liess
|
||||
* sich die ganze Pruefung entfernen, ohne dass ein Test rot wurde. Jetzt
|
||||
* faehrt `tests/anhang-route.test.ts` ihn echt; dieser Test hier faengt den
|
||||
* **naechsten**.
|
||||
*/
|
||||
const OHNE_PRUEFUNG = new Map([
|
||||
[
|
||||
"src/app/api/gesundheit/route.ts",
|
||||
"Healthcheck fuer Docker und den Reverse Proxy – muss ohne Anmeldung antworten",
|
||||
],
|
||||
["src/app/api/auth/abmelden/route.ts", "Abmelden kann keine gueltige Sitzung verlangen"],
|
||||
["src/app/api/auth/oidc/start/route.ts", "Beginn des Anmeldevorgangs"],
|
||||
["src/app/api/auth/oidc/rueckruf/route.ts", "Rueckruf des Anbieters, vor der Sitzung"],
|
||||
]);
|
||||
|
||||
function handlerDateien(ordner: string): string[] {
|
||||
const gefunden: string[] = [];
|
||||
for (const eintrag of readdirSync(ordner, { withFileTypes: true })) {
|
||||
const pfad = join(ordner, eintrag.name);
|
||||
if (eintrag.isDirectory()) gefunden.push(...handlerDateien(pfad));
|
||||
else if (eintrag.name === "route.ts") gefunden.push(pfad);
|
||||
}
|
||||
return gefunden;
|
||||
}
|
||||
|
||||
const befunde: string[] = [];
|
||||
const handler = handlerDateien("src/app/api");
|
||||
|
||||
for (const datei of handler) {
|
||||
const quelle = readFileSync(datei, "utf8");
|
||||
const prueft =
|
||||
quelle.includes("rechtErforderlich(") ||
|
||||
quelle.includes("sitzungErforderlich(") ||
|
||||
// Der SCIM-Weg: das Bearer-Token wird zur Quelle und damit zum Kunden.
|
||||
quelle.includes("quelleAusAnfrage(");
|
||||
if (!prueft && !OHNE_PRUEFUNG.has(datei)) {
|
||||
befunde.push(`${datei} prueft nichts und steht nicht in der Ausnahmeliste`);
|
||||
}
|
||||
}
|
||||
|
||||
expect(befunde).toEqual([]);
|
||||
expect(handler.length).toBeGreaterThan(8);
|
||||
|
||||
// Und die Gegenrichtung: eine Ausnahme fuer einen Handler, den es nicht mehr
|
||||
// gibt, verdeckt beim naechsten Mal einen echten Befund.
|
||||
for (const datei of OHNE_PRUEFUNG.keys()) {
|
||||
expect(handler, `${datei} steht in der Ausnahmeliste, existiert aber nicht`).toContain(datei);
|
||||
}
|
||||
});
|
||||
|
||||
it("erkennt eine Aktion ohne jede Pruefung", () => {
|
||||
// Der Test oben ist nachsichtiger geworden; dieser hier zeigt, dass er
|
||||
// trotzdem noch anschlaegt. Geprueft wird die Regel, nicht die Datei.
|
||||
|
||||
Reference in New Issue
Block a user