Files
claude-code/src/constants/__tests__/tools.test.ts
claude-code-best 7be08f53bd feat: 实现 Tool Search 基础设施层(CORE_TOOLS 白名单 + TF-IDF 索引 + ExecuteTool + 搜索增强)
- 新增 CORE_TOOLS 白名单常量(31 个核心工具),重构 isDeferredTool 为白名单制判定
- 新建 TF-IDF 工具索引模块(toolIndex.ts),复用 localSearch.ts 算法函数
- 新建 ExecuteTool 跨 API provider 统一工具执行入口
- 增强 ToolSearchTool:TF-IDF 搜索路径、discover: 模式、并行搜索合并、文本模式回退
- 新增 27 个单元测试,precheck 零错误通过(4108 tests pass)

Co-Authored-By: glm-5.1[1m] <zai-org@claude-code-best.win>
2026-05-08 22:29:15 +08:00

147 lines
4.3 KiB
TypeScript

import { describe, test, expect, beforeEach } from 'bun:test'
import { logMock } from '../../../tests/mocks/log'
import { debugMock } from '../../../tests/mocks/debug'
import { mock } from 'bun:test'
mock.module('src/utils/log.ts', logMock)
mock.module('src/utils/debug.ts', debugMock)
// Mock growthbook to cut analytics dependency
mock.module('src/services/analytics/growthbook.js', () => ({
getFeatureValue_CACHED_MAY_BE_STALE: () => false,
checkStatsigFeatureGate_CACHED_MAY_BE_STALE: () => false,
getFeatureValue_DEPRECATED: async () => undefined,
getFeatureValue_CACHED_WITH_REFRESH: async () => undefined,
hasGrowthBookEnvOverride: () => false,
getAllGrowthBookFeatures: () => ({}),
getGrowthBookConfigOverrides: () => ({}),
setGrowthBookConfigOverride: () => {},
clearGrowthBookConfigOverrides: () => {},
getApiBaseUrlHost: () => undefined,
onGrowthBookRefresh: () => {},
initializeGrowthBook: async () => {},
checkSecurityRestrictionGate: async () => false,
checkGate_CACHED_OR_BLOCKING: async () => false,
refreshGrowthBookAfterAuthChange: () => {},
resetGrowthBook: () => {},
refreshGrowthBookFeatures: async () => {},
setupPeriodicGrowthBookRefresh: () => {},
stopPeriodicGrowthBookRefresh: () => {},
}))
const { CORE_TOOLS } = await import('../tools.js')
const { isDeferredTool } = await import(
'@claude-code-best/builtin-tools/tools/ToolSearchTool/prompt.js'
)
type MockTool = {
name: string
alwaysLoad?: boolean
isMcp?: boolean
shouldDefer?: boolean
}
function makeTool(overrides: Partial<MockTool> = {}): MockTool {
return {
name: 'TestTool',
isMcp: false,
shouldDefer: undefined,
alwaysLoad: undefined,
...overrides,
}
}
describe('CORE_TOOLS', () => {
test('contains expected number of tools', () => {
// 7 SHELL_TOOL_NAMES + 22 independent tool names
expect(CORE_TOOLS.size).toBeGreaterThanOrEqual(29)
})
test('contains key core tool names', () => {
const expected = [
'Bash',
'Read',
'Edit',
'Write',
'Glob',
'Grep',
'Agent',
'AskUserQuestion',
'ToolSearch',
'WebSearch',
'WebFetch',
'Sleep',
'LSP',
'Skill',
'TeamCreate',
'TeamDelete',
'TaskCreate',
'TaskGet',
'TaskUpdate',
'TaskList',
'TaskOutput',
'TaskStop',
'TodoWrite',
'EnterPlanMode',
'ExitPlanMode',
'VerifyPlanExecution',
'NotebookEdit',
'StructuredOutput',
]
for (const name of expected) {
expect(CORE_TOOLS.has(name), `CORE_TOOLS should contain ${name}`).toBe(
true,
)
}
})
test('is a ReadonlySet', () => {
// ReadonlySet is not directly distinguishable at runtime from Set,
// but we verify the cast was applied by checking it's a Set
expect(CORE_TOOLS).toBeInstanceOf(Set)
// The `as ReadonlySet<string>` ensures type-level immutability
})
})
describe('isDeferredTool', () => {
test('returns false for core tools', () => {
const coreNames = ['Read', 'Edit', 'Bash', 'Glob', 'Grep', 'Agent']
for (const name of coreNames) {
const tool = makeTool({ name })
expect(
isDeferredTool(tool as never),
`${name} should not be deferred`,
).toBe(false)
}
})
test('returns false for tools with alwaysLoad: true even if not in CORE_TOOLS', () => {
const tool = makeTool({ name: 'CustomTool', alwaysLoad: true })
expect(isDeferredTool(tool as never)).toBe(false)
})
test('returns true for non-core built-in tools', () => {
const tool = makeTool({ name: 'ConfigTool' })
expect(isDeferredTool(tool as never)).toBe(true)
})
test('returns true for MCP tools', () => {
const tool = makeTool({ name: 'mcp__server__action', isMcp: true })
expect(isDeferredTool(tool as never)).toBe(true)
})
test('returns false for MCP tools with alwaysLoad: true', () => {
const tool = makeTool({
name: 'mcp__server__action',
isMcp: true,
alwaysLoad: true,
})
expect(isDeferredTool(tool as never)).toBe(false)
})
test('alwaysLoad takes precedence over CORE_TOOLS membership', () => {
// A tool in CORE_TOOLS with alwaysLoad: false should still not be deferred
const tool = makeTool({ name: 'Read', alwaysLoad: true })
expect(isDeferredTool(tool as never)).toBe(false)
})
})