diff --git a/app/sagas/__tests__/login.forcedLogout.test.ts b/app/sagas/__tests__/login.forcedLogout.test.ts new file mode 100644 index 0000000000..a6fd412b29 --- /dev/null +++ b/app/sagas/__tests__/login.forcedLogout.test.ts @@ -0,0 +1,142 @@ +jest.mock('../../lib/methods/getPermissions', () => ({ + getPermissions: jest.fn() +})); + +jest.mock('../../lib/methods/enterpriseModules', () => ({ + getEnterpriseModules: jest.fn(), + isOmnichannelModuleAvailable: jest.fn(() => false), + isOmnichannelStatusAvailable: jest.fn(() => false), + isVoipModuleAvailable: jest.fn(() => false) +})); + +jest.mock('../../lib/methods/getCustomEmojis', () => ({ + getCustomEmojis: jest.fn() +})); + +jest.mock('../../lib/methods/getRoles', () => ({ + getRoles: jest.fn() +})); + +jest.mock('../../lib/methods/getSlashCommands', () => ({ + getSlashCommands: jest.fn() +})); + +jest.mock('../../lib/methods/getSettings', () => ({ + subscribeSettings: jest.fn() +})); + +jest.mock('../../lib/methods/getUsersPresence', () => ({ + getUserPresence: jest.fn(), + refreshDmUsersPresence: jest.fn(), + subscribeUsersPresence: jest.fn() +})); + +jest.mock('../../lib/services/restApi', () => ({ + getUsersRoles: jest.fn(() => []), + registerPushToken: jest.fn(), + saveUserProfile: jest.fn(), + setUserPresenceAway: jest.fn() +})); + +jest.mock('../../lib/services/connect', () => ({ + disconnect: jest.fn(), + login: jest.fn(), + loginWithPassword: jest.fn() +})); + +jest.mock('../../lib/methods/logout', () => ({ + logout: jest.fn(), + removeServerData: jest.fn(), + removeServerDatabase: jest.fn() +})); + +jest.mock('../../lib/services/voip/MediaSessionInstance', () => ({ + mediaSessionInstance: { init: jest.fn(), reset: jest.fn() } +})); + +jest.mock('../../lib/services/voip/MediaSessionStore', () => ({ + mediaSessionStore: { getCurrentInstance: jest.fn(() => null) } +})); + +jest.mock('../../lib/services/voip/isInActiveVoipCall', () => ({ + isInActiveVoipCall: jest.fn(() => false) +})); + +jest.mock('../../lib/methods/helpers/localAuthentication', () => ({ + localAuthenticate: jest.fn() +})); + +jest.mock('../../lib/methods/helpers/info', () => ({ + showErrorAlert: jest.fn() +})); + +jest.mock('../../lib/services/sdk', () => ({ + __esModule: true, + default: { + current: { client: { host: '' } }, + subscribe: jest.fn() + } +})); + +jest.mock('../../lib/methods/helpers/log', () => ({ + ...jest.requireActual('../../lib/methods/helpers/log'), + __esModule: true, + default: jest.fn() +})); + +const mockServersQuery = { query: jest.fn(() => ({ fetch: jest.fn() })) }; + +jest.mock('../../lib/database', () => ({ + __esModule: true, + default: { + active: { get: jest.fn() }, + servers: { + get: jest.fn(() => mockServersQuery), + write: jest.fn(async (block: () => Promise) => block()) + } + } +})); + +import loginRoot from '../login'; +import { logout } from '../../actions/login'; +import { selectServerSuccess } from '../../actions/server'; +import UserPreferences from '../../lib/methods/userPreferences'; +import { TOKEN_KEY } from '../../lib/constants/keys'; +import { cancelSagaTasks, createRecordingStore, flushSagaMicrotasks } from '../../lib/testUtils/sagaStore'; + +afterEach(cancelSagaTasks); + +const LOGGED_OUT_SERVER = 'https://logged-out.rocket.chat'; +const OTHER_SERVER = 'https://other.rocket.chat'; + +const setRemainingServers = (servers: { id: string }[]): void => { + mockServersQuery.query.mockReturnValue({ fetch: jest.fn(() => Promise.resolve(servers)) }); +}; + +const runForcedLogout = async (): Promise => { + const { store } = createRecordingStore(loginRoot); + store.dispatch(selectServerSuccess({ server: LOGGED_OUT_SERVER, version: '7.0.0', name: 'Logged out' })); + store.dispatch(logout(true, 'Logged_out_by_server')); + await flushSagaMicrotasks(); + return store.getState().server.previousServer; +}; + +describe('login saga — a logout forced by the server', () => { + beforeEach(() => { + UserPreferences.removeItem(`${TOKEN_KEY}-${OTHER_SERVER}`); + jest.clearAllMocks(); + }); + + it('points previousServer at another logged in workspace, so the user can leave NewServerView', async () => { + setRemainingServers([{ id: OTHER_SERVER }]); + UserPreferences.setString(`${TOKEN_KEY}-${OTHER_SERVER}`, 'user-id'); + + expect(await runForcedLogout()).toBe(OTHER_SERVER); + }); + + it('leaves previousServer unset when no other workspace is logged in', async () => { + setRemainingServers([{ id: OTHER_SERVER }]); + + expect(await runForcedLogout()).toBeNull(); + }); +}); diff --git a/app/sagas/login.js b/app/sagas/login.js index 4c02e2e697..1b032e7b73 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -5,7 +5,7 @@ import { Q } from '@nozbe/watermelondb'; import dayjs from '../lib/dayjs'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions/app'; -import { selectServerRequest, serverFinishAdd } from '../actions/server'; +import { selectServerRequest, serverFinishAdd, serverInitAdd } from '../actions/server'; import { loginFailure, loginSuccess, logout as logoutAction, setUser } from '../actions/login'; import { roomsRequest } from '../actions/rooms'; import log, { events, logEvent } from '../lib/methods/helpers/log'; @@ -369,6 +369,12 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { } }; +const findLoggedInServer = function* findLoggedInServer() { + const serversCollection = database.servers.get('servers'); + const servers = yield serversCollection.query().fetch(); + return servers.find(({ id }) => UserPreferences.getString(`${TOKEN_KEY}-${id}`)); +}; + const handleLogout = function* handleLogout({ forcedByServer, message }) { yield put(encryptionStop()); yield put(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Logging_out') })); @@ -377,8 +383,13 @@ const handleLogout = function* handleLogout({ forcedByServer, message }) { try { yield call(logoutCall, { server }); + const loggedInServer = yield call(findLoggedInServer); + // if the user was logged out by the server if (forcedByServer) { + if (loggedInServer) { + yield put(serverInitAdd(loggedInServer.id)); + } yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); if (message) { showErrorAlert(I18n.t(message), I18n.t('Oops')); @@ -386,23 +397,10 @@ const handleLogout = function* handleLogout({ forcedByServer, message }) { yield delay(300); EventEmitter.emit('NewServer', { server }); } else { - const serversDB = database.servers; - // all servers - const serversCollection = serversDB.get('servers'); - const servers = yield serversCollection.query().fetch(); - - // see if there're other logged in servers and selects first one - if (servers.length > 0) { - for (let i = 0; i < servers.length; i += 1) { - const { id: newServer, version } = servers[i]; - const token = UserPreferences.getString(`${TOKEN_KEY}-${newServer}`); - if (token) { - yield put(selectServerRequest(newServer, version)); - return; - } - } + if (loggedInServer) { + yield put(selectServerRequest(loggedInServer.id, loggedInServer.version)); + return; } - // if there's no servers, go outside yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); } } catch (e) { @@ -453,23 +451,11 @@ const handleDeleteAccount = function* handleDeleteAccount() { try { yield call(removeServerData, { server }); yield call(removeServerDatabase, { server }); - const serversDB = database.servers; - // all servers - const serversCollection = serversDB.get('servers'); - const servers = yield serversCollection.query().fetch(); - - // see if there're other logged in servers and selects first one - if (servers.length > 0) { - for (let i = 0; i < servers.length; i += 1) { - const { id: newServer, version } = servers[i]; - const token = UserPreferences.getString(`${TOKEN_KEY}-${newServer}`); - if (token) { - yield put(selectServerRequest(newServer, version)); - return; - } - } + const loggedInServer = yield call(findLoggedInServer); + if (loggedInServer) { + yield put(selectServerRequest(loggedInServer.id, loggedInServer.version)); + return; } - // if there's no servers, go outside disconnect(); yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); } catch (e) {