/** * capabilityPolicy.test.ts — Unit test S25 * * capabilityPolicy è un modulo PURO (zero imports di runtime) → * nessun mock necessario, tutto testabile in isolamento. * * Copertura: * - checkPolicy: safe / medium / risky / dangerous / unknown * - requiresConfirm: tutti i livelli di rischio * - getRisk: spot checks * - listPolicyRules: completezza e assenza duplicati * - preview: troncatura a 200 chars */ import { describe, it, expect } from "vitest"; import { checkPolicy, requiresConfirm, getRisk, listPolicyRules, } from "@/lib/capabilityPolicy"; // ─── checkPolicy ───────────────────────────────────────────────────────────── describe("checkPolicy", () => { it("web_search → safe, allowed=true, preview contiene la query", () => { const r = checkPolicy("web_search", { query: "typescript vitest" }); expect(r.risk).toBe("safe"); expect(r.allowed).toBe(true); expect(r.preview).toContain("typescript vitest"); expect(r.tool).toBe("web_search"); }); it("read_file → safe, allowed=true, preview contiene il path", () => { const r = checkPolicy("read_file", { path: "/src/main.ts" }); expect(r.risk).toBe("safe"); expect(r.allowed).toBe(true); expect(r.preview).toContain("/src/main.ts"); }); it("write_file → medium, allowed=true", () => { const r = checkPolicy("write_file", { path: "/out.ts", content: "x" }); expect(r.risk).toBe("medium"); expect(r.allowed).toBe(true); }); it("pip_install → medium, allowed=true", () => { const r = checkPolicy("pip_install", { package: "numpy" }); expect(r.risk).toBe("medium"); expect(r.allowed).toBe(true); }); it("github_push → risky, allowed=true (richiede confirm ma non è bloccato)", () => { const r = checkPolicy("github_push", { branch: "main", message: "feat: test" }); expect(r.risk).toBe("risky"); expect(r.allowed).toBe(true); // risky è allowed, ma requiresConfirm=true }); it("exec_code_backend → risky, allowed=true", () => { const r = checkPolicy("exec_code_backend", { lang: "python", code: "print(1)" }); expect(r.risk).toBe("risky"); expect(r.allowed).toBe(true); expect(r.preview).toContain("python"); }); it("delete_file → dangerous, allowed=false (blocco automatico)", () => { const r = checkPolicy("delete_file", { path: "/critical.ts" }); expect(r.risk).toBe("dangerous"); expect(r.allowed).toBe(false); // dangerous non è mai allowed automaticamente expect(r.preview).toContain("/critical.ts"); }); it("clear_memory → dangerous, allowed=false", () => { const r = checkPolicy("clear_memory", {}); expect(r.risk).toBe("dangerous"); expect(r.allowed).toBe(false); }); it("clear_all_files → dangerous, allowed=false", () => { const r = checkPolicy("clear_all_files", {}); expect(r.risk).toBe("dangerous"); expect(r.allowed).toBe(false); }); it("tool sconosciuto → risky (default sicuro), allowed=true", () => { const r = checkPolicy("non_existent_tool_xyz", {}); expect(r.risk).toBe("risky"); expect(r.allowed).toBe(true); expect(r.label).toBe("Azione sconosciuta"); }); it("preview è sempre ≤200 chars anche con query lunghissima", () => { const longQuery = "a".repeat(500); const r = checkPolicy("web_search", { query: longQuery }); expect(r.preview.length).toBeLessThanOrEqual(200); }); it("checkPolicy senza args non crasha", () => { expect(() => checkPolicy("recall")).not.toThrow(); expect(() => checkPolicy("delete_file")).not.toThrow(); }); it("safe tools: recall, search_github, get_weather, get_news", () => { const safeTools = ["recall", "search_github", "get_weather", "get_news", "search_wikipedia"]; for (const tool of safeTools) { const r = checkPolicy(tool, {}); expect(r.risk, `${tool} dovrebbe essere safe`).toBe("safe"); expect(r.allowed).toBe(true); } }); }); // ─── requiresConfirm ───────────────────────────────────────────────────────── describe("requiresConfirm", () => { it("tools safe → false", () => { expect(requiresConfirm("web_search")).toBe(false); expect(requiresConfirm("read_file")).toBe(false); expect(requiresConfirm("recall")).toBe(false); expect(requiresConfirm("get_weather")).toBe(false); }); it("tools medium → false (no confirm, ma UI notifica)", () => { expect(requiresConfirm("write_file")).toBe(false); expect(requiresConfirm("pip_install")).toBe(false); expect(requiresConfirm("remember")).toBe(false); }); it("tools risky → true", () => { expect(requiresConfirm("github_push")).toBe(true); expect(requiresConfirm("exec_code_backend")).toBe(true); }); it("tools dangerous → true", () => { expect(requiresConfirm("delete_file")).toBe(true); expect(requiresConfirm("clear_memory")).toBe(true); expect(requiresConfirm("clear_all_files")).toBe(true); }); it("tool sconosciuto → true (fail-safe)", () => { expect(requiresConfirm("unknown_tool_abc")).toBe(true); }); }); // ─── getRisk ───────────────────────────────────────────────────────────────── describe("getRisk", () => { it("ritorna il livello corretto per ogni categoria", () => { expect(getRisk("web_search")).toBe("safe"); expect(getRisk("read_file")).toBe("safe"); expect(getRisk("write_file")).toBe("medium"); expect(getRisk("pip_install")).toBe("medium"); expect(getRisk("github_push")).toBe("risky"); expect(getRisk("delete_file")).toBe("dangerous"); expect(getRisk("clear_all_files")).toBe("dangerous"); }); it("tool sconosciuto → risky (default)", () => { expect(getRisk("completely_unknown_xyz")).toBe("risky"); }); }); // ─── listPolicyRules ───────────────────────────────────────────────────────── describe("listPolicyRules", () => { it("ritorna almeno 20 regole (obiettivo S25)", () => { const rules = listPolicyRules(); expect(rules.length).toBeGreaterThanOrEqual(20); }); it("ogni regola ha tool (string), risk (valido), label, description", () => { const validRisks = new Set(["safe", "medium", "risky", "dangerous"]); const rules = listPolicyRules(); for (const r of rules) { expect(typeof r.tool).toBe("string"); expect(r.tool.length, `tool vuoto in rule: ${JSON.stringify(r)}`).toBeGreaterThan(0); expect(validRisks.has(r.risk), `risk '${r.risk}' non valido`).toBe(true); expect(typeof r.label).toBe("string"); expect(typeof r.description).toBe("string"); } }); it("nessun tool duplicato nel registry", () => { const rules = listPolicyRules(); const names = rules.map(r => r.tool); const unique = new Set(names); expect(unique.size).toBe(names.length); }); it("ogni categoria di rischio ha almeno 1 tool", () => { const rules = listPolicyRules(); const bySafe = rules.filter(r => r.risk === "safe"); const byMedium = rules.filter(r => r.risk === "medium"); const byRisky = rules.filter(r => r.risk === "risky"); const byDangerous = rules.filter(r => r.risk === "dangerous"); expect(bySafe.length, "safe tools").toBeGreaterThan(0); expect(byMedium.length, "medium tools").toBeGreaterThan(0); expect(byRisky.length, "risky tools").toBeGreaterThan(0); expect(byDangerous.length, "dangerous tools").toBeGreaterThan(0); }); it("listPolicyRules ritorna copia (modifica non altera il registry)", () => { const rules = listPolicyRules(); rules.push({ tool: "fake_hack", risk: "safe", label: "x", description: "x" }); const rules2 = listPolicyRules(); expect(rules2.some(r => r.tool === "fake_hack")).toBe(false); }); });