Skip to content

Commit cd64e15

Browse files
committed
SearchFilesHandler.test
1 parent 3ef2ac9 commit cd64e15

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import * as path from "path"
2+
import { SearchFilesHandler } from "../SearchFilesHandler"
3+
import { Cline } from "../../../Cline"
4+
import { ToolUse } from "../../../assistant-message"
5+
import { formatResponse } from "../../../prompts/responses"
6+
import { regexSearchFiles } from "../../../../services/ripgrep" // Import the function to mock
7+
import { RooIgnoreController } from "../../../ignore/RooIgnoreController"
8+
import { telemetryService } from "../../../../services/telemetry/TelemetryService"
9+
import { getReadablePath } from "../../../../utils/path"
10+
11+
// --- Mocks ---
12+
jest.mock("../../../Cline")
13+
const MockCline = Cline as jest.MockedClass<typeof Cline>
14+
15+
jest.mock("../../../../services/ripgrep")
16+
const mockRegexSearchFiles = regexSearchFiles as jest.Mock
17+
18+
jest.mock("../../../ignore/RooIgnoreController")
19+
const MockRooIgnoreController = RooIgnoreController as jest.MockedClass<typeof RooIgnoreController>
20+
21+
jest.mock("../../../prompts/responses", () => ({
22+
formatResponse: {
23+
toolError: jest.fn((msg) => `ERROR: ${msg}`),
24+
toolResult: jest.fn((text) => text), // Simple mock
25+
},
26+
}))
27+
28+
jest.mock("../../../../services/telemetry/TelemetryService", () => ({
29+
telemetryService: {
30+
captureToolUsage: jest.fn(),
31+
},
32+
}))
33+
34+
jest.mock("../../../../utils/path", () => ({
35+
getReadablePath: jest.fn((cwd, p) => p || "mock/path"), // Simple mock
36+
}))
37+
38+
describe("SearchFilesHandler", () => {
39+
let mockClineInstance: jest.MockedObject<Cline>
40+
let mockRooIgnoreControllerInstance: jest.MockedObject<RooIgnoreController>
41+
let mockToolUse: ToolUse
42+
43+
beforeEach(() => {
44+
jest.clearAllMocks()
45+
46+
mockRooIgnoreControllerInstance = new MockRooIgnoreController(
47+
"/workspace",
48+
) as jest.MockedObject<RooIgnoreController>
49+
// No methods needed for default mock in this handler
50+
51+
mockClineInstance = {
52+
cwd: "/workspace",
53+
consecutiveMistakeCount: 0,
54+
taskId: "test-task-id",
55+
rooIgnoreController: mockRooIgnoreControllerInstance,
56+
ask: jest.fn(() => Promise.resolve({})),
57+
say: jest.fn(() => Promise.resolve()),
58+
pushToolResult: jest.fn(() => Promise.resolve()),
59+
handleErrorHelper: jest.fn(() => Promise.resolve()),
60+
sayAndCreateMissingParamError: jest.fn((tool, param) => Promise.resolve(`Missing ${param}`)),
61+
askApprovalHelper: jest.fn(() => Promise.resolve(true)), // Default approval
62+
providerRef: { deref: () => ({ getState: () => Promise.resolve({}) }) },
63+
emit: jest.fn(),
64+
getTokenUsage: jest.fn(() => ({})),
65+
removeClosingTag: jest.fn((tag, value) => value),
66+
} as unknown as jest.MockedObject<Cline>
67+
68+
mockToolUse = {
69+
type: "tool_use",
70+
name: "search_files",
71+
params: {
72+
path: "src",
73+
regex: "console\\.log",
74+
file_pattern: "*.ts", // Optional
75+
},
76+
partial: false,
77+
}
78+
79+
// Default search mock
80+
mockRegexSearchFiles.mockResolvedValue("Found 3 matches:\nfile1.ts:10: console.log('hello')\n...")
81+
})
82+
83+
// --- Test validateParams ---
84+
test("validateParams should throw if path is missing", () => {
85+
delete mockToolUse.params.path
86+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
87+
expect(() => handler.validateParams()).toThrow("Missing required parameter 'path'")
88+
})
89+
90+
test("validateParams should throw if regex is missing", () => {
91+
delete mockToolUse.params.regex
92+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
93+
expect(() => handler.validateParams()).toThrow("Missing required parameter 'regex'")
94+
})
95+
96+
test("validateParams should not throw if optional file_pattern is missing", () => {
97+
delete mockToolUse.params.file_pattern
98+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
99+
expect(() => handler.validateParams()).not.toThrow()
100+
})
101+
102+
// --- Test handlePartial ---
103+
test("handlePartial should call ask with tool info", async () => {
104+
mockToolUse.partial = true
105+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
106+
await handler.handle()
107+
expect(mockClineInstance.ask).toHaveBeenCalledWith(
108+
"tool",
109+
JSON.stringify({
110+
tool: "searchFiles",
111+
path: mockToolUse.params.path,
112+
regex: mockToolUse.params.regex,
113+
filePattern: mockToolUse.params.file_pattern,
114+
content: "",
115+
}),
116+
true,
117+
)
118+
})
119+
120+
// --- Test handleComplete ---
121+
test("handleComplete should fail if path param is missing", async () => {
122+
delete mockToolUse.params.path
123+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
124+
await handler.handle()
125+
expect(mockClineInstance.sayAndCreateMissingParamError).toHaveBeenCalledWith("search_files", "path")
126+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, "Missing path")
127+
expect(mockClineInstance.consecutiveMistakeCount).toBe(1)
128+
})
129+
130+
test("handleComplete should fail if regex param is missing", async () => {
131+
delete mockToolUse.params.regex
132+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
133+
await handler.handle()
134+
expect(mockClineInstance.sayAndCreateMissingParamError).toHaveBeenCalledWith("search_files", "regex")
135+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, "Missing regex")
136+
expect(mockClineInstance.consecutiveMistakeCount).toBe(1)
137+
})
138+
139+
test("handleComplete should call search, ask approval, and push result", async () => {
140+
const searchResult = "Found matches..."
141+
mockRegexSearchFiles.mockResolvedValue(searchResult)
142+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
143+
await handler.handle()
144+
145+
expect(mockRegexSearchFiles).toHaveBeenCalledWith(
146+
"/workspace", // cwd
147+
path.resolve("/workspace", "src"), // absolute path
148+
"console\\.log", // regex
149+
"*.ts", // file_pattern
150+
mockRooIgnoreControllerInstance,
151+
)
152+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
153+
mockToolUse,
154+
"tool",
155+
expect.stringContaining(`"content":"${searchResult}"`),
156+
)
157+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, searchResult)
158+
expect(telemetryService.captureToolUsage).toHaveBeenCalledWith(mockClineInstance.taskId, "search_files")
159+
expect(mockClineInstance.consecutiveMistakeCount).toBe(0)
160+
})
161+
162+
test("handleComplete should call search without file_pattern if not provided", async () => {
163+
delete mockToolUse.params.file_pattern
164+
const searchResult = "Found other matches..."
165+
mockRegexSearchFiles.mockResolvedValue(searchResult)
166+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
167+
await handler.handle()
168+
169+
expect(mockRegexSearchFiles).toHaveBeenCalledWith(
170+
"/workspace",
171+
path.resolve("/workspace", "src"),
172+
"console\\.log",
173+
undefined, // file_pattern should be undefined
174+
mockRooIgnoreControllerInstance,
175+
)
176+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
177+
mockToolUse,
178+
"tool",
179+
expect.stringContaining(`"content":"${searchResult}"`),
180+
)
181+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, searchResult)
182+
})
183+
184+
test("handleComplete should skip push if approval denied", async () => {
185+
;(mockClineInstance.askApprovalHelper as jest.Mock).mockResolvedValue(false) // Deny approval
186+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
187+
await handler.handle()
188+
189+
expect(mockRegexSearchFiles).toHaveBeenCalled() // Search still happens
190+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalled()
191+
expect(mockClineInstance.pushToolResult).not.toHaveBeenCalled() // Handled by helper
192+
expect(telemetryService.captureToolUsage).not.toHaveBeenCalled()
193+
})
194+
195+
test("handleComplete should handle errors during search", async () => {
196+
const searchError = new Error("Ripgrep failed")
197+
mockRegexSearchFiles.mockRejectedValue(searchError) // Make search throw
198+
const handler = new SearchFilesHandler(mockClineInstance, mockToolUse)
199+
await handler.handle()
200+
201+
expect(mockRegexSearchFiles).toHaveBeenCalled()
202+
expect(mockClineInstance.askApprovalHelper).not.toHaveBeenCalled() // Error before approval
203+
expect(mockClineInstance.handleErrorHelper).toHaveBeenCalledWith(mockToolUse, "searching files", searchError)
204+
expect(mockClineInstance.pushToolResult).not.toHaveBeenCalled() // Handled by error helper
205+
})
206+
})

0 commit comments

Comments
 (0)