Skip to content

Commit 597a036

Browse files
authored
Merge pull request #814 from camy-x/perf/credential-refresh-targeted-lookups
2 parents 5484d7e + a2646e9 commit 597a036

4 files changed

Lines changed: 312 additions & 37 deletions

File tree

apps/web/src/features/authFiles/hooks/useAuthFilesData.test.ts

Lines changed: 99 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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', () => ({
6768
vi.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

197199
beforeEach(() => {
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

37533829
describe('useAuthFilesData batchPatchFields', () => {

0 commit comments

Comments
 (0)