Merge commit from fork

* enforce content type multipart/form-data when we handle body.files

* fix unit tests
This commit is contained in:
mfsiega
2025-11-13 11:51:40 +01:00
committed by GitHub
parent e45a4b1073
commit c8d604d2c4
7 changed files with 31 additions and 5 deletions
@@ -18,6 +18,7 @@ import type {
IBinaryData,
INodeProperties,
} from 'n8n-workflow';
import * as a from 'node:assert';
import { cssVariables } from './constants';
import { validateAuth } from './GenericFunctions';
@@ -566,6 +567,7 @@ export class ChatTrigger extends Node {
private async handleFormData(context: IWebhookFunctions) {
const req = context.getRequestObject() as MultiPartFormData.Request;
a.ok(req.contentType === 'multipart/form-data', 'Expected multipart/form-data');
const options = context.getNodeParameter('options', {}) as IDataObject;
const { data, files } = req.body;
@@ -172,7 +172,10 @@ describe('Form Node', () => {
});
it('should return form data for POST request', async () => {
mockWebhookFunctions.getRequestObject.mockReturnValue({ method: 'POST' } as Request);
mockWebhookFunctions.getRequestObject.mockReturnValue({
method: 'POST',
contentType: 'multipart/form-data',
} as Request);
mockWebhookFunctions.getParentNodes.mockReturnValue([
{
type: 'n8n-nodes-base.formTrigger',
@@ -148,7 +148,11 @@ describe('FormTrigger', () => {
formFields: { values: formFields },
},
},
request: { method: 'POST' },
request: {
method: 'POST',
headers: { 'content-type': 'multipart/form-data' },
contentType: 'multipart/form-data',
},
bodyData,
});
@@ -317,7 +321,11 @@ describe('FormTrigger', () => {
formFields: { values: formFields },
},
},
request: { method: 'POST' },
request: {
method: 'POST',
headers: { 'content-type': 'multipart/form-data' },
contentType: 'multipart/form-data',
},
bodyData,
});
@@ -542,7 +542,10 @@ describe('FormTrigger, formWebhook', () => {
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
executeFunctions.getResponseObject.mockReturnValue({ status: mockStatus, end: mockEnd } as any);
executeFunctions.getRequestObject.mockReturnValue({ method: 'POST' } as any);
executeFunctions.getRequestObject.mockReturnValue({
method: 'POST',
contentType: 'multipart/form-data',
} as any);
executeFunctions.getBodyData.mockReturnValue({ data: bodyData, files: {} });
const result = await formWebhook(executeFunctions);
@@ -1310,7 +1313,9 @@ jest.mock('luxon', () => ({
describe('prepareFormReturnItem', () => {
const mockContext = mock<IWebhookFunctions>({
getRequestObject: jest.fn().mockReturnValue({ method: 'GET', query: {} }),
getRequestObject: jest
.fn()
.mockReturnValue({ method: 'GET', query: {}, contentType: 'multipart/form-data' }),
nodeHelpers: mock({
copyBinaryFile: jest.fn().mockResolvedValue({}),
}),
@@ -1456,6 +1461,7 @@ describe('prepareFormReturnItem', () => {
mockContext.getRequestObject.mockReturnValue({
method: 'POST',
query: { param: 'value' },
contentType: 'multipart/form-data',
} as unknown as Request);
const result = await prepareFormReturnItem(mockContext, [], 'test');
@@ -1467,6 +1473,7 @@ describe('prepareFormReturnItem', () => {
mockContext.getRequestObject.mockReturnValue({
method: 'POST',
query: {},
contentType: 'multipart/form-data',
} as unknown as Request);
const result = await prepareFormReturnItem(mockContext, [], 'test');
@@ -1,5 +1,6 @@
import type { Response } from 'express';
import isbot from 'isbot';
import * as a from 'node:assert';
import { DateTime } from 'luxon';
import type {
INodeExecutionData,
@@ -352,6 +353,8 @@ export async function prepareFormReturnItem(
mode: 'test' | 'production',
useWorkflowTimezone: boolean = false,
) {
const req = context.getRequestObject() as MultiPartFormData.Request;
a.ok(req.contentType === 'multipart/form-data', 'Expected multipart/form-data');
const bodyData = (context.getBodyData().data as IDataObject) ?? {};
const files = (context.getBodyData().files as IDataObject) ?? {};
@@ -12,6 +12,7 @@ import type {
INodeProperties,
} from 'n8n-workflow';
import { BINARY_ENCODING, NodeOperationError, Node } from 'n8n-workflow';
import * as a from 'node:assert';
import { pipeline } from 'stream/promises';
import { file as tmpFile } from 'tmp-promise';
import { v4 as uuid } from 'uuid';
@@ -316,6 +317,7 @@ export class Webhook extends Node {
prepareOutput: (data: INodeExecutionData) => INodeExecutionData[][],
) {
const req = context.getRequestObject() as MultiPartFormData.Request;
a.ok(req.contentType === 'multipart/form-data', 'Expected multipart/form-data');
const options = context.getNodeParameter('options', {}) as IDataObject;
const { data, files } = req.body;
@@ -330,6 +330,7 @@ describe('Send and Wait utils tests', () => {
it('should handle customForm POST webhook', async () => {
mockWebhookFunctions.getRequestObject.mockReturnValue({
method: 'POST',
contentType: 'multipart/form-data',
} as any);
mockWebhookFunctions.getNode.mockReturnValue({} as any);