Skip to content

Commit d060ed2

Browse files
committed
test(profile-sync-controller): prevent sign-out race condition
1 parent 03d4641 commit d060ed2

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

packages/profile-sync-controller/src/controllers/authentication/AuthenticationController.test.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@ describe('authentication/authentication-controller - performSignIn() with pairin
393393
});
394394

395395
const controller = new AuthenticationController({ messenger, metametrics });
396-
const requestCounter = jest.fn();
397-
mockEndpoints.mockPairSocialIdentifierUrl.on('request', requestCounter)
396+
const requestCounter = jest.fn();
397+
mockEndpoints.mockPairSocialIdentifierUrl.on('request', requestCounter);
398398

399399
// Call performSignIn 10 times in parallel
400400
const signInPromises = Array.from({ length: 10 }, () =>
@@ -448,6 +448,46 @@ describe('authentication/authentication-controller - performSignOut() tests', ()
448448
expect(controller.state.srpSessionData).toBeUndefined();
449449
expect(controller.state.socialPairingDone).toBe(false);
450450
});
451+
452+
it('prevents race condition where async pairing could set socialPairingDone to true after sign-out', async () => {
453+
const metametrics = createMockAuthMetaMetrics();
454+
const mockEndpoints = arrangeAuthAPIs();
455+
const { messenger, mockSeedlessOnboardingGetState } =
456+
createMockAuthenticationMessenger();
457+
458+
// Ensure social token is available for pairing
459+
mockSeedlessOnboardingGetState.mockReturnValue({
460+
accessToken: MOCK_SOCIAL_TOKEN,
461+
});
462+
463+
const controller = new AuthenticationController({ messenger, metametrics });
464+
465+
// Start sign-in which triggers async pairing
466+
await controller.performSignIn();
467+
// Immediately sign out before async pairing completes
468+
controller.performSignOut();
469+
470+
// Verify initial sign-out state
471+
expect(controller.state.isSignedIn).toBe(false);
472+
expect(controller.state.socialPairingDone).toBe(false);
473+
expect(controller.state.srpSessionData).toBeUndefined();
474+
475+
// Wait a bit for the async pairing operation to complete
476+
await waitFor(() => {
477+
expect(controller.state.pairingInProgress).toBe(false);
478+
});
479+
480+
// Verify that socialPairingDone remains false after sign-out
481+
expect(controller.state.isSignedIn).toBe(false);
482+
expect(controller.state.socialPairingDone).toBe(false);
483+
expect(controller.state.srpSessionData).toBeUndefined();
484+
expect(controller.state.pairingInProgress).toBe(false);
485+
486+
mockEndpoints.mockNonceUrl.done();
487+
mockEndpoints.mockSrpLoginUrl.done();
488+
mockEndpoints.mockOAuth2TokenUrl.done();
489+
mockEndpoints.mockPairSocialIdentifierUrl.done();
490+
});
451491
});
452492

453493
describe('authentication/authentication-controller - getBearerToken() tests', () => {

packages/profile-sync-controller/src/controllers/authentication/AuthenticationController.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,10 @@ export default class AuthenticationController extends BaseController<
403403
console.log(`pairing with social token success=${paired}`);
404404
if (paired) {
405405
this.update((state) => {
406-
state.socialPairingDone = true;
406+
// Prevents a race condition when sign-out is performed before pairing completes
407+
if (state.isSignedIn) {
408+
state.socialPairingDone = true;
409+
}
407410
});
408411
}
409412
} finally {

0 commit comments

Comments
 (0)