Skip to content

Commit 84b7091

Browse files
fix(mailer): address copilot review comments
- Remove duplicate JSDoc block in ResendProvider.send() - Add JSDoc to NodemailerProvider.send() with attachments params - Fix content type annotation to string|Buffer in index.js and provider.resend.js - Add provider.nodemailer.unit.tests.js covering attachments passthrough
1 parent 4659916 commit 84b7091

4 files changed

Lines changed: 125 additions & 4 deletions

File tree

lib/helpers/mailer/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const isConfigured = () => !!(config.mailer && config.mailer.from && !String(con
4646
* @param {Object} mail.params - Template parameters
4747
* @param {Array} [mail.attachments] - Optional attachments array
4848
* @param {string} mail.attachments[].filename - Attachment filename
49-
* @param {string} mail.attachments[].content - Attachment content (string or Buffer)
49+
* @param {string|Buffer} mail.attachments[].content - Attachment content (string or Buffer)
5050
* @returns {Promise<Object|null>} The send result or null if not configured
5151
*/
5252
const sendMail = async (mail) => {

lib/helpers/mailer/provider.nodemailer.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ export default class NodemailerProvider {
55
this.transport = nodemailer.createTransport(options);
66
}
77

8+
/**
9+
* @desc Send an email via Nodemailer
10+
* @param {Object} mail - Mail envelope
11+
* @param {string} mail.from - Sender address
12+
* @param {string} mail.to - Recipient address
13+
* @param {string} mail.subject - Email subject
14+
* @param {string} mail.html - HTML body
15+
* @param {Array} [mail.attachments] - Optional attachments
16+
* @param {string} mail.attachments[].filename - Filename
17+
* @param {string|Buffer} mail.attachments[].content - Attachment content (string or Buffer)
18+
* @returns {Promise<Object>} Nodemailer send result
19+
*/
820
async send({ from, to, subject, html, attachments }) {
921
const payload = { from, to, subject, html };
1022
if (attachments?.length) payload.attachments = attachments;

lib/helpers/mailer/provider.resend.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ export default class ResendProvider {
1919
* @param {string} mail.to - Recipient address
2020
* @param {string} mail.subject - Email subject
2121
* @param {string} mail.html - HTML body
22-
* @param {Array} [mail.attachments] - Optional attachments array
23-
* @param {string} mail.attachments[].filename - Attachment filename
24-
* @param {string} mail.attachments[].content - Attachment content (string or Buffer)
22+
* @param {Array} [mail.attachments] - Optional attachments
23+
* @param {string} mail.attachments[].filename - Filename
24+
* @param {string|Buffer} mail.attachments[].content - Content (string or Buffer)
2525
* @returns {Promise<Object>} Resend API response data
2626
*/
2727
async send({ from, to, subject, html, attachments }) {
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Module dependencies.
3+
*/
4+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
5+
6+
// Mock nodemailer before importing the provider
7+
jest.unstable_mockModule('nodemailer', () => ({
8+
default: {
9+
createTransport: jest.fn(),
10+
},
11+
}));
12+
13+
const nodemailer = (await import('nodemailer')).default;
14+
const { default: NodemailerProvider } = await import('../provider.nodemailer.js');
15+
16+
describe('NodemailerProvider unit tests:', () => {
17+
let provider;
18+
let sendMailMock;
19+
20+
beforeEach(() => {
21+
jest.clearAllMocks();
22+
sendMailMock = jest.fn();
23+
nodemailer.createTransport.mockReturnValue({ sendMail: sendMailMock });
24+
provider = new NodemailerProvider({ host: 'smtp.example.com', port: 587 });
25+
});
26+
27+
test('should create a nodemailer transport with provided options', () => {
28+
expect(nodemailer.createTransport).toHaveBeenCalledWith({ host: 'smtp.example.com', port: 587 });
29+
});
30+
31+
test('should send an email without attachments', async () => {
32+
const mockResult = { messageId: 'msg_123' };
33+
sendMailMock.mockResolvedValue(mockResult);
34+
35+
const result = await provider.send({
36+
from: 'sender@example.com',
37+
to: 'recipient@example.com',
38+
subject: 'Test',
39+
html: '<p>Hello</p>',
40+
});
41+
42+
expect(sendMailMock).toHaveBeenCalledWith({
43+
from: 'sender@example.com',
44+
to: 'recipient@example.com',
45+
subject: 'Test',
46+
html: '<p>Hello</p>',
47+
});
48+
expect(result).toEqual(mockResult);
49+
});
50+
51+
test('should send an email with attachments', async () => {
52+
const mockResult = { messageId: 'msg_456' };
53+
sendMailMock.mockResolvedValue(mockResult);
54+
55+
const csvContent = 'col1,col2\nval1,val2';
56+
const result = await provider.send({
57+
from: 'sender@example.com',
58+
to: 'recipient@example.com',
59+
subject: 'Test with attachment',
60+
html: '<p>See attached</p>',
61+
attachments: [{ filename: 'data.csv', content: csvContent }],
62+
});
63+
64+
expect(sendMailMock).toHaveBeenCalledWith({
65+
from: 'sender@example.com',
66+
to: 'recipient@example.com',
67+
subject: 'Test with attachment',
68+
html: '<p>See attached</p>',
69+
attachments: [{ filename: 'data.csv', content: csvContent }],
70+
});
71+
expect(result).toEqual(mockResult);
72+
});
73+
74+
test('should not include attachments key when attachments array is empty', async () => {
75+
const mockResult = { messageId: 'msg_789' };
76+
sendMailMock.mockResolvedValue(mockResult);
77+
78+
await provider.send({
79+
from: 'sender@example.com',
80+
to: 'recipient@example.com',
81+
subject: 'Test',
82+
html: '<p>Hello</p>',
83+
attachments: [],
84+
});
85+
86+
const calledWith = sendMailMock.mock.calls[0][0];
87+
expect(calledWith).not.toHaveProperty('attachments');
88+
});
89+
90+
test('should send with Buffer attachment content', async () => {
91+
const mockResult = { messageId: 'msg_buf' };
92+
sendMailMock.mockResolvedValue(mockResult);
93+
94+
const bufContent = Buffer.from('binary data');
95+
await provider.send({
96+
from: 'sender@example.com',
97+
to: 'recipient@example.com',
98+
subject: 'Buffer test',
99+
html: '<p>Buffer</p>',
100+
attachments: [{ filename: 'file.bin', content: bufContent }],
101+
});
102+
103+
expect(sendMailMock).toHaveBeenCalledWith(
104+
expect.objectContaining({
105+
attachments: [{ filename: 'file.bin', content: bufContent }],
106+
}),
107+
);
108+
});
109+
});

0 commit comments

Comments
 (0)