From 38a6140e794c5ba7b42d2b0f434a5e4c9b77eb54 Mon Sep 17 00:00:00 2001 From: yehorkardash Date: Tue, 9 Sep 2025 07:29:36 +0000 Subject: [PATCH] fix(Telegram Node): Use parameter index instead of 0 for binaryData (#19236) --- .../nodes/Telegram/Telegram.node.ts | 7 +- .../Telegram/tests/Telegram.node.test.ts | 333 +++++++++++++++++- 2 files changed, 335 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/nodes/Telegram/Telegram.node.ts b/packages/nodes-base/nodes/Telegram/Telegram.node.ts index 99d97f76a2..52e0abcac2 100644 --- a/packages/nodes-base/nodes/Telegram/Telegram.node.ts +++ b/packages/nodes-base/nodes/Telegram/Telegram.node.ts @@ -2146,10 +2146,10 @@ export class Telegram implements INodeType { let responseData; if (binaryData) { - const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0); + const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i); const itemBinaryData = items[i].binary![binaryPropertyName]; const propertyName = getPropertyName(operation); - const fileName = this.getNodeParameter('additionalFields.fileName', 0, '') as string; + const fileName = this.getNodeParameter('additionalFields.fileName', i, '') as string; const filename = fileName || itemBinaryData.fileName?.toString(); @@ -2214,7 +2214,7 @@ export class Telegram implements INodeType { ); const fileName = filePath.split('/').pop() as string; - const additionalFields = this.getNodeParameter('additionalFields', 0); + const additionalFields = this.getNodeParameter('additionalFields', i); const providedMimeType = additionalFields?.mimeType as string | undefined; const mimeType = providedMimeType ?? (lookup(fileName) || 'application/octet-stream'); @@ -2239,7 +2239,6 @@ export class Telegram implements INodeType { returnData.push(...executionData); continue; } - const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), { itemData: { item: i } }, diff --git a/packages/nodes-base/nodes/Telegram/tests/Telegram.node.test.ts b/packages/nodes-base/nodes/Telegram/tests/Telegram.node.test.ts index 137b649b41..564bd6f732 100644 --- a/packages/nodes-base/nodes/Telegram/tests/Telegram.node.test.ts +++ b/packages/nodes-base/nodes/Telegram/tests/Telegram.node.test.ts @@ -1,5 +1,10 @@ import { mockDeep } from 'jest-mock-extended'; -import type { IExecuteFunctions, INode } from 'n8n-workflow'; +import type { + IExecuteFunctions, + INode, + INodeExecutionData, + NodeExecutionWithMetadata, +} from 'n8n-workflow'; import * as GenericFunctions from '../GenericFunctions'; import { Telegram } from '../Telegram.node'; @@ -19,6 +24,12 @@ describe('Telegram node', () => { typeVersion: 1.2, } as INode); executeFunctionsMock.getInputData.mockReturnValue([{ json: {} }]); + executeFunctionsMock.helpers.returnJsonArray.mockImplementation( + (input) => input as INodeExecutionData[], + ); + executeFunctionsMock.helpers.constructExecutionMetaData.mockImplementation( + (input) => input as NodeExecutionWithMetadata[], + ); }); describe('file:get', () => { @@ -184,4 +195,324 @@ describe('Telegram node', () => { ); }); }); + + describe('message:sendPhoto with binary data', () => { + beforeEach(() => { + executeFunctionsMock.getNodeParameter.mockImplementation((paramName, index) => { + switch (paramName) { + case 'resource': + return 'message'; + case 'operation': + return 'sendPhoto'; + case 'binaryData': + return true; + case 'chatId': + return index === 0 ? 'chat-id-0' : index === 1 ? 'chat-id-1' : 'chat-id-2'; + case 'binaryPropertyName': + return index === 0 ? 'data0' : index === 1 ? 'data1' : 'data2'; + case 'additionalFields.fileName': + return index === 0 ? 'photo0.jpg' : index === 1 ? 'photo1.png' : 'photo2.gif'; + case 'additionalFields': + return {}; + default: + return undefined; + } + }); + }); + + it('should use correct index for binaryPropertyName parameter', async () => { + executeFunctionsMock.getInputData.mockReturnValue([ + { + json: {}, + binary: { + data0: { + data: 'binary-data-0', + mimeType: 'image/jpeg', + fileName: 'original0.jpg', + }, + }, + }, + { + json: {}, + binary: { + data1: { + data: 'binary-data-1', + mimeType: 'image/png', + fileName: 'original1.png', + }, + }, + }, + ]); + + apiRequestSpy.mockResolvedValue([{ result: { message_id: 123 } }]); + + await node.execute.call(executeFunctionsMock); + + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith('binaryPropertyName', 0); + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith('binaryPropertyName', 1); + expect(executeFunctionsMock.getNodeParameter).not.toHaveBeenCalledWith( + 'binaryPropertyName', + 0, + expect.anything(), + ); + expect(executeFunctionsMock.getNodeParameter).not.toHaveBeenCalledWith( + 'binaryPropertyName', + 1, + expect.anything(), + ); + }); + + it('should use correct index for additionalFields.fileName parameter', async () => { + executeFunctionsMock.getInputData.mockReturnValue([ + { + json: {}, + binary: { + data0: { + data: 'binary-data-0', + mimeType: 'image/jpeg', + }, + }, + }, + { + json: {}, + binary: { + data1: { + data: 'binary-data-1', + mimeType: 'image/png', + }, + }, + }, + ]); + + apiRequestSpy.mockResolvedValue([{ result: { message_id: 123 } }]); + + await node.execute.call(executeFunctionsMock); + + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith( + 'additionalFields.fileName', + 0, + '', + ); + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith( + 'additionalFields.fileName', + 1, + '', + ); + }); + + it('should use correct binary data for each item based on binaryPropertyName index', async () => { + executeFunctionsMock.getInputData.mockReturnValue([ + { + json: {}, + binary: { + data0: { + data: 'binary-data-0', + mimeType: 'image/jpeg', + fileName: 'original0.jpg', + }, + wrongData: { + data: 'wrong-binary-data', + mimeType: 'image/gif', + }, + }, + }, + { + json: {}, + binary: { + data1: { + data: 'binary-data-1', + mimeType: 'image/png', + fileName: 'original1.png', + }, + wrongData: { + data: 'wrong-binary-data', + mimeType: 'image/gif', + }, + }, + }, + ]); + + apiRequestSpy.mockResolvedValue([{ result: { message_id: 123 } }]); + + await node.execute.call(executeFunctionsMock); + + expect(apiRequestSpy).toHaveBeenCalledTimes(2); + + expect(apiRequestSpy).toHaveBeenNthCalledWith( + 1, + 'POST', + 'sendPhoto', + {}, + {}, + expect.objectContaining({ + formData: expect.objectContaining({ + chat_id: 'chat-id-0', + photo: expect.objectContaining({ + value: expect.any(Buffer), + options: expect.objectContaining({ + filename: 'photo0.jpg', + contentType: 'image/jpeg', + }), + }), + }), + }), + ); + + expect(apiRequestSpy).toHaveBeenNthCalledWith( + 2, + 'POST', + 'sendPhoto', + {}, + {}, + expect.objectContaining({ + formData: expect.objectContaining({ + chat_id: 'chat-id-1', + photo: expect.objectContaining({ + value: expect.any(Buffer), + options: expect.objectContaining({ + filename: 'photo1.png', + contentType: 'image/png', + }), + }), + }), + }), + ); + }); + + it('should fallback to binary fileName when additionalFields.fileName is empty', async () => { + executeFunctionsMock.getNodeParameter.mockImplementation((paramName, index) => { + switch (paramName) { + case 'resource': + return 'message'; + case 'operation': + return 'sendPhoto'; + case 'binaryData': + return true; + case 'chatId': + return index === 0 ? 'chat-id-0' : 'chat-id-1'; + case 'binaryPropertyName': + return index === 0 ? 'data0' : 'data1'; + case 'additionalFields.fileName': + return index === 0 ? '' : 'custom-name.jpg'; + case 'additionalFields': + return {}; + default: + return undefined; + } + }); + + executeFunctionsMock.getInputData.mockReturnValue([ + { + json: {}, + binary: { + data0: { + data: 'binary-data-0', + mimeType: 'image/jpeg', + fileName: 'fallback-name.jpg', + }, + }, + }, + { + json: {}, + binary: { + data1: { + data: 'binary-data-1', + mimeType: 'image/png', + fileName: 'original-name.png', + }, + }, + }, + ]); + + apiRequestSpy.mockResolvedValue([{ result: { message_id: 123 } }]); + + await node.execute.call(executeFunctionsMock); + + const expectFileName = (index: number, filename: string) => { + expect(apiRequestSpy).toHaveBeenNthCalledWith( + index, + 'POST', + 'sendPhoto', + {}, + {}, + { + formData: expect.objectContaining({ + photo: expect.objectContaining({ + options: expect.objectContaining({ + filename, + }), + }), + }), + }, + ); + }; + + expectFileName(1, 'fallback-name.jpg'); + expectFileName(2, 'custom-name.jpg'); + }); + + it('should process different chat IDs for multiple items correctly', async () => { + executeFunctionsMock.getInputData.mockReturnValue([ + { + json: {}, + binary: { + data0: { + data: 'binary-data-0', + mimeType: 'image/jpeg', + fileName: 'photo0.jpg', + }, + }, + }, + { + json: {}, + binary: { + data1: { + data: 'binary-data-1', + mimeType: 'image/png', + fileName: 'photo1.png', + }, + }, + }, + { + json: {}, + binary: { + data2: { + data: 'binary-data-2', + mimeType: 'image/gif', + fileName: 'photo2.gif', + }, + }, + }, + ]); + + apiRequestSpy.mockResolvedValue([{ result: { message_id: 123 } }]); + + await node.execute.call(executeFunctionsMock); + + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith('chatId', 0); + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith('chatId', 1); + expect(executeFunctionsMock.getNodeParameter).toHaveBeenCalledWith('chatId', 2); + + expect(apiRequestSpy).toHaveBeenCalledTimes(3); + + const expectChatId = (n: number, chatId: string) => { + expect(apiRequestSpy).toHaveBeenNthCalledWith( + n, + 'POST', + 'sendPhoto', + {}, + {}, + { + formData: expect.objectContaining({ + chat_id: chatId, + }), + }, + ); + }; + + expectChatId(1, 'chat-id-0'); + expectChatId(2, 'chat-id-1'); + expectChatId(3, 'chat-id-2'); + }); + }); });