// Unit-Tests (Story A8) für die Gap-Konsolidierung gegen die C8-Beispiele. // Lauf: npx tsx scripts/test-gap-consolidation.ts import { consolidate, gapPriority, isQuickWin, summarize, type RawGap } from "../src/lib/gap-consolidation"; let failed = 0; function check(name: string, cond: boolean, detail = "") { if (cond) console.log(` ok ${name}`); else { failed++; console.error(`FAIL ${name}${detail ? ` — ${detail}` : ""}`); } } function raw(p: Partial): RawGap { return { id: p.id ?? "x", source: p.source ?? "control", title: p.title ?? "", action: p.action ?? "", controls: p.controls ?? [], effort: p.effort ?? "mittel", maturityImpact: p.maturityImpact ?? 1, hasDependency: p.hasDependency ?? false, taskOrigin: p.taskOrigin ?? "wizard:gap", ...p, }; } // C8 §1 Prioritätsregeln + §4 Beispieltabelle. console.log("§1/§4 Priorität:"); check( "Kein Patchmanagement (MUSS<2 + Risiko) → Hoch", gapPriority(raw({ requirementType: "MUSS", maturity: 1, riskScore: 16, aboveAcceptance: true })) === "hoch", ); check( "Restore-Test fehlt (MUSS-Nachweis) → Hoch", gapPriority(raw({ requirementType: "MUSS", maturity: 1, kind: "nachweis" })) === "hoch", ); check("ISB=IT (FT-01 Konflikt) → Hoch", gapPriority(raw({ ftConflict: true, source: "role" })) === "hoch"); check( "SOLL Berechtigungs-Review nur Vorlage → Mittel", gapPriority(raw({ requirementType: "SOLL", kind: "verfahren", maturity: 1 })) === "mittel", ); check("Zonenbezeichnung redaktionell → Niedrig", gapPriority(raw({ kind: "redaktionell", maturity: 3 })) === "niedrig"); check( "MUSS-Control Reifegrad 2 unter Ziel 3 → Mittel", gapPriority(raw({ requirementType: "MUSS", maturity: 2, target: 3, kind: "nachweis" })) === "mittel", ); check("HOCH-Zusatzanforderung offen → Hoch", gapPriority(raw({ requirementType: "HOCH", maturity: 2, target: 3 })) === "hoch"); // C8 §3 Quick-Wins. console.log("§3 Quick-Wins:"); check("Restore-Test (organisatorisch, +1) → Quick-Win", isQuickWin(raw({ effort: "gering", maturityImpact: 1 }))); check("Patchmanagement (Tool) → kein Quick-Win", !isQuickWin(raw({ effort: "hoch", maturityImpact: 1 }))); check("Abhängigkeit → kein Quick-Win", !isQuickWin(raw({ effort: "gering", maturityImpact: 1, hasDependency: true }))); check("keine Reifegrad-Wirkung → kein Quick-Win", !isQuickWin(raw({ effort: "gering", maturityImpact: 0 }))); // C8 §2 Deduplizierung. console.log("§2 Deduplizierung:"); const dupSame = consolidate([ raw({ id: "control:5.2.3:nachweis", source: "control", controls: ["5.2.3"], kind: "nachweis", requirementType: "MUSS", maturity: 2, target: 3 }), raw({ id: "control:5.2.3:nachweis", source: "control", controls: ["5.2.3"], kind: "nachweis", requirementType: "MUSS", maturity: 2, target: 3 }), ]); check("gleiche Teilanforderung → ein Punkt", dupSame.length === 1); // Cross-Source-Merge: Risiko-Maßnahme + Control-Gaps derselben Controls (C8 §2, R-OPS-03-Beispiel). const crossMerge = consolidate([ raw({ id: "risk:R1", source: "risk", controls: ["5.2.3", "5.2.5"], riskScore: 16, aboveAcceptance: true, title: "Patchmanagement", taskId: "t1" }), raw({ id: "control:5.2.3:nachweis", source: "control", controls: ["5.2.3"], kind: "nachweis", requirementType: "MUSS", maturity: 2, target: 3 }), raw({ id: "control:5.2.5:nachweis", source: "control", controls: ["5.2.5"], kind: "nachweis", requirementType: "MUSS", maturity: 2, target: 3 }), raw({ id: "control:1.1.1:nachweis", source: "control", controls: ["1.1.1"], kind: "nachweis", requirementType: "MUSS", maturity: 2, target: 3 }), ]); check("Risiko absorbiert zugehörige Control-Gaps → 2 Punkte (Risiko + 1.1.1)", crossMerge.length === 2); const riskItem = crossMerge.find((i) => i.source === "risk")!; check("gemergter Risiko-Punkt hat Priorität Hoch", riskItem.priority === "hoch"); check("gemergter Risiko-Punkt vereint 5.2.3/5.2.5", riskItem.controls.includes("5.2.3") && riskItem.controls.includes("5.2.5")); check("gemergter Risiko-Punkt behält bestehende Aufgabe", riskItem.taskId === "t1"); check("Risiko-Punkt vor Control-Punkt sortiert (Priorität + betroffene Controls)", crossMerge[0].source === "risk"); // Sortierung: Hoch vor Mittel. console.log("Sortierung & Summary:"); const sorted = consolidate([ raw({ id: "a", source: "control", controls: ["3.1.1"], kind: "redaktionell", maturity: 3 }), raw({ id: "b", source: "control", controls: ["1.4.1"], requirementType: "MUSS", maturity: 1, kind: "verfahren" }), ]); check("Hoch (b) vor Niedrig (a)", sorted[0].id === "b" && sorted[1].id === "a"); const sum = summarize(sorted); check("Summary zählt korrekt", sum.total === 2 && sum.hoch === 1 && sum.niedrig === 1); console.log(failed === 0 ? "\nAlle Gap-Konsolidierungs-Tests grün." : `\n${failed} Test(s) fehlgeschlagen.`); process.exit(failed === 0 ? 0 : 1);