Files
craftvia/scripts/test-soa.ts
T
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

68 lines
3.4 KiB
TypeScript

// AP3 — Anwendbarkeitserklärung (SoA). Prüft Vorbefüllung, Bedingungs-Logik,
// Idempotenz und Vollständigkeits-Markierung gegen die lokale DB (Wegwerf-Mandant).
//
// Lauf: npx tsx scripts/test-soa.ts
import "dotenv/config";
import { prisma, dbForTenant } from "../src/server/db";
import { ensureSoaEntries, loadIsoSoaControls } from "../src/server/soa-statement";
import { isSoaEntryComplete } from "../src/lib/soa";
let failures = 0;
const ok = (c: boolean, m: string) => { console.log(`${c ? "✓" : "✗ FEHLER"} ${m}`); if (!c) failures++; };
const SLUG = "ap3-test-soa";
async function cleanup() {
const t = await prisma.tenant.findUnique({ where: { slug: SLUG }, select: { id: true } });
if (t) {
await prisma.soaEntry.deleteMany({ where: { tenantId: t.id } });
await prisma.policyVariable.deleteMany({ where: { tenantId: t.id } });
await prisma.tenant.delete({ where: { id: t.id } });
}
}
async function main() {
await cleanup();
const controls = loadIsoSoaControls();
ok(controls.length === 93, `loadIsoSoaControls: ${controls.length} Annex-A-Controls (erwartet 93)`);
const devControl = controls.find((c) => c.condition === "FLAG_DEV_INHOUSE");
const plainControl = controls.find((c) => c.condition === null);
ok(!!devControl && !!plainControl, "je ein Control mit/ohne Bedingung vorhanden");
const tenant = await prisma.tenant.create({ data: { name: "AP3 SoA", slug: SLUG } });
const t = tenant.id;
// Flags: DEV-Inhouse AUS → DEV-Controls default nicht anwendbar; Personendaten AN.
await prisma.policyVariable.createMany({
data: [
{ tenantId: t, key: "FLAG_DEV_INHOUSE", title: "Dev", kind: "boolean", value: "false" },
{ tenantId: t, key: "FLAG_PERSONAL_DATA", title: "PD", kind: "boolean", value: "true" },
],
});
const db = dbForTenant(t);
const n1 = await ensureSoaEntries(db, t);
ok(n1 === 93, `ensureSoaEntries legt 93 Zeilen an (${n1})`);
const n2 = await ensureSoaEntries(db, t);
ok(n2 === 0, "zweiter Lauf ist idempotent (0 neue Zeilen)");
ok((await prisma.soaEntry.count({ where: { tenantId: t } })) === 93, "93 SoA-Zeilen persistiert");
const devEntry = await prisma.soaEntry.findFirst({ where: { tenantId: t, control: devControl!.control } });
ok(devEntry?.applicable === false, `Bedingung greift: ${devControl!.control} (FLAG_DEV_INHOUSE aus) → nicht anwendbar`);
const plainEntry = await prisma.soaEntry.findFirst({ where: { tenantId: t, control: plainControl!.control } });
ok(plainEntry?.applicable === true, `Control ohne Bedingung (${plainControl!.control}) → anwendbar`);
// Vollständigkeit: frisch ohne Begründung → unvollständig; nach Begründung → vollständig.
ok(!isSoaEntryComplete({ applicable: true, justification: "" }), "Control ohne Begründung → unvollständig");
ok(isSoaEntryComplete({ applicable: false, justification: "Ausschluss: keine Eigenentwicklung" }), "Ausschluss mit Begründung → vollständig");
const incomplete = await prisma.soaEntry.count({ where: { tenantId: t, justification: "" } });
ok(incomplete === 93, "alle 93 Zeilen initial ohne Begründung (unvollständig)");
await cleanup();
console.log("\n✓ aufgeräumt (Wegwerf-Mandant entfernt)");
}
main()
.then(() => { console.log(failures === 0 ? "\nAP3-SoA 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); });