mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
test(n8n Form Trigger Node): Add tests, extract testing util for webhook triggers (no-changelog) (#11023)
This commit is contained in:
parent
cd916480c2
commit
f92637a9fe
316
packages/nodes-base/nodes/Form/test/FormTriggerV2.node.test.ts
Normal file
316
packages/nodes-base/nodes/Form/test/FormTriggerV2.node.test.ts
Normal file
|
@ -0,0 +1,316 @@
|
|||
import { mock } from 'jest-mock-extended';
|
||||
import { NodeOperationError, type INode } from 'n8n-workflow';
|
||||
|
||||
import { testVersionedWebhookTriggerNode } from '@test/nodes/TriggerHelpers';
|
||||
|
||||
import { FormTrigger } from '../FormTrigger.node';
|
||||
import type { FormField } from '../interfaces';
|
||||
|
||||
describe('FormTrigger', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render a form template with correct fields', async () => {
|
||||
const formFields: FormField[] = [
|
||||
{ fieldLabel: 'Name', fieldType: 'text', requiredField: true },
|
||||
{ fieldLabel: 'Age', fieldType: 'number', requiredField: false },
|
||||
{ fieldLabel: 'Notes', fieldType: 'textarea', requiredField: false },
|
||||
{
|
||||
fieldLabel: 'Gender',
|
||||
fieldType: 'select',
|
||||
requiredField: true,
|
||||
fieldOptions: { values: [{ option: 'Male' }, { option: 'Female' }] },
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Resume',
|
||||
fieldType: 'file',
|
||||
requiredField: true,
|
||||
acceptFileTypes: '.pdf,.doc',
|
||||
multipleFiles: false,
|
||||
},
|
||||
];
|
||||
|
||||
const { response, responseData } = await testVersionedWebhookTriggerNode(FormTrigger, 2, {
|
||||
mode: 'manual',
|
||||
node: {
|
||||
parameters: {
|
||||
formTitle: 'Test Form',
|
||||
formDescription: 'Test Description',
|
||||
responseMode: 'onReceived',
|
||||
formFields: { values: formFields },
|
||||
options: {
|
||||
appendAttribution: false,
|
||||
respondWithOptions: { values: { respondWith: 'text' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.render).toHaveBeenCalledWith('form-trigger', {
|
||||
appendAttribution: false,
|
||||
formDescription: 'Test Description',
|
||||
formFields: [
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-0',
|
||||
id: 'field-0',
|
||||
inputRequired: 'form-required',
|
||||
isInput: true,
|
||||
label: 'Name',
|
||||
placeholder: undefined,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-1',
|
||||
id: 'field-1',
|
||||
inputRequired: '',
|
||||
isInput: true,
|
||||
label: 'Age',
|
||||
placeholder: undefined,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-2',
|
||||
id: 'field-2',
|
||||
inputRequired: '',
|
||||
label: 'Notes',
|
||||
placeholder: undefined,
|
||||
isTextarea: true,
|
||||
},
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-3',
|
||||
id: 'field-3',
|
||||
inputRequired: 'form-required',
|
||||
isInput: true,
|
||||
label: 'Gender',
|
||||
placeholder: undefined,
|
||||
type: 'select',
|
||||
},
|
||||
{
|
||||
acceptFileTypes: '.pdf,.doc',
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-4',
|
||||
id: 'field-4',
|
||||
inputRequired: 'form-required',
|
||||
isFileInput: true,
|
||||
label: 'Resume',
|
||||
multipleFiles: '',
|
||||
placeholder: undefined,
|
||||
},
|
||||
],
|
||||
formSubmittedText: 'Your response has been recorded',
|
||||
formTitle: 'Test Form',
|
||||
n8nWebsiteLink:
|
||||
'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger&utm_campaign=instanceId',
|
||||
testRun: true,
|
||||
useResponseData: false,
|
||||
validForm: true,
|
||||
});
|
||||
|
||||
expect(responseData).toEqual({ noWebhookResponse: true });
|
||||
});
|
||||
|
||||
it('should return workflowData on POST request', async () => {
|
||||
const formFields: FormField[] = [
|
||||
{ fieldLabel: 'Name', fieldType: 'text', requiredField: true },
|
||||
{ fieldLabel: 'Age', fieldType: 'number', requiredField: false },
|
||||
{ fieldLabel: 'Date', fieldType: 'date', formatDate: 'dd MMM', requiredField: false },
|
||||
{ fieldLabel: 'Empty', fieldType: 'number', requiredField: false },
|
||||
{
|
||||
fieldLabel: 'Tags',
|
||||
fieldType: 'select',
|
||||
multiselect: true,
|
||||
requiredField: false,
|
||||
fieldOptions: { values: [{ option: 'Popular' }, { option: 'Recent' }] },
|
||||
},
|
||||
];
|
||||
|
||||
const bodyData = {
|
||||
data: {
|
||||
'field-0': 'John Doe',
|
||||
'field-1': '30',
|
||||
'field-2': '2024-08-31',
|
||||
'field-4': '{}',
|
||||
},
|
||||
};
|
||||
|
||||
const { responseData } = await testVersionedWebhookTriggerNode(FormTrigger, 2, {
|
||||
mode: 'manual',
|
||||
node: {
|
||||
parameters: {
|
||||
formTitle: 'Test Form',
|
||||
formDescription: 'Test Description',
|
||||
responseMode: 'onReceived',
|
||||
formFields: { values: formFields },
|
||||
},
|
||||
},
|
||||
request: { method: 'POST' },
|
||||
bodyData,
|
||||
});
|
||||
|
||||
expect(responseData).toEqual({
|
||||
webhookResponse: { status: 200 },
|
||||
workflowData: [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
Name: 'John Doe',
|
||||
Age: 30,
|
||||
Date: '31 Jan',
|
||||
Empty: null,
|
||||
Tags: {},
|
||||
submittedAt: expect.any(String),
|
||||
formMode: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('Respond to Webhook', () => {
|
||||
it('should throw when misconfigured', async () => {
|
||||
await expect(
|
||||
testVersionedWebhookTriggerNode(FormTrigger, 2, {
|
||||
node: {
|
||||
parameters: {
|
||||
responseMode: 'responseNode',
|
||||
},
|
||||
},
|
||||
request: { method: 'POST' },
|
||||
childNodes: [],
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new NodeOperationError(mock<INode>(), 'No Respond to Webhook node found in the workflow'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
testVersionedWebhookTriggerNode(FormTrigger, 2, {
|
||||
node: {
|
||||
parameters: {
|
||||
responseMode: 'onReceived',
|
||||
},
|
||||
},
|
||||
request: { method: 'POST' },
|
||||
childNodes: [
|
||||
{
|
||||
name: 'Test Respond To Webhook',
|
||||
type: 'n8n-nodes-base.respondToWebhook',
|
||||
typeVersion: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new NodeOperationError(mock<INode>(), 'n8n Form Trigger node not correctly configured'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw on invalid webhook authentication', async () => {
|
||||
const formFields: FormField[] = [
|
||||
{ fieldLabel: 'Name', fieldType: 'text', requiredField: true },
|
||||
{ fieldLabel: 'Age', fieldType: 'number', requiredField: false },
|
||||
];
|
||||
|
||||
const { responseData, response } = await testVersionedWebhookTriggerNode(FormTrigger, 2, {
|
||||
mode: 'manual',
|
||||
node: {
|
||||
parameters: {
|
||||
formTitle: 'Test Form',
|
||||
formDescription: 'Test Description',
|
||||
responseMode: 'onReceived',
|
||||
formFields: { values: formFields },
|
||||
authentication: 'basicAuth',
|
||||
},
|
||||
},
|
||||
request: { method: 'POST' },
|
||||
});
|
||||
|
||||
expect(responseData).toEqual({ noWebhookResponse: true });
|
||||
expect(response.status).toHaveBeenCalledWith(401);
|
||||
expect(response.setHeader).toHaveBeenCalledWith(
|
||||
'WWW-Authenticate',
|
||||
'Basic realm="Enter credentials"',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle files', async () => {
|
||||
const formFields: FormField[] = [
|
||||
{
|
||||
fieldLabel: 'Resume',
|
||||
fieldType: 'file',
|
||||
requiredField: true,
|
||||
acceptFileTypes: '.pdf,.doc',
|
||||
multipleFiles: false,
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Attachments',
|
||||
fieldType: 'file',
|
||||
requiredField: true,
|
||||
acceptFileTypes: '.pdf,.doc',
|
||||
multipleFiles: true,
|
||||
},
|
||||
];
|
||||
|
||||
const bodyData = {
|
||||
files: {
|
||||
'field-0': {
|
||||
originalFilename: 'resume.pdf',
|
||||
mimetype: 'application/json',
|
||||
filepath: '/resume.pdf',
|
||||
size: 200,
|
||||
},
|
||||
'field-1': [
|
||||
{
|
||||
originalFilename: 'attachment1.pdf',
|
||||
mimetype: 'application/json',
|
||||
filepath: '/attachment1.pdf',
|
||||
size: 201,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const { responseData } = await testVersionedWebhookTriggerNode(FormTrigger, 2, {
|
||||
mode: 'trigger',
|
||||
node: {
|
||||
parameters: {
|
||||
formTitle: 'Test Form',
|
||||
formDescription: 'Test Description',
|
||||
responseMode: 'onReceived',
|
||||
formFields: { values: formFields },
|
||||
},
|
||||
},
|
||||
request: { method: 'POST' },
|
||||
bodyData,
|
||||
});
|
||||
|
||||
expect(responseData?.webhookResponse).toEqual({ status: 200 });
|
||||
expect(responseData?.workflowData).toEqual([
|
||||
[
|
||||
expect.objectContaining({
|
||||
json: {
|
||||
Resume: {
|
||||
filename: 'resume.pdf',
|
||||
mimetype: 'application/json',
|
||||
size: 200,
|
||||
},
|
||||
Attachments: [
|
||||
{
|
||||
filename: 'attachment1.pdf',
|
||||
mimetype: 'application/json',
|
||||
size: 201,
|
||||
},
|
||||
],
|
||||
formMode: 'production',
|
||||
submittedAt: expect.any(String),
|
||||
},
|
||||
}),
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,153 +1,5 @@
|
|||
import { mock } from 'jest-mock-extended';
|
||||
import type { IWebhookFunctions } from 'n8n-workflow';
|
||||
import type { FormField } from '../interfaces';
|
||||
import { formWebhook, prepareFormData } from '../utils';
|
||||
|
||||
describe('FormTrigger, formWebhook', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call response render', async () => {
|
||||
const executeFunctions = mock<IWebhookFunctions>();
|
||||
const mockRender = jest.fn();
|
||||
|
||||
const formFields: FormField[] = [
|
||||
{ fieldLabel: 'Name', fieldType: 'text', requiredField: true },
|
||||
{ fieldLabel: 'Age', fieldType: 'number', requiredField: false },
|
||||
{
|
||||
fieldLabel: 'Gender',
|
||||
fieldType: 'select',
|
||||
requiredField: true,
|
||||
fieldOptions: { values: [{ option: 'Male' }, { option: 'Female' }] },
|
||||
},
|
||||
{
|
||||
fieldLabel: 'Resume',
|
||||
fieldType: 'file',
|
||||
requiredField: true,
|
||||
acceptFileTypes: '.pdf,.doc',
|
||||
multipleFiles: false,
|
||||
},
|
||||
];
|
||||
|
||||
executeFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any);
|
||||
executeFunctions.getNodeParameter.calledWith('options').mockReturnValue({});
|
||||
executeFunctions.getNodeParameter.calledWith('formTitle').mockReturnValue('Test Form');
|
||||
executeFunctions.getNodeParameter
|
||||
.calledWith('formDescription')
|
||||
.mockReturnValue('Test Description');
|
||||
executeFunctions.getNodeParameter.calledWith('responseMode').mockReturnValue('onReceived');
|
||||
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
|
||||
executeFunctions.getResponseObject.mockReturnValue({ render: mockRender } as any);
|
||||
executeFunctions.getRequestObject.mockReturnValue({ method: 'GET', query: {} } as any);
|
||||
executeFunctions.getMode.mockReturnValue('manual');
|
||||
executeFunctions.getInstanceId.mockReturnValue('instanceId');
|
||||
executeFunctions.getBodyData.mockReturnValue({ data: {}, files: {} });
|
||||
executeFunctions.getChildNodes.mockReturnValue([]);
|
||||
|
||||
await formWebhook(executeFunctions);
|
||||
|
||||
expect(mockRender).toHaveBeenCalledWith('form-trigger', {
|
||||
appendAttribution: true,
|
||||
formDescription: 'Test Description',
|
||||
formFields: [
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-0',
|
||||
id: 'field-0',
|
||||
inputRequired: 'form-required',
|
||||
isInput: true,
|
||||
label: 'Name',
|
||||
placeholder: undefined,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-1',
|
||||
id: 'field-1',
|
||||
inputRequired: '',
|
||||
isInput: true,
|
||||
label: 'Age',
|
||||
placeholder: undefined,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-2',
|
||||
id: 'field-2',
|
||||
inputRequired: 'form-required',
|
||||
isInput: true,
|
||||
label: 'Gender',
|
||||
placeholder: undefined,
|
||||
type: 'select',
|
||||
},
|
||||
{
|
||||
acceptFileTypes: '.pdf,.doc',
|
||||
defaultValue: '',
|
||||
errorId: 'error-field-3',
|
||||
id: 'field-3',
|
||||
inputRequired: 'form-required',
|
||||
isFileInput: true,
|
||||
label: 'Resume',
|
||||
multipleFiles: '',
|
||||
placeholder: undefined,
|
||||
},
|
||||
],
|
||||
formSubmittedText: 'Your response has been recorded',
|
||||
formTitle: 'Test Form',
|
||||
n8nWebsiteLink:
|
||||
'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger&utm_campaign=instanceId',
|
||||
testRun: true,
|
||||
useResponseData: false,
|
||||
validForm: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return workflowData on POST request', async () => {
|
||||
const executeFunctions = mock<IWebhookFunctions>();
|
||||
const mockStatus = jest.fn();
|
||||
const mockEnd = jest.fn();
|
||||
|
||||
const formFields: FormField[] = [
|
||||
{ fieldLabel: 'Name', fieldType: 'text', requiredField: true },
|
||||
{ fieldLabel: 'Age', fieldType: 'number', requiredField: false },
|
||||
];
|
||||
|
||||
const bodyData = {
|
||||
'field-0': 'John Doe',
|
||||
'field-1': '30',
|
||||
};
|
||||
|
||||
executeFunctions.getNode.mockReturnValue({ typeVersion: 2.1 } as any);
|
||||
executeFunctions.getNodeParameter.calledWith('options').mockReturnValue({});
|
||||
executeFunctions.getNodeParameter.calledWith('responseMode').mockReturnValue('onReceived');
|
||||
executeFunctions.getChildNodes.mockReturnValue([]);
|
||||
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
|
||||
executeFunctions.getResponseObject.mockReturnValue({ status: mockStatus, end: mockEnd } as any);
|
||||
executeFunctions.getRequestObject.mockReturnValue({ method: 'POST' } as any);
|
||||
executeFunctions.getMode.mockReturnValue('manual');
|
||||
executeFunctions.getInstanceId.mockReturnValue('instanceId');
|
||||
executeFunctions.getBodyData.mockReturnValue({ data: bodyData, files: {} });
|
||||
|
||||
const result = await formWebhook(executeFunctions);
|
||||
|
||||
expect(result).toEqual({
|
||||
webhookResponse: { status: 200 },
|
||||
workflowData: [
|
||||
[
|
||||
{
|
||||
json: {
|
||||
Name: 'John Doe',
|
||||
Age: 30,
|
||||
submittedAt: expect.any(String),
|
||||
formMode: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
import { prepareFormData } from '../utils';
|
||||
|
||||
describe('FormTrigger, prepareFormData', () => {
|
||||
it('should return valid form data with given parameters', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as n8nWorkflow from 'n8n-workflow';
|
||||
|
||||
import { createTestTriggerNode } from '@test/nodes/TriggerHelpers';
|
||||
import { testTriggerNode } from '@test/nodes/TriggerHelpers';
|
||||
|
||||
import { ScheduleTrigger } from '../ScheduleTrigger.node';
|
||||
|
||||
|
@ -21,7 +21,7 @@ describe('ScheduleTrigger', () => {
|
|||
|
||||
describe('trigger', () => {
|
||||
it('should emit on defined schedule', async () => {
|
||||
const { emit } = await createTestTriggerNode(ScheduleTrigger).trigger({
|
||||
const { emit } = await testTriggerNode(ScheduleTrigger, {
|
||||
timezone,
|
||||
node: { parameters: { rule: { interval: [{ field: 'hours', hoursInterval: 3 }] } } },
|
||||
workflowStaticData: { recurrenceRules: [] },
|
||||
|
@ -60,7 +60,7 @@ describe('ScheduleTrigger', () => {
|
|||
});
|
||||
|
||||
it('should emit on schedule defined as a cron expression', async () => {
|
||||
const { emit } = await createTestTriggerNode(ScheduleTrigger).trigger({
|
||||
const { emit } = await testTriggerNode(ScheduleTrigger, {
|
||||
timezone,
|
||||
node: {
|
||||
parameters: {
|
||||
|
@ -88,7 +88,7 @@ describe('ScheduleTrigger', () => {
|
|||
|
||||
it('should throw on invalid cron expressions', async () => {
|
||||
await expect(
|
||||
createTestTriggerNode(ScheduleTrigger).trigger({
|
||||
testTriggerNode(ScheduleTrigger, {
|
||||
timezone,
|
||||
node: {
|
||||
parameters: {
|
||||
|
@ -108,7 +108,8 @@ describe('ScheduleTrigger', () => {
|
|||
});
|
||||
|
||||
it('should emit when manually executed', async () => {
|
||||
const { emit } = await createTestTriggerNode(ScheduleTrigger).triggerManual({
|
||||
const { emit } = await testTriggerNode(ScheduleTrigger, {
|
||||
mode: 'manual',
|
||||
timezone,
|
||||
node: { parameters: { rule: { interval: [{ field: 'hours', hoursInterval: 3 }] } } },
|
||||
workflowStaticData: { recurrenceRules: [] },
|
||||
|
@ -134,7 +135,8 @@ describe('ScheduleTrigger', () => {
|
|||
|
||||
it('should throw on invalid cron expressions in manual mode', async () => {
|
||||
await expect(
|
||||
createTestTriggerNode(ScheduleTrigger).triggerManual({
|
||||
testTriggerNode(ScheduleTrigger, {
|
||||
mode: 'manual',
|
||||
timezone,
|
||||
node: {
|
||||
parameters: {
|
||||
|
|
|
@ -1,94 +1,167 @@
|
|||
import type * as express from 'express';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import get from 'lodash/get';
|
||||
import merge from 'lodash/merge';
|
||||
import { returnJsonArray, type InstanceSettings } from 'n8n-core';
|
||||
import { ScheduledTaskManager } from 'n8n-core/dist/ScheduledTaskManager';
|
||||
import type {
|
||||
IBinaryData,
|
||||
IDataObject,
|
||||
INode,
|
||||
INodeType,
|
||||
ITriggerFunctions,
|
||||
IWebhookFunctions,
|
||||
NodeTypeAndVersion,
|
||||
VersionedNodeType,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
type MockDeepPartial<T> = Parameters<typeof mock<T>>[0];
|
||||
|
||||
type TestTriggerNodeOptions = {
|
||||
mode?: 'manual' | 'trigger';
|
||||
node?: MockDeepPartial<INode>;
|
||||
timezone?: string;
|
||||
workflowStaticData?: IDataObject;
|
||||
};
|
||||
|
||||
type TriggerNodeTypeClass = new () => INodeType & Required<Pick<INodeType, 'trigger'>>;
|
||||
type TestWebhookTriggerNodeOptions = TestTriggerNodeOptions & {
|
||||
mode?: 'manual' | 'trigger';
|
||||
webhookName?: string;
|
||||
request?: MockDeepPartial<express.Request>;
|
||||
bodyData?: IDataObject;
|
||||
childNodes?: NodeTypeAndVersion[];
|
||||
};
|
||||
|
||||
export const createTestTriggerNode = (Trigger: TriggerNodeTypeClass) => {
|
||||
const trigger = new Trigger();
|
||||
export async function testVersionedTriggerNode(
|
||||
Trigger: new () => VersionedNodeType,
|
||||
version?: number,
|
||||
options: TestTriggerNodeOptions = {},
|
||||
) {
|
||||
const instance = new Trigger();
|
||||
return await testTriggerNode(instance.nodeVersions[version ?? instance.currentVersion], options);
|
||||
}
|
||||
|
||||
export async function testTriggerNode(
|
||||
Trigger: (new () => INodeType) | INodeType,
|
||||
options: TestTriggerNodeOptions = {},
|
||||
) {
|
||||
const trigger = 'description' in Trigger ? Trigger : new Trigger();
|
||||
const emit: jest.MockedFunction<ITriggerFunctions['emit']> = jest.fn();
|
||||
|
||||
const setupTriggerFunctions = (
|
||||
mode: WorkflowExecuteMode,
|
||||
options: TestTriggerNodeOptions = {},
|
||||
) => {
|
||||
const timezone = options.timezone ?? 'Europe/Berlin';
|
||||
const version = trigger.description.version;
|
||||
const node = merge(
|
||||
{
|
||||
type: trigger.description.name,
|
||||
name: trigger.description.defaults.name ?? `Test Node (${trigger.description.name})`,
|
||||
typeVersion: typeof version === 'number' ? version : version.at(-1),
|
||||
} satisfies Partial<INode>,
|
||||
options.node,
|
||||
) as INode;
|
||||
const workflow = mock<Workflow>({ timezone: options.timezone ?? 'Europe/Berlin' });
|
||||
const timezone = options.timezone ?? 'Europe/Berlin';
|
||||
const version = trigger.description.version;
|
||||
const node = merge(
|
||||
{
|
||||
type: trigger.description.name,
|
||||
name: trigger.description.defaults.name ?? `Test Node (${trigger.description.name})`,
|
||||
typeVersion: typeof version === 'number' ? version : version.at(-1),
|
||||
} satisfies Partial<INode>,
|
||||
options.node,
|
||||
) as INode;
|
||||
const workflow = mock<Workflow>({ timezone: options.timezone ?? 'Europe/Berlin' });
|
||||
|
||||
const scheduledTaskManager = new ScheduledTaskManager(mock<InstanceSettings>());
|
||||
const helpers = mock<ITriggerFunctions['helpers']>({
|
||||
returnJsonArray,
|
||||
registerCron: (cronExpression, onTick) =>
|
||||
scheduledTaskManager.registerCron(workflow, cronExpression, onTick),
|
||||
});
|
||||
const scheduledTaskManager = new ScheduledTaskManager(mock<InstanceSettings>());
|
||||
const helpers = mock<ITriggerFunctions['helpers']>({
|
||||
returnJsonArray,
|
||||
registerCron: (cronExpression, onTick) =>
|
||||
scheduledTaskManager.registerCron(workflow, cronExpression, onTick),
|
||||
});
|
||||
|
||||
const triggerFunctions = mock<ITriggerFunctions>({
|
||||
helpers,
|
||||
emit,
|
||||
getTimezone: () => timezone,
|
||||
getNode: () => node,
|
||||
getMode: () => mode,
|
||||
getWorkflowStaticData: () => options.workflowStaticData ?? {},
|
||||
getNodeParameter: (parameterName, fallback) => node.parameters[parameterName] ?? fallback,
|
||||
});
|
||||
const triggerFunctions = mock<ITriggerFunctions>({
|
||||
helpers,
|
||||
emit,
|
||||
getTimezone: () => timezone,
|
||||
getNode: () => node,
|
||||
getMode: () => options.mode ?? 'trigger',
|
||||
getWorkflowStaticData: () => options.workflowStaticData ?? {},
|
||||
getNodeParameter: (parameterName, fallback) => get(node.parameters, parameterName) ?? fallback,
|
||||
});
|
||||
|
||||
return triggerFunctions;
|
||||
};
|
||||
const response = await trigger.trigger?.call(triggerFunctions);
|
||||
|
||||
if (options.mode === 'manual') {
|
||||
expect(response?.manualTriggerFunction).toBeInstanceOf(Function);
|
||||
await response?.manualTriggerFunction?.();
|
||||
} else {
|
||||
expect(response?.manualTriggerFunction).toBeUndefined();
|
||||
}
|
||||
|
||||
return {
|
||||
trigger: async (options: TestTriggerNodeOptions = {}) => {
|
||||
const triggerFunctions = setupTriggerFunctions('trigger', options);
|
||||
|
||||
const response = await trigger.trigger.call(triggerFunctions);
|
||||
|
||||
expect(response?.manualTriggerFunction).toBeUndefined();
|
||||
|
||||
return {
|
||||
close: jest.fn(response?.closeFunction),
|
||||
emit,
|
||||
};
|
||||
},
|
||||
|
||||
triggerManual: async (options: TestTriggerNodeOptions = {}) => {
|
||||
const triggerFunctions = setupTriggerFunctions('manual', options);
|
||||
|
||||
const response = await trigger.trigger.call(triggerFunctions);
|
||||
|
||||
expect(response?.manualTriggerFunction).toBeInstanceOf(Function);
|
||||
|
||||
await response?.manualTriggerFunction?.();
|
||||
|
||||
return {
|
||||
close: jest.fn(response?.closeFunction),
|
||||
emit,
|
||||
};
|
||||
},
|
||||
close: jest.fn(response?.closeFunction),
|
||||
emit,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export async function testVersionedWebhookTriggerNode(
|
||||
Trigger: new () => VersionedNodeType,
|
||||
version?: number,
|
||||
options: TestWebhookTriggerNodeOptions = {},
|
||||
) {
|
||||
const instance = new Trigger();
|
||||
return await testWebhookTriggerNode(
|
||||
instance.nodeVersions[version ?? instance.currentVersion],
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
export async function testWebhookTriggerNode(
|
||||
Trigger: (new () => INodeType) | INodeType,
|
||||
options: TestWebhookTriggerNodeOptions = {},
|
||||
) {
|
||||
const trigger = 'description' in Trigger ? Trigger : new Trigger();
|
||||
|
||||
const timezone = options.timezone ?? 'Europe/Berlin';
|
||||
const version = trigger.description.version;
|
||||
const node = merge(
|
||||
{
|
||||
type: trigger.description.name,
|
||||
name: trigger.description.defaults.name ?? `Test Node (${trigger.description.name})`,
|
||||
typeVersion: typeof version === 'number' ? version : version.at(-1),
|
||||
} satisfies Partial<INode>,
|
||||
options.node,
|
||||
) as INode;
|
||||
const workflow = mock<Workflow>({ timezone: options.timezone ?? 'Europe/Berlin' });
|
||||
|
||||
const scheduledTaskManager = new ScheduledTaskManager(mock<InstanceSettings>());
|
||||
const helpers = mock<ITriggerFunctions['helpers']>({
|
||||
returnJsonArray,
|
||||
registerCron: (cronExpression, onTick) =>
|
||||
scheduledTaskManager.registerCron(workflow, cronExpression, onTick),
|
||||
});
|
||||
|
||||
const request = mock<express.Request>({
|
||||
method: 'GET',
|
||||
...options.request,
|
||||
});
|
||||
const response = mock<express.Response>({ status: jest.fn(() => mock<express.Response>()) });
|
||||
const webhookFunctions = mock<IWebhookFunctions>({
|
||||
helpers,
|
||||
nodeHelpers: {
|
||||
copyBinaryFile: jest.fn(async () => mock<IBinaryData>()),
|
||||
},
|
||||
getTimezone: () => timezone,
|
||||
getNode: () => node,
|
||||
getMode: () => options.mode ?? 'trigger',
|
||||
getInstanceId: () => 'instanceId',
|
||||
getBodyData: () => options.bodyData ?? {},
|
||||
getHeaderData: () => ({}),
|
||||
getInputConnectionData: async () => ({}),
|
||||
getNodeWebhookUrl: (name) => `/test-webhook-url/${name}`,
|
||||
getParamsData: () => ({}),
|
||||
getQueryData: () => ({}),
|
||||
getRequestObject: () => request,
|
||||
getResponseObject: () => response,
|
||||
getWebhookName: () => options.webhookName ?? 'default',
|
||||
getWorkflowStaticData: () => options.workflowStaticData ?? {},
|
||||
getNodeParameter: (parameterName, fallback) => get(node.parameters, parameterName) ?? fallback,
|
||||
getChildNodes: () => options.childNodes ?? [],
|
||||
});
|
||||
|
||||
const responseData = await trigger.webhook?.call(webhookFunctions);
|
||||
|
||||
return {
|
||||
responseData,
|
||||
response: webhookFunctions.getResponseObject(),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue