mattermost-mobile/app/actions/remote/command.test.ts
Joram Wilander 71f1d99041
Increase branch test coverage of actions/remote files (#8370)
* Increase branch test coverage for actions/remote/command

* Increase branch test coverage for actions/remote/session

* Increase branch test coverage for actions/remote/post
2024-11-25 12:02:34 -05:00

466 lines
19 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
import {createIntl} from 'react-intl';
import {doAppSubmit, postEphemeralCallResponseForCommandArgs} from '@actions/remote/apps';
import {AppCommandParser} from '@components/autocomplete/slash_suggestion/app_command_parser/app_command_parser';
import {AppCallResponseTypes} from '@constants/apps';
import DatabaseManager from '@database/manager';
import AppsManager from '@managers/apps_manager';
import IntegrationsManager from '@managers/integrations_manager';
import NetworkManager from '@managers/network_manager';
import {getConfig} from '@queries/servers/system';
import {showAppForm} from '@screens/navigation';
import {matchDeepLink, handleDeepLink} from '@utils/deep_link';
import {logDebug} from '@utils/log';
import {tryOpenURL} from '@utils/url';
import {
executeCommand,
executeAppCommand,
handleGotoLocation,
fetchCommands,
fetchSuggestions,
} from './command';
import type ServerDataOperator from '@database/operator/server_data_operator';
jest.mock('@actions/remote/apps');
jest.mock('@components/autocomplete/slash_suggestion/app_command_parser/app_command_parser');
jest.mock('@constants/apps');
jest.mock('@database/manager');
jest.mock('@managers/apps_manager');
jest.mock('@managers/integrations_manager');
jest.mock('@managers/network_manager');
jest.mock('@queries/servers/system');
jest.mock('@utils/log');
jest.mock('@utils/deep_link');
jest.mock('@utils/url');
jest.mock('@screens/navigation', () => {
const original = jest.requireActual('@screens/navigation');
return {
...original,
showAppForm: jest.fn(),
};
});
const serverUrl = 'baseHandler.test.com';
let operator: ServerDataOperator;
const intl = createIntl({
locale: 'en',
messages: {},
});
const channelId = 'channel_id';
const teamId = 'team_id';
const rootId = 'root_id';
const message = '/test command';
const args = {
channel_id: channelId,
team_id: teamId,
root_id: rootId,
parent_id: rootId,
};
const channel: Channel = {
id: channelId,
display_name: 'channelname',
team_id: teamId,
total_msg_count: 0,
group_constrained: true,
} as Channel;
const mockCommands = [{id: 'command1'}, {id: 'command2'}];
const mockSuggestions = ['suggestion1', 'suggestion2'];
const error = new Error('Test error');
const mockClient = {
executeCommand: jest.fn(() => ({trigger_id: 'trigger_id'})),
getCommandsList: jest.fn().mockResolvedValue(mockCommands),
getCommandAutocompleteSuggestionsList: jest.fn().mockResolvedValue(mockSuggestions),
};
beforeAll(() => {
// eslint-disable-next-line
// @ts-ignore
NetworkManager.getClient = () => mockClient;
});
beforeEach(async () => {
jest.clearAllMocks();
await DatabaseManager.init([serverUrl]);
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
});
afterEach(async () => {
await DatabaseManager.destroyServerDatabase(serverUrl);
});
describe('executeCommand', () => {
it('handle not found database', async () => {
const result = await executeCommand('invalid_url', intl, message, channelId, rootId);
expect(result).toEqual({error: 'invalid_url database not found'});
});
it('handle client error', async () => {
jest.spyOn(NetworkManager, 'getClient').mockImplementationOnce(() => {
throw error;
});
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(result).toEqual({error});
});
it('handle apps enabled', async () => {
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(true);
const parser = {
isAppCommand: jest.fn().mockReturnValue(true),
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.OK, text: 'Success'}});
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(AppsManager.isAppsEnabled).toHaveBeenCalledWith(serverUrl);
expect(parser.isAppCommand).toHaveBeenCalledWith(message);
expect(result).toEqual(await executeAppCommand(serverUrl, intl, parser as any, message, args));
});
it('handle apps enabled but not an app command', async () => {
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(true);
const parser = {
isAppCommand: jest.fn().mockReturnValue(false),
};
(AppCommandParser as jest.Mock).mockImplementation(() => parser);
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(AppsManager.isAppsEnabled).toHaveBeenCalledWith(serverUrl);
expect(parser.isAppCommand).toHaveBeenCalledWith(message);
expect(mockClient.executeCommand).toHaveBeenCalled();
expect(result).toEqual({data: {trigger_id: 'trigger_id'}});
});
it('handle apps disabled', async () => {
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(false);
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(AppsManager.isAppsEnabled).toHaveBeenCalledWith(serverUrl);
expect(mockClient.executeCommand).toHaveBeenCalled();
expect(result).toEqual({data: {trigger_id: 'trigger_id'}});
});
it('handle command execution with successful response', async () => {
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(false);
const mockSetTriggerId = jest.fn();
jest.spyOn(IntegrationsManager, 'getManager').mockReturnValue({
setTriggerId: mockSetTriggerId,
} as any);
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(mockClient.executeCommand).toHaveBeenCalledWith(message, args);
expect(mockSetTriggerId).toHaveBeenCalledWith('trigger_id');
expect(result).toEqual({data: {trigger_id: 'trigger_id'}});
});
it('handle /code command execution with successful response', async () => {
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(false);
const mockSetTriggerId = jest.fn();
jest.spyOn(IntegrationsManager, 'getManager').mockReturnValue({
setTriggerId: mockSetTriggerId,
} as any);
const result = await executeCommand(serverUrl, intl, '/code', channelId, rootId);
expect(mockClient.executeCommand).toHaveBeenCalledWith('/code ', args);
expect(mockSetTriggerId).toHaveBeenCalledWith('trigger_id');
expect(result).toEqual({data: {trigger_id: 'trigger_id'}});
});
it('handle command execution with no trigger id', async () => {
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(false);
mockClient.executeCommand.mockResolvedValueOnce({} as never);
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(mockClient.executeCommand).toHaveBeenCalledWith(message, args);
expect(result).toEqual({data: {}});
});
it('handle command execution with error response', async () => {
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
jest.spyOn(AppsManager, 'isAppsEnabled').mockResolvedValue(false);
mockClient.executeCommand.mockRejectedValue(error as never);
const result = await executeCommand(serverUrl, intl, message, channelId, rootId);
expect(mockClient.executeCommand).toHaveBeenCalledWith(message, args);
expect(logDebug).toHaveBeenCalledWith('error on executeCommand', error.message);
expect(result).toEqual({error});
});
});
describe('executeAppCommand', () => {
const msg = 'test message';
it('should handle a undefined creq', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({errorMessage: 'Error occurred'}),
};
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(result).toEqual({error: {message: 'Error occurred'}});
});
it('should handle a successful command execution with OK response', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.OK, text: 'Success'}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(postEphemeralCallResponseForCommandArgs).toHaveBeenCalledWith(serverUrl, {type: AppCallResponseTypes.OK, text: 'Success'}, 'Success', args);
expect(result).toEqual({data: {}});
});
it('should handle OK response with no text', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.OK}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(result).toEqual({data: {}});
});
it('should handle an error response', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({error: {text: 'Error occurred'}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(result).toEqual({error: {message: 'Error occurred'}});
});
it('should handle an error response with no text', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({error: {}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(result).toEqual({error: {message: 'Unknown error.'}});
});
it('should handle a form response', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {context: {}}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.FORM, form: {title: 'Form Title'}}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {context: {}}, intl);
expect(showAppForm).toHaveBeenCalledWith({title: 'Form Title'}, {});
expect(result).toEqual({data: {}});
});
it('should handle a form response with no form', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {context: {}}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.FORM}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {context: {}}, intl);
expect(result).toEqual({data: {}});
});
it('should handle a navigate response', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.NAVIGATE, navigate_to_url: 'https://navigate.com'}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(result).toEqual({data: {}});
});
it('should handle a navigate response with no url', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: AppCallResponseTypes.NAVIGATE}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(result).toEqual({data: {}});
});
it('should handle an unknown response type', async () => {
const parser = {
composeCommandSubmitCall: jest.fn().mockResolvedValue({creq: {}, errorMessage: null}),
};
(AppCommandParser as jest.Mock).mockReturnValue(parser);
(doAppSubmit as jest.Mock).mockResolvedValue({data: {type: 'UNKNOWN'}});
const result = await executeAppCommand(serverUrl, intl, parser as any, msg, args);
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
expect(result).toEqual({error: {message: 'App response type not supported. Response type: UNKNOWN.'}});
});
});
describe('handleGotoLocation', () => {
const location = 'https://example.com/some/path';
it('should return error if database not found', async () => {
(DatabaseManager.serverDatabases as any) = {};
const result = await handleGotoLocation(serverUrl, intl, location);
expect(result).toEqual({error: `${serverUrl} database not found`});
});
it('should handle deep link match found', async () => {
const mockOperator = {
database: {},
};
(DatabaseManager.serverDatabases as any) = {
[serverUrl]: {operator: mockOperator},
};
(getConfig as jest.Mock).mockResolvedValue({SiteURL: serverUrl});
(matchDeepLink as jest.Mock).mockReturnValue({url: location});
const result = await handleGotoLocation(serverUrl, intl, location);
expect(getConfig).toHaveBeenCalledWith(mockOperator.database);
expect(matchDeepLink).toHaveBeenCalledWith(location, serverUrl, serverUrl);
expect(handleDeepLink).toHaveBeenCalledWith(location, intl, location);
expect(result).toEqual({data: true});
});
it('should handle deep link match not found and URL opened successfully', async () => {
const mockOperator = {
database: {},
};
(DatabaseManager.serverDatabases as any) = {
[serverUrl]: {operator: mockOperator},
};
(getConfig as jest.Mock).mockResolvedValue({SiteURL: serverUrl});
(matchDeepLink as jest.Mock).mockReturnValue(null);
(tryOpenURL as jest.Mock).mockImplementation((url, onError) => onError());
const result = await handleGotoLocation(serverUrl, intl, location);
expect(getConfig).toHaveBeenCalledWith(mockOperator.database);
expect(matchDeepLink).toHaveBeenCalledWith(location, serverUrl, serverUrl);
expect(tryOpenURL).toHaveBeenCalledWith(location, expect.any(Function));
expect(result).toEqual({data: true});
});
it('should handle deep link match not found and URL open failed', async () => {
const mockOperator = {
database: {},
};
(DatabaseManager.serverDatabases as any) = {
[serverUrl]: {operator: mockOperator},
};
(getConfig as jest.Mock).mockResolvedValue({SiteURL: serverUrl});
(matchDeepLink as jest.Mock).mockReturnValue(null);
(tryOpenURL as jest.Mock).mockImplementation((url, onError) => onError());
const result = await handleGotoLocation(serverUrl, intl, location);
expect(getConfig).toHaveBeenCalledWith(mockOperator.database);
expect(matchDeepLink).toHaveBeenCalledWith(location, serverUrl, serverUrl);
expect(tryOpenURL).toHaveBeenCalledWith(location, expect.any(Function));
expect(result).toEqual({data: true});
});
});
describe('fetchCommands', () => {
it('should fetch commands successfully', async () => {
const result = await fetchCommands(serverUrl, teamId);
expect(mockClient.getCommandsList).toHaveBeenCalledWith(teamId);
expect(result).toEqual({commands: mockCommands});
});
it('should handle error during fetch commands', async () => {
mockClient.getCommandsList.mockRejectedValue(error);
const result = await fetchCommands(serverUrl, teamId);
expect(mockClient.getCommandsList).toHaveBeenCalledWith(teamId);
expect(logDebug).toHaveBeenCalledWith('error on fetchCommands', 'Test error');
expect(result).toEqual({error});
});
});
describe('fetchSuggestions', () => {
const term = 'test';
it('should fetch suggestions successfully', async () => {
const result = await fetchSuggestions(serverUrl, term, teamId, channelId, rootId);
expect(mockClient.getCommandAutocompleteSuggestionsList).toHaveBeenCalledWith(term, teamId, channelId, rootId);
expect(result).toEqual({suggestions: mockSuggestions});
});
it('should handle error during fetch suggestions', async () => {
mockClient.getCommandAutocompleteSuggestionsList.mockRejectedValue(error);
const result = await fetchSuggestions(serverUrl, term, teamId, channelId, rootId);
expect(mockClient.getCommandAutocompleteSuggestionsList).toHaveBeenCalledWith(term, teamId, channelId, rootId);
expect(logDebug).toHaveBeenCalledWith('error on fetchSuggestions', 'Test error');
expect(result).toEqual({error});
});
});