MM-59438 Add tests to actions/remote/session (#8296)
* Add tests to actions/remote/session * Fix styling * Fix typing in session_manager
This commit is contained in:
parent
2e8c9ce9a5
commit
e4d895df85
3 changed files with 297 additions and 4 deletions
284
app/actions/remote/session.test.ts
Normal file
284
app/actions/remote/session.test.ts
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import {GLOBAL_IDENTIFIERS, SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
|
||||
import {
|
||||
addPushProxyVerificationStateFromLogin,
|
||||
forceLogoutIfNecessary,
|
||||
fetchSessions,
|
||||
login,
|
||||
cancelSessionNotification,
|
||||
scheduleSessionNotification,
|
||||
sendPasswordResetEmail,
|
||||
ssoLogin,
|
||||
findSession,
|
||||
} from './session';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type {LoginArgs} from '@typings/database/database';
|
||||
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
const user1 = {id: 'userid1', username: 'user1', email: 'user1@mattermost.com', roles: ''} as UserProfile;
|
||||
|
||||
const session1 = {id: 'sessionid1', user_id: user1.id, device_id: 'deviceid', props: {csrf: 'csrfid'}} as Session;
|
||||
|
||||
const throwFunc = () => {
|
||||
throw Error('error');
|
||||
};
|
||||
|
||||
const mockClient = {
|
||||
login: jest.fn(() => user1),
|
||||
setCSRFToken: jest.fn(),
|
||||
setBearerToken: jest.fn(),
|
||||
getClientConfigOld: jest.fn(() => ({})),
|
||||
getClientLicenseOld: jest.fn(() => ({})),
|
||||
getSessions: jest.fn(() => [session1]),
|
||||
sendPasswordResetEmail: jest.fn(() => ({status: 200})),
|
||||
getMe: jest.fn(() => user1),
|
||||
};
|
||||
|
||||
let mockGetPushProxyVerificationState: jest.Mock;
|
||||
jest.mock('@store/ephemeral_store', () => {
|
||||
const original = jest.requireActual('@store/ephemeral_store');
|
||||
mockGetPushProxyVerificationState = jest.fn(() => 'verified');
|
||||
return {
|
||||
...original,
|
||||
getPushProxyVerificationState: mockGetPushProxyVerificationState,
|
||||
};
|
||||
});
|
||||
|
||||
let mockFetch: jest.Mock;
|
||||
jest.mock('@react-native-community/netinfo', () => {
|
||||
const original = jest.requireActual('@react-native-community/netinfo');
|
||||
mockFetch = jest.fn(() => ({isInternetReachable: true}));
|
||||
return {
|
||||
...original,
|
||||
fetch: mockFetch,
|
||||
};
|
||||
});
|
||||
|
||||
let mockCancelLocalNotification: jest.Mock;
|
||||
jest.mock('react-native-notifications', () => {
|
||||
const original = jest.requireActual('react-native-notifications');
|
||||
mockCancelLocalNotification = jest.fn();
|
||||
return {
|
||||
...original,
|
||||
Notifications: {
|
||||
...original.Notifications,
|
||||
cancelLocalNotification: mockCancelLocalNotification,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let mockGetCSRFFromCookie: jest.Mock;
|
||||
jest.mock('@utils/security', () => {
|
||||
const original = jest.requireActual('@utils/security');
|
||||
mockGetCSRFFromCookie = jest.fn(() => 'csrfid');
|
||||
return {
|
||||
...original,
|
||||
getCSRFFromCookie: mockGetCSRFFromCookie,
|
||||
};
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
NetworkManager.getClient = () => mockClient;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
});
|
||||
|
||||
describe('sessions', () => {
|
||||
it('addPushProxyVerificationStateFromLogin - handle not found database', async () => {
|
||||
const result = await addPushProxyVerificationStateFromLogin('foo');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('addPushProxyVerificationStateFromLogin - base case', async () => {
|
||||
const result = await addPushProxyVerificationStateFromLogin(serverUrl);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('forceLogoutIfNecessary - handle not found database', async () => {
|
||||
const result = await forceLogoutIfNecessary('foo', {});
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeTruthy();
|
||||
expect(result.logout).toBe(false);
|
||||
});
|
||||
|
||||
it('forceLogoutIfNecessary - logout expected from 401', async () => {
|
||||
await operator.handleSystem({systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_USER_ID, value: user1.id}], prepareRecordsOnly: false});
|
||||
|
||||
const result = await forceLogoutIfNecessary(serverUrl, {status_code: 401, url: '/api/v4/users/me'});
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.logout).toBe(true);
|
||||
});
|
||||
|
||||
it('forceLogoutIfNecessary - logout not expected', async () => {
|
||||
await operator.handleSystem({systems: [{id: SYSTEM_IDENTIFIERS.CURRENT_USER_ID, value: user1.id}], prepareRecordsOnly: false});
|
||||
|
||||
const result = await forceLogoutIfNecessary(serverUrl, {status_code: 500, url: '/api/v4/users/me'});
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeNull();
|
||||
expect(result.logout).toBe(false);
|
||||
});
|
||||
|
||||
it('fetchSessions - handle error', async () => {
|
||||
mockClient.getSessions.mockImplementationOnce(jest.fn(throwFunc));
|
||||
const result = await fetchSessions('foo', '');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('fetchSessions - base case', async () => {
|
||||
const result = await fetchSessions(serverUrl, user1.id);
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.length).toBe(1);
|
||||
});
|
||||
|
||||
it('login - base case', async () => {
|
||||
const result = await login(serverUrl, {config: {DiagnosticId: 'diagnosticid'}} as LoginArgs);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.failed).toBe(false);
|
||||
});
|
||||
|
||||
it('login - handle throw on login request', async () => {
|
||||
mockClient.login.mockImplementationOnce(jest.fn(throwFunc));
|
||||
|
||||
const result = await login(serverUrl, {config: {DiagnosticId: 'diagnosticid'}} as LoginArgs);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.failed).toBe(true);
|
||||
});
|
||||
|
||||
it('cancelSessionNotification - handle not found database', async () => {
|
||||
const result = await cancelSessionNotification('foo');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('cancelSessionNotification - base case', async () => {
|
||||
await operator.handleSystem({
|
||||
systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.SESSION_EXPIRATION,
|
||||
value: {
|
||||
id: 'sessionid1',
|
||||
notificationId: 'notificationid',
|
||||
expiresAt: 123,
|
||||
},
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
const result = await cancelSessionNotification(serverUrl);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('scheduleSessionNotification - handle not found database', async () => {
|
||||
const result = await scheduleSessionNotification('foo');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('scheduleSessionNotification - base case', async () => {
|
||||
await operator.handleSystem({
|
||||
systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.SESSION_EXPIRATION,
|
||||
value: {
|
||||
id: 'sessionid1',
|
||||
notificationId: 'notificationid',
|
||||
expiresAt: 123,
|
||||
},
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
const result = await scheduleSessionNotification(serverUrl);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sendPasswordResetEmail - handle error', async () => {
|
||||
mockClient.sendPasswordResetEmail.mockImplementationOnce(jest.fn(throwFunc));
|
||||
const result = await sendPasswordResetEmail('foo', '');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('sendPasswordResetEmail - base case', async () => {
|
||||
const result = await sendPasswordResetEmail(serverUrl, user1.email);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.status).toBe(200);
|
||||
});
|
||||
|
||||
it('ssoLogin - handle error', async () => {
|
||||
mockClient.getMe.mockImplementationOnce(jest.fn(throwFunc));
|
||||
const result = await ssoLogin('foo', '', '', '', '');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.failed).toBe(true);
|
||||
});
|
||||
|
||||
it('ssoLogin - base case', async () => {
|
||||
const result = await ssoLogin(serverUrl, 'servername', 'diagnosticid', 'authtoken', 'csrftoken');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.failed).toBe(false);
|
||||
});
|
||||
|
||||
it('findSession - handle not found database', async () => {
|
||||
const result = await findSession('foo', []);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('findSession - by id', async () => {
|
||||
await operator.handleSystem({
|
||||
systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.SESSION_EXPIRATION,
|
||||
value: {
|
||||
id: 'sessionid1',
|
||||
notificationId: 'notificationid',
|
||||
expiresAt: 123,
|
||||
},
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
const session = await findSession(serverUrl, [session1]);
|
||||
expect(session).toBeDefined();
|
||||
});
|
||||
|
||||
it('findSession - by device', async () => {
|
||||
await DatabaseManager.appDatabase?.operator.handleGlobal({
|
||||
globals: [{id: GLOBAL_IDENTIFIERS.DEVICE_TOKEN, value: 'deviceid'}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
const session = await findSession(serverUrl, [session1]);
|
||||
expect(session).toBeDefined();
|
||||
});
|
||||
|
||||
it('findSession - by csrf', async () => {
|
||||
const session = await findSession(serverUrl, [session1]);
|
||||
expect(session).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -40,23 +40,27 @@ export const addPushProxyVerificationStateFromLogin = async (serverUrl: string)
|
|||
if (systems.length) {
|
||||
await operator.handleSystem({systems, prepareRecordsOnly: false});
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
logDebug('error setting the push proxy verification state on login', error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
export const forceLogoutIfNecessary = async (serverUrl: string, err: unknown) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
return {error: `${serverUrl} database not found`, logout: false};
|
||||
}
|
||||
|
||||
const currentUserId = await getCurrentUserId(database);
|
||||
|
||||
if (isErrorWithStatusCode(err) && err.status_code === HTTP_UNAUTHORIZED && isErrorWithUrl(err) && err.url?.indexOf('/login') === -1 && currentUserId) {
|
||||
await logout(serverUrl);
|
||||
return {error: null, logout: true};
|
||||
}
|
||||
|
||||
return {error: null};
|
||||
return {error: null, logout: false};
|
||||
};
|
||||
|
||||
export const fetchSessions = async (serverUrl: string, currentUserId: string) => {
|
||||
|
|
@ -163,8 +167,11 @@ export const cancelSessionNotification = async (serverUrl: string) => {
|
|||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
} catch (e) {
|
||||
logError('cancelSessionNotification', e);
|
||||
return {error: e};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -196,9 +203,11 @@ export const scheduleSessionNotification = async (serverUrl: string) => {
|
|||
});
|
||||
}
|
||||
}
|
||||
return {};
|
||||
} catch (e) {
|
||||
logError('scheduleExpiredNotification', e);
|
||||
await forceLogoutIfNecessary(serverUrl, e);
|
||||
return {error: e};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -258,7 +267,7 @@ export const ssoLogin = async (serverUrl: string, serverDisplayName: string, ser
|
|||
}
|
||||
};
|
||||
|
||||
async function findSession(serverUrl: string, sessions: Session[]) {
|
||||
export async function findSession(serverUrl: string, sessions: Session[]) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const expiredSession = await getExpiredSession(database);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class SessionManager {
|
|||
if (!this.scheduling) {
|
||||
this.scheduling = true;
|
||||
const serverCredentials = await getAllServerCredentials();
|
||||
const promises: Array<Promise<void>> = [];
|
||||
const promises: Array<Promise<{error: unknown} | {error?: undefined}>> = [];
|
||||
for (const {serverUrl} of serverCredentials) {
|
||||
promises.push(scheduleSessionNotification(serverUrl));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue