import { mock, mockDeep } from 'jest-mock-extended'; import type { IExecuteFunctions, INode } from 'n8n-workflow'; import { Mailjet } from '../Mailjet.node'; import * as GenericFunctions from '../GenericFunctions'; describe('Mailjet Node', () => { let mailjetNode: Mailjet; let mockExecuteFunctions: jest.Mocked; const mailjetApiRequestSpy = jest.spyOn(GenericFunctions, 'mailjetApiRequest'); beforeEach(() => { mailjetNode = new Mailjet(); mockExecuteFunctions = mockDeep({ helpers: { constructExecutionMetaData: jest.fn().mockImplementation((data) => data), returnJsonArray: jest .fn() .mockImplementation((data) => Array.isArray(data) ? data.map((item) => ({ json: item })) : [{ json: data }], ), }, }); jest.clearAllMocks(); mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]); mockExecuteFunctions.getNode.mockReturnValue( mock({ id: 'test-mailjet-node', name: 'Mailjet Test', type: 'n8n-nodes-base.mailjet', typeVersion: 1, position: [0, 0], parameters: {}, }), ); mockExecuteFunctions.continueOnFail.mockReturnValue(false); }); afterEach(() => { jest.resetAllMocks(); }); describe('Email Resource - Send Operation', () => { beforeEach(() => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string, _: number) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, additionalFields: {}, }; return params[paramName]; }); mailjetApiRequestSpy.mockResolvedValue({ Messages: [ { Status: 'success', To: [{ Email: 'recipient@example.com', MessageID: 123456789 }], }, ], }); }); it('should send email with customCampaign field', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'my-custom-campaign', }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ CustomCampaign: 'my-custom-campaign', }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send email with deduplicateCampaign field set to true', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { deduplicateCampaign: true, }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ DeduplicateCampaign: true, }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should not include deduplicateCampaign field when set to false', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { deduplicateCampaign: false, }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.not.objectContaining({ DeduplicateCampaign: expect.anything(), }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send email with both customCampaign and deduplicateCampaign fields', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'my-campaign-2024', deduplicateCampaign: true, }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ CustomCampaign: 'my-campaign-2024', DeduplicateCampaign: true, }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send email without customCampaign and deduplicateCampaign when not provided', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: {}, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.not.objectContaining({ CustomCampaign: expect.anything(), DeduplicateCampaign: expect.anything(), }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send email with other additional fields alongside customCampaign', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test HTML Body

', text: 'Test Text Body', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { fromName: 'Test Sender', priority: 3, customCampaign: 'multi-field-campaign', trackOpens: 'enabled', }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ From: expect.objectContaining({ Name: 'Test Sender', }), Priority: 3, CustomCampaign: 'multi-field-campaign', TrackOpens: 'enabled', }), ]), }), ); expect(result).toHaveLength(1); }); }); describe('Email Resource - Send Template Operation', () => { beforeEach(() => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'sendTemplate', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', templateId: '12345', subject: 'Test Template Email', jsonParameters: false, additionalFields: {}, }; return params[paramName]; }); mailjetApiRequestSpy.mockResolvedValue({ Messages: [ { Status: 'success', To: [{ Email: 'recipient@example.com', MessageID: 987654321 }], }, ], }); }); it('should send template email with customCampaign field', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'sendTemplate', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', templateId: '12345', subject: 'Test Template Email', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'template-campaign', }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ TemplateID: 12345, CustomCampaign: 'template-campaign', }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send template email with deduplicateCampaign field set to true', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'sendTemplate', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', templateId: '12345', subject: 'Test Template Email', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { deduplicateCampaign: true, }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ TemplateID: 12345, DeduplicateCampaign: true, }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send template email with customCampaign but not include deduplicateCampaign when false', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'sendTemplate', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', templateId: '12345', subject: 'Test Template Email', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'template-campaign-v2', deduplicateCampaign: false, }, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ TemplateID: 12345, CustomCampaign: 'template-campaign-v2', }), ]), }), ); const calls = mailjetApiRequestSpy.mock.calls; const lastCall = calls[calls.length - 1]; expect(lastCall[2].Messages[0]).not.toHaveProperty('DeduplicateCampaign'); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); it('should send template email without customCampaign and deduplicateCampaign when not provided', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'sendTemplate', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', templateId: '12345', subject: 'Test Template Email', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: {}, }; return params[paramName]; }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledWith( 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.not.objectContaining({ CustomCampaign: expect.anything(), DeduplicateCampaign: expect.anything(), }), ]), }), ); expect(result).toHaveLength(1); expect(result[0]).toHaveLength(1); }); }); describe('Multiple Items Processing', () => { it('should handle multiple items with different customCampaign values', async () => { mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }, { json: {} }]); mockExecuteFunctions.getNodeParameter.mockImplementation( (paramName: string, index: number) => { const paramsForIndex: Array> = [ { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient1@example.com', subject: 'Test Email 1', html: '

Test 1

', text: 'Test 1', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'campaign-1', deduplicateCampaign: true, }, }, { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient2@example.com', subject: 'Test Email 2', html: '

Test 2

', text: 'Test 2', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'campaign-2', deduplicateCampaign: false, }, }, ]; return paramsForIndex[index][paramName]; }, ); mailjetApiRequestSpy.mockResolvedValue({ Messages: [{ Status: 'success' }], }); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(mailjetApiRequestSpy).toHaveBeenCalledTimes(2); expect(mailjetApiRequestSpy).toHaveBeenNthCalledWith( 1, 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ CustomCampaign: 'campaign-1', DeduplicateCampaign: true, }), ]), }), ); expect(mailjetApiRequestSpy).toHaveBeenNthCalledWith( 2, 'POST', '/v3.1/send', expect.objectContaining({ Messages: expect.arrayContaining([ expect.objectContaining({ CustomCampaign: 'campaign-2', }), ]), }), ); const secondCall = mailjetApiRequestSpy.mock.calls[1]; expect(secondCall[2].Messages[0]).not.toHaveProperty('DeduplicateCampaign'); expect(result[0]).toHaveLength(2); }); }); describe('Error Handling', () => { it('should handle API errors gracefully when continueOnFail is true', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test

', text: 'Test', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { customCampaign: 'error-campaign', }, }; return params[paramName]; }); mockExecuteFunctions.continueOnFail.mockReturnValue(true); mailjetApiRequestSpy.mockRejectedValue(new Error('API Error')); const result = await mailjetNode.execute.call(mockExecuteFunctions); expect(result).toHaveLength(1); expect(result[0][0].json).toHaveProperty('error', 'API Error'); }); it('should throw error when continueOnFail is false', async () => { mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => { const params: { [key: string]: any } = { resource: 'email', operation: 'send', fromEmail: 'sender@example.com', toEmail: 'recipient@example.com', subject: 'Test Email', html: '

Test

', text: 'Test', jsonParameters: false, variablesUi: { variablesValues: [] }, additionalFields: { deduplicateCampaign: true, }, }; return params[paramName]; }); mockExecuteFunctions.continueOnFail.mockReturnValue(false); mailjetApiRequestSpy.mockRejectedValue(new Error('API Error')); await expect(mailjetNode.execute.call(mockExecuteFunctions)).rejects.toThrow('API Error'); }); }); });