mattermost-mobile/app/actions/remote/apps.test.ts

412 lines
14 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {AppCallResponseTypes} from '@constants/apps';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
import {
handleBindingClick,
doAppSubmit,
doAppFetchForm,
doAppLookup,
postEphemeralCallResponseForPost,
postEphemeralCallResponseForChannel,
postEphemeralCallResponseForContext,
postEphemeralCallResponseForCommandArgs,
} from './apps';
import type PostModel from '@typings/database/models/servers/post';
const serverUrl = 'baseHandler.test.com';
const mockIntl = {
formatMessage: jest.fn((config) => {
return config.defaultMessage;
}),
} as any;
const mockBinding: AppBinding = {
app_id: 'app1',
location: 'location1',
label: 'Test Binding',
form: {
submit: {
path: '/submit/path',
},
source: {
path: '/source/path',
},
},
submit: {
path: '/submit/path',
},
};
const mockContext: AppContext = {
app_id: 'app1',
channel_id: 'channel1',
team_id: 'team1',
post_id: 'post1',
root_id: 'root1',
};
const mockResponse: AppCallResponse = {
type: AppCallResponseTypes.OK,
text: 'Success',
app_metadata: {
bot_user_id: 'bot1',
bot_username: 'testbot',
},
};
const mockNavigateResponse: AppCallResponse = {
type: AppCallResponseTypes.NAVIGATE,
text: 'Navigating',
navigate_to_url: 'https://example.com',
};
const mockPost = {
id: 'post1',
channelId: 'channel1',
rootId: 'root1',
} as PostModel;
const mockCommandArgs = {
channel_id: 'channel1',
root_id: 'root1',
} as CommandArgs;
const mockFormResponse: AppCallResponse = {
type: AppCallResponseTypes.FORM,
form: {
title: 'Test Form',
submit: {
path: '/submit/path',
},
},
};
const mockErrorResponse: AppCallResponse = {
type: AppCallResponseTypes.ERROR,
text: 'Error occurred',
};
const mockClient = {
executeAppCall: jest.fn(),
} as any;
jest.mock('@actions/local/post', () => {
const mockSendEphemeralPost = jest.fn().mockReturnValue({});
return {
sendEphemeralPost: mockSendEphemeralPost,
};
});
const mockSendEphemeralPost = jest.requireMock('@actions/local/post').sendEphemeralPost;
const throwFunc = () => {
throw Error('error');
};
describe('apps', () => {
beforeEach(async () => {
NetworkManager.getClient = () => mockClient;
await DatabaseManager.init([serverUrl]);
jest.clearAllMocks();
});
afterEach(async () => {
await DatabaseManager.destroyServerDatabase(serverUrl);
});
describe('handleBindingClick', () => {
it('should handle form source binding', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockFormResponse);
const result = await handleBindingClick(serverUrl, mockBinding, mockContext, mockIntl);
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.FORM);
expect(mockClient.executeAppCall).toHaveBeenCalledTimes(1);
});
it('should handle form binding', async () => {
const bindingWithForm = {...mockBinding};
delete bindingWithForm.form?.source;
const result = await handleBindingClick(serverUrl, bindingWithForm, mockContext, mockIntl);
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.FORM);
expect(mockClient.executeAppCall).not.toHaveBeenCalled();
});
it('should handle submit binding', async () => {
const bindingWithSubmit = {...mockBinding};
delete bindingWithSubmit.form;
mockClient.executeAppCall.mockResolvedValueOnce(mockResponse);
const result = await handleBindingClick(serverUrl, bindingWithSubmit, mockContext, mockIntl);
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.OK);
expect(mockClient.executeAppCall).toHaveBeenCalledTimes(1);
});
it('should handle malformed binding', async () => {
const malformedBinding = {...mockBinding};
delete malformedBinding.form;
delete malformedBinding.submit;
const result = await handleBindingClick(serverUrl, malformedBinding, mockContext, mockIntl);
expect(result.error).toBeDefined();
expect(mockClient.executeAppCall).not.toHaveBeenCalled();
});
});
describe('doAppSubmit', () => {
const mockCall = {
path: '/submit/path',
context: mockContext,
};
it('should handle navigation response', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockNavigateResponse);
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.NAVIGATE);
expect(result.data?.navigate_to_url).toBe('https://example.com');
});
it('should handle invalid navigation response', async () => {
const invalidNavigateResponse = {
type: AppCallResponseTypes.NAVIGATE,
text: 'Invalid Navigation',
};
mockClient.executeAppCall.mockResolvedValueOnce(invalidNavigateResponse);
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.error).toBeDefined();
expect(result.error?.text).toContain('no url was included');
});
it('should handle unknown response type', async () => {
const unknownResponse = {
type: 'UNKNOWN_TYPE',
text: 'Unknown',
};
mockClient.executeAppCall.mockResolvedValueOnce(unknownResponse);
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.error).toBeDefined();
expect(result.error?.text).toContain('not supported');
});
it('should handle successful submit', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockResponse);
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.OK);
});
it('should handle error response', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockErrorResponse);
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.error).toBeDefined();
expect(result.error?.type).toBe(AppCallResponseTypes.ERROR);
});
it('should handle form response', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockFormResponse);
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.FORM);
});
it('should handle network error', async () => {
mockClient.executeAppCall.mockRejectedValueOnce(new Error('Network error'));
const result = await doAppSubmit(serverUrl, mockCall, mockIntl) as any;
expect(result.error).toBeDefined();
expect(result.error?.text).toBe('Network error');
});
});
describe('doAppLookup', () => {
const mockCall = {
path: '/lookup/path',
context: mockContext,
};
it('should handle successful lookup', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockResponse);
const result = await doAppLookup(serverUrl, mockCall, mockIntl);
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.OK);
});
it('should handle error response', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockErrorResponse);
const result = await doAppLookup(serverUrl, mockCall, mockIntl);
expect(result.error).toBeDefined();
expect(result.error?.type).toBe(AppCallResponseTypes.ERROR);
});
it('should handle network error', async () => {
mockClient.executeAppCall.mockRejectedValueOnce(new Error('Network error'));
const result = await doAppLookup(serverUrl, mockCall, mockIntl);
expect(result.error).toBeDefined();
expect(result.error?.text).toBe('Network error');
});
});
describe('doAppFetchForm', () => {
const mockCall = {
path: '/form/path',
context: mockContext,
};
it('should handle successful form fetch', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockFormResponse);
const result = await doAppFetchForm(serverUrl, mockCall, mockIntl);
expect(result.data).toBeDefined();
expect(result.data?.type).toBe(AppCallResponseTypes.FORM);
});
it('should handle error response', async () => {
mockClient.executeAppCall.mockResolvedValueOnce(mockErrorResponse);
const result = await doAppFetchForm(serverUrl, mockCall, mockIntl);
expect(result.error).toBeDefined();
expect(result.error?.type).toBe(AppCallResponseTypes.ERROR);
});
it('should handle invalid form response', async () => {
const invalidFormResponse = {
type: AppCallResponseTypes.FORM,
form: {},
};
mockClient.executeAppCall.mockResolvedValueOnce(invalidFormResponse);
const result = await doAppFetchForm(serverUrl, mockCall, mockIntl);
expect(result.error).toBeDefined();
});
it('should handle thrown errors', async () => {
jest.spyOn(NetworkManager, 'getClient').mockImplementationOnce(throwFunc);
mockClient.executeAppCall.mockResolvedValueOnce(mockFormResponse);
const result = await doAppFetchForm(serverUrl, mockCall, mockIntl);
expect(result.error).toBeDefined();
expect(result.error?.type).toBe(AppCallResponseTypes.ERROR);
});
});
describe('ephemeral posts', () => {
const message = 'Test message';
beforeEach(() => {
mockSendEphemeralPost.mockClear();
});
it('should post ephemeral response for post', () => {
const result = postEphemeralCallResponseForPost(serverUrl, mockResponse, message, mockPost);
expect(result).toBeDefined();
expect(mockSendEphemeralPost).toHaveBeenCalledWith(
serverUrl,
message,
mockPost.channelId,
mockPost.rootId || mockPost.id,
mockResponse.app_metadata?.bot_user_id,
);
});
it('should post ephemeral response for channel', () => {
const result = postEphemeralCallResponseForChannel(serverUrl, mockResponse, message, mockPost.channelId);
expect(result).toBeDefined();
expect(mockSendEphemeralPost).toHaveBeenCalledWith(
serverUrl,
message,
mockPost.channelId,
'',
mockResponse.app_metadata?.bot_user_id,
);
});
it('should post ephemeral response for context', () => {
const result = postEphemeralCallResponseForContext(serverUrl, mockResponse, message, mockContext);
expect(result).toBeDefined();
expect(mockSendEphemeralPost).toHaveBeenCalledWith(
serverUrl,
message,
mockContext.channel_id,
mockContext.root_id || mockContext.post_id,
mockResponse.app_metadata?.bot_user_id,
);
});
it('should post ephemeral response for command args', () => {
const result = postEphemeralCallResponseForCommandArgs(serverUrl, mockResponse, message, mockCommandArgs);
expect(result).toBeDefined();
expect(mockSendEphemeralPost).toHaveBeenCalledWith(
serverUrl,
message,
mockCommandArgs.channel_id,
mockCommandArgs.root_id,
mockResponse.app_metadata?.bot_user_id,
);
});
it('should handle undefined bot_user_id', () => {
const responseWithoutBot = {...mockResponse, app_metadata: undefined};
const result = postEphemeralCallResponseForPost(serverUrl, responseWithoutBot, message, mockPost);
expect(result).toBeDefined();
expect(mockSendEphemeralPost).toHaveBeenCalledWith(
serverUrl,
message,
mockPost.channelId,
mockPost.rootId || mockPost.id,
undefined,
);
});
});
describe('error handling', () => {
it('should handle null binding', async () => {
const emptyBinding = {
app_id: 'app1',
location: 'location1',
label: 'Test Binding',
} as AppBinding;
const result = await handleBindingClick(serverUrl, emptyBinding, mockContext, mockIntl);
expect(result).toBeDefined();
expect(result.error).toBeDefined();
expect(result.error?.text).toBe('This binding is not properly formed. Contact the App developer.');
});
});
});