* 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>
269 lines
9.5 KiB
TypeScript
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'),
|
|
);
|
|
});
|
|
});
|