Skip to content

Commit

Permalink
improve simulating online/offline conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
zirgulis committed Nov 7, 2024
1 parent 4e06de3 commit adf8661
Showing 1 changed file with 86 additions and 72 deletions.
158 changes: 86 additions & 72 deletions tests/unit/NetworkTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as MainQueue from '@src/libs/Network/MainQueue';
import * as NetworkStore from '@src/libs/Network/NetworkStore';
import NetworkConnection from '@src/libs/NetworkConnection';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session as OnyxSession} from '@src/types/onyx';
import type {Network as OnyNetwork, Session as OnyxSession} from '@src/types/onyx';
import type ReactNativeOnyxMock from '../../__mocks__/react-native-onyx';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
Expand Down Expand Up @@ -132,81 +132,95 @@ describe('NetworkTests', () => {
});
});
});

test('failing to reauthenticate while offline should not log out user', async () => {
const TEST_USER_LOGIN = '[email protected]';
const TEST_USER_ACCOUNT_ID = 1;

let session: OnyxEntry<OnyxSession>;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => (session = val),
});

Check failure on line 136 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `⏎····`

Check failure on line 136 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `⏎····`
Onyx.connect({
key: ONYXKEYS.NETWORK,
// 1. Setup Phase - Initialize test user and state variables
const TEST_USER_LOGIN = '[email protected]';

Check failure on line 138 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `····`

Check failure on line 138 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `····`
const TEST_USER_ACCOUNT_ID = 1;

Check failure on line 139 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Replace `············` with `········`

Check failure on line 139 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Replace `············` with `········`
const defaultTimeout = 1000;

Check failure on line 140 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `····`

Check failure on line 140 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `····`

Check failure on line 141 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `····`

Check failure on line 141 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `····`
let sessionState: OnyxEntry<OnyxSession>;

Check failure on line 142 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Replace `············` with `········`

Check failure on line 142 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Replace `············` with `········`
let networkState: OnyxEntry<OnyNetwork>;

Check failure on line 143 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `····`

Check failure on line 143 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `····`

Check failure on line 144 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `····`

Check failure on line 144 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `····`
// Set up listeners for session and network state changes

Check failure on line 145 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Replace `············` with `········`

Check failure on line 145 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Replace `············` with `········`
Onyx.connect({

Check failure on line 146 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Delete `····`

Check failure on line 146 in tests/unit/NetworkTest.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Delete `····`
key: ONYXKEYS.SESSION,
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();

const initialAuthToken = sessionState?.authToken;
expect(initialAuthToken).toBeDefined();

// 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,
}),
)
// 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;

// 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();

// 4. First API Call Verification - Check ReconnectApp
const firstCall = mockedXhr.mock.calls.at(0) as [string, Record<string, unknown>];
expect(firstCall[0]).toBe('ReconnectApp');

// 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');

// 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});

// Trigger another reconnect due to network change
App.confirmReadyToOpenApp();
App.reconnectApp();
await waitForBatchedUpdates();

// 7. Wait and Verify - Wait for authenticate timeout and verify session
await new Promise((resolve) => {
setTimeout(resolve, defaultTimeout + 100);
});

// Verify the session remained intact and wasn't cleared
expect(sessionState?.authToken).toBe(initialAuthToken);
});

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 mockedXhr = jest.fn();
mockedXhr
// Call ReconnectApp with an expired token
.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,
}),
);

HttpUtils.xhr = mockedXhr;

// Initiate the requests
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');

// Get the response code of Authenticate call
const authenticateResponse = await (authenticateResults?.at(0)?.value as Promise<{jsonCode: string}>);

// Get the response code of the second Reconnect call
const reconnectResponse = await (reconnectResults?.at(1)?.value as Promise<{jsonCode: string}>);

// Authenticate request should return 200
expect(authenticateResponse.jsonCode).toBe(CONST.JSON_CODE.SUCCESS);

// The second ReconnectApp should return 200
expect(reconnectResponse.jsonCode).toBe(CONST.JSON_CODE.SUCCESS);

// check if the user is still logged in
expect(session?.authToken).not.toBeUndefined();
});

test('consecutive API calls eventually succeed when authToken is expired', () => {
// Given a test user login and account ID
const TEST_USER_LOGIN = '[email protected]';
Expand Down

0 comments on commit adf8661

Please sign in to comment.