import { NodeTestHarness } from '@nodes-testing/node-test-harness'; import type { Request, Response } from 'express'; import { mock } from 'jest-mock-extended'; import type { IWebhookFunctions } from 'n8n-workflow'; import { Webhook } from '../Webhook.node'; describe('Test Webhook Node', () => { new NodeTestHarness().setupTests(); describe('handleFormData', () => { const node = new Webhook(); const context = mock({ nodeHelpers: mock(), }); context.getNodeParameter.calledWith('options').mockReturnValue({}); context.getNode.calledWith().mockReturnValue({ type: 'n8n-nodes-base.webhook', typeVersion: 1.1, } as any); const req = mock(); req.contentType = 'multipart/form-data'; context.getRequestObject.mockReturnValue(req); it('should handle when no files are present', async () => { req.body = { files: {}, }; const returnData = await node.webhook(context); expect(returnData.workflowData?.[0][0].binary).toBeUndefined(); expect(context.nodeHelpers.copyBinaryFile).not.toHaveBeenCalled(); }); it('should handle when files are present', async () => { req.body = { files: { file1: {} }, }; const returnData = await node.webhook(context); expect(returnData.workflowData?.[0][0].binary).not.toBeUndefined(); expect(context.nodeHelpers.copyBinaryFile).toHaveBeenCalled(); }); }); describe('streaming response mode', () => { const node = new Webhook(); const context = mock({ nodeHelpers: mock(), }); const req = mock(); const res = mock(); beforeEach(() => { jest.clearAllMocks(); context.getRequestObject.mockReturnValue(req); context.getResponseObject.mockReturnValue(res); context.getChildNodes.mockReturnValue([]); context.getNode.mockReturnValue({ type: 'n8n-nodes-base.webhook', typeVersion: 2, name: 'Webhook', } as any); context.getNodeParameter.mockImplementation((paramName: string) => { if (paramName === 'options') return {}; if (paramName === 'responseMode') return 'streaming'; return undefined; }); req.headers = {}; req.params = {}; req.query = {}; req.body = { message: 'test' }; Object.defineProperty(req, 'ips', { value: [], configurable: true }); Object.defineProperty(req, 'ip', { value: '127.0.0.1', configurable: true }); res.writeHead.mockImplementation(() => res); res.flushHeaders.mockImplementation(() => undefined); }); it('should enable streaming when responseMode is "streaming"', async () => { const result = await node.webhook(context); // Verify streaming headers are set expect(res.writeHead).toHaveBeenCalledWith(200, { 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); expect(res.flushHeaders).toHaveBeenCalled(); // Verify response structure for streaming expect(result).toEqual({ noWebhookResponse: true, workflowData: expect.any(Array), }); }); it('should not enable streaming when responseMode is not "streaming"', async () => { context.getNodeParameter.mockImplementation((paramName: string) => { if (paramName === 'options') return {}; if (paramName === 'responseMode') return 'onReceived'; return undefined; }); const result = await node.webhook(context); // Verify streaming headers are NOT set expect(res.writeHead).not.toHaveBeenCalled(); expect(res.flushHeaders).not.toHaveBeenCalled(); // Verify normal response structure expect(result).toEqual({ webhookResponse: undefined, workflowData: expect.any(Array), }); }); it('should handle multipart form data with streaming enabled', async () => { req.contentType = 'multipart/form-data'; req.body = { data: { message: 'Hello' }, files: {}, }; const result = await node.webhook(context); // For multipart form data, streaming is handled in handleFormData method // The current implementation returns normal workflowData for form data expect(result).toEqual({ workflowData: expect.any(Array), }); }); }); });