import { describe, expect, it } from "vitest"; import { type RequestedToolValidation, validateRequestedTool, } from "#src/handlers/permission-gate-handler"; // ── helpers ──────────────────────────────────────────────────────────────── function makeTools(names: string[]): { name: string }[] { return names.map((name) => ({ name })); } const TOOLS = makeTools(["read", "bash", "edit"]); // ── validateRequestedTool ────────────────────────────────────────────────── describe("validateRequestedTool", () => { describe("missing / unresolvable tool name", () => { it("blocks when event has no name field", () => { const result = validateRequestedTool({ type: "tool_call" }, TOOLS); expect(result.status).toBe("block"); expect( (result as Extract) .reason, ).toBeTruthy(); }); }); describe("unregistered tool", () => { it("blocks when the tool name is not in the registered list", () => { const result = validateRequestedTool({ name: "unknown-tool" }, TOOLS); expect(result.status).toBe("block"); }); }); describe("registered tool (ok path)", () => { it("returns ok with the raw tool name for a known tool", () => { const result = validateRequestedTool({ name: "read" }, TOOLS); expect(result).toEqual({ status: "ok", toolName: "read" }); }); it("returns the raw name as it appeared in the event (not normalised)", () => { // If an alias mechanism were to normalise "Read" → "read", // validateRequestedTool still returns the raw value from the event. // Without aliases the raw name and registered name are the same; this // asserts the contract that toolName comes from the event, not from the // registration lookup's normalizedToolName field. const result = validateRequestedTool({ name: "bash" }, TOOLS); expect(result).toEqual({ status: "ok", toolName: "bash" }); }); }); });