Skip to content

Commit

Permalink
fix: nodemailer plugin hanging on sendEmail
Browse files Browse the repository at this point in the history
  • Loading branch information
bugslifesolutions committed Jan 3, 2024
1 parent 6633e3a commit 5ea89f0
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-geese-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bugslifesolutions/bl-api-plugin-nodemailer": minor
---

Fix Nodemailer plugin sendEmail job hanging and not sending.
27 changes: 23 additions & 4 deletions packages/bl-api-plugin-nodemailer/src/util/NodemailerMsalProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import nodemailer from "nodemailer";
const userTokens = {};
/**
* @name sendEmail
* @summary Sends the provided email using the Nodemailer transport and
* shop's NodemailerTransportConfig.
* @summary Sends the provided email using the nodemailerTransportOptions within
* the shop's appSettings.
* @param {Object} context App context
* @param {Object} job Current sendEmail job being processed
* @param {Function} sendEmailCompleted Called when email was successfully sent
Expand All @@ -22,11 +22,29 @@ export default async function sendEmail(context, {
await context.queries.appSettings(context, shopId);

const transporter = nodemailer.createTransport(nodemailerTransportOptions);
if (nodemailerTransportOptions.pool !== true) {
try {
const newToken = await getNewToken(nodemailerTransportOptions);
otherEmailFields.auth = {
user: nodemailerTransportOptions.auth.user,
accessToken: newToken.accessToken,
refreshToken: newToken.refreshToken
};
} catch (error) {
sendEmailFailed(job, `sending email for job failed: ${error.toString()}`);
transporter.close();
return;
}
}
transporter.set("oauth2_provision_cb", async (user, renew, callback) => {
let userToken = userTokens[user];
if (renew || !userToken) {
userToken = await getNewToken(nodemailerTransportOptions);
userTokens[user] = userToken;
try {
userToken = await getNewToken(nodemailerTransportOptions);
userTokens[user] = userToken;
} catch (error) {
return callback(error);
}
}

if (!userToken) {
Expand All @@ -41,6 +59,7 @@ export default async function sendEmail(context, {
} else {
sendEmailCompleted(job, `sending email job to ${to} succeeded.`);
}
transporter.close();
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import sendEmail from "./NodemailerMsalProxy";

const tenantId = "5cbfe33a-4b4f-4dd1-ab00-5d79bb3c5865";
const wrapWithDone = (done, fn) => (...args) => {
try {
fn(...args);
done();
} catch (error) {
done(error);
}
};

const mockAppSettings = {
nodemailerTransportOptions: {
pool: true,
Expand All @@ -11,14 +19,28 @@ const mockAppSettings = {
debug: true, // include SMTP traffic in the logs
auth: {
type: "OAuth2",
user: "weborders-noreply@applianceshack.com",
clientId: "a6e5896b-4222-4a4f-b38c-4d6a8ef8c98f",
user: process.env.nodemailerUser,
clientId: process.env.nodemailerClientId,
clientSecret: process.env.nodemailerClientSecret,
authority: `https://login.microsoftonline.com/${tenantId}`
authority: process.env.nodemailerAuthority
}
}
};
let failed;
let completed;

beforeEach(() => {
const { nodemailerTransportOptions } = mockAppSettings;
nodemailerTransportOptions.pool = true;
const { auth } = nodemailerTransportOptions;
auth.clientSecret = process.env.nodemailerClientSecret;
auth.user = process.env.nodemailerUser;
auth.clientId = process.env.nodemailerClientId;
auth.clientSecret = process.env.nodemailerClientSecret;
auth.authority = process.env.nodemailerAuthority;
failed = jest.fn();
completed = jest.fn();
});

test("skipci:calls queries.appSettings and returns the appSettings", (done) => {
// This is silly
Expand All @@ -37,12 +59,48 @@ test("skipci:calls queries.appSettings and returns the appSettings", (done) => {
text: "Hello world",
html: "<p>Hello world</p>"
},
sendEmailCompleted: () => {
done();
sendEmailCompleted: wrapWithDone(done, (...args) => {
completed(...args);
expect(completed).toHaveBeenCalled();
}),
sendEmailFailed: wrapWithDone(done, (...args) => {
failed(...args);
expect(failed).not.toHaveBeenCalled();
})
}
);

expect(appSettings).toHaveBeenCalled();
})();
}, 100000);

test("skipci:sendEmail fails when clientCredential is undefined", (done) => {
// This is silly
(async () => {
mockAppSettings.nodemailerTransportOptions.auth.clientSecret = undefined;
mockAppSettings.nodemailerTransportOptions.pool = false; // force oauth2
const appSettings = jest.fn().mockName("appSettings")
.mockReturnValue(Promise.resolve(mockAppSettings));

await sendEmail(
{ queries: { appSettings } },
{
job: {
shopId: 1,
from: mockAppSettings.nodemailerTransportOptions.auth.user,
to: "test_nodemailer@bugslifesolutions.com",
subject: "Test email NodemailerProxy.test.js",
text: "Hello world",
html: "<p>Hello world</p>"
},
sendEmailFailed: () => {
done();
}
sendEmailCompleted: wrapWithDone(done, (...args) => {
completed(...args);
expect(completed).not.toHaveBeenCalled();
}),
sendEmailFailed: wrapWithDone(done, (...args) => {
failed(...args);
expect(failed).toHaveBeenCalled();
})
}
);

Expand Down

0 comments on commit 5ea89f0

Please sign in to comment.