diff --git a/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts b/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts index 12c5b98b92..37bd59c4c2 100644 --- a/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts +++ b/packages/nodes-base/nodes/Mandrill/Mandrill.node.ts @@ -766,8 +766,8 @@ export class Mandrill implements INodeType { message.from_name = options.fromName; } - if (options.subaccount) { - message.subaccount = options.subaccount; + if (options.subAccount) { + message.subaccount = options.subAccount.toString(); } const body: Body = { diff --git a/packages/nodes-base/nodes/Mandrill/test/GenericFunctions.test.ts b/packages/nodes-base/nodes/Mandrill/test/GenericFunctions.test.ts new file mode 100644 index 0000000000..d70bc49e52 --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/test/GenericFunctions.test.ts @@ -0,0 +1,143 @@ +import { + getGoogleAnalyticsDomainsArray, + getTags, + getToEmailArray, + validateJSON, +} from '../GenericFunctions'; + +describe('Mandrill GenericFunctions', () => { + describe('getToEmailArray', () => { + it('should convert single email to array', () => { + const result = getToEmailArray('test@example.com'); + expect(result).toEqual([ + { + email: 'test@example.com', + type: 'to', + }, + ]); + }); + + it('should convert comma-separated emails to array', () => { + const result = getToEmailArray('test1@example.com,test2@example.com,test3@example.com'); + expect(result).toEqual([ + { + email: 'test1@example.com', + type: 'to', + }, + { + email: 'test2@example.com', + type: 'to', + }, + { + email: 'test3@example.com', + type: 'to', + }, + ]); + }); + + it('should handle emails with spaces after commas', () => { + const result = getToEmailArray('test1@example.com, test2@example.com, test3@example.com'); + expect(result).toEqual([ + { + email: 'test1@example.com', + type: 'to', + }, + { + email: ' test2@example.com', + type: 'to', + }, + { + email: ' test3@example.com', + type: 'to', + }, + ]); + }); + }); + + describe('getGoogleAnalyticsDomainsArray', () => { + it('should convert single domain to array', () => { + const result = getGoogleAnalyticsDomainsArray('example.com'); + expect(result).toEqual(['example.com']); + }); + + it('should convert comma-separated domains to array', () => { + const result = getGoogleAnalyticsDomainsArray('example.com,test.com,demo.org'); + expect(result).toEqual(['example.com', 'test.com', 'demo.org']); + }); + + it('should handle domains with spaces after commas', () => { + const result = getGoogleAnalyticsDomainsArray('example.com, test.com, demo.org'); + expect(result).toEqual(['example.com', ' test.com', ' demo.org']); + }); + + it('should handle empty string', () => { + const result = getGoogleAnalyticsDomainsArray(''); + expect(result).toEqual(['']); + }); + }); + + describe('getTags', () => { + it('should convert single tag to array', () => { + const result = getTags('newsletter'); + expect(result).toEqual(['newsletter']); + }); + + it('should convert comma-separated tags to array', () => { + const result = getTags('newsletter,marketing,promotion'); + expect(result).toEqual(['newsletter', 'marketing', 'promotion']); + }); + + it('should handle tags with spaces after commas', () => { + const result = getTags('newsletter, marketing, promotion'); + expect(result).toEqual(['newsletter', ' marketing', ' promotion']); + }); + + it('should handle empty string', () => { + const result = getTags(''); + expect(result).toEqual(['']); + }); + }); + + describe('validateJSON', () => { + it('should parse valid JSON object', () => { + const result = validateJSON('{"Test": "value", "number": 123}'); + expect(result).toEqual({ Test: 'value', number: 123 }); + }); + + it('should parse valid JSON array', () => { + const result = validateJSON('[{"name": "Test", "value": "data"}]'); + expect(result).toEqual([{ name: 'Test', value: 'data' }]); + }); + + it('should return empty array for invalid JSON', () => { + const result = validateJSON('invalid json'); + expect(result).toEqual([]); + }); + + it('should return empty array for undefined input', () => { + const result = validateJSON(undefined); + expect(result).toEqual([]); + }); + + it('should return null for null JSON string', () => { + const result = validateJSON('null'); + expect(result).toEqual(null); + }); + + it('should parse nested JSON correctly', () => { + const result = validateJSON('{"metadata": {"key": "value"}, "array": [1, 2, 3]}'); + expect(result).toEqual({ + metadata: { key: 'value' }, + array: [1, 2, 3], + }); + }); + + it('should handle JSON with special characters', () => { + const result = validateJSON('{"message": "Hello\\nWorld\\t!", "emoji": "🎉"}'); + expect(result).toEqual({ + message: 'Hello\nWorld\t!', + emoji: '🎉', + }); + }); + }); +}); diff --git a/packages/nodes-base/nodes/Mandrill/test/Mandrill.node.test.ts b/packages/nodes-base/nodes/Mandrill/test/Mandrill.node.test.ts new file mode 100644 index 0000000000..d9d9365972 --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/test/Mandrill.node.test.ts @@ -0,0 +1,101 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; +import { mock, mockDeep } from 'jest-mock-extended'; +import type { ILoadOptionsFunctions, INode } from 'n8n-workflow'; +import nock from 'nock'; + +import { Mandrill } from '../Mandrill.node'; + +describe('Test Mandrill Node', () => { + describe('Messages', () => { + const mandrillNock = nock('https://mandrillapp.com/api/1.0'); + + beforeAll(() => { + // Mock sendTemplate API call with subaccount verification + mandrillNock + .post('/messages/send-template.json', (body) => { + // Verify that subaccount is properly passed to the API + return ( + body.message && + body.message.subaccount === 'test-subaccount' && + body.template_name === 'test-template' + ); + }) + .reply(200, [ + { + email: 'recipient@example.com', + status: 'sent', + reject_reason: null, + _id: 'test-message-id-456', + }, + ]); + + // Mock sendTemplate API call specifically for subaccount regression test + mandrillNock + .post('/messages/send-template.json', (body) => { + // Verify regression test scenario: subaccount is properly passed + return ( + body.message && + body.message.subaccount === 'regression-test-subaccount' && + body.template_name === 'test-template-subaccount' + ); + }) + .reply(200, [ + { + email: 'recipient@example.com', + status: 'sent', + reject_reason: null, + _id: 'test-subaccount-message-id', + }, + ]); + + // Mock sendHtml API call + mandrillNock.post('/messages/send.json').reply(200, [ + { + email: 'recipient@example.com', + status: 'rejected', + _id: 'test-message-id-123', + reject_reason: 'global-block', + queued_reason: null, + }, + ]); + }); + + afterAll(() => mandrillNock.done()); + + new NodeTestHarness().setupTests({ + workflowFiles: [ + 'sendTemplate.workflow.json', + 'sendTemplateWithSubaccount.workflow.json', + 'sendHtml.workflow.json', + ], + }); + }); + + describe('loadOptions', () => { + describe('getTemplates', () => { + it('should return a list of Mandrill templates', async () => { + const mandrill = new Mandrill(); + const loadOptionsFunctions = mockDeep(); + loadOptionsFunctions.getNode.mockReturnValue(mock()); + loadOptionsFunctions.getCredentials.mockResolvedValue({ apiKey: 'test-api-key' }); + loadOptionsFunctions.helpers.request.mockResolvedValue([ + { + slug: 'template-1', + name: 'Test Template 1', + }, + { + slug: 'template-2', + name: 'Test Template 2', + }, + ]); + + const result = await mandrill.methods.loadOptions.getTemplates.call(loadOptionsFunctions); + + expect(result).toEqual([ + { name: 'Test Template 1', value: 'template-1' }, + { name: 'Test Template 2', value: 'template-2' }, + ]); + }); + }); + }); +}); diff --git a/packages/nodes-base/nodes/Mandrill/test/sendHtml.workflow.json b/packages/nodes-base/nodes/Mandrill/test/sendHtml.workflow.json new file mode 100644 index 0000000000..142e9ea866 --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/test/sendHtml.workflow.json @@ -0,0 +1,72 @@ +{ + "name": "sendHtml", + "nodes": [ + { + "parameters": {}, + "id": "test-trigger-node-id", + "name": "When clicking 'Execute workflow'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0] + }, + { + "parameters": { + "operation": "sendHtml", + "fromEmail": "sender@example.com", + "toEmail": "recipient@example.com", + "options": { + "html": "

Test HTML Content

", + "subject": "Test HTML Subject" + } + }, + "id": "test-mandrill-node-id", + "name": "Mandrill", + "type": "n8n-nodes-base.mandrill", + "typeVersion": 1, + "position": [220, 0], + "credentials": { + "mandrillApi": { + "id": "test-credentials-id", + "name": "Test Mandrill API" + } + } + } + ], + "connections": { + "When clicking 'Execute workflow'": { + "main": [ + [ + { + "node": "Mandrill", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Mandrill": [ + { + "json": { + "email": "recipient@example.com", + "status": "rejected", + "_id": "test-message-id-123", + "reject_reason": "global-block", + "queued_reason": null + } + } + ] + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "test-version-id", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "test-instance-id" + }, + "id": "test-workflow-id-2", + "tags": [] +} diff --git a/packages/nodes-base/nodes/Mandrill/test/sendTemplate.workflow.json b/packages/nodes-base/nodes/Mandrill/test/sendTemplate.workflow.json new file mode 100644 index 0000000000..1bc6769f1a --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/test/sendTemplate.workflow.json @@ -0,0 +1,71 @@ +{ + "name": "sendTemplate", + "nodes": [ + { + "parameters": {}, + "id": "test-trigger-node-id", + "name": "When clicking 'Execute workflow'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0] + }, + { + "parameters": { + "resource": "message", + "operation": "sendTemplate", + "template": "test-template", + "fromEmail": "sender@example.com", + "toEmail": "recipient@example.com", + "options": { + "subAccount": "test-subaccount" + } + }, + "id": "test-mandrill-node-id", + "name": "Mandrill", + "type": "n8n-nodes-base.mandrill", + "typeVersion": 1, + "position": [220, 0], + "credentials": { + "mandrillApi": { + "id": "test-credentials-id", + "name": "Test Mandrill API" + } + } + } + ], + "connections": { + "When clicking 'Execute workflow'": { + "main": [ + [ + { + "node": "Mandrill", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Mandrill": [ + { + "json": { + "email": "recipient@example.com", + "status": "sent", + "reject_reason": null, + "_id": "test-message-id-456" + } + } + ] + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "test-version-id", + "meta": { + "instanceId": "test-instance" + }, + "id": "test-workflow-id", + "tags": [] +} diff --git a/packages/nodes-base/nodes/Mandrill/test/sendTemplateWithSubaccount.workflow.json b/packages/nodes-base/nodes/Mandrill/test/sendTemplateWithSubaccount.workflow.json new file mode 100644 index 0000000000..46e071db13 --- /dev/null +++ b/packages/nodes-base/nodes/Mandrill/test/sendTemplateWithSubaccount.workflow.json @@ -0,0 +1,72 @@ +{ + "name": "sendTemplateWithSubaccount", + "nodes": [ + { + "parameters": {}, + "id": "test-trigger-node-id", + "name": "When clicking 'Execute workflow'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0] + }, + { + "parameters": { + "resource": "message", + "operation": "sendTemplate", + "template": "test-template-subaccount", + "fromEmail": "sender@example.com", + "toEmail": "recipient@example.com", + "options": { + "subAccount": "regression-test-subaccount", + "subject": "Testing subaccount functionality" + } + }, + "id": "test-mandrill-node-id", + "name": "Mandrill", + "type": "n8n-nodes-base.mandrill", + "typeVersion": 1, + "position": [220, 0], + "credentials": { + "mandrillApi": { + "id": "test-credentials-id", + "name": "Test Mandrill API" + } + } + } + ], + "connections": { + "When clicking 'Execute workflow'": { + "main": [ + [ + { + "node": "Mandrill", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Mandrill": [ + { + "json": { + "email": "recipient@example.com", + "status": "sent", + "reject_reason": null, + "_id": "test-subaccount-message-id" + } + } + ] + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "versionId": "test-version-id", + "meta": { + "instanceId": "test-instance" + }, + "id": "test-workflow-subaccount-id", + "tags": [] +}