From 944401b5534c7994346ffe98d3aca5086e898441 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 5 May 2026 16:41:40 -0700 Subject: [PATCH 1/3] Restore commented-out code --- src/libs/Reauthentication.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/Reauthentication.ts b/src/libs/Reauthentication.ts index 47cbc45f3f02..3a4fffbeb27f 100644 --- a/src/libs/Reauthentication.ts +++ b/src/libs/Reauthentication.ts @@ -140,10 +140,9 @@ function reauthenticate(command = ''): Promise { // Prevent reauthentication if credentials are missing (e.g. after sign out) if (!credentials?.autoGeneratedLogin || !credentials?.autoGeneratedPassword) { Log.info('[Reauthenticate] No credentials available, redirecting to sign in'); - // The following lines are commented out to test if it's the cause of #fireroom-2026-01-28-user-signout - // setIsAuthenticating(false); - // redirectToSignIn('No credentials available'); - // return false; + setIsAuthenticating(false); + redirectToSignIn('No credentials available'); + return false; } Log.info(`[Reauthenticate] Re-authenticating with ${shouldUseNewPartnerName ? 'new' : 'old'} partner name`); From 08eacb199400baa7962a6451997d637deafd536a Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 5 May 2026 16:56:56 -0700 Subject: [PATCH 2/3] test: add unit tests for reauthenticate missing-credentials redirect Covers the case where reauthenticate() is called with no credentials, verifying that it calls redirectToSignIn('No credentials available') and releases the network authenticating state instead of proceeding to call Authenticate with undefined credentials. Co-authored-by: Cursor --- tests/unit/libs/ReauthenticationTest.ts | 94 +++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/unit/libs/ReauthenticationTest.ts diff --git a/tests/unit/libs/ReauthenticationTest.ts b/tests/unit/libs/ReauthenticationTest.ts new file mode 100644 index 000000000000..aaaca6a4b8dd --- /dev/null +++ b/tests/unit/libs/ReauthenticationTest.ts @@ -0,0 +1,94 @@ +import redirectToSignIn from '@libs/actions/SignInRedirect'; +import * as NetworkStore from '@libs/Network/NetworkStore'; +import reauthenticate from '@libs/Reauthentication'; +import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; + +jest.mock('@src/libs/Log'); + +jest.mock('@src/libs/actions/SignInRedirect', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue(undefined), +})); + +jest.mock('@src/libs/Network/NetworkStore', () => ({ + getCredentials: jest.fn(), + hasReadRequiredDataFromStorage: jest.fn(), + setIsAuthenticating: jest.fn(), + setAuthToken: jest.fn(), + getAuthToken: jest.fn(), + checkRequiredData: jest.fn(), + resetHasReadRequiredDataFromStorage: jest.fn(), +})); + +jest.mock('@src/libs/SessionUtils', () => ({ + checkIfShouldUseNewPartnerName: jest.fn().mockReturnValue(true), +})); + +jest.mock('@src/libs/telemetry/trackAuthenticationError', () => ({ + __esModule: true, + default: jest.fn(), +})); + +const mockRedirectToSignIn = redirectToSignIn as jest.Mock; +const mockGetCredentials = NetworkStore.getCredentials as jest.Mock; +const mockHasReadRequiredDataFromStorage = NetworkStore.hasReadRequiredDataFromStorage as jest.Mock; +const mockSetIsAuthenticating = NetworkStore.setIsAuthenticating as jest.Mock; + +beforeEach(() => { + jest.clearAllMocks(); + mockHasReadRequiredDataFromStorage.mockResolvedValue(undefined); +}); + +describe('reauthenticate', () => { + describe('when credentials are missing', () => { + it('redirects to sign in when credentials are null', async () => { + mockGetCredentials.mockReturnValue(null); + + const result = await reauthenticate('TestCommand'); + await waitForBatchedUpdates(); + + expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); + expect(result).toBe(false); + }); + + it('redirects to sign in when credentials are empty object', async () => { + mockGetCredentials.mockReturnValue({}); + + const result = await reauthenticate('TestCommand'); + await waitForBatchedUpdates(); + + expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); + expect(result).toBe(false); + }); + + it('redirects to sign in when only autoGeneratedLogin is present', async () => { + mockGetCredentials.mockReturnValue({autoGeneratedLogin: 'test-login'}); + + const result = await reauthenticate('TestCommand'); + await waitForBatchedUpdates(); + + expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); + expect(result).toBe(false); + }); + + it('redirects to sign in when only autoGeneratedPassword is present', async () => { + mockGetCredentials.mockReturnValue({autoGeneratedPassword: 'test-password'}); + + const result = await reauthenticate('TestCommand'); + await waitForBatchedUpdates(); + + expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); + expect(result).toBe(false); + }); + + it('stops the authenticating state before redirecting', async () => { + mockGetCredentials.mockReturnValue(null); + + await reauthenticate('TestCommand'); + await waitForBatchedUpdates(); + + // setIsAuthenticating(false) should be called to release the network pause + expect(mockSetIsAuthenticating).toHaveBeenCalledWith(false); + }); + }); +}); From fd3aba5aca3fabd1660f8aa5389d3884dc98ad06 Mon Sep 17 00:00:00 2001 From: rory Date: Tue, 5 May 2026 17:08:07 -0700 Subject: [PATCH 3/3] refactor(test): move reauthenticate missing-credentials test into SessionTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the standalone ReauthenticationTest file with a single focused test in the existing SessionTest suite. Calls reauthenticate() directly (no API stack needed) — after beforeEach Onyx.clear(), NetworkStore credentials are null, which is the exact condition the fix guards against. Co-authored-by: Cursor --- tests/actions/SessionTest.ts | 17 +++++ tests/unit/libs/ReauthenticationTest.ts | 94 ------------------------- 2 files changed, 17 insertions(+), 94 deletions(-) delete mode 100644 tests/unit/libs/ReauthenticationTest.ts diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index de12c9cc8200..c72354545a73 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -16,6 +16,7 @@ import {setHasRadio} from '@libs/NetworkState'; import PushNotification from '@libs/Notification/PushNotification'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection import '@libs/Notification/PushNotification/subscribeToPushNotifications'; +import reauthenticate from '@libs/Reauthentication'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import * as SessionUtil from '@src/libs/actions/Session'; @@ -62,6 +63,22 @@ beforeEach(() => { }); describe('Session', () => { + test('reauthenticate redirects to sign in with "No credentials available" when credentials are missing', async () => { + // Given no signed-in user — beforeEach calls Onyx.clear(), so NetworkStore's credentials are null + + const redirectToSignInSpy = jest.spyOn(SignInRedirect, 'default').mockImplementation(() => Promise.resolve()); + + // When reauthenticate is called with no credentials stored + const result = await reauthenticate('TestCommand'); + await waitForBatchedUpdates(); + + // Then it should redirect to sign in instead of attempting to call Authenticate with undefined credentials + expect(result).toBe(false); + expect(redirectToSignInSpy).toHaveBeenCalledWith('No credentials available'); + + redirectToSignInSpy.mockRestore(); + }); + test('Authenticate is called with saved credentials when a session expires', async () => { // Given a test user and set of authToken with subscriptions to session and credentials const TEST_USER_LOGIN = 'test@testguy.com'; diff --git a/tests/unit/libs/ReauthenticationTest.ts b/tests/unit/libs/ReauthenticationTest.ts deleted file mode 100644 index aaaca6a4b8dd..000000000000 --- a/tests/unit/libs/ReauthenticationTest.ts +++ /dev/null @@ -1,94 +0,0 @@ -import redirectToSignIn from '@libs/actions/SignInRedirect'; -import * as NetworkStore from '@libs/Network/NetworkStore'; -import reauthenticate from '@libs/Reauthentication'; -import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; - -jest.mock('@src/libs/Log'); - -jest.mock('@src/libs/actions/SignInRedirect', () => ({ - __esModule: true, - default: jest.fn().mockResolvedValue(undefined), -})); - -jest.mock('@src/libs/Network/NetworkStore', () => ({ - getCredentials: jest.fn(), - hasReadRequiredDataFromStorage: jest.fn(), - setIsAuthenticating: jest.fn(), - setAuthToken: jest.fn(), - getAuthToken: jest.fn(), - checkRequiredData: jest.fn(), - resetHasReadRequiredDataFromStorage: jest.fn(), -})); - -jest.mock('@src/libs/SessionUtils', () => ({ - checkIfShouldUseNewPartnerName: jest.fn().mockReturnValue(true), -})); - -jest.mock('@src/libs/telemetry/trackAuthenticationError', () => ({ - __esModule: true, - default: jest.fn(), -})); - -const mockRedirectToSignIn = redirectToSignIn as jest.Mock; -const mockGetCredentials = NetworkStore.getCredentials as jest.Mock; -const mockHasReadRequiredDataFromStorage = NetworkStore.hasReadRequiredDataFromStorage as jest.Mock; -const mockSetIsAuthenticating = NetworkStore.setIsAuthenticating as jest.Mock; - -beforeEach(() => { - jest.clearAllMocks(); - mockHasReadRequiredDataFromStorage.mockResolvedValue(undefined); -}); - -describe('reauthenticate', () => { - describe('when credentials are missing', () => { - it('redirects to sign in when credentials are null', async () => { - mockGetCredentials.mockReturnValue(null); - - const result = await reauthenticate('TestCommand'); - await waitForBatchedUpdates(); - - expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); - expect(result).toBe(false); - }); - - it('redirects to sign in when credentials are empty object', async () => { - mockGetCredentials.mockReturnValue({}); - - const result = await reauthenticate('TestCommand'); - await waitForBatchedUpdates(); - - expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); - expect(result).toBe(false); - }); - - it('redirects to sign in when only autoGeneratedLogin is present', async () => { - mockGetCredentials.mockReturnValue({autoGeneratedLogin: 'test-login'}); - - const result = await reauthenticate('TestCommand'); - await waitForBatchedUpdates(); - - expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); - expect(result).toBe(false); - }); - - it('redirects to sign in when only autoGeneratedPassword is present', async () => { - mockGetCredentials.mockReturnValue({autoGeneratedPassword: 'test-password'}); - - const result = await reauthenticate('TestCommand'); - await waitForBatchedUpdates(); - - expect(mockRedirectToSignIn).toHaveBeenCalledWith('No credentials available'); - expect(result).toBe(false); - }); - - it('stops the authenticating state before redirecting', async () => { - mockGetCredentials.mockReturnValue(null); - - await reauthenticate('TestCommand'); - await waitForBatchedUpdates(); - - // setIsAuthenticating(false) should be called to release the network pause - expect(mockSetIsAuthenticating).toHaveBeenCalledWith(false); - }); - }); -});