Files
craftvia/scripts/test-doc-control.ts
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

74 lines
4.6 KiB
TypeScript

// AP5 — Dokumentenlenkung: Prüfzyklus/-termin, Neuversion + Historie, Lesebestätigung
// je Version. Prüft die reine Zyklus-Logik und die Daten-Invarianten (Wegwerf-Mandant).
//
// Lauf: npx tsx scripts/test-doc-control.ts
import "dotenv/config";
import { prisma } from "../src/server/db";
import { computeNextReview, isReviewDue, bumpMinorVersion } from "../src/lib/review-cycle";
let failures = 0;
const ok = (c: boolean, m: string) => { console.log(`${c ? "✓" : "✗ FEHLER"} ${m}`); if (!c) failures++; };
const SLUG = "ap5-test-doc";
async function cleanup() {
const t = await prisma.tenant.findUnique({ where: { slug: SLUG }, select: { id: true } });
if (!t) return;
await prisma.policyAcknowledgement.deleteMany({ where: { tenantId: t.id } });
await prisma.policyDocumentVersion.deleteMany({ where: { tenantId: t.id } });
await prisma.policyDocument.deleteMany({ where: { tenantId: t.id } });
await prisma.tenant.delete({ where: { id: t.id } });
}
async function main() {
await cleanup();
// ── Reine Zyklus-Logik ──────────────────────────────────────────────────────
const from = new Date("2026-01-15T00:00:00Z");
const next = computeNextReview(from, "jährlich");
ok(next?.getUTCFullYear() === 2027 && next?.getUTCMonth() === 0, "computeNextReview: jährlich → +12 Monate");
ok(computeNextReview(from, null) === null, "kein Zyklus → kein Termin");
ok(isReviewDue(new Date("2020-01-01")) === true && isReviewDue(new Date("2999-01-01")) === false, "isReviewDue erkennt Überfälligkeit");
ok(bumpMinorVersion("1.0") === "1.1" && bumpMinorVersion("2.9") === "2.10" && bumpMinorVersion("3") === "3.1", "bumpMinorVersion korrekt");
// ── Daten-Invarianten ───────────────────────────────────────────────────────
const tenant = await prisma.tenant.create({ data: { name: "AP5", slug: SLUG } });
const t = tenant.id;
const doc = await prisma.policyDocument.create({
data: { tenantId: t, code: "R08", type: "RICHTLINIE", title: "Zugriffskontrolle", version: "1.0", rawMarkdown: "# Zugriffskontrolle" },
select: { id: true, version: true },
});
// Lesebestätigung v1.0 durch zwei Nutzer; Duplikat (gleicher Nutzer/Version) wird verhindert.
await prisma.policyAcknowledgement.create({ data: { tenantId: t, policyDocumentId: doc.id, userId: "user-1", version: "1.0" } });
await prisma.policyAcknowledgement.create({ data: { tenantId: t, policyDocumentId: doc.id, userId: "user-2", version: "1.0" } });
let dupBlocked = false;
try {
await prisma.policyAcknowledgement.create({ data: { tenantId: t, policyDocumentId: doc.id, userId: "user-1", version: "1.0" } });
} catch { dupBlocked = true; }
ok(dupBlocked, "Lesebestätigung je (Dokument, Nutzer, Version) nur einmal (Unique)");
ok((await prisma.policyAcknowledgement.count({ where: { policyDocumentId: doc.id, version: "1.0" } })) === 2, "2 Lesebestätigungen für v1.0");
// „Als geprüft markieren": Momentaufnahme + Minor-Bump.
await prisma.policyDocumentVersion.create({ data: { tenantId: t, policyDocumentId: doc.id, version: "1.0", title: "Zugriffskontrolle", changeNote: "Turnusmäßige Überprüfung", createdBy: "user-1" } });
const newVersion = bumpMinorVersion(doc.version);
await prisma.policyDocument.update({ where: { id: doc.id }, data: { version: newVersion, reviewCycle: "jährlich", nextReviewAt: computeNextReview(new Date(), "jährlich") } });
const after = await prisma.policyDocument.findUnique({ where: { id: doc.id }, include: { versionHistory: true } });
ok(after?.version === "1.1", "Neuversion nach Prüfung (v1.0 → v1.1)");
ok((after?.versionHistory.length ?? 0) >= 1, "Versionshistorie enthält die frühere Version (≥2 Versionen sichtbar: Historie + aktuell)");
// Version-Scoping: alte Bestätigungen (v1.0) zählen NICHT für v1.1.
ok((await prisma.policyAcknowledgement.count({ where: { policyDocumentId: doc.id, version: "1.1" } })) === 0, "Lesebestätigungen sind versions-scoped (v1.1 startet bei 0)");
// Prüftermin gesetzt.
ok(after?.reviewCycle === "jährlich" && !!after?.nextReviewAt, "Prüfzyklus + nächster Prüftermin gesetzt");
await cleanup();
console.log("\n✓ aufgeräumt (Wegwerf-Mandant entfernt)");
}
main()
.then(() => { console.log(failures === 0 ? "\nAP5-Dokumentenlenkung grün." : `\n${failures} Prüfung(en) fehlgeschlagen.`); process.exit(failures === 0 ? 0 : 1); })
.catch(async (e) => { console.error(e); await cleanup().catch(() => {}); process.exit(1); });