Skip to content

Commit

Permalink
test(Telegram Node): Add some tests for Telegram (no-changelog) (#11043)
Browse files Browse the repository at this point in the history
  • Loading branch information
Joffcom authored Dec 2, 2024
1 parent 0a8a57e commit 7ad4bad
Show file tree
Hide file tree
Showing 7 changed files with 2,156 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/nodes-base/nodes/Telegram/TelegramTrigger.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export class TelegramTrigger implements INodeType {
) as IDataObject;

// When the image is sent from the desktop app telegram does not resize the image
// So return the only image avaiable
// So return the only image available
// Basically the Image Size parameter would work just when the images comes from the mobile app
if (image === undefined) {
image = bodyData[key]!.photo![0];
Expand Down
335 changes: 335 additions & 0 deletions packages/nodes-base/nodes/Telegram/tests/GenericFunctions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
import {
NodeApiError,
type IDataObject,
type IExecuteFunctions,
type IHookFunctions,
type IHttpRequestMethods,
type ILoadOptionsFunctions,
type IWebhookFunctions,
} from 'n8n-workflow';

import {
addAdditionalFields,
apiRequest,
getPropertyName,
getSecretToken,
} from '../GenericFunctions';

describe('Telegram > GenericFunctions', () => {
describe('apiRequest', () => {
let mockThis: IHookFunctions & IExecuteFunctions & ILoadOptionsFunctions & IWebhookFunctions;
const credentials = { baseUrl: 'https://api.telegram.org', accessToken: 'testToken' };
beforeEach(() => {
mockThis = {
getCredentials: jest.fn(),
helpers: {
request: jest.fn(),
},
getNode: jest.fn(),
} as unknown as IHookFunctions &
IExecuteFunctions &
ILoadOptionsFunctions &
IWebhookFunctions;

jest.clearAllMocks();
});

it('should make a successful API request', async () => {
const method: IHttpRequestMethods = 'POST';
const endpoint = 'sendMessage';
const body: IDataObject = { text: 'Hello, world!' };
const query: IDataObject = { chat_id: '12345' };
const option: IDataObject = { headers: { 'Custom-Header': 'value' } };

(mockThis.getCredentials as jest.Mock).mockResolvedValue(credentials);
(mockThis.helpers.request as jest.Mock).mockResolvedValue({ success: true });

const result = await apiRequest.call(mockThis, method, endpoint, body, query, option);

expect(mockThis.getCredentials).toHaveBeenCalledWith('telegramApi');
expect(mockThis.helpers.request).toHaveBeenCalledWith({
headers: { 'Custom-Header': 'value' },
method: 'POST',
uri: 'https://api.telegram.org/bottestToken/sendMessage',
body: { text: 'Hello, world!' },
qs: { chat_id: '12345' },
json: true,
});
expect(result).toEqual({ success: true });
});

it('should handle an API request with no body and query', async () => {
const method: IHttpRequestMethods = 'GET';
const endpoint = 'getMe';
const body: IDataObject = {};
const query: IDataObject = {};

(mockThis.getCredentials as jest.Mock).mockResolvedValue(credentials);
(mockThis.helpers.request as jest.Mock).mockResolvedValue({ success: true });

const result = await apiRequest.call(mockThis, method, endpoint, body, query);

expect(mockThis.getCredentials).toHaveBeenCalledWith('telegramApi');
expect(mockThis.helpers.request).toHaveBeenCalledWith({
headers: {},
method: 'GET',
uri: 'https://api.telegram.org/bottestToken/getMe',
json: true,
});
expect(result).toEqual({ success: true });
});

it('should handle an API request with no additional options', async () => {
const method: IHttpRequestMethods = 'POST';
const endpoint = 'sendMessage';
const body: IDataObject = { text: 'Hello, world!' };

(mockThis.getCredentials as jest.Mock).mockResolvedValue(credentials);
(mockThis.helpers.request as jest.Mock).mockResolvedValue({ success: true });

const result = await apiRequest.call(mockThis, method, endpoint, body);

expect(mockThis.getCredentials).toHaveBeenCalledWith('telegramApi');
expect(mockThis.helpers.request).toHaveBeenCalledWith({
headers: {},
method: 'POST',
uri: 'https://api.telegram.org/bottestToken/sendMessage',
body: { text: 'Hello, world!' },
json: true,
});
expect(result).toEqual({ success: true });
});

it('should throw a NodeApiError on request failure', async () => {
const method: IHttpRequestMethods = 'POST';
const endpoint = 'sendMessage';
const body: IDataObject = { text: 'Hello, world!' };

(mockThis.getCredentials as jest.Mock).mockResolvedValue(credentials);
(mockThis.helpers.request as jest.Mock).mockRejectedValue(new Error('Request failed'));

await expect(apiRequest.call(mockThis, method, endpoint, body)).rejects.toThrow(NodeApiError);

expect(mockThis.getCredentials).toHaveBeenCalledWith('telegramApi');
expect(mockThis.helpers.request).toHaveBeenCalledWith({
headers: {},
method: 'POST',
uri: 'https://api.telegram.org/bottestToken/sendMessage',
body: { text: 'Hello, world!' },
json: true,
});
});
});
describe('addAdditionalFields', () => {
let mockThis: IExecuteFunctions;

beforeEach(() => {
mockThis = {
getNodeParameter: jest.fn(),
} as unknown as IExecuteFunctions;

jest.clearAllMocks();
});

it('should add additional fields and attribution for sendMessage operation', () => {
const body: IDataObject = { text: 'Hello, world!' };
const index = 0;
const nodeVersion = 1.1;
const instanceId = '45';

(mockThis.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
switch (paramName) {
case 'operation':
return 'sendMessage';
case 'additionalFields':
return { appendAttribution: true };
case 'replyMarkup':
return 'none';
default:
return '';
}
});

addAdditionalFields.call(mockThis, body, index, nodeVersion, instanceId);

expect(body).toEqual({
text: 'Hello, world!\n\n_This message was sent automatically with _[n8n](https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.telegram_45)',
parse_mode: 'Markdown',
disable_web_page_preview: true,
});
});

it('should add reply markup for inlineKeyboard', () => {
const body: IDataObject = { text: 'Hello, world!' };
const index = 0;

(mockThis.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
switch (paramName) {
case 'operation':
return 'sendMessage';
case 'additionalFields':
return {};
case 'replyMarkup':
return 'inlineKeyboard';
case 'inlineKeyboard':
return {
rows: [
{
row: {
buttons: [
{ text: 'Button 1', additionalFields: { url: 'https://example.com' } },
{ text: 'Button 2' },
],
},
},
],
};
default:
return '';
}
});

addAdditionalFields.call(mockThis, body, index);

expect(body).toEqual({
text: 'Hello, world!',
disable_web_page_preview: true,
parse_mode: 'Markdown',
reply_markup: {
inline_keyboard: [
[{ text: 'Button 1', url: 'https://example.com' }, { text: 'Button 2' }],
],
},
});
});

it('should add reply markup for forceReply', () => {
const body: IDataObject = { text: 'Hello, world!' };
const index = 0;

(mockThis.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
switch (paramName) {
case 'operation':
return 'sendMessage';
case 'additionalFields':
return {};
case 'replyMarkup':
return 'forceReply';
case 'forceReply':
return { force_reply: true };
default:
return '';
}
});

addAdditionalFields.call(mockThis, body, index);

expect(body).toEqual({
text: 'Hello, world!',
disable_web_page_preview: true,
parse_mode: 'Markdown',
reply_markup: { force_reply: true },
});
});

it('should add reply markup for replyKeyboardRemove', () => {
const body: IDataObject = { text: 'Hello, world!' };
const index = 0;

(mockThis.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
switch (paramName) {
case 'operation':
return 'sendMessage';
case 'additionalFields':
return {};
case 'replyMarkup':
return 'replyKeyboardRemove';
case 'replyKeyboardRemove':
return { remove_keyboard: true };
default:
return '';
}
});

addAdditionalFields.call(mockThis, body, index);

expect(body).toEqual({
text: 'Hello, world!',
disable_web_page_preview: true,
parse_mode: 'Markdown',
reply_markup: { remove_keyboard: true },
});
});

it('should handle nodeVersion 1.2 and set disable_web_page_preview', () => {
const body: IDataObject = { text: 'Hello, world!' };
const index = 0;
const nodeVersion = 1.2;

(mockThis.getNodeParameter as jest.Mock).mockImplementation((paramName: string) => {
switch (paramName) {
case 'operation':
return 'sendMessage';
case 'additionalFields':
return {};
case 'replyMarkup':
return 'none';
default:
return '';
}
});

addAdditionalFields.call(mockThis, body, index, nodeVersion);

expect(body).toEqual({
disable_web_page_preview: true,
parse_mode: 'Markdown',
text: 'Hello, world!\n\n_This message was sent automatically with _[n8n](https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.telegram)',
});
});
});
describe('getPropertyName', () => {
it('should return the property name by removing "send" and converting to lowercase', () => {
expect(getPropertyName('sendMessage')).toBe('message');
expect(getPropertyName('sendEmail')).toBe('email');
expect(getPropertyName('sendNotification')).toBe('notification');
});

it('should return the original string in lowercase if it does not contain "send"', () => {
expect(getPropertyName('receiveMessage')).toBe('receivemessage');
expect(getPropertyName('fetchData')).toBe('fetchdata');
});

it('should return an empty string if the input is "send"', () => {
expect(getPropertyName('send')).toBe('');
});

it('should handle empty strings', () => {
expect(getPropertyName('')).toBe('');
});
});
describe('getSecretToken', () => {
const mockThis = {
getWorkflow: jest.fn().mockReturnValue({ id: 'workflow123' }),
getNode: jest.fn().mockReturnValue({ id: 'node123' }),
} as unknown as IHookFunctions & IWebhookFunctions;

beforeEach(() => {
jest.clearAllMocks();
});

it('should return a valid secret token', () => {
const secretToken = getSecretToken.call(mockThis);

expect(secretToken).toBe('workflow123_node123');
});

it('should remove invalid characters from the secret token', () => {
mockThis.getNode().id = 'node@123';
mockThis.getWorkflow().id = 'workflow#123';

const secretToken = getSecretToken.call(mockThis);
expect(secretToken).toBe('workflow123_node123');
});
});
});
37 changes: 37 additions & 0 deletions packages/nodes-base/nodes/Telegram/tests/Helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { get } from 'lodash';
import type { IDataObject, IExecuteFunctions, IGetNodeParameterOptions, INode } from 'n8n-workflow';

export const telegramNode: INode = {
id: 'b3039263-29ad-4476-9894-51dfcc5a706d',
name: 'Telegram node',
typeVersion: 1.2,
type: 'n8n-nodes-base.telegram',
position: [0, 0],
parameters: {
resource: 'callback',
operation: 'answerQuery',
},
};

export const createMockExecuteFunction = (nodeParameters: IDataObject) => {
const fakeExecuteFunction = {
getInputData() {
return [{ json: {} }];
},
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject | undefined,
options?: IGetNodeParameterOptions | undefined,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return telegramNode;
},
helpers: {},
continueOnFail: () => false,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
Loading

0 comments on commit 7ad4bad

Please sign in to comment.