mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 22:35:51 +00:00
- 新增 tests/mocks/log.ts 和 tests/mocks/debug.ts,覆盖源文件全部实际导出 - 移除旧 mock 中不存在的导出(logToFile、logEvent、getLogFilePath) - 13 个测试文件改为使用共享 mock,避免定义分散和不一致 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
242 lines
7.3 KiB
TypeScript
242 lines
7.3 KiB
TypeScript
import { mock, describe, expect, test } from "bun:test";
|
|
import { debugMock } from "../../../../../../tests/mocks/debug";
|
|
|
|
// ─── Mocks for agentToolUtils.ts dependencies ───
|
|
// Only mock modules that are truly unavailable or cause side effects.
|
|
// Do NOT mock common/shared modules (zod/v4, bootstrap/state, etc.) to avoid
|
|
// corrupting the module cache for other test files in the same Bun process.
|
|
|
|
const noop = () => {};
|
|
|
|
mock.module("bun:bundle", () => ({ feature: () => false }));
|
|
|
|
mock.module("src/constants/tools.js", () => ({
|
|
ALL_AGENT_DISALLOWED_TOOLS: new Set(),
|
|
ASYNC_AGENT_ALLOWED_TOOLS: new Set(),
|
|
CUSTOM_AGENT_DISALLOWED_TOOLS: new Set(),
|
|
IN_PROCESS_TEAMMATE_ALLOWED_TOOLS: new Set(),
|
|
}));
|
|
|
|
mock.module("src/services/AgentSummary/agentSummary.js", () => ({
|
|
startAgentSummarization: noop,
|
|
}));
|
|
|
|
mock.module("src/services/analytics/index.js", () => ({
|
|
logEvent: noop,
|
|
logEventAsync: async () => {},
|
|
stripProtoFields: (v: any) => v,
|
|
attachAnalyticsSink: noop,
|
|
_resetForTesting: noop,
|
|
AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS: undefined,
|
|
}));
|
|
|
|
mock.module("src/services/api/dumpPrompts.js", () => ({
|
|
clearDumpState: noop,
|
|
}));
|
|
|
|
mock.module("src/Tool.js", () => ({
|
|
toolMatchesName: () => false,
|
|
findToolByName: noop,
|
|
}));
|
|
|
|
// messages.ts is complex - provide stubs for all named exports
|
|
mock.module("src/utils/messages.ts", () => ({
|
|
extractTextContent: (content: any[]) =>
|
|
content?.filter?.((b: any) => b.type === "text")?.map?.((b: any) => b.text)?.join("") ?? "",
|
|
getLastAssistantMessage: () => null,
|
|
SYNTHETIC_MESSAGES: new Set(),
|
|
INTERRUPT_MESSAGE: "",
|
|
INTERRUPT_MESSAGE_FOR_TOOL_USE: "",
|
|
CANCEL_MESSAGE: "",
|
|
REJECT_MESSAGE: "",
|
|
REJECT_MESSAGE_WITH_REASON_PREFIX: "",
|
|
SUBAGENT_REJECT_MESSAGE: "",
|
|
SUBAGENT_REJECT_MESSAGE_WITH_REASON_PREFIX: "",
|
|
PLAN_REJECTION_PREFIX: "",
|
|
DENIAL_WORKAROUND_GUIDANCE: "",
|
|
NO_RESPONSE_REQUESTED: "",
|
|
SYNTHETIC_TOOL_RESULT_PLACEHOLDER: "",
|
|
SYNTHETIC_MODEL: "",
|
|
AUTO_REJECT_MESSAGE: noop,
|
|
DONT_ASK_REJECT_MESSAGE: noop,
|
|
withMemoryCorrectionHint: (s: string) => s,
|
|
deriveShortMessageId: () => "",
|
|
isClassifierDenial: () => false,
|
|
buildYoloRejectionMessage: () => "",
|
|
buildClassifierUnavailableMessage: () => "",
|
|
isEmptyMessageText: () => true,
|
|
createAssistantMessage: noop,
|
|
createAssistantAPIErrorMessage: noop,
|
|
createUserMessage: noop,
|
|
prepareUserContent: noop,
|
|
createUserInterruptionMessage: noop,
|
|
createSyntheticUserCaveatMessage: noop,
|
|
formatCommandInputTags: noop,
|
|
}));
|
|
|
|
mock.module("src/tasks/LocalAgentTask/LocalAgentTask.js", () => ({
|
|
completeAgentTask: noop,
|
|
createActivityDescriptionResolver: () => ({}),
|
|
createProgressTracker: () => ({}),
|
|
enqueueAgentNotification: noop,
|
|
failAgentTask: noop,
|
|
getProgressUpdate: () => ({ tokenCount: 0, toolUseCount: 0 }),
|
|
getTokenCountFromTracker: () => 0,
|
|
isLocalAgentTask: () => false,
|
|
killAsyncAgent: noop,
|
|
updateAgentProgress: noop,
|
|
updateProgressFromMessage: noop,
|
|
}));
|
|
|
|
mock.module("src/utils/debug.ts", debugMock);
|
|
|
|
mock.module("src/utils/errors.js", () => ({
|
|
ClaudeError: class extends Error {},
|
|
MalformedCommandError: class extends Error {},
|
|
AbortError: class extends Error {},
|
|
ConfigParseError: class extends Error {},
|
|
ShellError: class extends Error {},
|
|
TeleportOperationError: class extends Error {},
|
|
TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS: class extends Error {},
|
|
isAbortError: () => false,
|
|
hasExactErrorMessage: () => false,
|
|
toError: (e: any) => e instanceof Error ? e : new Error(String(e)),
|
|
errorMessage: (e: any) => String(e),
|
|
getErrnoCode: () => undefined,
|
|
isENOENT: () => false,
|
|
getErrnoPath: () => undefined,
|
|
shortErrorStack: () => "",
|
|
isFsInaccessible: () => false,
|
|
classifyAxiosError: () => ({ category: "unknown" }),
|
|
}));
|
|
|
|
mock.module("src/utils/forkedAgent.js", () => ({}));
|
|
|
|
mock.module("src/utils/permissions/yoloClassifier.js", () => ({
|
|
buildTranscriptForClassifier: () => "",
|
|
classifyYoloAction: () => null,
|
|
}));
|
|
|
|
mock.module("src/utils/task/sdkProgress.js", () => ({
|
|
emitTaskProgress: noop,
|
|
}));
|
|
|
|
mock.module("src/utils/tokens.js", () => ({
|
|
getTokenCountFromUsage: () => 0,
|
|
}));
|
|
|
|
mock.module("src/tools/ExitPlanModeTool/constants.js", () => ({
|
|
EXIT_PLAN_MODE_V2_TOOL_NAME: "exit_plan_mode",
|
|
}));
|
|
|
|
mock.module("src/tools/AgentTool/constants.js", () => ({
|
|
AGENT_TOOL_NAME: "agent",
|
|
LEGACY_AGENT_TOOL_NAME: "task",
|
|
}));
|
|
|
|
mock.module("src/tools/AgentTool/loadAgentsDir.js", () => ({}));
|
|
|
|
mock.module("src/state/AppState.js", () => ({}));
|
|
|
|
mock.module("src/types/ids.js", () => ({
|
|
asAgentId: (id: string) => id,
|
|
}));
|
|
|
|
// Break circular dep
|
|
mock.module("src/tools/AgentTool/AgentTool.tsx", () => ({
|
|
AgentTool: {},
|
|
inputSchema: {},
|
|
outputSchema: {},
|
|
default: {},
|
|
}));
|
|
|
|
const {
|
|
countToolUses,
|
|
getLastToolUseName,
|
|
} = await import("../agentToolUtils");
|
|
|
|
function makeAssistantMessage(content: any[]): any {
|
|
return { type: "assistant", message: { content } };
|
|
}
|
|
|
|
function makeUserMessage(text: string): any {
|
|
return { type: "user", message: { content: text } };
|
|
}
|
|
|
|
describe("countToolUses", () => {
|
|
test("counts tool_use blocks in messages", () => {
|
|
const messages = [
|
|
makeAssistantMessage([
|
|
{ type: "tool_use", name: "Read" },
|
|
{ type: "text", text: "hello" },
|
|
]),
|
|
];
|
|
expect(countToolUses(messages)).toBe(1);
|
|
});
|
|
|
|
test("returns 0 for messages without tool_use", () => {
|
|
const messages = [
|
|
makeAssistantMessage([{ type: "text", text: "hello" }]),
|
|
];
|
|
expect(countToolUses(messages)).toBe(0);
|
|
});
|
|
|
|
test("returns 0 for empty array", () => {
|
|
expect(countToolUses([])).toBe(0);
|
|
});
|
|
|
|
test("counts multiple tool_use blocks across messages", () => {
|
|
const messages = [
|
|
makeAssistantMessage([{ type: "tool_use", name: "Read" }]),
|
|
makeUserMessage("ok"),
|
|
makeAssistantMessage([{ type: "tool_use", name: "Write" }]),
|
|
];
|
|
expect(countToolUses(messages)).toBe(2);
|
|
});
|
|
|
|
test("counts tool_use in single message with multiple blocks", () => {
|
|
const messages = [
|
|
makeAssistantMessage([
|
|
{ type: "tool_use", name: "Read" },
|
|
{ type: "tool_use", name: "Grep" },
|
|
{ type: "tool_use", name: "Write" },
|
|
]),
|
|
];
|
|
expect(countToolUses(messages)).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("getLastToolUseName", () => {
|
|
test("returns last tool name from assistant message", () => {
|
|
const msg = makeAssistantMessage([
|
|
{ type: "tool_use", name: "Read" },
|
|
{ type: "tool_use", name: "Write" },
|
|
]);
|
|
expect(getLastToolUseName(msg)).toBe("Write");
|
|
});
|
|
|
|
test("returns undefined for message without tool_use", () => {
|
|
const msg = makeAssistantMessage([{ type: "text", text: "hello" }]);
|
|
expect(getLastToolUseName(msg)).toBeUndefined();
|
|
});
|
|
|
|
test("returns the last tool when multiple tool_uses present", () => {
|
|
const msg = makeAssistantMessage([
|
|
{ type: "tool_use", name: "Read" },
|
|
{ type: "tool_use", name: "Grep" },
|
|
{ type: "tool_use", name: "Edit" },
|
|
]);
|
|
expect(getLastToolUseName(msg)).toBe("Edit");
|
|
});
|
|
|
|
test("returns undefined for non-assistant message", () => {
|
|
const msg = makeUserMessage("hello");
|
|
expect(getLastToolUseName(msg)).toBeUndefined();
|
|
});
|
|
|
|
test("handles message with null content", () => {
|
|
const msg = { type: "assistant", message: { content: null } } as any;
|
|
expect(getLastToolUseName(msg)).toBeUndefined();
|
|
});
|
|
});
|