mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-28 07:17:04 +02:00
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.14.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (25.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Roman Davydchuk <roman.davydchuk@n8n.io>
775 lines
24 KiB
TypeScript
775 lines
24 KiB
TypeScript
import type { Request, Response } from 'express';
|
|
import { type MockProxy, mock } from 'jest-mock-extended';
|
|
import type {
|
|
IExecuteFunctions,
|
|
INodeProperties,
|
|
IWebhookFunctions,
|
|
IWorkflowSettings,
|
|
} from 'n8n-workflow';
|
|
import { NodeOperationError, WAIT_INDEFINITELY } from 'n8n-workflow';
|
|
|
|
import { configureWaitTillDate } from '../configureWaitTillDate.util';
|
|
import {
|
|
getSendAndWaitProperties,
|
|
getSendAndWaitConfig,
|
|
createEmail,
|
|
sendAndWaitWebhook,
|
|
} from '../utils';
|
|
|
|
describe('Send and Wait utils tests', () => {
|
|
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
|
let mockWebhookFunctions: MockProxy<IWebhookFunctions>;
|
|
|
|
beforeEach(() => {
|
|
mockExecuteFunctions = mock<IExecuteFunctions>();
|
|
mockWebhookFunctions = mock<IWebhookFunctions>();
|
|
mockWebhookFunctions.getWorkflowSettings.mockReturnValue(mock<IWorkflowSettings>({}));
|
|
});
|
|
|
|
describe('getSendAndWaitProperties', () => {
|
|
it('should return properties with correct display options', () => {
|
|
const targetProperties: INodeProperties[] = [
|
|
{
|
|
displayName: 'Test Property',
|
|
name: 'testProperty',
|
|
type: 'string',
|
|
default: '',
|
|
},
|
|
];
|
|
const extraOptions: INodeProperties[] = [
|
|
{
|
|
displayName: 'Extra Property',
|
|
name: 'extraProperty',
|
|
type: 'string',
|
|
default: '',
|
|
},
|
|
];
|
|
|
|
const result = getSendAndWaitProperties(targetProperties, undefined, undefined, {
|
|
extraOptions,
|
|
});
|
|
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
name: 'options',
|
|
options: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
name: 'extraProperty',
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('should include extra options when provided', () => {
|
|
const targetProperties: INodeProperties[] = [
|
|
{
|
|
displayName: 'Test Property',
|
|
name: 'testProperty',
|
|
type: 'string',
|
|
default: '',
|
|
},
|
|
];
|
|
const extraOptions: INodeProperties[] = [
|
|
{
|
|
displayName: 'Extra Property',
|
|
name: 'extraProperty',
|
|
type: 'string',
|
|
default: '',
|
|
},
|
|
];
|
|
const result = getSendAndWaitProperties(targetProperties, undefined, undefined, {
|
|
extraOptions,
|
|
});
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
displayOptions: {
|
|
show: {
|
|
resource: ['message'],
|
|
operation: ['sendAndWait'],
|
|
},
|
|
},
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getSendAndWaitConfig', () => {
|
|
it('should return correct config for single approval', () => {
|
|
mockExecuteFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
message: 'Test message',
|
|
subject: 'Test subject',
|
|
'approvalOptions.values': {
|
|
approvalType: 'single',
|
|
approveLabel: 'Approve',
|
|
buttonApprovalStyle: 'primary',
|
|
},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
mockExecuteFunctions.getSignedResumeUrl.mockReturnValue(
|
|
'http://localhost/waiting-webhook/nodeID?approved=true&signature=abc',
|
|
);
|
|
const config = getSendAndWaitConfig(mockExecuteFunctions);
|
|
|
|
expect(config).toEqual({
|
|
appendAttribution: undefined,
|
|
title: 'Test subject',
|
|
message: 'Test message',
|
|
options: [
|
|
{
|
|
label: 'Approve',
|
|
style: 'primary',
|
|
url: 'http://localhost/waiting-webhook/nodeID?approved=true&signature=abc',
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should return correct config for double approval', () => {
|
|
mockExecuteFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
message: 'Test message',
|
|
subject: 'Test subject',
|
|
'approvalOptions.values': {
|
|
approvalType: 'double',
|
|
approveLabel: 'Approve',
|
|
buttonApprovalStyle: 'primary',
|
|
disapproveLabel: 'Reject',
|
|
buttonDisapprovalStyle: 'secondary',
|
|
},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
mockExecuteFunctions.getSignedResumeUrl.mockReturnValueOnce(
|
|
'http://localhost/waiting-webhook/nodeID?approved=true&signature=abc',
|
|
);
|
|
mockExecuteFunctions.getSignedResumeUrl.mockReturnValueOnce(
|
|
'http://localhost/waiting-webhook/nodeID?approved=false&signature=abc',
|
|
);
|
|
|
|
const config = getSendAndWaitConfig(mockExecuteFunctions);
|
|
|
|
expect(config.options).toHaveLength(2);
|
|
expect(config.options).toEqual(
|
|
expect.arrayContaining([
|
|
{
|
|
label: 'Reject',
|
|
style: 'secondary',
|
|
url: 'http://localhost/waiting-webhook/nodeID?approved=false&signature=abc',
|
|
},
|
|
{
|
|
label: 'Approve',
|
|
style: 'primary',
|
|
url: 'http://localhost/waiting-webhook/nodeID?approved=true&signature=abc',
|
|
},
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('createEmail', () => {
|
|
beforeEach(() => {
|
|
mockExecuteFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
sendTo: 'test@example.com',
|
|
message: 'Test message',
|
|
subject: 'Test subject',
|
|
'approvalOptions.values': {
|
|
approvalType: 'single',
|
|
approveLabel: 'Approve',
|
|
buttonApprovalStyle: 'primary',
|
|
},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
mockExecuteFunctions.getSignedResumeUrl.mockReturnValue('http://localhost/testNodeId');
|
|
});
|
|
|
|
it('should create a valid email object', () => {
|
|
const email = createEmail(mockExecuteFunctions);
|
|
|
|
expect(email).toEqual({
|
|
to: 'test@example.com',
|
|
subject: 'Test subject',
|
|
body: '',
|
|
htmlBody: expect.stringContaining('Test message'),
|
|
});
|
|
});
|
|
|
|
it('should throw NodeOperationError for invalid email address', () => {
|
|
mockExecuteFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
sendTo: 'invalid@@email.com',
|
|
message: 'Test message',
|
|
subject: 'Test subject',
|
|
'approvalOptions.values': {
|
|
approvalType: 'single',
|
|
},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
expect(() => createEmail(mockExecuteFunctions)).toThrow(NodeOperationError);
|
|
});
|
|
});
|
|
|
|
describe('sendAndWaitWebhook', () => {
|
|
it('should handle approved webhook', async () => {
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
query: { approved: 'true' },
|
|
} as any);
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({
|
|
webhookResponse: expect.any(String),
|
|
workflowData: [[{ json: { data: { approved: true } } }]],
|
|
});
|
|
});
|
|
|
|
it('should handle disapproved webhook', async () => {
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
query: { approved: 'false' },
|
|
} as any);
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({
|
|
webhookResponse: expect.any(String),
|
|
workflowData: [[{ json: { data: { approved: false } } }]],
|
|
});
|
|
});
|
|
|
|
it('should handle freeText GET webhook', async () => {
|
|
const mockRender = jest.fn();
|
|
const mockSetHeader = jest.fn();
|
|
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'GET',
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
render: mockRender,
|
|
setHeader: mockSetHeader,
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
responseType: 'freeText',
|
|
message: 'Test message',
|
|
options: {},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({
|
|
noWebhookResponse: true,
|
|
});
|
|
|
|
expect(mockSetHeader).toHaveBeenCalledWith(
|
|
'Content-Security-Policy',
|
|
'sandbox allow-downloads allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts allow-top-navigation-by-user-activation allow-top-navigation-to-custom-protocols',
|
|
);
|
|
|
|
expect(mockRender).toHaveBeenCalledWith('form-trigger', {
|
|
testRun: false,
|
|
formTitle: '',
|
|
formDescription: 'Test message',
|
|
formDescriptionMetadata: 'Test message',
|
|
formSubmittedHeader: 'Got it, thanks',
|
|
formSubmittedText: 'This page can be closed now',
|
|
n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger',
|
|
formFields: [
|
|
{
|
|
id: 'field-0',
|
|
errorId: 'error-field-0',
|
|
label: 'Response',
|
|
inputRequired: 'form-required',
|
|
defaultValue: '',
|
|
isTextarea: true,
|
|
},
|
|
],
|
|
appendAttribution: true,
|
|
buttonLabel: 'Submit',
|
|
});
|
|
});
|
|
|
|
it('should handle freeText POST webhook', async () => {
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'POST',
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getBodyData.mockReturnValue({
|
|
data: {
|
|
'field-0': 'test value',
|
|
},
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
responseType: 'freeText',
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result.workflowData).toEqual([[{ json: { data: { text: 'test value' } } }]]);
|
|
});
|
|
|
|
it('should handle customForm GET webhook', async () => {
|
|
const mockRender = jest.fn();
|
|
const mockSetHeader = jest.fn();
|
|
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'GET',
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
render: mockRender,
|
|
setHeader: mockSetHeader,
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
responseType: 'customForm',
|
|
message: 'Test message',
|
|
defineForm: 'fields',
|
|
'formFields.values': [{ label: 'Field 1', fieldType: 'text', requiredField: true }],
|
|
options: {
|
|
responseFormTitle: 'Test title',
|
|
responseFormDescription: 'Test description',
|
|
responseFormButtonLabel: 'Test button',
|
|
responseFormCustomCss: 'body { background-color: red; }',
|
|
},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({
|
|
noWebhookResponse: true,
|
|
});
|
|
|
|
expect(mockSetHeader).toHaveBeenCalledWith(
|
|
'Content-Security-Policy',
|
|
'sandbox allow-downloads allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts allow-top-navigation-by-user-activation allow-top-navigation-to-custom-protocols',
|
|
);
|
|
|
|
expect(mockRender).toHaveBeenCalledWith('form-trigger', {
|
|
testRun: false,
|
|
formTitle: 'Test title',
|
|
formDescription: 'Test description',
|
|
formDescriptionMetadata: 'Test description',
|
|
formSubmittedHeader: 'Got it, thanks',
|
|
formSubmittedText: 'This page can be closed now',
|
|
n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger',
|
|
formFields: [
|
|
{
|
|
id: 'field-0',
|
|
errorId: 'error-field-0',
|
|
inputRequired: 'form-required',
|
|
defaultValue: '',
|
|
isInput: true,
|
|
type: 'text',
|
|
},
|
|
],
|
|
appendAttribution: true,
|
|
buttonLabel: 'Test button',
|
|
dangerousCustomCss: 'body { background-color: red; }',
|
|
});
|
|
});
|
|
|
|
it('should resolve expressions in HTML fields for customForm GET webhook', async () => {
|
|
const mockRender = jest.fn();
|
|
const mockSetHeader = jest.fn();
|
|
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'GET',
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
render: mockRender,
|
|
setHeader: mockSetHeader,
|
|
} as any);
|
|
|
|
// Mock evaluateExpression to resolve the expression
|
|
mockWebhookFunctions.evaluateExpression.mockImplementation((expression) => {
|
|
if (expression === '{{ $json.videoUrl }}') {
|
|
return 'https://example.com/video.mp4';
|
|
}
|
|
return expression;
|
|
});
|
|
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
responseType: 'customForm',
|
|
message: 'Test message',
|
|
defineForm: 'fields',
|
|
'formFields.values': [
|
|
{
|
|
fieldLabel: 'Custom HTML',
|
|
fieldType: 'html',
|
|
// Use <source> tag inside <video> since sanitizeHtml allows src on source, not video
|
|
html: '<video controls><source src="{{ $json.videoUrl }}" type="video/mp4" /></video>',
|
|
},
|
|
],
|
|
options: {},
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(mockRender).toHaveBeenCalledWith(
|
|
'form-trigger',
|
|
expect.objectContaining({
|
|
formFields: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
html: '<video controls><source src="https://example.com/video.mp4" type="video/mp4"></source></video>',
|
|
}),
|
|
]),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should handle customForm POST webhook', async () => {
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'POST',
|
|
contentType: 'multipart/form-data',
|
|
} as any);
|
|
mockWebhookFunctions.getNode.mockReturnValue({} as any);
|
|
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
responseType: 'customForm',
|
|
defineForm: 'fields',
|
|
'formFields.values': [
|
|
{
|
|
fieldLabel: 'test 1',
|
|
fieldType: 'text',
|
|
},
|
|
],
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
mockWebhookFunctions.getBodyData.mockReturnValue({
|
|
data: {
|
|
'field-0': 'test value',
|
|
},
|
|
} as any);
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result.workflowData).toEqual([[{ json: { data: { 'test 1': 'test value' } } }]]);
|
|
});
|
|
|
|
it('should return noWebhookResponse if method GET and user-agent is bot', async () => {
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'GET',
|
|
headers: {
|
|
'user-agent': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
|
|
},
|
|
query: { approved: 'false' },
|
|
} as any);
|
|
|
|
const send = jest.fn();
|
|
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
send,
|
|
} as any);
|
|
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => {
|
|
const params: { [key: string]: any } = {
|
|
responseType: 'approval',
|
|
};
|
|
return params[parameterName];
|
|
});
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(send).toHaveBeenCalledWith('');
|
|
expect(result).toEqual({ noWebhookResponse: true });
|
|
});
|
|
|
|
it('should return noWebhookResponse if user-agent is empty (Microsoft Preview Service)', async () => {
|
|
const send = jest.fn();
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
headers: {},
|
|
query: { approved: 'true' },
|
|
} as unknown as Request);
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
send,
|
|
} as unknown as Response);
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation(
|
|
(parameterName: string, fallbackValue?: any) => {
|
|
const params: Record<string, unknown> = { responseType: 'approval' };
|
|
return params[parameterName] ?? fallbackValue;
|
|
},
|
|
);
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({ noWebhookResponse: true });
|
|
expect(send).toHaveBeenCalledWith('');
|
|
});
|
|
|
|
it.each([
|
|
'SkypeSpaces/1.0',
|
|
'Microsoft Teams/1.0',
|
|
'SkypeUriPreview Preview/1.0',
|
|
'Preview Service/1.0',
|
|
])(
|
|
'should return noWebhookResponse if user-agent contains %s (Microsoft Preview Service)',
|
|
async (userAgent) => {
|
|
const send = jest.fn();
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
headers: { 'user-agent': userAgent },
|
|
query: { approved: 'true' },
|
|
} as unknown as Request);
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
send,
|
|
} as unknown as Response);
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation(
|
|
(parameterName: string, fallbackValue?: any) => {
|
|
const params: Record<string, unknown> = { responseType: 'approval' };
|
|
return params[parameterName] ?? fallbackValue;
|
|
},
|
|
);
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({ noWebhookResponse: true });
|
|
expect(send).toHaveBeenCalledWith('');
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
['freeText' as const, ''],
|
|
['freeText' as const, 'SkypeUriPreview Preview/1.0'],
|
|
['customForm' as const, ''],
|
|
['customForm' as const, 'SkypeUriPreview Preview/1.0'],
|
|
])(
|
|
'should not block Microsoft Preview Service when responseType is %s (user-agent: %s)',
|
|
async (responseType, userAgent) => {
|
|
const mockRender = jest.fn();
|
|
const mockSetHeader = jest.fn();
|
|
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
|
method: 'GET',
|
|
headers: { 'user-agent': userAgent },
|
|
query: {},
|
|
} as unknown as Request);
|
|
mockWebhookFunctions.getResponseObject.mockReturnValue({
|
|
render: mockRender,
|
|
setHeader: mockSetHeader,
|
|
} as unknown as Response);
|
|
const formFieldParams: Record<string, unknown> =
|
|
responseType === 'customForm'
|
|
? {
|
|
defineForm: 'fields',
|
|
'formFields.values': [{ label: 'Field 1', fieldType: 'text', requiredField: true }],
|
|
}
|
|
: {};
|
|
mockWebhookFunctions.getNodeParameter.mockImplementation(
|
|
(parameterName: string, fallbackValue?: any) => {
|
|
const params: Record<string, unknown> = {
|
|
responseType,
|
|
message: 'Test message',
|
|
options: {},
|
|
...formFieldParams,
|
|
};
|
|
return params[parameterName] ?? fallbackValue;
|
|
},
|
|
);
|
|
|
|
const result = await sendAndWaitWebhook.call(mockWebhookFunctions);
|
|
|
|
expect(result).toEqual({ noWebhookResponse: true });
|
|
expect(mockRender).toHaveBeenCalled();
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('configureWaitTillDate', () => {
|
|
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
|
|
|
beforeEach(() => {
|
|
mockExecuteFunctions = mock<IExecuteFunctions>();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should return WAIT_INDEFINITELY if limitWaitTime is empty', () => {
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({});
|
|
const result = configureWaitTillDate(mockExecuteFunctions);
|
|
expect(result).toBe(WAIT_INDEFINITELY);
|
|
});
|
|
|
|
it('should calculate future date correctly for afterTimeInterval with minutes', () => {
|
|
const resumeAmount = 5;
|
|
const resumeUnit = 'minutes';
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({
|
|
limitType: 'afterTimeInterval',
|
|
resumeAmount,
|
|
resumeUnit,
|
|
});
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions);
|
|
const expectedDate = new Date(new Date().getTime() + 5 * 60 * 1000);
|
|
expect(result.getTime()).toBeCloseTo(expectedDate.getTime(), -2); // Allowing 100ms difference
|
|
});
|
|
|
|
it('should calculate future date correctly for afterTimeInterval with hours', () => {
|
|
const resumeAmount = 2;
|
|
const resumeUnit = 'hours';
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({
|
|
limitType: 'afterTimeInterval',
|
|
resumeAmount,
|
|
resumeUnit,
|
|
});
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions);
|
|
const expectedDate = new Date(new Date().getTime() + 2 * 60 * 60 * 1000);
|
|
expect(result.getTime()).toBeCloseTo(expectedDate.getTime(), -2);
|
|
});
|
|
|
|
it('should calculate future date correctly for afterTimeInterval with days', () => {
|
|
const resumeAmount = 1;
|
|
const resumeUnit = 'days';
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({
|
|
limitType: 'afterTimeInterval',
|
|
resumeAmount,
|
|
resumeUnit,
|
|
});
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions);
|
|
const expectedDate = new Date(new Date().getTime() + 1 * 24 * 60 * 60 * 1000);
|
|
expect(result.getTime()).toBeCloseTo(expectedDate.getTime(), -2);
|
|
});
|
|
|
|
it('should return the specified maxDateAndTime for maxDateAndTime limitType', () => {
|
|
const maxDateAndTime = '2023-12-31T23:59:59Z';
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({
|
|
limitType: 'maxDateAndTime',
|
|
maxDateAndTime,
|
|
});
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions);
|
|
expect(result).toEqual(new Date(maxDateAndTime));
|
|
});
|
|
|
|
it('should throw NodeOperationError for invalid maxDateAndTime format', () => {
|
|
const invalidMaxDateAndTime = 'invalid-date';
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValue({
|
|
limitType: 'maxDateAndTime',
|
|
maxDateAndTime: invalidMaxDateAndTime,
|
|
});
|
|
|
|
expect(() => configureWaitTillDate(mockExecuteFunctions)).toThrow(NodeOperationError);
|
|
expect(() => configureWaitTillDate(mockExecuteFunctions)).toThrow(
|
|
'Could not configure Limit Wait Time',
|
|
);
|
|
});
|
|
|
|
it('should throw NodeOperationError for invalid resumeAmount or resumeUnit', () => {
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValue({
|
|
limitType: 'afterTimeInterval',
|
|
resumeAmount: 'invalid',
|
|
resumeUnit: 'minutes',
|
|
});
|
|
|
|
expect(() => configureWaitTillDate(mockExecuteFunctions)).toThrow(NodeOperationError);
|
|
expect(() => configureWaitTillDate(mockExecuteFunctions)).toThrow(
|
|
'Could not configure Limit Wait Time',
|
|
);
|
|
});
|
|
|
|
it('should return WAIT_INDEFINITELY when limitWaitTime is false', () => {
|
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(false);
|
|
const result = configureWaitTillDate(mockExecuteFunctions, 'root');
|
|
expect(result).toBe(WAIT_INDEFINITELY);
|
|
});
|
|
|
|
it('should calculate minutes correctly in root location', () => {
|
|
mockExecuteFunctions.getNodeParameter
|
|
.mockReturnValueOnce(true) // limitWaitTime
|
|
.mockReturnValueOnce('afterTimeInterval') // limitType
|
|
.mockReturnValueOnce(15) // resumeAmount
|
|
.mockReturnValueOnce('minutes'); // resumeUnit
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions, 'root');
|
|
const expectedDate = new Date(new Date().getTime() + 15 * 60 * 1000);
|
|
expect(result.getTime()).toBeCloseTo(expectedDate.getTime(), -2);
|
|
});
|
|
|
|
it('should calculate hours correctly in root location', () => {
|
|
mockExecuteFunctions.getNodeParameter
|
|
.mockReturnValueOnce(true)
|
|
.mockReturnValueOnce('afterTimeInterval')
|
|
.mockReturnValueOnce(3)
|
|
.mockReturnValueOnce('hours');
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions, 'root');
|
|
const expectedDate = new Date(new Date().getTime() + 3 * 60 * 60 * 1000);
|
|
expect(result.getTime()).toBeCloseTo(expectedDate.getTime(), -2);
|
|
});
|
|
|
|
it('should calculate days correctly in root location', () => {
|
|
mockExecuteFunctions.getNodeParameter
|
|
.mockReturnValueOnce(true)
|
|
.mockReturnValueOnce('afterTimeInterval')
|
|
.mockReturnValueOnce(5)
|
|
.mockReturnValueOnce('days');
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions, 'root');
|
|
const expectedDate = new Date(new Date().getTime() + 5 * 24 * 60 * 60 * 1000);
|
|
expect(result.getTime()).toBeCloseTo(expectedDate.getTime(), -2);
|
|
});
|
|
|
|
it('should handle maxDateAndTime in root location', () => {
|
|
const maxDateAndTime = '2024-12-31T23:59:59Z';
|
|
mockExecuteFunctions.getNodeParameter
|
|
.mockReturnValueOnce(true)
|
|
.mockReturnValueOnce('maxDateAndTime')
|
|
.mockReturnValueOnce(maxDateAndTime);
|
|
|
|
const result = configureWaitTillDate(mockExecuteFunctions, 'root');
|
|
expect(result).toEqual(new Date(maxDateAndTime));
|
|
});
|
|
|
|
it('should throw error for invalid date in root location', () => {
|
|
mockExecuteFunctions.getNodeParameter
|
|
.mockReturnValueOnce(true)
|
|
.mockReturnValueOnce('maxDateAndTime')
|
|
.mockReturnValueOnce('not-a-valid-date');
|
|
|
|
expect(() => configureWaitTillDate(mockExecuteFunctions, 'root')).toThrow(NodeOperationError);
|
|
});
|
|
|
|
it('should throw error for invalid resumeAmount in root location', () => {
|
|
mockExecuteFunctions.getNodeParameter
|
|
.mockReturnValueOnce(true)
|
|
.mockReturnValueOnce('afterTimeInterval')
|
|
.mockReturnValueOnce('not-a-number')
|
|
.mockReturnValueOnce('minutes');
|
|
|
|
expect(() => configureWaitTillDate(mockExecuteFunctions, 'root')).toThrow(NodeOperationError);
|
|
});
|
|
});
|