mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
- FileEditTool/utils.test.ts: 24 tests (normalizeQuotes, stripTrailingWhitespace, findActualString, preserveQuoteStyle, applyEditToFile) - permissions/permissions.test.ts: 13 tests (getDenyRuleForTool, getAskRuleForTool, getDenyRuleForAgent, filterDeniedAgents) - tools.test.ts: 扩展 5 tests (filterToolsByDenyRules 过滤逻辑) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
166 lines
5.9 KiB
TypeScript
166 lines
5.9 KiB
TypeScript
import { mock, describe, expect, test } from "bun:test";
|
|
|
|
// Mock log.ts to cut the heavy dependency chain
|
|
mock.module("src/utils/log.ts", () => ({
|
|
logError: () => {},
|
|
logToFile: () => {},
|
|
getLogDisplayTitle: () => "",
|
|
logEvent: () => {},
|
|
logMCPError: () => {},
|
|
logMCPDebug: () => {},
|
|
dateToFilename: (d: Date) => d.toISOString().replace(/[:.]/g, "-"),
|
|
getLogFilePath: () => "/tmp/mock-log",
|
|
attachErrorLogSink: () => {},
|
|
getInMemoryErrors: () => [],
|
|
loadErrorLogs: async () => [],
|
|
getErrorLogByIndex: async () => null,
|
|
captureAPIRequest: () => {},
|
|
_resetErrorLogForTesting: () => {},
|
|
}));
|
|
|
|
// Mock slowOperations to avoid bun:bundle
|
|
mock.module("src/utils/slowOperations.ts", () => ({
|
|
jsonStringify: JSON.stringify,
|
|
jsonParse: JSON.parse,
|
|
slowLogging: { enabled: false },
|
|
clone: (v: any) => structuredClone(v),
|
|
cloneDeep: (v: any) => structuredClone(v),
|
|
callerFrame: () => "",
|
|
SLOW_OPERATION_THRESHOLD_MS: 100,
|
|
writeFileSync_DEPRECATED: () => {},
|
|
}));
|
|
|
|
const {
|
|
getDenyRuleForTool,
|
|
getAskRuleForTool,
|
|
getDenyRuleForAgent,
|
|
filterDeniedAgents,
|
|
} = await import("../permissions");
|
|
|
|
import { getEmptyToolPermissionContext } from "../../../Tool";
|
|
|
|
// ─── Helper ─────────────────────────────────────────────────────────────
|
|
|
|
function makeContext(opts: {
|
|
denyRules?: string[];
|
|
askRules?: string[];
|
|
}) {
|
|
const ctx = getEmptyToolPermissionContext();
|
|
const deny: Record<string, string[]> = {};
|
|
const ask: Record<string, string[]> = {};
|
|
|
|
// alwaysDenyRules stores raw rule strings — getDenyRules() calls
|
|
// permissionRuleValueFromString internally
|
|
if (opts.denyRules?.length) {
|
|
deny["localSettings"] = opts.denyRules;
|
|
}
|
|
if (opts.askRules?.length) {
|
|
ask["localSettings"] = opts.askRules;
|
|
}
|
|
|
|
return {
|
|
...ctx,
|
|
alwaysDenyRules: deny,
|
|
alwaysAskRules: ask,
|
|
} as any;
|
|
}
|
|
|
|
function makeTool(name: string, mcpInfo?: { serverName: string; toolName: string }) {
|
|
return { name, mcpInfo };
|
|
}
|
|
|
|
// ─── getDenyRuleForTool ─────────────────────────────────────────────────
|
|
|
|
describe("getDenyRuleForTool", () => {
|
|
test("returns null when no deny rules", () => {
|
|
const ctx = makeContext({});
|
|
expect(getDenyRuleForTool(ctx, makeTool("Bash"))).toBeNull();
|
|
});
|
|
|
|
test("returns matching deny rule for tool", () => {
|
|
const ctx = makeContext({ denyRules: ["Bash"] });
|
|
const result = getDenyRuleForTool(ctx, makeTool("Bash"));
|
|
expect(result).not.toBeNull();
|
|
expect(result!.ruleValue.toolName).toBe("Bash");
|
|
});
|
|
|
|
test("returns null for non-matching tool", () => {
|
|
const ctx = makeContext({ denyRules: ["Bash"] });
|
|
expect(getDenyRuleForTool(ctx, makeTool("Read"))).toBeNull();
|
|
});
|
|
|
|
test("rule with content does not match whole-tool deny", () => {
|
|
// getDenyRuleForTool uses toolMatchesRule which requires ruleContent === undefined
|
|
// Rules like "Bash(rm -rf)" only match specific invocations, not the entire tool
|
|
const ctx = makeContext({ denyRules: ["Bash(rm -rf)"] });
|
|
const result = getDenyRuleForTool(ctx, makeTool("Bash"));
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── getAskRuleForTool ──────────────────────────────────────────────────
|
|
|
|
describe("getAskRuleForTool", () => {
|
|
test("returns null when no ask rules", () => {
|
|
const ctx = makeContext({});
|
|
expect(getAskRuleForTool(ctx, makeTool("Bash"))).toBeNull();
|
|
});
|
|
|
|
test("returns matching ask rule", () => {
|
|
const ctx = makeContext({ askRules: ["Write"] });
|
|
const result = getAskRuleForTool(ctx, makeTool("Write"));
|
|
expect(result).not.toBeNull();
|
|
});
|
|
|
|
test("returns null for non-matching tool", () => {
|
|
const ctx = makeContext({ askRules: ["Write"] });
|
|
expect(getAskRuleForTool(ctx, makeTool("Bash"))).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── getDenyRuleForAgent ────────────────────────────────────────────────
|
|
|
|
describe("getDenyRuleForAgent", () => {
|
|
test("returns null when no deny rules", () => {
|
|
const ctx = makeContext({});
|
|
expect(getDenyRuleForAgent(ctx, "Agent", "Explore")).toBeNull();
|
|
});
|
|
|
|
test("returns matching deny rule for agent type", () => {
|
|
const ctx = makeContext({ denyRules: ["Agent(Explore)"] });
|
|
const result = getDenyRuleForAgent(ctx, "Agent", "Explore");
|
|
expect(result).not.toBeNull();
|
|
});
|
|
|
|
test("returns null for non-matching agent type", () => {
|
|
const ctx = makeContext({ denyRules: ["Agent(Explore)"] });
|
|
expect(getDenyRuleForAgent(ctx, "Agent", "Research")).toBeNull();
|
|
});
|
|
});
|
|
|
|
// ─── filterDeniedAgents ─────────────────────────────────────────────────
|
|
|
|
describe("filterDeniedAgents", () => {
|
|
test("returns all agents when no deny rules", () => {
|
|
const ctx = makeContext({});
|
|
const agents = [{ agentType: "Explore" }, { agentType: "Research" }];
|
|
expect(filterDeniedAgents(agents, ctx, "Agent")).toEqual(agents);
|
|
});
|
|
|
|
test("filters out denied agent type", () => {
|
|
const ctx = makeContext({ denyRules: ["Agent(Explore)"] });
|
|
const agents = [{ agentType: "Explore" }, { agentType: "Research" }];
|
|
const result = filterDeniedAgents(agents, ctx, "Agent");
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]!.agentType).toBe("Research");
|
|
});
|
|
|
|
test("returns empty array when all agents denied", () => {
|
|
const ctx = makeContext({
|
|
denyRules: ["Agent(Explore)", "Agent(Research)"],
|
|
});
|
|
const agents = [{ agentType: "Explore" }, { agentType: "Research" }];
|
|
expect(filterDeniedAgents(agents, ctx, "Agent")).toEqual([]);
|
|
});
|
|
});
|