Skip to content

Commit 7c3045f

Browse files
taltasclaude
andcommitted
test(cli): improve coverage for autonomous mode and validation paths
Add comprehensive test coverage for: - AskDispatcher error handling and autonomous mode paths - run.ts provider base URL validation in non-autonomous mode - Disabled ask handling mode - Partial message handling - Unknown ask types with onInputRequired callback - Error recovery (removing asks from handled set on error) - API request failure handling with onInputRequired - Followup questions with onInputRequired in autonomous mode This addresses codecov/patch coverage gaps by testing previously uncovered branches in autonomous mode execution and validation. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 355110c commit 7c3045f

2 files changed

Lines changed: 188 additions & 0 deletions

File tree

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import type { ClineMessage, ClineAsk } from "@roo-code/types"
2+
import { AskDispatcher } from "../ask-dispatcher.js"
3+
import type { OutputManager } from "../output-manager.js"
4+
import type { PromptManager } from "../prompt-manager.js"
5+
6+
const createMockOutputManager = (): OutputManager =>
7+
({
8+
output: vi.fn(),
9+
markDisplayed: vi.fn(),
10+
}) as unknown as OutputManager
11+
12+
const createMockPromptManager = (): PromptManager =>
13+
({
14+
promptForInput: vi.fn(),
15+
promptForYesNo: vi.fn(),
16+
promptWithTimeout: vi.fn(),
17+
}) as unknown as PromptManager
18+
19+
describe("AskDispatcher", () => {
20+
let mockOutputManager: OutputManager
21+
let mockPromptManager: PromptManager
22+
let sendMessageMock: ReturnType<typeof vi.fn>
23+
24+
beforeEach(() => {
25+
mockOutputManager = createMockOutputManager()
26+
mockPromptManager = createMockPromptManager()
27+
sendMessageMock = vi.fn()
28+
})
29+
30+
describe("handleAsk - disabled mode", () => {
31+
it("returns handled=false when disabled", async () => {
32+
const dispatcher = new AskDispatcher({
33+
outputManager: mockOutputManager,
34+
promptManager: mockPromptManager,
35+
sendMessage: sendMessageMock,
36+
disabled: true,
37+
})
38+
39+
const message: ClineMessage = {
40+
ts: 1,
41+
type: "ask",
42+
ask: "followup",
43+
text: "test",
44+
partial: false,
45+
} as ClineMessage
46+
47+
const result = await dispatcher.handleAsk(message)
48+
expect(result.handled).toBe(false)
49+
})
50+
})
51+
52+
describe("handleAsk - partial messages", () => {
53+
it("skips partial messages", async () => {
54+
const dispatcher = new AskDispatcher({
55+
outputManager: mockOutputManager,
56+
promptManager: mockPromptManager,
57+
sendMessage: sendMessageMock,
58+
})
59+
60+
const message: ClineMessage = {
61+
ts: 1,
62+
type: "ask",
63+
ask: "followup",
64+
text: "test",
65+
partial: true,
66+
} as ClineMessage
67+
68+
const result = await dispatcher.handleAsk(message)
69+
expect(result.handled).toBe(false)
70+
})
71+
})
72+
73+
describe("handleAsk - unknown ask type in non-interactive mode", () => {
74+
it("calls onInputRequired for unknown ask types", async () => {
75+
const onInputRequired = vi.fn()
76+
const dispatcher = new AskDispatcher({
77+
outputManager: mockOutputManager,
78+
promptManager: mockPromptManager,
79+
sendMessage: sendMessageMock,
80+
nonInteractive: true,
81+
onInputRequired,
82+
})
83+
84+
const message: ClineMessage = {
85+
ts: 1,
86+
type: "ask",
87+
ask: "unknown_ask_type" as ClineAsk,
88+
text: "test message",
89+
partial: false,
90+
} as ClineMessage
91+
92+
const result = await dispatcher.handleAsk(message)
93+
expect(result.handled).toBe(true)
94+
expect(onInputRequired).toHaveBeenCalledWith("unknown_ask_type", "test message")
95+
})
96+
})
97+
98+
describe("handleAsk - error handling", () => {
99+
it("removes ask from handled set on error", async () => {
100+
const dispatcher = new AskDispatcher({
101+
outputManager: mockOutputManager,
102+
promptManager: mockPromptManager,
103+
sendMessage: () => {
104+
throw new Error("sendMessage error")
105+
},
106+
})
107+
108+
const message: ClineMessage = {
109+
ts: 1,
110+
type: "ask",
111+
ask: "command_output",
112+
text: "test",
113+
partial: false,
114+
} as ClineMessage
115+
116+
const result = await dispatcher.handleAsk(message)
117+
expect(result.handled).toBe(false)
118+
expect(result.error).toBeInstanceOf(Error)
119+
120+
// Should be able to handle again after error
121+
expect(dispatcher.isHandled(1)).toBe(false)
122+
})
123+
})
124+
125+
describe("api_req_failed handling", () => {
126+
it("calls onInputRequired when provided", async () => {
127+
const onInputRequired = vi.fn()
128+
const dispatcher = new AskDispatcher({
129+
outputManager: mockOutputManager,
130+
promptManager: mockPromptManager,
131+
sendMessage: sendMessageMock,
132+
onInputRequired,
133+
})
134+
135+
const message: ClineMessage = {
136+
ts: 1,
137+
type: "ask",
138+
ask: "api_req_failed",
139+
text: "API error",
140+
partial: false,
141+
} as ClineMessage
142+
143+
const result = await dispatcher.handleAsk(message)
144+
expect(result.handled).toBe(true)
145+
expect(onInputRequired).toHaveBeenCalledWith("api_req_failed", "API error")
146+
})
147+
})
148+
149+
describe("followup handling with onInputRequired", () => {
150+
it("calls onInputRequired in non-interactive mode", async () => {
151+
const onInputRequired = vi.fn()
152+
const dispatcher = new AskDispatcher({
153+
outputManager: mockOutputManager,
154+
promptManager: mockPromptManager,
155+
sendMessage: sendMessageMock,
156+
nonInteractive: true,
157+
onInputRequired,
158+
})
159+
160+
const message: ClineMessage = {
161+
ts: 1,
162+
type: "ask",
163+
ask: "followup",
164+
text: JSON.stringify({ question: "What next?", suggest: [{ answer: "Continue" }] }),
165+
partial: false,
166+
} as ClineMessage
167+
168+
const result = await dispatcher.handleAsk(message)
169+
expect(result.handled).toBe(true)
170+
expect(onInputRequired).toHaveBeenCalledWith("followup", "What next?")
171+
})
172+
})
173+
})

apps/cli/src/commands/cli/__tests__/run.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,21 @@ describe("run command validation", () => {
389389
})
390390
})
391391

392+
describe("providerBaseUrl validation", () => {
393+
it("should reject providerBaseUrl with non-openrouter provider in non-autonomous mode", async () => {
394+
const flags = createFlagOptions({
395+
providerBaseUrl: "https://custom-base-url.com",
396+
provider: "anthropic",
397+
print: true,
398+
})
399+
400+
await expect(run("test", flags)).rejects.toThrow("process.exit: 1")
401+
expect(consoleErrorSpy).toHaveBeenCalledWith(
402+
expect.stringContaining("--provider-base-url is currently supported only with --provider openrouter"),
403+
)
404+
})
405+
})
406+
392407
describe("prompt file validation", () => {
393408
it("should reject non-existent prompt file", async () => {
394409
const flags = createFlagOptions({

0 commit comments

Comments
 (0)