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