Files
my-pi/pi-permission-system/test/handlers/gates/skill-input-gate-pipeline.test.ts
T

162 lines
5.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
formatSkillDenyNotice,
SkillInputGatePipeline,
} from "#src/handlers/gates/skill-input-gate-pipeline";
import {
makeGateRunner,
makeNotifier,
makeSkillInputInputs,
} from "#test/helpers/gate-fixtures";
import { makeCheckResult } from "#test/helpers/handler-fixtures";
// ── formatSkillDenyNotice ─────────────────────────────────────────────────
describe("formatSkillDenyNotice", () => {
it("includes the skill name in the message (no agent)", () => {
const msg = formatSkillDenyNotice("librarian", null);
expect(msg).toContain("librarian");
});
it("includes the skill name and agent name when agent is present", () => {
const msg = formatSkillDenyNotice("librarian", "code-agent");
expect(msg).toContain("librarian");
expect(msg).toContain("code-agent");
});
});
// ── SkillInputGatePipeline.evaluate ───────────────────────────────────────
describe("SkillInputGatePipeline.evaluate", () => {
// ── notifier behaviour ──────────────────────────────────────────────────
it("calls notifier.warn when the skill is denied", async () => {
const inputs = makeSkillInputInputs({
checkPermission: () => makeCheckResult({ state: "deny" }),
});
const notifier = makeNotifier();
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
await pipeline.evaluate("librarian", null, notifier, runner);
expect(notifier.warn).toHaveBeenCalledOnce();
expect(notifier.warn).toHaveBeenCalledWith(
expect.stringContaining("librarian"),
);
});
it("does not call notifier.warn when the skill is allowed", async () => {
const inputs = makeSkillInputInputs({
checkPermission: () => makeCheckResult({ state: "allow" }),
});
const notifier = makeNotifier();
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
await pipeline.evaluate("librarian", null, notifier, runner);
expect(notifier.warn).not.toHaveBeenCalled();
});
it("does not call notifier.warn when the skill requires approval (ask)", async () => {
const inputs = makeSkillInputInputs({
checkPermission: () => makeCheckResult({ state: "ask" }),
});
const notifier = makeNotifier();
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
await pipeline.evaluate("librarian", null, notifier, runner);
expect(notifier.warn).not.toHaveBeenCalled();
});
it("includes agent name in the deny notice when agent is present", async () => {
const inputs = makeSkillInputInputs({
checkPermission: () => makeCheckResult({ state: "deny" }),
});
const notifier = makeNotifier();
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
await pipeline.evaluate("librarian", "code-agent", notifier, runner);
expect(notifier.warn).toHaveBeenCalledWith(
expect.stringContaining("code-agent"),
);
});
// ── outcome mapping ─────────────────────────────────────────────────────
it("returns allow when the gate passes", async () => {
const inputs = makeSkillInputInputs({
checkPermission: () => makeCheckResult({ state: "allow" }),
});
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
const result = await pipeline.evaluate(
"librarian",
null,
makeNotifier(),
runner,
);
expect(result).toEqual({ action: "allow" });
});
it("returns block when the gate denies", async () => {
const inputs = makeSkillInputInputs({
checkPermission: () =>
makeCheckResult({ state: "deny", matchedPattern: "*" }),
});
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
const result = await pipeline.evaluate(
"librarian",
null,
makeNotifier(),
runner,
);
expect(result).toEqual({
action: "block",
reason: expect.stringContaining("librarian"),
});
});
// ── checkPermission call ────────────────────────────────────────────────
it("calls checkPermission with the skill surface, skill name, and agent name", async () => {
const inputs = makeSkillInputInputs();
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
await pipeline.evaluate("explorer", "code-agent", makeNotifier(), runner);
expect(inputs.checkPermission).toHaveBeenCalledWith(
"skill",
{ name: "explorer" },
"code-agent",
);
});
it("calls checkPermission with undefined agentName when agentName is null", async () => {
const inputs = makeSkillInputInputs();
const { runner } = makeGateRunner();
const pipeline = new SkillInputGatePipeline(inputs);
await pipeline.evaluate("explorer", null, makeNotifier(), runner);
expect(inputs.checkPermission).toHaveBeenCalledWith(
"skill",
{ name: "explorer" },
undefined,
);
});
});