/** * Tests that handleInput emits permissions:decision events for skill input gates. */ import { describe, expect, it, vi } from "vitest"; import type { AskEscalator } from "#src/authority/authorizer-selection"; import { DECIDED_BY_HUMAN } from "#test/helpers/decision-fixtures"; import { getDecisionEvents, makeCheckResult, makeCtx, makeHandler, } from "#test/helpers/handler-fixtures"; // ── helpers ──────────────────────────────────────────────────────────────── /** Build a checkPermission mock returning a skill-surface result. */ function makeSkillCheckPermission(state: "allow" | "deny" | "ask") { return vi.fn().mockReturnValue( makeCheckResult({ state, toolName: "skill", source: "skill", origin: "global", matchedPattern: "*", }), ); } // ── tests ────────────────────────────────────────────────────────────────── describe("handleInput decision events — skill gate", () => { it("does not emit when input is not a skill invocation", async () => { const { handler, events } = makeHandler(); await handler.handleInput({ text: "hello world" }, makeCtx()); expect(getDecisionEvents(events)).toHaveLength(0); }); it("emits allow with policy_allow for an allowed skill", async () => { const { handler, events } = makeHandler({ session: { checkPermission: makeSkillCheckPermission("allow") }, }); await handler.handleInput({ text: "/skill:librarian" }, makeCtx()); const decisions = getDecisionEvents(events); expect(decisions).toHaveLength(1); expect(decisions[0]).toMatchObject({ surface: "skill", value: "librarian", result: "allow", resolution: "policy_allow", }); }); it("emits deny with policy_deny for a denied skill", async () => { const { handler, events } = makeHandler({ session: { checkPermission: makeSkillCheckPermission("deny") }, }); await handler.handleInput({ text: "/skill:restricted" }, makeCtx()); const decisions = getDecisionEvents(events); expect(decisions).toHaveLength(1); expect(decisions[0]).toMatchObject({ surface: "skill", value: "restricted", result: "deny", resolution: "policy_deny", }); }); it("emits a decision event on the escalate path", async () => { const { handler, events } = makeHandler({ session: { checkPermission: makeSkillCheckPermission("ask"), }, prompter: { escalate: vi.fn().mockResolvedValue({ approved: true, state: "approved", decidedBy: DECIDED_BY_HUMAN, }), }, }); await handler.handleInput({ text: "/skill:explorer" }, makeCtx()); const decisions = getDecisionEvents(events); expect(decisions).toHaveLength(1); expect(decisions[0]).toMatchObject({ surface: "skill", value: "explorer", result: "allow", resolution: "user_approved", }); }); });