claude-code/docs/test-plans/01-tool-system.md
2026-04-01 21:19:41 +08:00

6.8 KiB
Raw Blame History

Tool 系统测试计划

概述

Tool 系统是 Claude Code 的核心,负责工具的定义、注册、发现和过滤。本计划覆盖 src/Tool.ts 中的工具接口与工具函数、src/tools.ts 中的注册/过滤逻辑,以及各工具目录下可独立测试的纯函数。

被测文件

文件 关键导出
src/Tool.ts buildTool, toolMatchesName, findToolByName, getEmptyToolPermissionContext, filterToolProgressMessages
src/tools.ts parseToolPreset, filterToolsByDenyRules, getAllBaseTools, getTools, assembleToolPool
src/tools/shared/gitOperationTracking.ts parseGitCommitId, detectGitOperation
src/tools/shared/spawnMultiAgent.ts resolveTeammateModel, generateUniqueTeammateName
src/tools/GrepTool/GrepTool.ts applyHeadLimit, formatLimitInfo(内部辅助函数)
src/tools/FileEditTool/utils.ts 字符串匹配/补丁相关纯函数

测试用例

src/Tool.ts

describe('buildTool')

  • test('fills in default isEnabled as true') — 不传 isEnabled 时,构建的 tool.isEnabled() 应返回 true
  • test('fills in default isConcurrencySafe as false') — 默认值应为 falsefail-closed
  • test('fills in default isReadOnly as false') — 默认假设有写操作
  • test('fills in default isDestructive as false') — 默认非破坏性
  • test('fills in default checkPermissions as allow') — 默认 checkPermissions 应返回 { behavior: 'allow', updatedInput }
  • test('fills in default userFacingName from tool name') — userFacingName 默认应返回 tool.name
  • test('preserves explicitly provided methods') — 传入自定义 isEnabled 等方法时应覆盖默认值
  • test('preserves all non-defaultable properties') — name, inputSchema, call, description 等属性原样保留

describe('toolMatchesName')

  • test('returns true for exact name match') — { name: 'Bash' } 匹配 'Bash'
  • test('returns false for non-matching name') — { name: 'Bash' } 不匹配 'Read'
  • test('returns true when name matches an alias') — { name: 'Bash', aliases: ['BashTool'] } 匹配 'BashTool'
  • test('returns false when aliases is undefined') — { name: 'Bash' } 不匹配 'BashTool'
  • test('returns false when aliases is empty') — { name: 'Bash', aliases: [] } 不匹配 'BashTool'

describe('findToolByName')

  • test('finds tool by primary name') — 从 tools 列表中按 name 找到工具
  • test('finds tool by alias') — 从 tools 列表中按 alias 找到工具
  • test('returns undefined when no match') — 找不到时返回 undefined
  • test('returns first match when duplicates exist') — 多个同名工具时返回第一个

describe('getEmptyToolPermissionContext')

  • test('returns default permission mode') — mode 应为 'default'
  • test('returns empty maps and arrays') — additionalWorkingDirectories 为空 Maprules 为空对象
  • test('returns isBypassPermissionsModeAvailable as false')

describe('filterToolProgressMessages')

  • test('filters out hook_progress messages') — 移除 type 为 hook_progress 的消息
  • test('keeps tool progress messages') — 保留非 hook_progress 的消息
  • test('returns empty array for empty input')
  • test('handles messages without type field') — data 不含 type 时应保留

src/tools.ts

describe('parseToolPreset')

  • test('returns "default" for "default" input') — 精确匹配
  • test('returns "default" for "Default" input') — 大小写不敏感
  • test('returns null for unknown preset') — 未知字符串返回 null
  • test('returns null for empty string')

describe('filterToolsByDenyRules')

  • test('returns all tools when no deny rules') — 空 deny 规则不过滤任何工具
  • test('filters out tools matching blanket deny rule') — deny rule { toolName: 'Bash' } 应移除 Bash
  • test('does not filter tools with content-specific deny rules') — deny rule { toolName: 'Bash', ruleContent: 'rm -rf' } 不移除 Bash只在运行时阻止特定命令
  • test('filters MCP tools by server name prefix') — deny rule mcp__server 应移除该 server 下所有工具
  • test('preserves tools not matching any deny rule')

describe('getAllBaseTools')

  • test('returns a non-empty array of tools') — 至少包含核心工具
  • test('each tool has required properties') — 每个工具应有 name, inputSchema, call 等属性
  • test('includes BashTool, FileReadTool, FileEditTool') — 核心工具始终存在
  • test('includes TestingPermissionTool when NODE_ENV is test') — 需设置 env

describe('getTools')

  • test('returns filtered tools based on permission context') — 根据 deny rules 过滤
  • test('returns simple tools in CLAUDE_CODE_SIMPLE mode') — 仅返回 Bash/Read/Edit
  • test('filters disabled tools via isEnabled') — isEnabled 返回 false 的工具被排除

src/tools/shared/gitOperationTracking.ts

describe('parseGitCommitId')

  • test('extracts commit hash from git commit output') — 从 [main abc1234] message 中提取 abc1234
  • test('returns null for non-commit output') — 无法解析时返回 null
  • test('handles various branch name formats') — [feature/foo abc1234]

describe('detectGitOperation')

  • test('detects git commit operation') — 命令含 git commit 时识别为 commit
  • test('detects git push operation') — 命令含 git push 时识别
  • test('returns null for non-git commands') — 非 git 命令返回 null
  • test('detects git merge operation')
  • test('detects git rebase operation')

src/tools/shared/spawnMultiAgent.ts

describe('resolveTeammateModel')

  • test('returns specified model when provided')
  • test('falls back to default model when not specified')

describe('generateUniqueTeammateName')

  • test('generates a name when no existing names') — 无冲突时返回基础名
  • test('appends suffix when name conflicts') — 与已有名称冲突时添加后缀
  • test('handles multiple conflicts') — 多次冲突时递增后缀

Mock 需求

依赖 Mock 方式 说明
bun:bundle (feature) 已 polyfill 为 () => false 不需额外 mock
process.env bun:test mock 测试 USER_TYPENODE_ENVCLAUDE_CODE_SIMPLE
getDenyRuleForTool mock module filterToolsByDenyRules 测试中需控制返回值
isToolSearchEnabledOptimistic mock module getAllBaseTools 中条件加载

集成测试场景

放在 tests/integration/tool-chain.test.ts

describe('Tool registration and discovery')

  • test('getAllBaseTools returns tools that can be found by findToolByName') — 注册 → 查找完整链路
  • test('filterToolsByDenyRules + getTools produces consistent results') — 过滤管线一致性
  • test('assembleToolPool deduplicates built-in and MCP tools') — 合并去重逻辑