Files
FPT/src/lib/versioning-coverage.test.ts
T
admGitAICDS f22b0a3f27
Deploy App / deploy (push) Successful in 1m55s
Uebersicht der offenen Punkte statt Assistent, Bestaetigung je Phasenzelle
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 15:02:15 +02:00

89 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Wächter über die Vollständigkeit der Historie.
//
// Die Versionierung hängt daran, dass JEDER inhaltsverändernde Endpunkt `touchScenario`
// aufruft. Ein vergessener Pfad fällt nicht auf -- er erzeugt einfach still keine Version,
// und die Historie hat eine Lücke, die man erst Wochen später bemerkt. Dieser Test liest die
// Route-Dateien und prüft das statisch.
import { describe, it, expect } from "vitest";
import { readFileSync, readdirSync } from "node:fs";
import path from "node:path";
const API = path.join(process.cwd(), "src", "app", "api");
function routeFiles(dir: string): string[] {
const out: string[] = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...routeFiles(full));
else if (entry.name === "route.ts") out.push(full);
}
return out;
}
// Endpunkte, die schreiben, aber bewusst KEINE Version erzeugen -- jeweils mit Begründung.
const EXEMPT: Record<string, string> = {
"auth/login": "kein Szenario betroffen",
"auth/logout": "kein Szenario betroffen",
"auth/register": "kein Szenario betroffen",
"auth/change-password": "kein Szenario betroffen",
"plans/[planId]": "ändert nur den Plan-Namen bzw. löscht den ganzen Plan kein Szenario-Inhalt",
"scenarios/[scenarioId]/versions": "erzeugt Versionen selbst (Hauptversion / Wiederherstellen)",
"scenarios/[scenarioId]/versions/[versionId]": "erzeugt Versionen selbst",
"plans/[planId]/actuals":
"Ist-Werte sind eine Beobachtung, keine Planänderung sie verändern kein Szenario",
"plans/[planId]/actuals/[setId]": "dito (Löschen eines Ist-Satzes)",
"plans/[planId]/analyses": "gespeicherte Analysen sind read-only Momentaufnahmen kein Szenario betroffen",
"plans/[planId]/analyses/[analysisId]": "dito (Öffnen/Löschen einer Analyse)",
"plans/[planId]/reports": "Berichte sind erzeugte Dokumente sie verändern kein Szenario",
"plans/[planId]/reports/[reportId]": "dito (Herunterladen/Löschen eines Berichts)",
};
describe("Versionierung: Abdeckung der Schreibpfade", () => {
const files = routeFiles(API);
it("findet überhaupt Endpunkte", () => {
expect(files.length).toBeGreaterThan(10);
});
it("ruft in jedem inhaltsverändernden Endpunkt touchScenario auf", () => {
const missing: string[] = [];
for (const file of files) {
const src = readFileSync(file, "utf8");
const rel = path
.relative(API, file)
.replace(/\\/g, "/")
.replace(/\/route\.ts$/, "");
// Schreibt dieser Endpunkt überhaupt?
const mutates = /export async function (POST|PUT|PATCH|DELETE)/.test(src);
if (!mutates) continue;
if (rel in EXEMPT) continue;
// Ein DELETE, das das Szenario selbst entfernt, braucht keine Version mehr.
const onlyDeletesScenario =
rel === "scenarios/[scenarioId]" && !/export async function (POST|PUT|PATCH)/.test(src);
if (onlyDeletesScenario) continue;
if (!src.includes("touchScenario(")) missing.push(rel);
}
expect(
missing,
`Diese Endpunkte verändern Inhalte, erzeugen aber keine Version:\n ${missing.join(
"\n "
)}\nEntweder touchScenario ergänzen oder in EXEMPT mit Begründung eintragen.`
).toEqual([]);
});
it("hält die Ausnahmeliste frei von Karteileichen", () => {
// Eine Ausnahme für einen Endpunkt, den es nicht mehr gibt, verschleiert später eine
// echte Lücke.
const known = new Set(
files.map((f) => path.relative(API, f).replace(/\\/g, "/").replace(/\/route\.ts$/, ""))
);
const stale = Object.keys(EXEMPT).filter((k) => !known.has(k));
expect(stale, `Ausnahmen ohne zugehörigen Endpunkt: ${stale.join(", ")}`).toEqual([]);
});
});