mattermost-mobile/app/client/rest/tracking.test.ts
Elias Nahum 2f3dfbbbfa
Update dependencies and upgrade to RN 0.76.5 (#8421)
* update dev deps

* partial update dependencies

* update watermelondb

* update rn to 0.76.5, expo to 52.0.18 and others

* upgrade android firebase

* upgrade detox deps

* fix package-lock.json

* update emm and paste-input

* update turbo-log

* update network library

* fix tests

* review feedback

* fix Keyboard blocking signIn button

* Fall back to iphone 14 iOS 17.2 simulator as app crashes on iOS 17.4

* changes in deleteCredentialsForServer is causing a crash

* withOptions x2 clearly is wrong

* re-add cli-platform-apple fix

* fix: RN 0.76.5 issue with bottom sheet disappearing (#8478)

* experiment, using view vs BottomSheetView

* revert previous & try disabling enableDynamicSizing

* revert an unintended removal

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
2025-01-16 07:11:32 -07:00

269 lines
9.5 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import test_helper from '@test/test_helper';
import * as ClientConstants from './constants';
import ClientTracking from './tracking';
import type {APIClientInterface, ClientResponseMetrics} from '@mattermost/react-native-network-client';
jest.mock('react-native', () => ({
DeviceEventEmitter: {
emit: jest.fn(),
},
Platform: {
OS: 'ios',
select: jest.fn(({ios, default: def}: any) => {
return ios ?? def;
}),
},
}));
jest.mock('@mattermost/rnutils', () => ({
getIOSAppGroupDetails: jest.fn().mockRejectedValue(''),
isRunningInSplitView: jest.fn().mockReturnValue({isSplit: false, isTablet: false}),
}));
jest.mock('expo-crypto', () => ({
randomUUID: jest.fn(() => '12345678-1234-1234-1234-1234567890ab'),
}));
jest.mock('@i18n', () => ({
t: jest.fn((key) => key),
}));
jest.mock('@utils/file', () => ({
getFormattedFileSize: jest.fn((size) => `${size} bytes`),
}));
jest.mock('@utils/log', () => ({
logDebug: jest.fn(),
logInfo: jest.fn(),
}));
jest.mock('@utils/server', () => ({
semverFromServerVersion: jest.fn((version) => version),
}));
jest.mock('@init/credentials', () => ({
setServerCredentials: jest.fn(),
}));
describe('ClientTraking', () => {
const apiClientMock = {
baseUrl: 'https://example.com',
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
delete: jest.fn(),
};
let client: ClientTracking;
beforeEach(() => {
jest.clearAllMocks();
client = new ClientTracking(apiClientMock as unknown as APIClientInterface);
});
it('should set bearer token', () => {
const token = 'testToken';
client.setBearerToken(token);
expect(client.requestHeaders[ClientConstants.HEADER_AUTH]).toBe(`${ClientConstants.HEADER_BEARER} ${token}`);
expect(require('@init/credentials').setServerCredentials).toHaveBeenCalledWith(apiClientMock.baseUrl, token);
});
it('should set CSRF token', () => {
const token = 'csrfToken';
client.setCSRFToken(token);
expect(client.csrfToken).toBe(token);
});
it('should get request headers', () => {
client.setCSRFToken('csrfToken');
client.setBearerToken('testToken');
const headers = client.getRequestHeaders('POST');
expect(headers[ClientConstants.HEADER_AUTH]).toBe(`${ClientConstants.HEADER_BEARER} testToken`);
expect(headers[ClientConstants.HEADER_X_CSRF_TOKEN]).toBe('csrfToken');
});
it('should initialize and track group data', () => {
client.initTrackGroup('testGroup');
expect(client.requestGroups.has('testGroup')).toBe(true);
client.trackRequest('testGroup', 'https://example.com/api', {size: 100, compressedSize: 50} as ClientResponseMetrics);
const group = client.requestGroups.get('testGroup')!;
expect(group.totalSize).toBe(100);
expect(group.totalCompressedSize).toBe(50);
expect(group.urls['https://example.com/api'].count).toBe(1);
});
it('should increment and decrement request count', () => {
client.initTrackGroup('testGroup');
client.incrementRequestCount('testGroup');
let group = client.requestGroups.get('testGroup')!;
expect(group.activeCount).toBe(1);
client.decrementRequestCount('testGroup');
group = client.requestGroups.get('testGroup')!;
expect(group.activeCount).toBe(0);
});
it('should handle request completion', () => {
client.initTrackGroup('testGroup');
client.handleRequestCompletion('testGroup');
expect(require('@utils/log').logDebug).toHaveBeenCalledWith('Group "testGroup" completed.');
});
it('should clear completion timer', () => {
client.initTrackGroup('testGroup');
const group = client.requestGroups.get('testGroup')!;
group.completionTimer = setTimeout(() => {}, 100);
client.clearCompletionTimer('testGroup');
expect(group.completionTimer).toBeUndefined();
});
it('should check if all requests are completed', () => {
client.initTrackGroup('testGroup');
const result = client.allRequestsCompleted('testGroup');
expect(result).toBe(true);
});
it('should send telemetry event', () => {
client.initTrackGroup('testGroup');
const group = client.requestGroups.get('testGroup')!;
group.urls = {
'https://example.com/api': {
count: 2,
metrics: {latency: 200, networkType: 'Wi-Fi', tlsCipherSuite: 'none', tlsVersion: 'none', isCached: false, httpVersion: 'h2', compressedSize: 10 * 1024, size: 6 * 1024 * 1024, connectionTime: 0, startTime: 0, endTime: 100, speedInMbps: 1},
},
};
client.sendTelemetryEvent('testGroup', group, 1000);
expect(require('@utils/log').logInfo).toHaveBeenCalled();
});
it('should build request options', () => {
const options = {
body: {key: 'value'},
method: 'POST',
noRetry: true,
timeoutInterval: 5000,
headers: {custom: 'header'},
};
const result = client.buildRequestOptions(options);
expect(result.headers?.custom).toBe('header');
expect(result.retryPolicyConfiguration?.retryLimit).toBe(0);
expect(result.timeoutInterval).toBe(5000);
});
it('should perform fetch with tracking', async () => {
apiClientMock.get.mockResolvedValue({
ok: true,
data: {success: true},
headers: {},
});
const options = {
method: 'GET',
groupLabel: 'testGroup',
};
const result = await client.doFetchWithTracking('https://example.com/api', options);
expect(result).toEqual({success: true});
});
it('should handle fetch errors', async () => {
apiClientMock.get.mockRejectedValue(new Error('Request failed'));
const options = {
method: 'GET',
groupLabel: 'testGroup',
};
await expect(client.doFetchWithTracking('https://example.com/api', options)).rejects.toThrow('Received invalid response from the server.');
});
it('should call increment and decrement the same number of times as doFetchWithTracking, and handleRequestCompletion only once', async () => {
const incrementSpy = jest.spyOn(client, 'incrementRequestCount');
const decrementSpy = jest.spyOn(client, 'decrementRequestCount');
const handleRequestCompletionSpy = jest.spyOn(client, 'handleRequestCompletion');
apiClientMock.get.mockImplementation(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
ok: true,
data: {success: true},
headers: {},
metrics: {latency: 200, size: 500, compressedSize: 300},
});
}, 200); // Simulate a 100ms network delay
});
});
client.initTrackGroup('testGroup');
// Simulate multiple fetch calls
const promises = [
client.doFetchWithTracking('https://example.com/api1', {method: 'GET', groupLabel: 'testGroup'}),
client.doFetchWithTracking('https://example.com/api2', {method: 'GET', groupLabel: 'testGroup'}),
client.doFetchWithTracking('https://example.com/api3', {method: 'GET', groupLabel: 'testGroup'}),
];
// Wait for all fetches to resolve
await Promise.all(promises);
const group = client.requestGroups.get('testGroup')!;
expect(group.activeCount).toBe(0);
await test_helper.wait(100);
expect(handleRequestCompletionSpy).toHaveBeenCalledTimes(1);
expect(incrementSpy).toHaveBeenCalledTimes(3);
expect(decrementSpy).toHaveBeenCalledTimes(3);
});
it('should track duplicate requests correctly and log duplicate details', async () => {
const logDebugSpy = jest.spyOn(require('@utils/log'), 'logDebug');
apiClientMock.get.mockResolvedValue({
ok: true,
data: {success: true},
headers: {},
metrics: {latency: 150, size: 200, compressedSize: 100},
});
client.initTrackGroup('testGroup');
// Simulate multiple fetch calls to the same URL
const promises = [
client.doFetchWithTracking('https://example.com/api', {method: 'GET', groupLabel: 'testGroup'}),
client.doFetchWithTracking('https://example.com/api', {method: 'GET', groupLabel: 'testGroup'}),
client.doFetchWithTracking('https://example.com/api', {method: 'GET', groupLabel: 'testGroup'}),
];
// Wait for all fetches to resolve
await Promise.all(promises);
// Verify that the group tracked duplicates correctly
const group = client.requestGroups.get('testGroup')!;
expect(group.urls['https://example.com/api'].count).toBe(3); // URL was requested 3 times
expect(group.totalSize).toBe(600); // Total size = 3 * 200
expect(group.totalCompressedSize).toBe(300); // Total compressed size = 3 * 100
await test_helper.wait(100);
// Verify duplicate logging
expect(logDebugSpy).toHaveBeenCalledWith(
'Duplicate URLs:\n',
expect.stringContaining('https://example.com/api'),
);
});
});