import { type MockProxy, mock } from 'jest-mock-extended'; import type { IExecuteFunctions, INodeProperties, IWebhookFunctions } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; import { getSendAndWaitProperties, getSendAndWaitConfig, createEmail, sendAndWaitWebhook, } from '../utils'; describe('Send and Wait utils tests', () => { let mockExecuteFunctions: MockProxy; let mockWebhookFunctions: MockProxy; beforeEach(() => { mockExecuteFunctions = mock(); mockWebhookFunctions = mock(); }); describe('getSendAndWaitProperties', () => { it('should return properties with correct display options', () => { const targetProperties: INodeProperties[] = [ { displayName: 'Test Property', name: 'testProperty', type: 'string', default: '', }, ]; const result = getSendAndWaitProperties(targetProperties); 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.evaluateExpression.mockImplementation((expression: string) => { const expressions: { [key: string]: string } = { '{{ $execution?.resumeUrl }}': 'http://localhost', '{{ $nodeId }}': 'testNodeId', }; return expressions[expression]; }); const config = getSendAndWaitConfig(mockExecuteFunctions); expect(config).toEqual({ title: 'Test subject', message: 'Test message', url: 'http://localhost/testNodeId', options: [ { label: 'Approve', value: 'true', style: 'primary', }, ], }); }); 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.evaluateExpression.mockImplementation((expression: string) => { const expressions: { [key: string]: string } = { '{{ $execution?.resumeUrl }}': 'http://localhost', '{{ $nodeId }}': 'testNodeId', }; return expressions[expression]; }); const config = getSendAndWaitConfig(mockExecuteFunctions); expect(config.options).toHaveLength(2); expect(config.options).toEqual( expect.arrayContaining([ { label: 'Reject', value: 'false', style: 'secondary', }, { label: 'Approve', value: 'true', style: 'primary', }, ]), ); }); }); 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.evaluateExpression.mockImplementation((expression: string) => { const expressions: { [key: string]: string } = { '{{ $execution?.resumeUrl }}': 'http://localhost', '{{ $nodeId }}': 'testNodeId', }; return expressions[expression]; }); }); 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(); mockWebhookFunctions.getRequestObject.mockReturnValue({ method: 'GET', } as any); mockWebhookFunctions.getResponseObject.mockReturnValue({ render: mockRender, } 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(mockRender).toHaveBeenCalledWith('form-trigger', { testRun: false, validForm: true, formTitle: '', formDescription: '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(); mockWebhookFunctions.getRequestObject.mockReturnValue({ method: 'GET', } as any); mockWebhookFunctions.getResponseObject.mockReturnValue({ render: mockRender, } 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', }, }; return params[parameterName]; }); const result = await sendAndWaitWebhook.call(mockWebhookFunctions); expect(result).toEqual({ noWebhookResponse: true, }); expect(mockRender).toHaveBeenCalledWith('form-trigger', { testRun: false, validForm: true, formTitle: 'Test title', formDescription: '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', }); }); it('should handle customForm POST webhook', async () => { mockWebhookFunctions.getRequestObject.mockReturnValue({ method: 'POST', } 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' } } }]]); }); }); });