@@ -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 {
@@ -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} )
0 commit comments