mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-05 18:38:54 +00:00
* auto cache rolling cleanup for posts * ai threads rolling cleanup * auto rolling cleanup of playbook runs * fix to import and use renamed module * makes post cleanup atomic by moving processing to sql layer * restore previous file viewer post id and use full error logging * performs vacuum as last step and removes unused code * return early on empty channel array * address gallery items without a post id * prevent drafts to be deleted as part of auto cache cleanups * unset channel oldest visible on channel switch * clear oldest visible on empty array * revert early return to prevent flickering * restore previous playbookrun id on unmount * fixes issue creating data retention deletion batches * move active threads root cleanup protection to SQL execution * notify observers of range changes in post lists * in flight protection for auto cache cleanup runs * add space around operator Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * show warnings as snackbars related to ephemeral mode settings snackbar can now be persistent for some warnings requiring the user to acknowledge reading the message by manually dismissing it. * adds warnings when mobile ephemeral mode settings change snackbar component has been augmented to show user dismissable messages that require user interaction and to support longer messages with title and description. A header icon continuously show when ephemeral mode or zero persistence is enabled for a particular server. * fix i18n * renders snackbar properly when changing from persistent to ephemeral versions * reset visibility for tooltip when ephemeral mode config changes * show ephemeral mode warnings only for active server * renames added tests to start with should... * guards against a race casing to wipe an untracked server * enqueue ephemeral mode config changes so wipe operations don't race * auto cache cleanup warning shows only for active server * shows snackbar instead of tooltip on ephemeral mode icon tap * drops virtual set to true from jest mocks of real modules this was causing some flaky tests that failed depending on ordering while running in CI. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
192 lines
8.4 KiB
TypeScript
192 lines
8.4 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {wipeServerDatabaseWithRetry} from '@actions/local/ephemeral_mode/wipe';
|
|
import {terminateSession} from '@actions/local/session';
|
|
import {refetchCurrentUser} from '@actions/remote/user';
|
|
import {SNACK_BAR_TYPE} from '@constants/snack_bar';
|
|
import DatabaseManager from '@database/manager';
|
|
import {getServerCredentials} from '@init/credentials';
|
|
import EphemeralModeManager from '@managers/ephemeral_mode_manager';
|
|
import WebsocketManager from '@managers/websocket_manager';
|
|
import {getCurrentChannelId, getCurrentTeamId, getPushVerificationStatus, prepareCommonSystemValues} from '@queries/servers/system';
|
|
import {showSnackBar} from '@utils/snack_bar';
|
|
|
|
import {applyPersistenceModeChange} from './refresh';
|
|
|
|
jest.mock('@actions/local/ephemeral_mode/wipe', () => ({
|
|
wipeServerFiles: jest.fn(),
|
|
wipeServerDatabaseWithRetry: jest.fn(),
|
|
}));
|
|
jest.mock('@actions/local/session', () => ({
|
|
cancelSessionNotification: jest.fn(),
|
|
terminateSession: jest.fn().mockResolvedValue({}),
|
|
}));
|
|
jest.mock('@actions/remote/user', () => ({
|
|
refetchCurrentUser: jest.fn(),
|
|
}));
|
|
jest.mock('@init/credentials', () => ({
|
|
getServerCredentials: jest.fn(),
|
|
}));
|
|
jest.mock('@managers/ephemeral_mode_manager', () => ({
|
|
__esModule: true,
|
|
default: {removeServer: jest.fn(), addServer: jest.fn(), isZeroPersistenceMode: jest.fn()},
|
|
}));
|
|
jest.mock('@managers/websocket_manager', () => ({
|
|
__esModule: true,
|
|
default: {invalidateClient: jest.fn(), createClient: jest.fn(), initializeClient: jest.fn()},
|
|
}));
|
|
jest.mock('@queries/servers/system', () => ({
|
|
getCurrentChannelId: jest.fn(),
|
|
getCurrentTeamId: jest.fn(),
|
|
getPushVerificationStatus: jest.fn(),
|
|
prepareCommonSystemValues: jest.fn(),
|
|
}));
|
|
jest.mock('@utils/log');
|
|
jest.mock('@utils/snack_bar', () => ({
|
|
showSnackBar: jest.fn(),
|
|
}));
|
|
|
|
describe('applyPersistenceModeChange', () => {
|
|
const serverUrl = 'https://server.test';
|
|
let setActiveServerDatabaseSpy: jest.SpyInstance;
|
|
const mockOperator = {batchRecords: jest.fn().mockResolvedValue(undefined)};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
jest.mocked(wipeServerDatabaseWithRetry).mockResolvedValue({success: true});
|
|
setActiveServerDatabaseSpy = jest.spyOn(DatabaseManager, 'setActiveServerDatabase').mockResolvedValue(undefined);
|
|
jest.spyOn(DatabaseManager, 'getActiveServerUrl').mockResolvedValue(serverUrl);
|
|
jest.spyOn(DatabaseManager, 'getServerDatabaseAndOperator').mockReturnValue({
|
|
database: {} as never,
|
|
operator: mockOperator as never,
|
|
});
|
|
jest.mocked(refetchCurrentUser).mockResolvedValue({currentUserId: 'u1'});
|
|
jest.mocked(getPushVerificationStatus).mockResolvedValue('');
|
|
jest.mocked(getCurrentTeamId).mockResolvedValue('');
|
|
jest.mocked(getCurrentChannelId).mockResolvedValue('');
|
|
jest.mocked(prepareCommonSystemValues).mockResolvedValue([]);
|
|
jest.mocked(getServerCredentials).mockResolvedValue({
|
|
serverUrl,
|
|
userId: 'u1',
|
|
token: 'tok',
|
|
preauthSecret: 'preauth',
|
|
});
|
|
jest.mocked(EphemeralModeManager.isZeroPersistenceMode).mockReturnValue(false);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
it('should wipe the server DB and files, re-seed user state, and create a WS client', async () => {
|
|
const result = await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(WebsocketManager.invalidateClient).toHaveBeenCalledWith(serverUrl);
|
|
expect(EphemeralModeManager.removeServer).toHaveBeenCalledWith(serverUrl);
|
|
expect(wipeServerDatabaseWithRetry).toHaveBeenCalledWith(serverUrl);
|
|
expect(refetchCurrentUser).toHaveBeenCalledWith(serverUrl, undefined);
|
|
expect(WebsocketManager.createClient).toHaveBeenCalledWith(serverUrl, 'tok', 'preauth');
|
|
expect(WebsocketManager.initializeClient).toHaveBeenCalledWith(serverUrl);
|
|
expect(setActiveServerDatabaseSpy).toHaveBeenCalledWith(serverUrl);
|
|
expect(terminateSession).not.toHaveBeenCalled();
|
|
expect(EphemeralModeManager.addServer).toHaveBeenCalledWith(serverUrl, {cleanFileCache: false});
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('should skip WS client creation when there are no stored credentials', async () => {
|
|
jest.mocked(getServerCredentials).mockResolvedValue(null);
|
|
|
|
await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(WebsocketManager.createClient).not.toHaveBeenCalled();
|
|
expect(WebsocketManager.initializeClient).not.toHaveBeenCalled();
|
|
expect(terminateSession).not.toHaveBeenCalled();
|
|
expect(EphemeralModeManager.addServer).toHaveBeenCalledWith(serverUrl, {cleanFileCache: false});
|
|
});
|
|
|
|
it('should skip setActiveServerDatabase when the server is not active', async () => {
|
|
jest.spyOn(DatabaseManager, 'getActiveServerUrl').mockResolvedValue('https://other.test');
|
|
|
|
await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(setActiveServerDatabaseSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should terminate the session when the wipe throws', async () => {
|
|
const wipeError = new Error('disk full');
|
|
jest.mocked(wipeServerDatabaseWithRetry).mockRejectedValue(wipeError);
|
|
|
|
const result = await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(terminateSession).toHaveBeenCalledWith(serverUrl, false);
|
|
expect(EphemeralModeManager.addServer).not.toHaveBeenCalled();
|
|
expect(result).toEqual({error: wipeError});
|
|
});
|
|
|
|
it('should terminate the session when the wipe exhausts all retries', async () => {
|
|
jest.mocked(wipeServerDatabaseWithRetry).mockResolvedValue({success: false});
|
|
|
|
const result = await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(terminateSession).toHaveBeenCalledWith(serverUrl, false);
|
|
expect(EphemeralModeManager.addServer).not.toHaveBeenCalled();
|
|
expect(result.error).toBeTruthy();
|
|
});
|
|
|
|
it('should terminate the session when restoration fails after a successful wipe', async () => {
|
|
const fetchError = new Error('network error');
|
|
jest.mocked(refetchCurrentUser).mockRejectedValue(fetchError);
|
|
|
|
const result = await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(terminateSession).toHaveBeenCalledWith(serverUrl, false);
|
|
expect(EphemeralModeManager.addServer).not.toHaveBeenCalled();
|
|
expect(result).toEqual({error: fetchError});
|
|
});
|
|
|
|
it('should terminate the session when refetchCurrentUser resolves with an error', async () => {
|
|
jest.mocked(refetchCurrentUser).mockResolvedValue({error: 'No user fetched'});
|
|
|
|
const result = await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(terminateSession).toHaveBeenCalledWith(serverUrl, false);
|
|
expect(EphemeralModeManager.addServer).not.toHaveBeenCalled();
|
|
expect(result.error).toBeTruthy();
|
|
});
|
|
|
|
it('should show the zero persistence snackbar when the active server enters zero persistence mode', async () => {
|
|
jest.mocked(EphemeralModeManager.isZeroPersistenceMode).mockReturnValue(true);
|
|
|
|
await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(showSnackBar).toHaveBeenCalledWith({barType: SNACK_BAR_TYPE.EPHEMERAL_MODE_ZERO_PERSISTENCE_ACTIVE});
|
|
});
|
|
|
|
it('should not show the zero persistence snackbar when the server is not active', async () => {
|
|
jest.spyOn(DatabaseManager, 'getActiveServerUrl').mockResolvedValue('https://other.test');
|
|
jest.mocked(EphemeralModeManager.isZeroPersistenceMode).mockReturnValue(true);
|
|
|
|
await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(showSnackBar).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not show the zero persistence snackbar when the server did not enter zero persistence mode', async () => {
|
|
jest.mocked(EphemeralModeManager.isZeroPersistenceMode).mockReturnValue(false);
|
|
|
|
await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(showSnackBar).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not show the zero persistence snackbar when the wipe fails and the session is terminated', async () => {
|
|
jest.mocked(wipeServerDatabaseWithRetry).mockResolvedValue({success: false});
|
|
jest.mocked(EphemeralModeManager.isZeroPersistenceMode).mockReturnValue(true);
|
|
|
|
await applyPersistenceModeChange(serverUrl);
|
|
|
|
expect(showSnackBar).not.toHaveBeenCalled();
|
|
});
|
|
});
|