mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user