Skip to content

Commit

Permalink
add improved reauth while offline test
Browse files Browse the repository at this point in the history
  • Loading branch information
zirgulis committed Nov 18, 2024
1 parent ff16617 commit c9cb455
Showing 1 changed file with 54 additions and 39 deletions.
93 changes: 54 additions & 39 deletions tests/unit/NetworkTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as SequentialQueue from '@src/libs/Network/SequentialQueue';
import NetworkConnection from '@src/libs/NetworkConnection';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session as OnyxSession} from '@src/types/onyx';
import type OnyxNetwork from '@src/types/onyx/Network';
import type ReactNativeOnyxMock from '../../__mocks__/react-native-onyx';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
Expand Down Expand Up @@ -148,77 +149,91 @@ describe('NetworkTests', () => {
});

test('failing to reauthenticate while offline should not log out user', async () => {
// 1. Setup Phase - Initialize test user and state variables
const TEST_USER_LOGIN = '[email protected]';
const TEST_USER_ACCOUNT_ID = 1;
const defaultTimeout = 1000;

let session: OnyxEntry<OnyxSession>;
let sessionState: OnyxEntry<OnyxSession>;
let networkState: OnyxEntry<OnyxNetwork>;

// Set up listeners for session and network state changes
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => (session = val),
callback: (val) => (sessionState = val),
});

Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: (val) => (networkState = val),
});

// Sign in test user and wait for updates
await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN);
await waitForBatchedUpdates();

expect(session?.authToken).not.toBeUndefined();

// Turn off the network
await Onyx.set(ONYXKEYS.NETWORK, {isOffline: true});
const initialAuthToken = sessionState?.authToken;
expect(initialAuthToken).toBeDefined();

const mockedXhr = jest.fn();
mockedXhr
// Call ReconnectApp with an expired token
// 2. Mock Setup Phase - Configure XHR mocks for the test sequence
const mockedXhr = jest
.fn()
// First mock: ReconnectApp will fail with NOT_AUTHENTICATED
.mockImplementationOnce(() =>
Promise.resolve({
jsonCode: CONST.JSON_CODE.NOT_AUTHENTICATED,
}),
)
// Call Authenticate
.mockImplementationOnce(() =>
Promise.resolve({
jsonCode: CONST.JSON_CODE.SUCCESS,
authToken: 'newAuthToken',
}),
)
// Call ReconnectApp again, it should connect with a new token
.mockImplementationOnce(() =>
Promise.resolve({
jsonCode: CONST.JSON_CODE.SUCCESS,
}),
);
// Second mock: Authenticate with network check and delay
.mockImplementationOnce(() => {
// Check network state immediately
if (networkState?.isOffline) {
return Promise.reject(new Error('Network request failed'));
}

// create a delayed response. Timeout will expire after the second App.reconnectApp();
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Network request failed'));
}, defaultTimeout);
});
});

HttpUtils.xhr = mockedXhr;

// Initiate the requests
// 3. Test Execution Phase - Start with online network
await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false});

// Trigger reconnect which will fail due to expired token
App.confirmReadyToOpenApp();
App.reconnectApp();
await waitForBatchedUpdates();

// Turn the network back online
await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false});

// Filter requests results by request name
const reconnectResults = (HttpUtils.xhr as Mock).mock.results.filter((_, index) => (HttpUtils.xhr as Mock)?.mock?.calls?.at(index)?.[0] === 'ReconnectApp');
const authenticateResults = (HttpUtils.xhr as Mock).mock.results.filter((_, index) => (HttpUtils.xhr as Mock)?.mock?.calls?.at(index)?.[0] === 'Authenticate');
// 4. First API Call Verification - Check ReconnectApp
const firstCall = mockedXhr.mock.calls.at(0) as [string, Record<string, unknown>];
expect(firstCall[0]).toBe('ReconnectApp');

// Get the response code of Authenticate call
const authenticateResponse = await (authenticateResults?.at(0)?.value as Promise<{jsonCode: string}>);
// 5. Authentication Start - Verify authenticate was triggered
await waitForBatchedUpdates();
const secondCall = mockedXhr.mock.calls.at(1) as [string, Record<string, unknown>];
expect(secondCall[0]).toBe('Authenticate');

// Get the response code of the second Reconnect call
const reconnectResponse = await (reconnectResults?.at(1)?.value as Promise<{jsonCode: string}>);
// 6. Network State Change - Set offline and back online while authenticate is pending
await Onyx.set(ONYXKEYS.NETWORK, {isOffline: true});
await Onyx.set(ONYXKEYS.NETWORK, {isOffline: false});

// Authenticate request should return 200
expect(authenticateResponse.jsonCode).toBe(CONST.JSON_CODE.SUCCESS);
// Trigger another reconnect due to network change
App.confirmReadyToOpenApp();
App.reconnectApp();
await waitForBatchedUpdates();

// The second ReconnectApp should return 200
expect(reconnectResponse.jsonCode).toBe(CONST.JSON_CODE.SUCCESS);
// 7. Wait and Verify - Wait for authenticate timeout and verify session
await new Promise((resolve) => {
setTimeout(resolve, defaultTimeout + 100);
});

// check if the user is still logged in
expect(session?.authToken).not.toBeUndefined();
// Verify the session remained intact and wasn't cleared
expect(sessionState?.authToken).toBe(initialAuthToken);
});

test('consecutive API calls eventually succeed when authToken is expired', () => {
Expand Down

0 comments on commit c9cb455

Please sign in to comment.