// 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); });