diff --git a/packages/nodes-base/nodes/Brevo/Brevo.node.ts b/packages/nodes-base/nodes/Brevo/Brevo.node.ts index 2a3fa4165ee..678fba3a0ed 100644 --- a/packages/nodes-base/nodes/Brevo/Brevo.node.ts +++ b/packages/nodes-base/nodes/Brevo/Brevo.node.ts @@ -14,7 +14,7 @@ export class Brevo implements INodeType { name: 'sendInBlue', icon: 'file:brevo.svg', group: ['transform'], - version: 1, + version: [1, 1.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Brevo API', defaults: { diff --git a/packages/nodes-base/nodes/Brevo/EmailDescription.ts b/packages/nodes-base/nodes/Brevo/EmailDescription.ts index c81e413dc08..dcdc76b4e7d 100644 --- a/packages/nodes-base/nodes/Brevo/EmailDescription.ts +++ b/packages/nodes-base/nodes/Brevo/EmailDescription.ts @@ -133,6 +133,26 @@ const sendHtmlEmailFields: INodeProperties[] = [ show: { resource: ['email'], operation: ['send'], + '@version': [1], + }, + }, + default: '', + required: true, + routing: { + send: { + preSend: [BrevoNode.Validators.validateAndCompileRecipientEmails], + }, + }, + }, + { + displayName: 'Recipients', + name: 'recipients', + type: 'string', + displayOptions: { + show: { + resource: ['email'], + operation: ['send'], + '@version': [{ _cnd: { gte: 1.1 } }], }, }, default: '', @@ -191,6 +211,11 @@ const sendHtmlEmailFields: INodeProperties[] = [ placeholder: 'Add BCC', type: 'fixedCollection', default: {}, + displayOptions: { + show: { + '@version': [1], + }, + }, options: [ { name: 'receipientBcc', @@ -211,12 +236,48 @@ const sendHtmlEmailFields: INodeProperties[] = [ }, }, }, + { + displayName: 'Recipients BCC', + name: 'recipientsBCC', + placeholder: 'Add BCC', + type: 'fixedCollection', + default: {}, + displayOptions: { + show: { + '@version': [{ _cnd: { gte: 1.1 } }], + }, + }, + options: [ + { + name: 'recipientBcc', + displayName: 'Recipient', + values: [ + { + displayName: 'Recipient', + name: 'bcc', + type: 'string', + default: '', + }, + ], + }, + ], + routing: { + send: { + preSend: [BrevoNode.Validators.validateAndCompileBCCEmails], + }, + }, + }, { displayName: 'Recipients CC', name: 'receipientsCC', placeholder: 'Add CC', type: 'fixedCollection', default: {}, + displayOptions: { + show: { + '@version': [1], + }, + }, options: [ { name: 'receipientCc', @@ -237,6 +298,37 @@ const sendHtmlEmailFields: INodeProperties[] = [ }, }, }, + { + displayName: 'Recipients CC', + name: 'recipientsCC', + placeholder: 'Add CC', + type: 'fixedCollection', + default: {}, + displayOptions: { + show: { + '@version': [{ _cnd: { gte: 1.1 } }], + }, + }, + options: [ + { + name: 'recipientCc', + displayName: 'Recipient', + values: [ + { + displayName: 'Recipient', + name: 'cc', + type: 'string', + default: '', + }, + ], + }, + ], + routing: { + send: { + preSend: [BrevoNode.Validators.validateAndCompileCCEmails], + }, + }, + }, { displayName: 'Email Tags', name: 'emailTags', @@ -334,6 +426,26 @@ const sendHtmlTemplateEmailFields: INodeProperties[] = [ show: { resource: ['email'], operation: ['sendTemplate'], + '@version': [1], + }, + }, + default: '', + required: true, + routing: { + send: { + preSend: [BrevoNode.Validators.validateAndCompileRecipientEmails], + }, + }, + }, + { + displayName: 'Recipients', + name: 'recipients', + type: 'string', + displayOptions: { + show: { + resource: ['email'], + operation: ['sendTemplate'], + '@version': [{ _cnd: { gte: 1.1 } }], }, }, default: '', diff --git a/packages/nodes-base/nodes/Brevo/GenericFunctions.ts b/packages/nodes-base/nodes/Brevo/GenericFunctions.ts index 38918f1cb6d..29368c2c75d 100644 --- a/packages/nodes-base/nodes/Brevo/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Brevo/GenericFunctions.ts @@ -188,13 +188,20 @@ export namespace BrevoNode { return result as ValidatedEmail; } + // v1 stored recipients under a misspelled key. v1.1 (NODE-5367) corrects + // the spelling; this picks which key each version actually reads. + function isLegacyVersion(this: IExecuteSingleFunctions): boolean { + return this.getNode().typeVersion < 1.1; + } + export async function validateAndCompileCCEmails( this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions, ): Promise { - const ccData = this.getNodeParameter( - 'additionalFields.receipientsCC.receipientCc', - ) as JsonObject; + const path = isLegacyVersion.call(this) + ? 'additionalFields.receipientsCC.receipientCc' + : 'additionalFields.recipientsCC.recipientCc'; + const ccData = this.getNodeParameter(path) as JsonObject; const { cc } = ccData; const { body } = requestOptions; const data = validateEmailStrings({ cc: cc as string }); @@ -207,9 +214,10 @@ export namespace BrevoNode { this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions, ): Promise { - const bccData = this.getNodeParameter( - 'additionalFields.receipientsBCC.receipientBcc', - ) as JsonObject; + const path = isLegacyVersion.call(this) + ? 'additionalFields.receipientsBCC.receipientBcc' + : 'additionalFields.recipientsBCC.recipientBcc'; + const bccData = this.getNodeParameter(path) as JsonObject; const { bcc } = bccData; const { body } = requestOptions; const data = validateEmailStrings({ bcc: bcc as string }); @@ -222,7 +230,9 @@ export namespace BrevoNode { this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions, ): Promise { - const to = this.getNodeParameter('receipients') as string; + const to = this.getNodeParameter( + isLegacyVersion.call(this) ? 'receipients' : 'recipients', + ) as string; const { body } = requestOptions; const data = validateEmailStrings({ to }); Object.assign(body!, data); diff --git a/packages/nodes-base/nodes/Brevo/test/GenericFunctions.test.ts b/packages/nodes-base/nodes/Brevo/test/GenericFunctions.test.ts index 34efc1f0c61..10cff8c5c5a 100644 --- a/packages/nodes-base/nodes/Brevo/test/GenericFunctions.test.ts +++ b/packages/nodes-base/nodes/Brevo/test/GenericFunctions.test.ts @@ -65,6 +65,7 @@ describe('Brevo - email validation', () => { it('applies content access restrictions when validating addresses', async () => { const context = { getNodeParameter: vi.fn().mockReturnValue('recipient@example.com'), + getNode: vi.fn().mockReturnValue({ typeVersion: 1 }), } as unknown as IExecuteSingleFunctions; await BrevoNode.Validators.validateAndCompileRecipientEmails.call(context, { @@ -80,6 +81,93 @@ describe('Brevo - email validation', () => { }); }); +function makeVersionedContext(typeVersion: number, params: Record) { + return { + getNode: vi.fn().mockReturnValue({ typeVersion }), + getNodeParameter: vi.fn((name: string) => params[name]), + } as unknown as IExecuteSingleFunctions; +} + +describe('Brevo - recipients/CC/BCC spelling by node version (NODE-5367)', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('validateAndCompileRecipientEmails reads the legacy "receipients" key on v1', async () => { + const context = makeVersionedContext(1, { receipients: 'legacy@example.com' }); + + await BrevoNode.Validators.validateAndCompileRecipientEmails.call(context, { + url: '', + body: {}, + }); + + expect(mailComposerOptions).toHaveBeenCalledWith( + expect.objectContaining({ to: 'legacy@example.com' }), + ); + }); + + it('validateAndCompileRecipientEmails reads the corrected "recipients" key on v1.1', async () => { + const context = makeVersionedContext(1.1, { recipients: 'correct@example.com' }); + + await BrevoNode.Validators.validateAndCompileRecipientEmails.call(context, { + url: '', + body: {}, + }); + + expect(mailComposerOptions).toHaveBeenCalledWith( + expect.objectContaining({ to: 'correct@example.com' }), + ); + }); + + it('validateAndCompileCCEmails reads the legacy nested CC path on v1', async () => { + const context = makeVersionedContext(1, { + 'additionalFields.receipientsCC.receipientCc': { cc: 'legacy-cc@example.com' }, + }); + + await BrevoNode.Validators.validateAndCompileCCEmails.call(context, { url: '', body: {} }); + + expect(mailComposerOptions).toHaveBeenCalledWith( + expect.objectContaining({ cc: 'legacy-cc@example.com' }), + ); + }); + + it('validateAndCompileCCEmails reads the corrected nested CC path on v1.1', async () => { + const context = makeVersionedContext(1.1, { + 'additionalFields.recipientsCC.recipientCc': { cc: 'correct-cc@example.com' }, + }); + + await BrevoNode.Validators.validateAndCompileCCEmails.call(context, { url: '', body: {} }); + + expect(mailComposerOptions).toHaveBeenCalledWith( + expect.objectContaining({ cc: 'correct-cc@example.com' }), + ); + }); + + it('validateAndCompileBCCEmails reads the legacy nested BCC path on v1', async () => { + const context = makeVersionedContext(1, { + 'additionalFields.receipientsBCC.receipientBcc': { bcc: 'legacy-bcc@example.com' }, + }); + + await BrevoNode.Validators.validateAndCompileBCCEmails.call(context, { url: '', body: {} }); + + expect(mailComposerOptions).toHaveBeenCalledWith( + expect.objectContaining({ bcc: 'legacy-bcc@example.com' }), + ); + }); + + it('validateAndCompileBCCEmails reads the corrected nested BCC path on v1.1', async () => { + const context = makeVersionedContext(1.1, { + 'additionalFields.recipientsBCC.recipientBcc': { bcc: 'correct-bcc@example.com' }, + }); + + await BrevoNode.Validators.validateAndCompileBCCEmails.call(context, { url: '', body: {} }); + + expect(mailComposerOptions).toHaveBeenCalledWith( + expect.objectContaining({ bcc: 'correct-bcc@example.com' }), + ); + }); +}); + describe('Brevo - validateAndCompileAttachmentsData', () => { const validate = BrevoNode.Validators.validateAndCompileAttachmentsData;