Skip to content

Commit fff23ed

Browse files
committed
test: expand URI callback coverage and address review feedback
1 parent 3bd4e43 commit fff23ed

6 files changed

Lines changed: 158 additions & 14 deletions

File tree

‎src/__tests__/ClineProvider.history-resume-delegation.spec.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,7 +1787,9 @@ describe("History resume delegation - parent metadata transitions", () => {
17871787
fU: (h: HistoryItem) => HistoryItem,
17881788
sU: (h: HistoryItem) => HistoryItem,
17891789
) => {
1790+
// Child double provides id and status, the fields read by this updater.
17901791
fU(childItem as unknown as HistoryItem)
1792+
// Parent double provides id, status, delegation links, timestamp, task, token counts, and cost.
17911793
sU(parentItem as unknown as HistoryItem)
17921794
return []
17931795
},

‎src/__tests__/api-subtask.spec.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ describe("API.clearCurrentTask()", () => {
6666
beforeEach(() => {
6767
vi.clearAllMocks()
6868
provider = makeProviderMock()
69+
// Double provides event methods, context, task access, eviction, abandonment, and webview posting.
6970
api = new API({} as OutputChannel, provider as unknown as ClineProvider, makeClineProviderFactory())
7071
})
7172

@@ -87,6 +88,7 @@ describe("API.abandonSubtask()", () => {
8788
beforeEach(() => {
8889
vi.clearAllMocks()
8990
provider = makeProviderMock()
91+
// Double provides event methods, context, task access, eviction, abandonment, and webview posting.
9092
api = new API({} as OutputChannel, provider as unknown as ClineProvider, makeClineProviderFactory())
9193
})
9294

‎src/activate/__tests__/CodeActionProvider.spec.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ describe("CodeActionProvider", () => {
6969
beforeEach(() => {
7070
provider = new CodeActionProvider()
7171

72+
// Document double provides getText, lineAt, lineCount, and uri.fsPath.
7273
mockDocument = {
7374
getText: vi.fn(),
7475
lineAt: vi.fn(),

‎src/activate/__tests__/handleUri.spec.ts‎

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
mockSetZooCodeUserInfo,
1414
mockVisibleProvider,
1515
} = vi.hoisted(() => {
16+
// Visible provider double provides the OpenRouter, Requesty, and Zoo Code callbacks.
1617
const mockVisibleProvider = {
1718
handleOpenRouterCallback: vi.fn(),
1819
handleRequestyCallback: vi.fn(),
@@ -45,13 +46,70 @@ import type { ClineProvider } from "../../core/webview/ClineProvider"
4546

4647
const makeUri = (uri: Pick<vscode.Uri, "path" | "query">): vscode.Uri => uri as vscode.Uri
4748

49+
const makeCallbackProvider = (callback = vi.fn()): ClineProvider =>
50+
// Provider double provides handleZooCodeCallback only.
51+
({ handleZooCodeCallback: callback }) as unknown as ClineProvider
52+
4853
describe("handleUri", () => {
4954
beforeEach(() => {
5055
vi.clearAllMocks()
5156
mockGetVisibleInstance.mockReturnValue(mockVisibleProvider)
5257
mockGetAllInstances.mockReturnValue([mockVisibleProvider])
5358
})
5459

60+
it.each(["/openrouter", "/requesty"])("dispatches %s codes without losing literal plus signs", async (path) => {
61+
await handleUri(makeUri({ path, query: "code=a+b%2Bc&baseUrl=https%3A%2F%2Fexample.com" }))
62+
63+
if (path === "/openrouter") {
64+
expect(mockVisibleProvider.handleOpenRouterCallback).toHaveBeenCalledWith("a+b+c")
65+
expect(mockVisibleProvider.handleRequestyCallback).not.toHaveBeenCalled()
66+
} else {
67+
expect(mockVisibleProvider.handleRequestyCallback).toHaveBeenCalledWith("a+b+c", "https://example.com")
68+
expect(mockVisibleProvider.handleOpenRouterCallback).not.toHaveBeenCalled()
69+
}
70+
})
71+
72+
it("passes null when Requesty has no base URL", async () => {
73+
await handleUri(makeUri({ path: "/requesty", query: "code=test-code" }))
74+
expect(mockVisibleProvider.handleRequestyCallback).toHaveBeenCalledWith("test-code", null)
75+
})
76+
77+
it.each(["/openrouter", "/requesty"])("ignores %s when no provider is visible", async (path) => {
78+
mockGetVisibleInstance.mockReturnValue(null)
79+
await handleUri(makeUri({ path, query: "code=test-code" }))
80+
expect(mockVisibleProvider.handleOpenRouterCallback).not.toHaveBeenCalled()
81+
expect(mockVisibleProvider.handleRequestyCallback).not.toHaveBeenCalled()
82+
})
83+
84+
it.each(["/openrouter", "/requesty", "/auth-callback", "/unknown"])(
85+
"ignores %s without callback credentials",
86+
async (path) => {
87+
await handleUri(makeUri({ path, query: "" }))
88+
expect(mockVisibleProvider.handleOpenRouterCallback).not.toHaveBeenCalled()
89+
expect(mockVisibleProvider.handleRequestyCallback).not.toHaveBeenCalled()
90+
expect(mockHandleZooCodeAuthCallback).not.toHaveBeenCalled()
91+
expect(mockSetZooCodeUserInfo).not.toHaveBeenCalled()
92+
expect(mockVisibleProvider.handleZooCodeCallback).not.toHaveBeenCalled()
93+
},
94+
)
95+
96+
it("logs non-Error callback failures and continues to the next provider", async () => {
97+
mockHandleZooCodeAuthCallback.mockResolvedValue(true)
98+
const log = vi.spyOn(console, "error").mockImplementation(() => {})
99+
const failingProvider = makeCallbackProvider(vi.fn().mockRejectedValue("storage unavailable"))
100+
mockGetAllInstances.mockReturnValue([failingProvider, mockVisibleProvider])
101+
try {
102+
await handleUri(makeUri({ path: "/auth-callback", query: "token=test-token" }))
103+
expect(log).toHaveBeenCalledWith(
104+
"Failed to persist Zoo Gateway token for a provider instance:",
105+
"storage unavailable",
106+
)
107+
expect(mockVisibleProvider.handleZooCodeCallback).toHaveBeenCalledWith("test-token")
108+
} finally {
109+
log.mockRestore()
110+
}
111+
})
112+
55113
it("ignores legacy cloud auth callback", async () => {
56114
await handleUri(
57115
makeUri({
@@ -146,8 +204,8 @@ describe("handleUri", () => {
146204
// stayed unauthenticated until reload.
147205
mockHandleZooCodeAuthCallback.mockResolvedValue(true)
148206

149-
const hiddenProvider = { handleZooCodeCallback: vi.fn() } as unknown as ClineProvider
150-
const secondHidden = { handleZooCodeCallback: vi.fn() } as unknown as ClineProvider
207+
const hiddenProvider = makeCallbackProvider()
208+
const secondHidden = makeCallbackProvider()
151209
mockGetAllInstances.mockReturnValue([mockVisibleProvider, hiddenProvider, secondHidden])
152210

153211
await handleUri(
@@ -172,14 +230,14 @@ describe("handleUri", () => {
172230

173231
const order: string[] = []
174232
const makeProvider = (name: string) =>
175-
({
176-
handleZooCodeCallback: vi.fn(async () => {
233+
makeCallbackProvider(
234+
vi.fn(async () => {
177235
order.push(`${name}:start`)
178236
// Yield to the event loop so a concurrent call would interleave.
179237
await new Promise((resolve) => setTimeout(resolve, 0))
180238
order.push(`${name}:end`)
181239
}),
182-
}) as unknown as ClineProvider
240+
)
183241

184242
const a = makeProvider("a")
185243
const b = makeProvider("b")
@@ -198,12 +256,12 @@ describe("handleUri", () => {
198256
it("continues fan-out when one instance fails to persist the callback token", async () => {
199257
mockHandleZooCodeAuthCallback.mockResolvedValue(true)
200258

201-
const failingProvider = {
202-
handleZooCodeCallback: vi.fn(async () => {
259+
const failingProvider = makeCallbackProvider(
260+
vi.fn(async () => {
203261
throw new Error("profile store unavailable")
204262
}),
205-
} as unknown as ClineProvider
206-
const healthyProvider = { handleZooCodeCallback: vi.fn() } as unknown as ClineProvider
263+
)
264+
const healthyProvider = makeCallbackProvider()
207265
mockGetAllInstances.mockReturnValue([failingProvider, healthyProvider])
208266

209267
await handleUri(

‎src/activate/registerCommands.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,9 @@ export const registerCommands = (options: RegisterCommandOptions) => {
7676
// lifecycle alongside the command registration, so it's intentionally
7777
// excluded from this map.
7878
//
79-
// Callback shape mirrors VS Code's own `commands.registerCommand` signature
80-
// (`(...args: any[]) => any`), with the return narrowed to `unknown` so
81-
// callers must inspect before using. `never[]` permits the heterogeneous
82-
// callbacks here (`importSettings` takes an optional `filePath?: string`,
83-
// others take none) while VS Code dispatches positional args dynamically.
79+
// `never[]` deliberately lets callbacks with different parameters share one map
80+
// type while rejecting direct calls with arguments. VS Code dispatches positional
81+
// arguments dynamically; `unknown` requires callers to inspect returned values.
8482
type CommandCallback = (...args: never[]) => unknown
8583
const getCommandsMap = ({
8684
context,
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { EventEmitter } from "events"
2+
import type { OutputChannel } from "vscode"
3+
import { describe, expect, it, vi } from "vitest"
4+
5+
import { RooCodeEventName, type TaskEvents, type TaskProviderEvents } from "@roo-code/types"
6+
7+
import type { Task } from "../../core/task/Task"
8+
import type { ClineProvider } from "../../core/webview/ClineProvider"
9+
import { makeClineProviderFactory } from "../../test-utils/provider"
10+
import { API } from "../api"
11+
12+
vi.mock("vscode")
13+
vi.mock("@roo-code/ipc", () => ({ IpcServer: vi.fn() }))
14+
15+
function setup() {
16+
const provider = new EventEmitter<TaskProviderEvents>()
17+
const output: OutputChannel = {
18+
name: "test",
19+
append: vi.fn(),
20+
appendLine: vi.fn(),
21+
replace: vi.fn(),
22+
clear: vi.fn(),
23+
show: vi.fn(),
24+
hide: vi.fn(),
25+
dispose: vi.fn(),
26+
}
27+
// The provider double supplies the typed event subscription API used by the constructor.
28+
const api = new API(output, provider as unknown as ClineProvider, makeClineProviderFactory())
29+
return { api, provider }
30+
}
31+
32+
describe("API delegation event forwarding", () => {
33+
it.each([
34+
RooCodeEventName.TaskDelegated,
35+
RooCodeEventName.TaskDelegationCompleted,
36+
RooCodeEventName.TaskDelegationResumed,
37+
])("forwards %s from tasks with the owning task ID", (event) => {
38+
const { api, provider } = setup()
39+
const listener = vi.fn()
40+
api.on(event, listener)
41+
const task = Object.assign(new EventEmitter<TaskEvents>(), { taskId: "parent-task" })
42+
// The task double supplies taskId and typed event subscriptions for TaskCreated wiring.
43+
provider.emit(RooCodeEventName.TaskCreated, task as unknown as Task)
44+
45+
if (event === RooCodeEventName.TaskDelegationCompleted) {
46+
task.emit(event, "child-task", "Child finished successfully")
47+
expect(listener).toHaveBeenCalledExactlyOnceWith("parent-task", "child-task", "Child finished successfully")
48+
} else {
49+
if (event === RooCodeEventName.TaskDelegated) {
50+
task.emit(RooCodeEventName.TaskDelegated, "child-task")
51+
} else {
52+
task.emit(RooCodeEventName.TaskDelegationResumed, "child-task")
53+
}
54+
expect(listener).toHaveBeenCalledExactlyOnceWith("parent-task", "child-task")
55+
}
56+
})
57+
58+
it.each([
59+
RooCodeEventName.TaskDelegated,
60+
RooCodeEventName.TaskDelegationCompleted,
61+
RooCodeEventName.TaskDelegationResumed,
62+
])("forwards %s from providers with the supplied parent ID", (event) => {
63+
const { api, provider } = setup()
64+
const listener = vi.fn()
65+
api.on(event, listener)
66+
67+
if (event === RooCodeEventName.TaskDelegationCompleted) {
68+
provider.emit(event, "other-parent", "child-task", "Child finished successfully")
69+
expect(listener).toHaveBeenCalledExactlyOnceWith(
70+
"other-parent",
71+
"child-task",
72+
"Child finished successfully",
73+
)
74+
} else {
75+
if (event === RooCodeEventName.TaskDelegated) {
76+
provider.emit(RooCodeEventName.TaskDelegated, "other-parent", "child-task")
77+
} else {
78+
provider.emit(RooCodeEventName.TaskDelegationResumed, "other-parent", "child-task")
79+
}
80+
expect(listener).toHaveBeenCalledExactlyOnceWith("other-parent", "child-task")
81+
}
82+
})
83+
})

0 commit comments

Comments
 (0)