Spaces:
Sleeping
Sleeping
| /** | |
| * 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); | |
| }); | |
| }); | |