// Test der nachträglichen Framework-Umschaltung (Admin: setTenantFrameworks). // // Spiegelt die Datenoperationen der Server-Action gegen die lokale DB und stellt den // Ausgangszustand danach wieder her. Der Auth-Guard (`requirePlatformFullAdmin`) wird // dabei NICHT durchlaufen — er ist identisch zu den sechs übrigen Admin-Aktionen. // // Geprüft wird: // 1. Aktivieren — Framework-Zeile entsteht, 120 ISO-Anforderungen werden angelegt, // die 321 TISAX-Anforderungen bleiben unangetastet, Flags stehen richtig. // 2. Deaktivieren — Zugehörigkeit fällt weg, ISO-Anforderungen werden STILLGELEGT // (archivedAt), nicht gelöscht; SoA-Einträge bleiben vollständig erhalten. // 3. Wiederherstellung — der Mandant steht am Ende exakt wie vorher. // // Lauf: npx tsx scripts/test-framework-toggle.ts // Nutzt die lokale Postgres-DB; .env liegt im Worktree. import "dotenv/config"; import { join } from "node:path"; import type { Framework } from "@prisma/client"; import { prisma, dbForTenant } from "../src/server/db"; import { reconcilePackage, stampPackageVersion } from "../prisma/import-policies"; import { resolvePackageForTenant, getTenantFrameworks } from "../prisma/template-store"; const SEED = join(process.cwd(), "seed", "isms-vorlagenpaket-v2"); let failed = 0; function check(ok: boolean, label: string, detail = "") { if (!ok) failed++; console.log(` ${ok ? "✓" : "✗"} ${label}${detail ? " — " + detail : ""}`); } async function state(tenantId: string) { const db = dbForTenant(tenantId); const [fw, tisax, tisaxArch, iso, isoArch, soa, flags] = await Promise.all([ getTenantFrameworks(prisma, tenantId), db.policyRequirement.count({ where: { framework: "TISAX", archivedAt: null } }), db.policyRequirement.count({ where: { framework: "TISAX", archivedAt: { not: null } } }), db.policyRequirement.count({ where: { framework: "ISO_27001", archivedAt: null } }), db.policyRequirement.count({ where: { framework: "ISO_27001", archivedAt: { not: null } } }), db.soaEntry.count(), db.policyVariable.findMany({ where: { key: { in: ["FLAG_FW_TISAX", "FLAG_FW_ISO27001"] } }, select: { key: true, value: true }, orderBy: { key: "asc" }, }), ]); return { fw, tisax, tisaxArch, iso, isoArch, soa, flags: flags.map((f) => `${f.key}=${f.value}`).join(" ") }; } /** Datenoperationen aus `setTenantFrameworks` (ohne Auth-Guard und revalidatePath). */ async function apply(tenantId: string, wanted: Framework[]) { const current = await getTenantFrameworks(prisma, tenantId); const added = wanted.filter((f) => !current.includes(f)); const removed = current.filter((f) => !wanted.includes(f)); const db = dbForTenant(tenantId); for (const [i, framework] of wanted.entries()) { await prisma.tenantFramework.upsert({ where: { tenantId_framework: { tenantId, framework } }, update: { isPrimary: i === 0 }, create: { tenantId, framework, isPrimary: i === 0 }, }); } for (const [i, framework] of added.entries()) { const { pkg } = await resolvePackageForTenant(prisma, tenantId, SEED, framework); await reconcilePackage(prisma, tenantId, pkg, { framework, reconcileShared: i === 0 }); await stampPackageVersion(prisma, tenantId, pkg.version, framework); } if (removed.length > 0) { await prisma.tenantFramework.deleteMany({ where: { tenantId, framework: { in: removed } } }); await db.policyRequirement.updateMany({ where: { framework: { in: removed }, archivedAt: null }, data: { archivedAt: new Date() }, }); } await db.policyVariable.updateMany({ where: { key: "FLAG_FW_TISAX" }, data: { value: String(wanted.includes("TISAX")) } }); await db.policyVariable.updateMany({ where: { key: "FLAG_FW_ISO27001" }, data: { value: String(wanted.includes("ISO_27001")) } }); } async function main() { const tenant = await prisma.tenant.findFirst({ where: { slug: "demo" }, select: { id: true, name: true } }); if (!tenant) { console.log("Mandant demo nicht gefunden — Test uebersprungen."); return; } const before = await state(tenant.id); console.log(`Mandant: ${tenant.name}`); console.log(` vorher: ${before.fw.join("+")} · TISAX=${before.tisax}/${before.tisaxArch} · ISO=${before.iso}/${before.isoArch} · SoA=${before.soa} · ${before.flags}`); try { console.log("\n1. ISO aktivieren"); await apply(tenant.id, [...before.fw, "ISO_27001"] as Framework[]); const on = await state(tenant.id); console.log(` ${on.fw.join("+")} · TISAX=${on.tisax}/${on.tisaxArch} · ISO=${on.iso}/${on.isoArch} · ${on.flags}`); check(on.fw.includes("ISO_27001"), "Framework-Zugehörigkeit angelegt"); check(on.iso === 120, "120 ISO-Anforderungen aktiv", `${on.iso}`); check(on.tisax === before.tisax && on.tisaxArch === before.tisaxArch, "TISAX-Anforderungen unangetastet", `${on.tisax} aktiv / ${on.tisaxArch} archiviert`); check(on.flags.includes("FLAG_FW_ISO27001=true") && on.flags.includes("FLAG_FW_TISAX=true"), "Sichtbarkeits-Flags gesetzt", on.flags); // Gepflegte SoA-Inhalte anlegen — sonst liefe die Erhaltungsprüfung unten über 0 → 0. const db0 = dbForTenant(tenant.id); for (const control of ["A.5.15", "A.8.5", "A.7.7"]) { await db0.soaEntry.upsert({ where: { tenantId_framework_control: { tenantId: tenant.id, framework: "ISO_27001", control } }, update: { justification: "Testbegruendung", implementationStatus: "umgesetzt" }, create: { tenantId: tenant.id, framework: "ISO_27001", control, justification: "Testbegruendung", implementationStatus: "umgesetzt", applicable: true, }, }); } const gepflegt = await db0.soaEntry.count({ where: { justification: "Testbegruendung" } }); check(gepflegt === 3, "SoA-Einträge zum Test gepflegt", `${gepflegt}`); console.log("\n2. ISO wieder deaktivieren"); await apply(tenant.id, before.fw as Framework[]); const off = await state(tenant.id); console.log(` ${off.fw.join("+")} · TISAX=${off.tisax}/${off.tisaxArch} · ISO=${off.iso}/${off.isoArch} · SoA=${off.soa} · ${off.flags}`); check(!off.fw.includes("ISO_27001"), "Zugehörigkeit entfernt"); check(off.iso === 0 && off.isoArch === 120, "ISO-Anforderungen stillgelegt statt gelöscht", `aktiv=${off.iso} archiviert=${off.isoArch}`); check(off.tisax === before.tisax, "TISAX weiterhin unangetastet", `${off.tisax}`); const ueberlebt = await dbForTenant(tenant.id).soaEntry.count({ where: { justification: "Testbegruendung" } }); check(ueberlebt === 3, "gepflegte SoA-Begründungen überleben das Deaktivieren unverändert", `${ueberlebt} von 3`); check(off.flags.includes("FLAG_FW_ISO27001=false"), "Flag zurückgesetzt", off.flags); console.log("\n3. Erneut aktivieren — stillgelegte Anforderungen reaktivieren"); await apply(tenant.id, [...before.fw, "ISO_27001"] as Framework[]); const again = await state(tenant.id); check(again.iso === 120 && again.isoArch === 0, "Re-Import reaktiviert statt zu duplizieren", `aktiv=${again.iso} archiviert=${again.isoArch}`); } finally { console.log("\n4. Ausgangszustand wiederherstellen"); await apply(tenant.id, before.fw as Framework[]); const db = dbForTenant(tenant.id); await db.policyRequirement.deleteMany({ where: { framework: "ISO_27001" } }); await db.soaEntry.deleteMany({ where: { framework: "ISO_27001" } }); const end = await state(tenant.id); console.log(` ${end.fw.join("+")} · TISAX=${end.tisax}/${end.tisaxArch} · ISO=${end.iso}/${end.isoArch} · ${end.flags}`); check(JSON.stringify(end) === JSON.stringify(before), "Mandant steht wie vorher", JSON.stringify(end) === JSON.stringify(before) ? "" : `${JSON.stringify(before)} → ${JSON.stringify(end)}`); } } main() .catch((e) => { console.error(e); failed++; }) .finally(async () => { await prisma.$disconnect(); console.log(`\n${failed === 0 ? "OK — alle Prüfungen bestanden" : `FEHLGESCHLAGEN — ${failed} Prüfung(en)`}`); process.exit(failed === 0 ? 0 : 1); });