From 6ae4999ef99310d39c43cde611966875787b331b Mon Sep 17 00:00:00 2001 From: krisn0x <10799186+krisn0x@users.noreply.github.com> Date: Fri, 19 Dec 2025 19:05:25 +0200 Subject: [PATCH] fix(Microsoft OneDrive Node): Don't overwrite filename from node parameters when uploading binary file (#22949) --- .../OneDrive/MicrosoftOneDrive.node.ts | 21 ++- .../OneDrive/test/node/file.download.test.ts | 11 ++ .../OneDrive/test/node/file.upload.test.ts | 165 ++++++++++++++++++ 3 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.upload.test.ts diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts index 4774325d9fb..ed35f50cf6e 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.ts @@ -19,7 +19,7 @@ export class MicrosoftOneDrive implements INodeType { name: 'microsoftOneDrive', icon: 'file:oneDrive.svg', group: ['input'], - version: 1, + version: [1, 1.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Microsoft OneDrive API', defaults: { @@ -63,6 +63,7 @@ export class MicrosoftOneDrive implements INodeType { const items = this.getInputData(); const returnData: INodeExecutionData[] = []; const length = items.length; + const nodeVersion = this.getNode().typeVersion; let responseData; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); @@ -217,12 +218,20 @@ export class MicrosoftOneDrive implements INodeType { const body = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); let encodedFilename; - if (fileName !== '') { - encodedFilename = encodeURIComponent(fileName); - } + if (nodeVersion >= 1.1) { + if (fileName !== '') { + encodedFilename = encodeURIComponent(fileName); + } else if (binaryData.fileName !== undefined) { + encodedFilename = encodeURIComponent(binaryData.fileName); + } + } else { + if (fileName !== '') { + encodedFilename = encodeURIComponent(fileName); + } - if (binaryData.fileName !== undefined) { - encodedFilename = encodeURIComponent(binaryData.fileName); + if (binaryData.fileName !== undefined) { + encodedFilename = encodeURIComponent(binaryData.fileName); + } } responseData = await microsoftApiRequest.call( diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.download.test.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.download.test.ts index 6d9e573efaf..42f1c2f439d 100644 --- a/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.download.test.ts +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.download.test.ts @@ -30,6 +30,15 @@ describe('Test MicrosoftOneDrive, file > download', () => { const httpRequest = jest.fn(async () => ({ body: mock() })); const prepareBinaryData = jest.fn(async () => ({ data: 'testBinary' })); + const mockNode = { + id: 'test-node-id', + name: 'Microsoft OneDrive Test', + type: 'n8n-nodes-base.microsoftOneDrive', + typeVersion: 1.1, + position: [0, 0] as [number, number], + parameters: {}, + }; + beforeEach(() => { mockExecuteFunctions = mock(); microsoftOneDrive = new MicrosoftOneDrive(); @@ -40,6 +49,8 @@ describe('Test MicrosoftOneDrive, file > download', () => { returnJsonArray: jest.fn((data) => [data]), constructExecutionMetaData: jest.fn((data) => data), } as any; + + mockExecuteFunctions.getNode.mockReturnValue(mockNode); }); afterEach(() => { diff --git a/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.upload.test.ts b/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.upload.test.ts new file mode 100644 index 00000000000..2b9b8c79523 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/OneDrive/test/node/file.upload.test.ts @@ -0,0 +1,165 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { IExecuteFunctions, IBinaryData } from 'n8n-workflow'; + +import * as genericFunctions from '../../GenericFunctions'; +import { MicrosoftOneDrive } from '../../MicrosoftOneDrive.node'; + +jest.mock('../../GenericFunctions', () => ({ + ...jest.requireActual('../../GenericFunctions'), + microsoftApiRequest: jest.fn(async function () { + return JSON.stringify({ + id: 'uploadedFileId', + name: 'uploadedFile.txt', + }); + }), +})); + +describe('Test MicrosoftOneDrive, file > upload', () => { + let mockExecuteFunctions: MockProxy; + let microsoftOneDrive: MicrosoftOneDrive; + const getBinaryDataBuffer = jest.fn(async () => Buffer.from('test content')); + + const mockNode = { + id: 'test-node-id', + name: 'Microsoft OneDrive Test', + type: 'n8n-nodes-base.microsoftOneDrive', + typeVersion: 1.1, + position: [0, 0] as [number, number], + parameters: {}, + }; + + beforeEach(() => { + mockExecuteFunctions = mock(); + microsoftOneDrive = new MicrosoftOneDrive(); + + mockExecuteFunctions.helpers = { + assertBinaryData: jest.fn(() => { + return { + data: 'base64data', + mimeType: 'text/plain', + fileName: 'binaryFileName.txt', + } as IBinaryData; + }), + getBinaryDataBuffer, + returnJsonArray: jest.fn((data) => [data]), + constructExecutionMetaData: jest.fn((data) => data), + } as any; + + mockExecuteFunctions.getNode.mockReturnValue(mockNode); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should use filename from node parameters even when binary has a filename (version 1.1+)', async () => { + const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }]; + mockExecuteFunctions.getInputData.mockReturnValue(items); + mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => { + if (key === 'resource') return 'file'; + if (key === 'operation') return 'upload'; + if (key === 'parentId') return 'parentFolderId'; + if (key === 'fileName') return 'customFileName.txt'; + if (key === 'binaryData') return true; + if (key === 'binaryPropertyName') return 'data'; + }); + + await microsoftOneDrive.execute.call(mockExecuteFunctions); + + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1); + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith( + 'PUT', + '/drive/items/parentFolderId:/customFileName.txt:/content', + expect.any(Buffer), + {}, + undefined, + { 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) }, + {}, + ); + }); + + it('should use binary filename when node parameter filename is empty', async () => { + const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }]; + mockExecuteFunctions.getInputData.mockReturnValue(items); + mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => { + if (key === 'resource') return 'file'; + if (key === 'operation') return 'upload'; + if (key === 'parentId') return 'parentFolderId'; + if (key === 'fileName') return ''; + if (key === 'binaryData') return true; + if (key === 'binaryPropertyName') return 'data'; + }); + + await microsoftOneDrive.execute.call(mockExecuteFunctions); + + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1); + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith( + 'PUT', + '/drive/items/parentFolderId:/binaryFileName.txt:/content', + expect.any(Buffer), + {}, + undefined, + { 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) }, + {}, + ); + }); + + it('should properly encode special characters in filename from node parameters', async () => { + const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }]; + mockExecuteFunctions.getInputData.mockReturnValue(items); + mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => { + if (key === 'resource') return 'file'; + if (key === 'operation') return 'upload'; + if (key === 'parentId') return 'parentFolderId'; + if (key === 'fileName') return 'file with spaces & special.txt'; + if (key === 'binaryData') return true; + if (key === 'binaryPropertyName') return 'data'; + }); + + await microsoftOneDrive.execute.call(mockExecuteFunctions); + + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1); + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith( + 'PUT', + '/drive/items/parentFolderId:/file%20with%20spaces%20%26%20special.txt:/content', + expect.any(Buffer), + {}, + undefined, + { 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) }, + {}, + ); + }); + + it('should always use binary filename even when node parameter has a filename (version 1 - legacy bug)', async () => { + mockExecuteFunctions.getNode.mockReturnValue({ + ...mockNode, + typeVersion: 1, + }); + + const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }]; + mockExecuteFunctions.getInputData.mockReturnValue(items); + mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => { + if (key === 'resource') return 'file'; + if (key === 'operation') return 'upload'; + if (key === 'parentId') return 'parentFolderId'; + if (key === 'fileName') return 'customFileName.txt'; + if (key === 'binaryData') return true; + if (key === 'binaryPropertyName') return 'data'; + }); + + await microsoftOneDrive.execute.call(mockExecuteFunctions); + + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1); + // In version 1, binary filename always overwrites the parameter filename + expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith( + 'PUT', + '/drive/items/parentFolderId:/binaryFileName.txt:/content', + expect.any(Buffer), + {}, + undefined, + { 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) }, + {}, + ); + }); +});