Skip to content

Commit 2118e4f

Browse files
committed
ExecuteCommandHandler.test
1 parent 2cd2a43 commit 2118e4f

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { ExecuteCommandHandler } from "../ExecuteCommandHandler"
2+
import { Cline } from "../../../Cline"
3+
import { ToolUse } from "../../../assistant-message"
4+
import { formatResponse } from "../../../prompts/responses"
5+
import { RooIgnoreController } from "../../../ignore/RooIgnoreController" // Assuming path
6+
import { telemetryService } from "../../../../services/telemetry/TelemetryService"
7+
8+
// --- Mocks ---
9+
jest.mock("../../../Cline")
10+
const MockCline = Cline as jest.MockedClass<typeof Cline>
11+
12+
jest.mock("../../../ignore/RooIgnoreController") // Mock the RooIgnoreController class
13+
const MockRooIgnoreController = RooIgnoreController as jest.MockedClass<typeof RooIgnoreController>
14+
15+
jest.mock("../../../prompts/responses", () => ({
16+
formatResponse: {
17+
toolError: jest.fn((msg) => `ERROR: ${msg}`),
18+
toolResult: jest.fn((text) => text), // Simple mock
19+
rooIgnoreError: jest.fn((file) => `RooIgnore Error: ${file}`),
20+
},
21+
}))
22+
23+
jest.mock("../../../../services/telemetry/TelemetryService", () => ({
24+
telemetryService: {
25+
captureToolUsage: jest.fn(),
26+
},
27+
}))
28+
29+
describe("ExecuteCommandHandler", () => {
30+
let mockClineInstance: jest.MockedObject<Cline>
31+
let mockRooIgnoreControllerInstance: jest.MockedObject<RooIgnoreController>
32+
let mockToolUse: ToolUse
33+
34+
beforeEach(() => {
35+
jest.clearAllMocks()
36+
37+
// Create mock instance for RooIgnoreController, providing mock CWD
38+
mockRooIgnoreControllerInstance = new MockRooIgnoreController(
39+
"/workspace",
40+
) as jest.MockedObject<RooIgnoreController>
41+
// Explicitly assign a mock function to validateCommand on the instance
42+
mockRooIgnoreControllerInstance.validateCommand = jest.fn().mockReturnValue(undefined) // Default: command is allowed
43+
44+
mockClineInstance = {
45+
cwd: "/workspace",
46+
consecutiveMistakeCount: 0,
47+
taskId: "test-task-id",
48+
rooIgnoreController: mockRooIgnoreControllerInstance, // Assign mock instance
49+
didRejectTool: false,
50+
ask: jest.fn(() => Promise.resolve({})), // Default ask response
51+
say: jest.fn(() => Promise.resolve()),
52+
pushToolResult: jest.fn(() => Promise.resolve()),
53+
handleErrorHelper: jest.fn(() => Promise.resolve()),
54+
sayAndCreateMissingParamError: jest.fn((tool, param) => Promise.resolve(`Missing ${param}`)),
55+
askApprovalHelper: jest.fn(() => Promise.resolve(true)), // Default approval
56+
executeCommandTool: jest.fn(() => Promise.resolve([false, "Command output"])), // Default success
57+
providerRef: { deref: () => ({ getState: () => Promise.resolve({}) }) },
58+
emit: jest.fn(),
59+
getTokenUsage: jest.fn(() => ({})),
60+
removeClosingTag: jest.fn((tag, value) => value), // Simple mock
61+
} as unknown as jest.MockedObject<Cline>
62+
63+
mockToolUse = {
64+
type: "tool_use",
65+
name: "execute_command",
66+
params: {
67+
command: "echo 'hello'",
68+
},
69+
partial: false,
70+
}
71+
})
72+
73+
// --- Test validateParams ---
74+
test("validateParams should throw if command is missing", () => {
75+
delete mockToolUse.params.command
76+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
77+
expect(() => handler.validateParams()).toThrow("Missing required parameter 'command'")
78+
})
79+
80+
test("validateParams should not throw if cwd is missing", () => {
81+
delete mockToolUse.params.cwd
82+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
83+
expect(() => handler.validateParams()).not.toThrow()
84+
})
85+
86+
// --- Test handlePartial ---
87+
test("handlePartial should call ask with command and partial flag", async () => {
88+
mockToolUse.partial = true
89+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
90+
await handler.handle()
91+
expect(mockClineInstance.ask).toHaveBeenCalledWith("command", mockToolUse.params.command, true)
92+
})
93+
94+
// --- Test handleComplete ---
95+
test("handleComplete should fail if command param is missing", async () => {
96+
delete mockToolUse.params.command
97+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
98+
await handler.handle()
99+
expect(mockClineInstance.sayAndCreateMissingParamError).toHaveBeenCalledWith("execute_command", "command")
100+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, "Missing command")
101+
expect(mockClineInstance.consecutiveMistakeCount).toBe(1)
102+
})
103+
104+
test("handleComplete should fail if command accesses ignored file", async () => {
105+
const ignoredFile = ".env"
106+
mockRooIgnoreControllerInstance.validateCommand.mockReturnValue(ignoredFile)
107+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
108+
await handler.handle()
109+
110+
expect(mockRooIgnoreControllerInstance.validateCommand).toHaveBeenCalledWith(mockToolUse.params.command)
111+
expect(mockClineInstance.say).toHaveBeenCalledWith("rooignore_error", ignoredFile)
112+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(
113+
mockToolUse,
114+
"ERROR: RooIgnore Error: .env", // Based on mock formatResponse
115+
)
116+
expect(mockClineInstance.askApprovalHelper).not.toHaveBeenCalled()
117+
expect(mockClineInstance.executeCommandTool).not.toHaveBeenCalled()
118+
})
119+
120+
test("handleComplete should ask for approval and execute command", async () => {
121+
const commandResult = "Success output"
122+
;(mockClineInstance.executeCommandTool as jest.Mock).mockResolvedValue([false, commandResult])
123+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
124+
await handler.handle()
125+
126+
expect(mockRooIgnoreControllerInstance.validateCommand).toHaveBeenCalledWith(mockToolUse.params.command)
127+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
128+
mockToolUse,
129+
"command",
130+
mockToolUse.params.command,
131+
)
132+
expect(mockClineInstance.executeCommandTool).toHaveBeenCalledWith(mockToolUse.params.command, undefined) // No custom cwd
133+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, commandResult)
134+
expect(telemetryService.captureToolUsage).toHaveBeenCalledWith(mockClineInstance.taskId, "execute_command")
135+
expect(mockClineInstance.consecutiveMistakeCount).toBe(0)
136+
})
137+
138+
test("handleComplete should execute command with custom cwd", async () => {
139+
mockToolUse.params.cwd = "/custom/dir"
140+
const commandResult = "Success output in custom dir"
141+
;(mockClineInstance.executeCommandTool as jest.Mock).mockResolvedValue([false, commandResult])
142+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
143+
await handler.handle()
144+
145+
expect(mockRooIgnoreControllerInstance.validateCommand).toHaveBeenCalledWith(mockToolUse.params.command)
146+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
147+
mockToolUse,
148+
"command",
149+
mockToolUse.params.command,
150+
)
151+
expect(mockClineInstance.executeCommandTool).toHaveBeenCalledWith(mockToolUse.params.command, "/custom/dir") // Check custom cwd
152+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, commandResult)
153+
expect(telemetryService.captureToolUsage).toHaveBeenCalledWith(mockClineInstance.taskId, "execute_command")
154+
})
155+
156+
test("handleComplete should skip execution if approval denied", async () => {
157+
;(mockClineInstance.askApprovalHelper as jest.Mock).mockResolvedValue(false) // Deny approval
158+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
159+
await handler.handle()
160+
161+
expect(mockRooIgnoreControllerInstance.validateCommand).toHaveBeenCalledWith(mockToolUse.params.command)
162+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
163+
mockToolUse,
164+
"command",
165+
mockToolUse.params.command,
166+
)
167+
expect(mockClineInstance.executeCommandTool).not.toHaveBeenCalled()
168+
expect(mockClineInstance.pushToolResult).not.toHaveBeenCalled() // Handled by helper
169+
})
170+
171+
test("handleComplete should handle user rejection during execution", async () => {
172+
const rejectionResult = "User rejected during execution"
173+
;(mockClineInstance.executeCommandTool as jest.Mock).mockResolvedValue([true, rejectionResult]) // Simulate mid-execution rejection
174+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
175+
await handler.handle()
176+
177+
expect(mockRooIgnoreControllerInstance.validateCommand).toHaveBeenCalledWith(mockToolUse.params.command)
178+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
179+
mockToolUse,
180+
"command",
181+
mockToolUse.params.command,
182+
)
183+
expect(mockClineInstance.executeCommandTool).toHaveBeenCalledWith(mockToolUse.params.command, undefined)
184+
expect(mockClineInstance.didRejectTool).toBe(true) // Check rejection flag
185+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, rejectionResult) // Push the rejection result
186+
expect(telemetryService.captureToolUsage).toHaveBeenCalledWith(mockClineInstance.taskId, "execute_command")
187+
})
188+
189+
test("handleComplete should handle errors during execution", async () => {
190+
const execError = new Error("Command failed")
191+
;(mockClineInstance.executeCommandTool as jest.Mock).mockRejectedValue(execError)
192+
const handler = new ExecuteCommandHandler(mockClineInstance, mockToolUse)
193+
await handler.handle()
194+
195+
expect(mockRooIgnoreControllerInstance.validateCommand).toHaveBeenCalledWith(mockToolUse.params.command)
196+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
197+
mockToolUse,
198+
"command",
199+
mockToolUse.params.command,
200+
)
201+
expect(mockClineInstance.executeCommandTool).toHaveBeenCalledWith(mockToolUse.params.command, undefined)
202+
expect(mockClineInstance.handleErrorHelper).toHaveBeenCalledWith(mockToolUse, "executing command", execError)
203+
expect(mockClineInstance.pushToolResult).not.toHaveBeenCalled() // Error helper handles result
204+
})
205+
})

0 commit comments

Comments
 (0)