#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
+50
-2
@@ -12,12 +12,12 @@ import {
|
||||
} from "@/lib/anhaenge";
|
||||
import {
|
||||
AnhangFehler,
|
||||
anhaengeDesLaufs,
|
||||
anhangEntfernen,
|
||||
anhangLesen,
|
||||
anhangPfad,
|
||||
anhangSpeichern,
|
||||
} from "@/server/services/anhaenge";
|
||||
import { anhaengeDesLaufs } from "@/server/queries/anhaenge";
|
||||
import { MandantFehler } from "@/server/mandant";
|
||||
import { LaufFehler } from "@/server/services/laeufe";
|
||||
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
|
||||
@@ -393,7 +393,7 @@ describe.runIf(datenbankVerfuegbar)("Anhänge", () => {
|
||||
admin,
|
||||
);
|
||||
|
||||
const liste = await anhaengeDesLaufs(laufId);
|
||||
const liste = await anhaengeDesLaufs(laufId, admin);
|
||||
expect(liste).toHaveLength(1);
|
||||
expect(liste[0]).toMatchObject({
|
||||
schrittergebnisId: ergebnisId,
|
||||
@@ -402,6 +402,54 @@ describe.runIf(datenbankVerfuegbar)("Anhänge", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("engt die Liste selbst auf die sichtbaren Kunden ein – ohne Vorprüfung des Aufrufers", async () => {
|
||||
/*
|
||||
* **Die Zusage von #037.** Vorher nahm `anhaengeDesLaufs` nur eine
|
||||
* Lauf-Kennung und wendete keine Sicht an; dass niemand fremde Anhänge sah,
|
||||
* hing daran, dass beide Aufrufer den Lauf vorher prüften. Das war kein
|
||||
* Schaden und trotzdem die falsche Form: ein Lesemodell wendet seine Sicht
|
||||
* selbst an, **damit kein Aufrufer sie vergessen kann** (CLAUDE.md).
|
||||
*
|
||||
* Dieser Test ruft die Funktion deshalb **direkt** und ohne jede
|
||||
* Vorprüfung — so wie es der dritte Aufrufer täte, der die Prüfung vergisst.
|
||||
*/
|
||||
const vorher = process.env["OIDC_CUSTOMER_GROUP_PREFIX"];
|
||||
process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = "kunde-";
|
||||
try {
|
||||
const { admin, laufId, schrittId } = await aufbau();
|
||||
await testPrisma.einstellung.update({
|
||||
where: { id: "global" },
|
||||
data: { modus: Betriebsmodus.DIENSTLEISTUNG },
|
||||
});
|
||||
await anhangSpeichern(
|
||||
{
|
||||
laufId,
|
||||
vorlagenSchrittId: schrittId,
|
||||
dateiname: "fremd.png",
|
||||
medientyp: "image/png",
|
||||
inhalt: new Uint8Array(PNG),
|
||||
},
|
||||
admin,
|
||||
);
|
||||
|
||||
const fremd = await testPrisma.kunde.create({ data: { name: "Fremd GmbH", kuerzel: "FRD" } });
|
||||
const techniker = await testPrisma.benutzer.create({
|
||||
data: { email: "tina@firma.example", name: "Tina", rolle: "TECHNIKER" },
|
||||
});
|
||||
await testPrisma.benutzerKunde.create({
|
||||
data: { benutzerId: techniker.id, kundeId: fremd.id },
|
||||
});
|
||||
|
||||
// Der Administrator sieht den Anhang.
|
||||
expect(await anhaengeDesLaufs(laufId, admin)).toHaveLength(1);
|
||||
// Der auf einen anderen Kunden beschränkte Techniker nicht.
|
||||
expect(await anhaengeDesLaufs(laufId, { id: techniker.id, rolle: techniker.rolle })).toHaveLength(0);
|
||||
} finally {
|
||||
if (vorher === undefined) delete process.env["OIDC_CUSTOMER_GROUP_PREFIX"];
|
||||
else process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = vorher;
|
||||
}
|
||||
});
|
||||
|
||||
it("hält die Mandantengrenze in der Datenbank, nicht nur im Dienst", async () => {
|
||||
const { laufId, schrittId, ergebnisId } = await aufbau();
|
||||
const fremd = await testPrisma.kunde.create({ data: { name: "Fremd GmbH", kuerzel: "FREMD" } });
|
||||
|
||||
@@ -2,7 +2,16 @@ import { chromium } from "@playwright/test";
|
||||
import "dotenv/config";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { ABTEILUNGEN } from "@/../prisma/seed-daten/stammdaten";
|
||||
import { anmelden, SEED_GERAET, SEED_TAG, SEED_VORLAGE } from "./helfer";
|
||||
import { passwortHashen } from "@/server/auth/benutzer";
|
||||
import {
|
||||
anmelden,
|
||||
E2E_MITARBEITER_MAIL,
|
||||
E2E_MITARBEITER_NAME,
|
||||
E2E_MITARBEITER_PASSWORT,
|
||||
SEED_GERAET,
|
||||
SEED_TAG,
|
||||
SEED_VORLAGE,
|
||||
} from "./helfer";
|
||||
import { e2eAufraeumenMitBericht } from "./aufraeumen";
|
||||
|
||||
/**
|
||||
@@ -114,6 +123,42 @@ export default async function vorbereiten(): Promise<void> {
|
||||
});
|
||||
process.env["E2E_BETRIEBSMODUS"] = einstellung?.modus ?? "INTERN";
|
||||
|
||||
/*
|
||||
* 2d. Das Konto der Rolle `MITARBEITER` fuer `/meine-geraete` (#037).
|
||||
*
|
||||
* Es muss an einer `Mitarbeiter`-Zeile haengen — der CHECK
|
||||
* `Benutzer_mitarbeiter_nur_bei_rolle` verlangt das in **beide** Richtungen
|
||||
* (#035): ein MITARBEITER ohne Person waere ein Recht, das sich auf niemanden
|
||||
* bezieht. Genommen wird die erste Person des Seed-Bestands, denn sie hat
|
||||
* Geraete, und ohne Geraet zeigte die Seite nur ihren Leerzustand.
|
||||
*/
|
||||
const person = await prisma.mitarbeiter.findFirst({
|
||||
where: { aktiv: true, geraete: { some: {} } },
|
||||
select: { id: true, kundeId: true, anzeigename: true },
|
||||
orderBy: { anzeigename: "asc" },
|
||||
});
|
||||
if (!person) {
|
||||
fehlen(
|
||||
"Es gibt keine Person mit einem Gerät.",
|
||||
"pnpm db:seed laufen lassen — die Selbstauskunft braucht eine Person, der ein Gerät gehört.",
|
||||
);
|
||||
}
|
||||
await prisma.benutzer.upsert({
|
||||
where: { email: E2E_MITARBEITER_MAIL },
|
||||
create: {
|
||||
email: E2E_MITARBEITER_MAIL,
|
||||
name: E2E_MITARBEITER_NAME,
|
||||
rolle: "MITARBEITER",
|
||||
passwortHash: await passwortHashen(E2E_MITARBEITER_PASSWORT),
|
||||
mitarbeiterId: person.id,
|
||||
aktiv: true,
|
||||
},
|
||||
// Ein Durchgang, der vorher abgebrochen ist, laesst das Konto stehen. Die
|
||||
// Person kann dabei eine andere sein als beim letzten Mal.
|
||||
update: { rolle: "MITARBEITER", mitarbeiterId: person.id, aktiv: true },
|
||||
});
|
||||
process.env["E2E_MITARBEITER_PERSON"] = person.anzeigename;
|
||||
|
||||
/*
|
||||
* 3. Liest der Server **dieselbe** Datenbank?
|
||||
*
|
||||
|
||||
+47
-1
@@ -20,6 +20,22 @@ export const SEED_VORLAGE = "PC Neuinstallation";
|
||||
|
||||
/** Das Beispielgerät aus dem Seed. Sein Aufkleber steht im Abnahmekriterium. */
|
||||
export const SEED_GERAET = "PC-001";
|
||||
|
||||
/**
|
||||
* Ein Konto der Rolle `MITARBEITER`, nur fuer die Ablaeufe (#037).
|
||||
*
|
||||
* **Nicht im Seed**, obwohl das kuerzer waere: ein Konto mit bekanntem Passwort
|
||||
* gehoert nicht in einen Seed, der auch produktiv laeuft. Der E2E-Vorlauf legt
|
||||
* es an, der Nachlauf nimmt es wieder mit — es traegt `ZZ` im Namen und faellt
|
||||
* damit unter `tests/e2e/aufraeumen.ts`.
|
||||
*
|
||||
* Gebraucht wird es, weil `/meine-geraete` das Recht `selbstauskunft` verlangt
|
||||
* und das **nur** `MITARBEITER` hat: ein Administrator saehe dort „kein
|
||||
* Zugriff“, und ein Ablauf haette die Verweigerungsseite gemessen.
|
||||
*/
|
||||
export const E2E_MITARBEITER_MAIL = "zz.e2e.mitarbeiter@firma.example";
|
||||
export const E2E_MITARBEITER_PASSWORT = "zz-e2e-nur-fuer-ablaeufe";
|
||||
export const E2E_MITARBEITER_NAME = "ZZ E2E Mitarbeiterkonto";
|
||||
export const SEED_TAG = "LZ-7K3F9Q";
|
||||
|
||||
export async function anmelden(seite: Page): Promise<void> {
|
||||
@@ -31,10 +47,40 @@ export async function anmelden(seite: Page): Promise<void> {
|
||||
"Administrator passen, den der Seed angelegt hat.",
|
||||
);
|
||||
}
|
||||
/*
|
||||
* Wartet auf `/`, und zwar genau so streng wie vorher. Lockerer („irgendwas
|
||||
* ausser /anmelden“) sah harmlos aus und machte zwei Ablaeufe im vollen
|
||||
* Durchgang unzuverlaessig: der naechste Schritt begann, bevor die Uebersicht
|
||||
* stand. Der Admin landet immer auf `/` — fuer ihn gibt es keinen Grund, das
|
||||
* aufzuweichen.
|
||||
*/
|
||||
await anmeldenMit(seite, email, passwort, "**/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Anmelden mit beliebigen Zugangsdaten.
|
||||
*
|
||||
* `ziel` ist das Muster, auf das gewartet wird. Ohne Angabe genuegt es, dass die
|
||||
* Anmeldeseite verlassen ist: ein `MITARBEITER` landet auf `/meine-geraete`,
|
||||
* weil `startseite(rolle)` das Ziel an der Rolle entscheidet (#035), und ein
|
||||
* Warten auf `/` liefe dort in den Zeitablauf.
|
||||
*/
|
||||
export async function anmeldenMit(
|
||||
seite: Page,
|
||||
email: string,
|
||||
passwort: string,
|
||||
ziel?: string,
|
||||
): Promise<void> {
|
||||
await seite.goto("/anmelden");
|
||||
await seite.fill('input[name="email"]', email);
|
||||
await seite.fill('input[name="passwort"]', passwort);
|
||||
await Promise.all([seite.waitForURL("**/"), seite.click('button[type="submit"]')]);
|
||||
await Promise.all([
|
||||
seite.waitForURL(ziel ?? ((adresse) => !adresse.pathname.startsWith("/anmelden"))),
|
||||
seite.click('button[type="submit"]'),
|
||||
]);
|
||||
// Und bis die Seite wirklich steht: ein `waitForURL` endet an der Adresse,
|
||||
// nicht am geladenen Dokument.
|
||||
await seite.waitForLoadState("load");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+72
-5
@@ -1,7 +1,15 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import "dotenv/config";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { SEED_TAG, SEED_VORLAGE, anmelden, betriebsmodus } from "./helfer";
|
||||
import {
|
||||
E2E_MITARBEITER_MAIL,
|
||||
E2E_MITARBEITER_PASSWORT,
|
||||
SEED_TAG,
|
||||
SEED_VORLAGE,
|
||||
anmelden,
|
||||
anmeldenMit,
|
||||
betriebsmodus,
|
||||
} from "./helfer";
|
||||
|
||||
/**
|
||||
* Am Handy scrollt nichts nach rechts (#018).
|
||||
@@ -191,10 +199,10 @@ const SEITEN: readonly Hauptseite[] = [
|
||||
* Zustellungs-Protokoll sind Tabellen, und Tabellen sind die Stellen, an denen
|
||||
* horizontales Scrollen entsteht.
|
||||
*
|
||||
* Nicht dabei: `/meine-geraete` (#035) — es verlangt das Recht
|
||||
* `selbstauskunft`, und das hat nur `MITARBEITER`. Dieser Ablauf meldet sich
|
||||
* als Admin an, der dort „kein Zugriff“ sähe; gemessen würde die
|
||||
* Verweigerungsseite. Dafür braucht es ein eigenes Konto, siehe #037.
|
||||
* `/meine-geraete` steht **nicht** in dieser Liste, sondern in einem eigenen
|
||||
* Block darunter: die Seite verlangt `selbstauskunft`, und das hat nur
|
||||
* `MITARBEITER`. Mit der Anmeldung dieses Ablaufs (Admin) würde die
|
||||
* Verweigerungsseite gemessen — und die scrollt garantiert nicht (#037).
|
||||
*/
|
||||
{ name: "Vorlagen: Bausteine", pfad: () => "/vorlagen/bausteine" },
|
||||
{ name: "Admin: Schnellstart", pfad: () => "/admin/schnellstart" },
|
||||
@@ -283,6 +291,65 @@ test.describe("Am Handy bei 390 px", () => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* `/meine-geraete` braucht eine andere Anmeldung (#037).
|
||||
*
|
||||
* Eigener Block statt eines Sonderfalls in der Schleife oben: die Seitenliste
|
||||
* dort gilt für **eine** Rolle, und ein `if` darin hätte beim nächsten Eintrag
|
||||
* geerbt werden müssen. Die Messung selbst ist dieselbe Funktion.
|
||||
*/
|
||||
test.describe("Am Handy bei 390 px: die Selbstauskunft", () => {
|
||||
const PFAD = "/meine-geraete";
|
||||
|
||||
test(`scrollt „Meine Geräte“ nicht horizontal`, async ({ page }) => {
|
||||
test.slow();
|
||||
await anmeldenMit(page, E2E_MITARBEITER_MAIL, E2E_MITARBEITER_PASSWORT);
|
||||
|
||||
await page.goto(PFAD);
|
||||
/*
|
||||
* Dieselbe Vorsicht wie oben, hier noch wichtiger: hätte das Konto die Rolle
|
||||
* verloren, landete es auf `/kein-zugriff` — und der Ablauf hätte grün
|
||||
* gemeldet, ohne die Seite gesehen zu haben.
|
||||
*/
|
||||
expect(new URL(page.url()).pathname, `${PFAD} hat weitergeleitet.`).toBe(PFAD);
|
||||
await page.waitForLoadState("load");
|
||||
|
||||
// Und es ist wirklich die Selbstauskunft, nicht eine leere Hülle: der Name
|
||||
// der verknüpften Person steht darauf.
|
||||
const person = process.env["E2E_MITARBEITER_PERSON"];
|
||||
if (person) {
|
||||
await expect(page.getByText(person, { exact: false }).first()).toBeVisible();
|
||||
}
|
||||
|
||||
const masse = await messen(page);
|
||||
expect(
|
||||
masse.scroll,
|
||||
`„Meine Geräte“ (${PFAD}) scrollt horizontal: erwartet <= ${masse.sichtbar}, ` +
|
||||
`war ${masse.scroll} — ${masse.scroll - masse.sichtbar} px zu breit.` +
|
||||
(masse.uebeltaeter.length > 0 ? `\n Zu weit rechts: ${masse.uebeltaeter.join("\n ")}` : ""),
|
||||
).toBeLessThanOrEqual(masse.sichtbar);
|
||||
});
|
||||
|
||||
test("sieht dort keinen Weg in die Verwaltung", async ({ page }) => {
|
||||
/*
|
||||
* Die Gegenprobe zur Rolle, und sie gehört hierher: die Seitenleiste nennt
|
||||
* seit #035 an jedem Eintrag ihr Recht, damit einem `MITARBEITER` keine
|
||||
* sechs Links angeboten werden, die alle auf `/kein-zugriff` führen. Das
|
||||
* ist in Vitest als Rollenmatrix geprüft — dass es im **gerenderten** DOM
|
||||
* ankommt, nur hier.
|
||||
*/
|
||||
await anmeldenMit(page, E2E_MITARBEITER_MAIL, E2E_MITARBEITER_PASSWORT);
|
||||
await page.goto("/meine-geraete");
|
||||
|
||||
for (const ziel of ["/admin", "/vorlagen", "/laeufe", "/mitarbeiter", "/geraete", "/archiv"]) {
|
||||
await expect(
|
||||
page.locator(`a[href^="${ziel}"]`),
|
||||
`Ein MITARBEITER bekommt einen Link auf ${ziel} angeboten.`,
|
||||
).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
if (laufId) {
|
||||
await prisma.kommentar.deleteMany({ where: { laufId } });
|
||||
|
||||
@@ -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