@@ -7,6 +7,7 @@ const { mocks } = vi.hoisted(() => {
77 return {
88 mocks : {
99 list : vi . fn ( ) ,
10+ lookup : vi . fn ( ) ,
1011 saveJsonObject : vi . fn ( ) ,
1112 uploadFiles : vi . fn ( ) ,
1213 deleteFiles : vi . fn ( ) ,
@@ -67,6 +68,7 @@ vi.mock('@/stores', () => ({
6768vi . mock ( '@/services/api' , ( ) => ( {
6869 authFilesApi : {
6970 list : mocks . list ,
71+ lookup : mocks . lookup ,
7072 saveJsonObject : mocks . saveJsonObject ,
7173 uploadFiles : mocks . uploadFiles ,
7274 deleteFiles : mocks . deleteFiles ,
@@ -196,6 +198,7 @@ const mountUseAuthFilesData = (
196198
197199beforeEach ( ( ) => {
198200 mocks . list . mockReset ( ) ;
201+ mocks . lookup . mockReset ( ) ;
199202 mocks . saveJsonObject . mockReset ( ) ;
200203 mocks . uploadFiles . mockReset ( ) ;
201204 mocks . deleteFiles . mockReset ( ) ;
@@ -210,6 +213,7 @@ beforeEach(() => {
210213 mocks . showConfirmation . mockReset ( ) ;
211214
212215 mocks . list . mockResolvedValue ( { files : [ ] } ) ;
216+ mocks . lookup . mockResolvedValue ( [ ] ) ;
213217 mocks . saveJsonObject . mockResolvedValue ( undefined ) ;
214218 mocks . uploadFiles . mockResolvedValue ( { status : 'ok' , uploaded : 0 , files : [ ] , failed : [ ] } ) ;
215219 mocks . deleteFiles . mockResolvedValue ( { deleted : 0 , failed : [ ] , files : [ ] } ) ;
@@ -3376,9 +3380,7 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
33763380 ...file ,
33773381 last_refresh : '2026-01-01T00:00:01Z' ,
33783382 } as AuthFileItem ;
3379- mocks . list . mockResolvedValueOnce ( { files : [ file ] } ) . mockResolvedValue ( {
3380- files : [ refreshedFile ] ,
3381- } ) ;
3383+ mocks . lookup . mockResolvedValueOnce ( [ file ] ) . mockResolvedValue ( [ refreshedFile ] ) ;
33823384 const hook = mountUseAuthFilesData ( ) ;
33833385 let request ! : Promise < void > ;
33843386
@@ -3439,9 +3441,10 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
34393441 id_token : { plan_type : 'plus' } ,
34403442 } ,
34413443 ] ;
3442- mocks . list
3443- . mockResolvedValueOnce ( { files : [ refreshedFiles [ 0 ] , file ] } )
3444- . mockResolvedValue ( { files : refreshedFiles } ) ;
3444+ mocks . lookup
3445+ . mockResolvedValueOnce ( [ refreshedFiles [ 0 ] , file ] )
3446+ . mockResolvedValueOnce ( [ refreshedFiles [ 1 ] ] )
3447+ . mockResolvedValue ( refreshedFiles ) ;
34453448 const operationKey = 'shared-codex.json\u0000auth-2' ;
34463449 let request ! : Promise < void > ;
34473450
@@ -3494,6 +3497,47 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
34943497 hook . unmount ( ) ;
34953498 } ) ;
34963499
3500+ it ( 'reconciles the refreshed source without replacing unrelated account rows' , async ( ) => {
3501+ const unrelated = {
3502+ id : 'claude-runtime-auth-id' ,
3503+ name : 'claude-account.json' ,
3504+ authIndex : 'claude-auth-1' ,
3505+ type : 'claude' ,
3506+ note : 'keep-me' ,
3507+ } as AuthFileItem ;
3508+ const file = {
3509+ id : 'codex-runtime-auth-id' ,
3510+ name : 'codex-account.json' ,
3511+ authIndex : 'auth-1' ,
3512+ type : 'codex' ,
3513+ last_refresh : '2026-01-01T00:00:00Z' ,
3514+ } as AuthFileItem ;
3515+ const refreshedFile = {
3516+ ...file ,
3517+ last_refresh : '2026-01-02T00:00:00Z' ,
3518+ } as AuthFileItem ;
3519+ mocks . list . mockResolvedValue ( { files : [ unrelated , file ] } ) ;
3520+ mocks . lookup
3521+ . mockResolvedValueOnce ( [ file ] )
3522+ . mockResolvedValueOnce ( [ file ] )
3523+ . mockResolvedValue ( [ refreshedFile ] ) ;
3524+ const hook = mountUseAuthFilesData ( ) ;
3525+
3526+ await act ( async ( ) => {
3527+ await hook . getCurrent ( ) . loadFiles ( ) ;
3528+ await hook . getCurrent ( ) . handleCredentialRefresh ( file ) ;
3529+ } ) ;
3530+
3531+ expect ( hook . getCurrent ( ) . files ) . toEqual ( [ unrelated , refreshedFile ] ) ;
3532+ expect ( mocks . lookup ) . toHaveBeenNthCalledWith ( 1 , { name : 'codex-account.json' } ) ;
3533+ expect ( mocks . lookup ) . toHaveBeenNthCalledWith ( 3 , {
3534+ name : 'codex-account.json' ,
3535+ authIndex : 'auth-1' ,
3536+ } ) ;
3537+ expect ( mocks . list ) . toHaveBeenCalledTimes ( 1 ) ;
3538+ hook . unmount ( ) ;
3539+ } ) ;
3540+
34973541 it ( 'keeps the request pending when CPA does not confirm the refresh in time' , async ( ) => {
34983542 vi . useFakeTimers ( ) ;
34993543 const file : AuthFileItem = {
@@ -3504,7 +3548,7 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
35043548 last_refresh : '2026-01-01T00:00:00Z' ,
35053549 id_token : { plan_type : 'free' } ,
35063550 } ;
3507- mocks . list . mockResolvedValue ( { files : [ file ] } ) ;
3551+ mocks . lookup . mockResolvedValue ( [ file ] ) ;
35083552 const onCredentialFilesChanged = vi . fn ( ) ;
35093553 const hook = mountUseAuthFilesData ( undefined , undefined , onCredentialFilesChanged ) ;
35103554 let request ! : Promise < void > ;
@@ -3521,7 +3565,8 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
35213565 await request ;
35223566 } ) ;
35233567
3524- expect ( mocks . list ) . toHaveBeenCalledTimes ( 16 ) ;
3568+ expect ( mocks . lookup ) . toHaveBeenCalledTimes ( 18 ) ;
3569+ expect ( mocks . list ) . not . toHaveBeenCalled ( ) ;
35253570 expect ( hook . getCurrent ( ) . files ) . toEqual ( [ file ] ) ;
35263571 expect (
35273572 hook . getCurrent ( ) . credentialRefreshing [ 'codex-account.json\u0000auth-1' ]
@@ -3550,9 +3595,10 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
35503595 account_id : 'replacement-account' ,
35513596 last_refresh : '2026-01-02T00:00:00Z' ,
35523597 } as AuthFileItem ;
3553- mocks . list . mockResolvedValueOnce ( { files : [ original ] } ) . mockResolvedValue ( {
3554- files : [ replacement ] ,
3555- } ) ;
3598+ mocks . lookup
3599+ . mockResolvedValueOnce ( [ original ] )
3600+ . mockResolvedValueOnce ( [ original ] )
3601+ . mockResolvedValue ( [ replacement ] ) ;
35563602 const hook = mountUseAuthFilesData ( ) ;
35573603 let request ! : Promise < void > ;
35583604
@@ -3599,10 +3645,12 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
35993645 type : 'codex' ,
36003646 last_refresh : '2026-01-01T00:00:00Z' ,
36013647 } ;
3602- mocks . list
3603- . mockResolvedValueOnce ( { files : [ file ] } )
3604- . mockResolvedValueOnce ( { files : [ file ] } )
3605- . mockResolvedValue ( { files : [ { ...file , last_refresh : '2026-01-02T00:00:00Z' } ] } ) ;
3648+ mocks . lookup
3649+ . mockResolvedValueOnce ( [ file ] )
3650+ . mockResolvedValueOnce ( [ file ] )
3651+ . mockResolvedValueOnce ( [ file ] )
3652+ . mockResolvedValueOnce ( [ file ] )
3653+ . mockResolvedValue ( [ { ...file , last_refresh : '2026-01-02T00:00:00Z' } ] ) ;
36063654 const hook = mountUseAuthFilesData ( 'connection-a' ) ;
36073655 let firstRequest ! : Promise < void > ;
36083656 let secondRequest ! : Promise < void > ;
@@ -3651,7 +3699,7 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
36513699 type : 'codex' ,
36523700 last_refresh : '2026-01-01T00:00:00Z' ,
36533701 } ;
3654- mocks . list . mockResolvedValue ( { files : [ file ] } ) ;
3702+ mocks . lookup . mockResolvedValue ( [ file ] ) ;
36553703 mocks . requestCredentialRefresh
36563704 . mockReturnValueOnce ( firstRequest . promise )
36573705 . mockReturnValueOnce ( secondRequest . promise ) ;
@@ -3676,9 +3724,7 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
36763724 expect ( layoutRequest ) . toBeDefined ( ) ;
36773725
36783726 await act ( async ( ) => {
3679- mocks . list . mockResolvedValue ( {
3680- files : [ { ...file , last_refresh : '2026-01-02T00:00:00Z' } ] ,
3681- } ) ;
3727+ mocks . lookup . mockResolvedValue ( [ { ...file , last_refresh : '2026-01-02T00:00:00Z' } ] ) ;
36823728 firstRequest . resolve ( ) ;
36833729 secondRequest . resolve ( ) ;
36843730 await Promise . all ( [ initialRequest , layoutRequest ] ) ;
@@ -3694,7 +3740,7 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
36943740 authIndex : 'auth-1' ,
36953741 type : 'codex' ,
36963742 } as AuthFileItem ;
3697- mocks . list . mockResolvedValue ( { files : [ file ] } ) ;
3743+ mocks . lookup . mockResolvedValue ( [ file ] ) ;
36983744 const hook = mountUseAuthFilesData ( ) ;
36993745
37003746 await act ( async ( ) => {
@@ -3732,9 +3778,7 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
37323778 type : 'codex' ,
37333779 account_id : 'original-account' ,
37343780 } as AuthFileItem ;
3735- mocks . list . mockResolvedValue ( {
3736- files : [ { ...original , account_id : 'replacement-account' } ] ,
3737- } ) ;
3781+ mocks . lookup . mockResolvedValue ( [ { ...original , account_id : 'replacement-account' } ] ) ;
37383782 const hook = mountUseAuthFilesData ( ) ;
37393783
37403784 await act ( async ( ) => {
@@ -3748,6 +3792,38 @@ describe('useAuthFilesData handleCredentialRefresh', () => {
37483792 ) ;
37493793 hook . unmount ( ) ;
37503794 } ) ;
3795+
3796+ it ( 'rejects a runtime identity collision outside the target source' , async ( ) => {
3797+ const original = {
3798+ id : 'runtime-auth-1' ,
3799+ name : 'source-a.json' ,
3800+ authIndex : 'auth-1' ,
3801+ type : 'codex' ,
3802+ account_id : 'account-a' ,
3803+ } as AuthFileItem ;
3804+ const collision = {
3805+ id : 'runtime-auth-1' ,
3806+ name : 'source-b.json' ,
3807+ authIndex : 'auth-2' ,
3808+ type : 'codex' ,
3809+ account_id : 'account-b' ,
3810+ } as AuthFileItem ;
3811+ mocks . lookup . mockResolvedValueOnce ( [ original ] ) . mockResolvedValueOnce ( [ original , collision ] ) ;
3812+ const hook = mountUseAuthFilesData ( ) ;
3813+
3814+ await act ( async ( ) => {
3815+ await hook . getCurrent ( ) . handleCredentialRefresh ( original ) ;
3816+ } ) ;
3817+
3818+ expect ( mocks . lookup ) . toHaveBeenNthCalledWith ( 1 , { name : 'source-a.json' } ) ;
3819+ expect ( mocks . lookup ) . toHaveBeenNthCalledWith ( 2 , { name : 'runtime-auth-1' } ) ;
3820+ expect ( mocks . requestCredentialRefresh ) . not . toHaveBeenCalled ( ) ;
3821+ expect ( mocks . showNotification ) . toHaveBeenCalledWith (
3822+ 'auth_files.credential_refresh_failed:source-a.json' ,
3823+ 'error'
3824+ ) ;
3825+ hook . unmount ( ) ;
3826+ } ) ;
37513827} ) ;
37523828
37533829describe ( 'useAuthFilesData batchPatchFields' , ( ) => {
0 commit comments