Skip to content

Commit 317619b

Browse files
James Mtendamemacursoragent
andcommitted
test(zoo-gateway): cover auth callback profile sync and sign-out
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ca45230 commit 317619b

2 files changed

Lines changed: 236 additions & 0 deletions

File tree

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,14 @@ vi.mock("../../../api/providers/fetchers/modelCache", () => ({
230230
getModelsFromCache: vi.fn().mockReturnValue(undefined),
231231
}))
232232

233+
vi.mock("../../../services/zoo-code-auth", () => ({
234+
getZooCodeBaseUrl: vi.fn(() => "https://www.zoocode.dev"),
235+
getCachedZooCodeToken: vi.fn(),
236+
handleAuthCallback: vi.fn(),
237+
setZooCodeUserInfo: vi.fn(),
238+
disconnectZooCode: vi.fn(),
239+
}))
240+
233241
vi.mock("../../../shared/modes", () => ({
234242
modes: [
235243
{
@@ -3664,4 +3672,151 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
36643672
vi.mocked(fsUtils.fileExistsAtPath).mockRestore()
36653673
})
36663674
})
3675+
3676+
describe("Zoo Code auth profile sync", () => {
3677+
beforeEach(async () => {
3678+
const { getCachedZooCodeToken } = await import("../../../services/zoo-code-auth")
3679+
vi.mocked(getCachedZooCodeToken).mockReturnValue("")
3680+
})
3681+
3682+
describe("handleZooCodeCallback", () => {
3683+
it("creates a Zoo Gateway profile when none exists", async () => {
3684+
vi.spyOn(provider, "getState").mockResolvedValue({
3685+
apiConfiguration: { zooGatewayModelId: "anthropic/claude-sonnet-4" },
3686+
} as any)
3687+
vi.spyOn(provider.contextProxy, "getProviderSettings").mockReturnValue({
3688+
apiProvider: "anthropic",
3689+
} as any)
3690+
vi.spyOn(provider.contextProxy, "getValues").mockReturnValue({
3691+
currentApiConfigName: "Anthropic",
3692+
} as any)
3693+
const upsertSpy = vi.spyOn(provider, "upsertProviderProfile").mockResolvedValue("profile-id")
3694+
vi.spyOn(provider, "postStateToWebview").mockResolvedValue(undefined)
3695+
;(provider as any).providerSettingsManager = {
3696+
listConfig: vi.fn().mockResolvedValue([]),
3697+
}
3698+
3699+
await provider.handleZooCodeCallback("zoo_ext_token")
3700+
3701+
expect(upsertSpy).toHaveBeenCalledWith(
3702+
"Zoo Gateway",
3703+
expect.objectContaining({
3704+
apiProvider: "zoo-gateway",
3705+
zooSessionToken: "zoo_ext_token",
3706+
zooGatewayBaseUrl: "https://www.zoocode.dev/api/gateway/v1",
3707+
}),
3708+
false,
3709+
)
3710+
})
3711+
3712+
it("updates every zoo-gateway profile and activates only the active one", async () => {
3713+
vi.spyOn(provider, "getState").mockResolvedValue({
3714+
apiConfiguration: { zooGatewayModelId: "anthropic/claude-sonnet-4" },
3715+
} as any)
3716+
vi.spyOn(provider.contextProxy, "getProviderSettings").mockReturnValue({
3717+
apiProvider: "zoo-gateway",
3718+
} as any)
3719+
vi.spyOn(provider.contextProxy, "getValues").mockReturnValue({
3720+
currentApiConfigName: "Zoo Gateway",
3721+
} as any)
3722+
const upsertSpy = vi.spyOn(provider, "upsertProviderProfile").mockResolvedValue("profile-id")
3723+
const saveConfig = vi.fn().mockResolvedValue(undefined)
3724+
vi.spyOn(provider, "postStateToWebview").mockResolvedValue(undefined)
3725+
;(provider as any).providerSettingsManager = {
3726+
listConfig: vi.fn().mockResolvedValue([
3727+
{ name: "Zoo Gateway", apiProvider: "zoo-gateway" },
3728+
{ name: "Backup Zoo", apiProvider: "zoo-gateway" },
3729+
]),
3730+
getProfile: vi
3731+
.fn()
3732+
.mockResolvedValueOnce({
3733+
apiProvider: "zoo-gateway",
3734+
zooSessionToken: "old-token",
3735+
zooGatewayBaseUrl: "https://old.example/api/gateway/v1",
3736+
})
3737+
.mockResolvedValueOnce({
3738+
apiProvider: "zoo-gateway",
3739+
zooSessionToken: "old-token",
3740+
}),
3741+
saveConfig,
3742+
}
3743+
3744+
await provider.handleZooCodeCallback("new-token")
3745+
3746+
expect(upsertSpy).toHaveBeenCalledWith(
3747+
"Zoo Gateway",
3748+
expect.objectContaining({
3749+
zooSessionToken: "new-token",
3750+
zooGatewayBaseUrl: "https://www.zoocode.dev/api/gateway/v1",
3751+
}),
3752+
true,
3753+
)
3754+
expect(saveConfig).toHaveBeenCalledWith(
3755+
"Backup Zoo",
3756+
expect.objectContaining({
3757+
zooSessionToken: "new-token",
3758+
zooGatewayBaseUrl: "https://www.zoocode.dev/api/gateway/v1",
3759+
}),
3760+
)
3761+
})
3762+
3763+
it("logs and posts state when profile persistence fails", async () => {
3764+
vi.spyOn(provider, "getState").mockRejectedValue(new Error("state unavailable"))
3765+
vi.spyOn(provider, "postStateToWebview").mockResolvedValue(undefined)
3766+
;(provider as any).providerSettingsManager = {
3767+
listConfig: vi.fn().mockResolvedValue([]),
3768+
}
3769+
3770+
await provider.handleZooCodeCallback("zoo_ext_token")
3771+
3772+
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
3773+
expect.stringContaining("[handleZooCodeCallback] Failed to save zoo-gateway profile"),
3774+
)
3775+
})
3776+
})
3777+
3778+
describe("ensureZooGatewayProfileSeeded", () => {
3779+
it("does nothing when no cached auth token exists", async () => {
3780+
const handleSpy = vi.spyOn(provider, "handleZooCodeCallback").mockResolvedValue(undefined)
3781+
3782+
;(provider as any).providerSettingsManager = {
3783+
listConfig: vi.fn(),
3784+
}
3785+
3786+
await (provider as any).ensureZooGatewayProfileSeeded()
3787+
3788+
expect(handleSpy).not.toHaveBeenCalled()
3789+
})
3790+
3791+
it("skips seeding when every zoo-gateway profile already has the current token", async () => {
3792+
const { getCachedZooCodeToken } = await import("../../../services/zoo-code-auth")
3793+
vi.mocked(getCachedZooCodeToken).mockReturnValue("current-token")
3794+
const handleSpy = vi.spyOn(provider, "handleZooCodeCallback").mockResolvedValue(undefined)
3795+
3796+
;(provider as any).providerSettingsManager = {
3797+
listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]),
3798+
getProfile: vi.fn().mockResolvedValue({ zooSessionToken: "current-token" }),
3799+
}
3800+
3801+
await (provider as any).ensureZooGatewayProfileSeeded()
3802+
3803+
expect(handleSpy).not.toHaveBeenCalled()
3804+
})
3805+
3806+
it("re-seeds when any zoo-gateway profile has a stale or missing token", async () => {
3807+
const { getCachedZooCodeToken } = await import("../../../services/zoo-code-auth")
3808+
vi.mocked(getCachedZooCodeToken).mockReturnValue("fresh-token")
3809+
const handleSpy = vi.spyOn(provider, "handleZooCodeCallback").mockResolvedValue(undefined)
3810+
3811+
;(provider as any).providerSettingsManager = {
3812+
listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]),
3813+
getProfile: vi.fn().mockResolvedValue({ zooSessionToken: "stale-token" }),
3814+
}
3815+
3816+
await (provider as any).ensureZooGatewayProfileSeeded()
3817+
3818+
expect(handleSpy).toHaveBeenCalledWith("fresh-token")
3819+
})
3820+
})
3821+
})
36673822
})

src/core/webview/__tests__/webviewMessageHandler.spec.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type { Mock } from "vitest"
44

55
// Mock dependencies - must come before imports
66
vi.mock("../../../api/providers/fetchers/modelCache")
7+
vi.mock("../../../services/zoo-code-auth", () => ({
8+
disconnectZooCode: vi.fn().mockResolvedValue(undefined),
9+
}))
710
vi.mock("../../../api/providers/fetchers/lmstudio", () => ({
811
getLMStudioModels: vi.fn(),
912
}))
@@ -1149,3 +1152,81 @@ describe("webviewMessageHandler - downloadErrorDiagnostics", () => {
11491152
expect(generateErrorDiagnostics).not.toHaveBeenCalled()
11501153
})
11511154
})
1155+
1156+
describe("zooCodeSignOut", () => {
1157+
beforeEach(() => {
1158+
vi.clearAllMocks()
1159+
})
1160+
1161+
it("disconnects Zoo Code and clears tokens from all zoo-gateway profiles", async () => {
1162+
const { disconnectZooCode } = await import("../../../services/zoo-code-auth")
1163+
const upsertProviderProfile = vi.fn().mockResolvedValue(undefined)
1164+
const saveConfig = vi.fn().mockResolvedValue(undefined)
1165+
1166+
;(mockClineProvider as any).contextProxy = {
1167+
...mockClineProvider.contextProxy,
1168+
getProviderSettings: vi.fn().mockReturnValue({ apiProvider: "zoo-gateway" }),
1169+
getValues: vi.fn().mockReturnValue({ currentApiConfigName: "Zoo Gateway" }),
1170+
}
1171+
;(mockClineProvider as any).providerSettingsManager = {
1172+
listConfig: vi.fn().mockResolvedValue([
1173+
{ name: "Zoo Gateway", apiProvider: "zoo-gateway" },
1174+
{ name: "Backup Zoo", apiProvider: "zoo-gateway" },
1175+
]),
1176+
getProfile: vi
1177+
.fn()
1178+
.mockResolvedValueOnce({
1179+
apiProvider: "zoo-gateway",
1180+
zooSessionToken: "token-active",
1181+
zooGatewayModelId: "anthropic/claude-sonnet-4",
1182+
})
1183+
.mockResolvedValueOnce({
1184+
apiProvider: "zoo-gateway",
1185+
zooSessionToken: "token-backup",
1186+
}),
1187+
saveConfig,
1188+
}
1189+
;(mockClineProvider as any).upsertProviderProfile = upsertProviderProfile
1190+
1191+
await webviewMessageHandler(mockClineProvider, { type: "zooCodeSignOut" })
1192+
1193+
expect(disconnectZooCode).toHaveBeenCalled()
1194+
expect(upsertProviderProfile).toHaveBeenCalledWith(
1195+
"Zoo Gateway",
1196+
expect.not.objectContaining({ zooSessionToken: expect.anything() }),
1197+
true,
1198+
)
1199+
expect(saveConfig).toHaveBeenCalledWith(
1200+
"Backup Zoo",
1201+
expect.not.objectContaining({ zooSessionToken: expect.anything() }),
1202+
)
1203+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
1204+
})
1205+
1206+
it("still clears the in-memory handler when the active profile token is already empty on disk", async () => {
1207+
const upsertProviderProfile = vi.fn().mockResolvedValue(undefined)
1208+
1209+
;(mockClineProvider as any).contextProxy = {
1210+
...mockClineProvider.contextProxy,
1211+
getProviderSettings: vi.fn().mockReturnValue({ apiProvider: "zoo-gateway" }),
1212+
getValues: vi.fn().mockReturnValue({ currentApiConfigName: "Zoo Gateway" }),
1213+
}
1214+
;(mockClineProvider as any).providerSettingsManager = {
1215+
listConfig: vi.fn().mockResolvedValue([{ name: "Zoo Gateway", apiProvider: "zoo-gateway" }]),
1216+
getProfile: vi.fn().mockResolvedValue({
1217+
apiProvider: "zoo-gateway",
1218+
zooGatewayModelId: "anthropic/claude-sonnet-4",
1219+
}),
1220+
saveConfig: vi.fn(),
1221+
}
1222+
;(mockClineProvider as any).upsertProviderProfile = upsertProviderProfile
1223+
1224+
await webviewMessageHandler(mockClineProvider, { type: "zooCodeSignOut" })
1225+
1226+
expect(upsertProviderProfile).toHaveBeenCalledWith(
1227+
"Zoo Gateway",
1228+
expect.not.objectContaining({ zooSessionToken: expect.anything() }),
1229+
true,
1230+
)
1231+
})
1232+
})

0 commit comments

Comments
 (0)