test/claude-permission-controls.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var kit = require("../lib/yoke/adapters/claude").contractTestKit;
function workerFixture(send) {
var receive;
return {
worker: {
process: { killed: false, exitCode: null },
exitPromise: Promise.resolve(),
permissionModeTimeoutMs: 5,
onMessage: function(handler) { receive = handler; },
send: send || function() { return true; },
},
receive: function(message) { receive(message); },
};
}
test("PreToolUse policy defers undecided calls and maps mandatory decisions", async function () {
var options = { hooks: { PreToolUse: [{ hooks: [async function() { return { continue: true }; }] }] } };
var seenOptions = null;
kit.attachPreToolUsePolicy(options, function(toolName, input, hookOptions) {
seenOptions = hookOptions;
if (toolName === "Read") return { behavior: "allow", updatedInput: input };
if (toolName === "Blocked") return { behavior: "deny", message: "Mandatory policy" };
return null;
});
assert.equal(options.hooks.PreToolUse.length, 2);
var hook = options.hooks.PreToolUse[1].hooks[0];
assert.deepEqual(await hook({ tool_name: "Other", tool_input: {} }), { continue: true });
var allowed = await hook({ tool_name: "Read", tool_input: { path: "a" }, tool_use_id: "t1" });
assert.equal(allowed.hookSpecificOutput.permissionDecision, "allow");
assert.deepEqual(allowed.hookSpecificOutput.updatedInput, { path: "a" });
var denied = await hook({ tool_name: "Blocked", tool_input: {} });
assert.equal(denied.hookSpecificOutput.permissionDecision, "deny");
assert.equal(denied.hookSpecificOutput.permissionDecisionReason, "Mandatory policy");
var signal = {};
await hook({ tool_name: "Other", tool_input: {}, tool_use_id: "input-id" }, "argument-id", { signal: signal });
assert.equal(seenOptions.toolUseID, "argument-id");
assert.equal(seenOptions.signal, signal);
assert.deepEqual(await hook(), { continue: true });
});
test("worker permission setter resolves only after its correlated acknowledgement", async function () {
var sent;
var fixture = workerFixture(function(message) { sent = message; return true; });
var handle = kit.createWorkerQueryHandle(fixture.worker);
var pending = handle.setPermissionMode("auto");
assert.equal(sent.mode, "auto");
fixture.receive({ type: "permission_mode_result", requestId: sent.requestId });
await pending;
});
test("worker permission setter rejects correlated errors and failed sends", async function () {
var sent;
var fixture = workerFixture(function(message) { sent = message; return true; });
var handle = kit.createWorkerQueryHandle(fixture.worker);
var pending = handle.setPermissionMode("auto");
fixture.receive({ type: "permission_mode_result", requestId: sent.requestId, error: "SDK refused" });
await assert.rejects(pending, /SDK refused/);
var failed = workerFixture(function() { return false; });
await assert.rejects(kit.createWorkerQueryHandle(failed.worker).setPermissionMode("auto"), /unavailable/);
var thrown = workerFixture(function() { throw new Error("IPC send failed"); });
await assert.rejects(kit.createWorkerQueryHandle(thrown.worker).setPermissionMode("auto"), /IPC send failed/);
});
test("worker permission setter rejects query completion, worker exit, close, and timeout", async function () {
var doneFixture = workerFixture();
var doneHandle = kit.createWorkerQueryHandle(doneFixture.worker);
var donePending = doneHandle.setPermissionMode("auto");
doneFixture.receive({ type: "query_done" });
await assert.rejects(donePending, /ended/);
var exitFixture = workerFixture();
var exitHandle = kit.createWorkerQueryHandle(exitFixture.worker);
var exitPending = exitHandle.setPermissionMode("auto");
exitFixture.receive({ type: "query_error", error: "Worker exited with code 1", exitCode: 1 });
await assert.rejects(exitPending, /exited with code 1/);
var closeFixture = workerFixture();
var closeHandle = kit.createWorkerQueryHandle(closeFixture.worker);
var closePending = closeHandle.setPermissionMode("auto");
closeHandle.close();
await assert.rejects(closePending, /closed/);
var timeoutFixture = workerFixture();
await assert.rejects(kit.createWorkerQueryHandle(timeoutFixture.worker).setPermissionMode("auto"), /Timed out/);
});
test("worker MCP policy setter has correlated success and errors", async function () {
var sent;
var fixture = workerFixture(function(message) { sent = message; return true; });
var handle = kit.createWorkerQueryHandle(fixture.worker);
var pending = handle.setMcpPermissionModeOverride("github", "auto");
fixture.receive({ type: "mcp_permission_mode_result", requestId: sent.requestId, result: { applied: true } });
assert.deepEqual(await pending, { applied: true });
var errorPending = handle.setMcpPermissionModeOverride("github", "default");
fixture.receive({ type: "mcp_permission_mode_result", requestId: sent.requestId, error: "not applicable" });
await assert.rejects(errorPending, /not applicable/);
});
test("worker bridge converts synchronous PreToolUse policy errors into correlated denial", async function () {
var sent = [];
var fixture = workerFixture(function(message) { sent.push(message); return true; });
kit.createWorkerQueryHandle(fixture.worker, null, null, null, null, null, null, function() {
throw new Error("policy failed");
});
fixture.receive({ type: "pre_tool_policy_request", requestId: "policy-1", toolName: "Write", input: {} });
await new Promise(function(resolve) { setImmediate(resolve); });
assert.equal(sent[0].type, "pre_tool_policy_response");
assert.equal(sent[0].requestId, "policy-1");
assert.equal(sent[0].error, "policy failed");
});