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>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// 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);
|
||||
});
|
||||
Reference in New Issue
Block a user