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'); }); }); });