Skip to content

Commit 77df206

Browse files
rhelmerVinnl
andauthored
use structured logging for email utils (#5230)
* use structured logging for email utils * add module name mapping for uuid and jest --------- Co-authored-by: Vincent <[email protected]>
1 parent 1af5c46 commit 77df206

File tree

4 files changed

+44
-22
lines changed

4 files changed

+44
-22
lines changed

jest.config.cjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ const customJestConfig = {
113113
// ],
114114

115115
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
116-
// moduleNameMapper: {},
116+
moduleNameMapper: {
117+
// Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
118+
uuid: require.resolve("uuid"),
119+
},
117120

118121
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
119122
modulePathIgnorePatterns: ["e2e/"],

src/app/functions/server/logging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const logger = createLogger({
1616
level: "info",
1717
// In GCP environments, use cloud logging instead of stdout.
1818
// FIXME https://mozilla-hub.atlassian.net/browse/MNTOR-2401 - enable for stage and production
19+
/* c8 ignore next 3 - cannot test this outside of GCP currently */
1920
transports: ["gcpdev"].includes(process.env.APP_ENV ?? "local")
2021
? [loggingWinston]
2122
: [new transports.Console()],

src/utils/email.test.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,24 @@
55
import { test, expect, jest } from "@jest/globals";
66
import type { createTransport, Transporter } from "nodemailer";
77
import { randomToken } from "./email";
8+
import type * as loggerModule from "../app/functions/server/logging";
89

910
jest.mock("nodemailer", () => {
1011
return {
1112
createTransport: jest.fn(),
1213
};
1314
});
1415

16+
jest.mock("../app/functions/server/logging", () => {
17+
return {
18+
logger: {
19+
info: jest.fn(),
20+
warn: jest.fn(),
21+
error: jest.fn(),
22+
},
23+
};
24+
});
25+
1526
test("EmailUtils.sendEmail before .init() fails", async () => {
1627
expect.assertions(1);
1728

@@ -25,9 +36,9 @@ test("EmailUtils.sendEmail before .init() fails", async () => {
2536
});
2637

2738
test("EmailUtils.init with empty host uses jsonTransport", async () => {
28-
const mockedConsoleInfo = jest
29-
.spyOn(console, "info")
30-
.mockImplementation(() => undefined);
39+
const mockedLoggerModule = jest.requireMock<typeof loggerModule>(
40+
"../app/functions/server/logging",
41+
);
3142
const mockedNodemailer: {
3243
createTransport: jest.MockedFunction<typeof createTransport>;
3344
} = jest.requireMock("nodemailer");
@@ -37,7 +48,7 @@ test("EmailUtils.init with empty host uses jsonTransport", async () => {
3748
expect(mockedNodemailer.createTransport).toHaveBeenCalledWith({
3849
jsonTransport: true,
3950
});
40-
expect(mockedConsoleInfo).toHaveBeenCalledWith("smtpUrl-empty", {
51+
expect(mockedLoggerModule.logger.info).toHaveBeenCalledWith("smtpUrl-empty", {
4152
message: "EmailUtils will log a JSON response instead of sending emails.",
4253
});
4354
});
@@ -65,9 +76,9 @@ test("EmailUtils.sendEmail with recipient, subject, template, context calls gTra
6576
const mockedNodemailer: {
6677
createTransport: jest.MockedFunction<typeof createTransport>;
6778
} = jest.requireMock("nodemailer");
68-
const mockedConsoleInfo = jest
69-
.spyOn(console, "info")
70-
.mockImplementation(() => undefined);
79+
const mockedLoggerModule = jest.requireMock<typeof loggerModule>(
80+
"../app/functions/server/logging",
81+
);
7182
const { initEmail, sendEmail } = await import("./email");
7283

7384
const testSmtpUrl = "smtps://test:test@test:1";
@@ -86,13 +97,16 @@ test("EmailUtils.sendEmail with recipient, subject, template, context calls gTra
8697
expect(
8798
await sendEmail("[email protected]", "subject", "<html>test</html>"),
8899
).toBe(sendMailInfo);
89-
expect(mockedConsoleInfo).toHaveBeenCalledWith("sent_email", sendMailInfo);
100+
expect(mockedLoggerModule.logger.info).toHaveBeenCalledWith(
101+
"sent_email",
102+
sendMailInfo,
103+
);
90104
});
91105

92106
test("EmailUtils.sendEmail rejects with error", async () => {
93-
const mockedConsoleError = jest
94-
.spyOn(console, "error")
95-
.mockImplementation(() => undefined);
107+
const mockedLoggerModule = jest.requireMock<typeof loggerModule>(
108+
"../app/functions/server/logging",
109+
);
96110
const mockedNodemailer: {
97111
createTransport: jest.MockedFunction<typeof createTransport>;
98112
} = jest.requireMock("nodemailer");
@@ -109,16 +123,16 @@ test("EmailUtils.sendEmail rejects with error", async () => {
109123
await expect(() =>
110124
sendEmail("[email protected]", "subject", "<html>test</html>"),
111125
).rejects.toThrow("error");
112-
expect(mockedConsoleError).toHaveBeenCalled();
126+
expect(mockedLoggerModule.logger.error).toHaveBeenCalled();
113127
});
114128

115129
test("EmailUtils.init with empty host uses jsonTransport. logs messages", async () => {
116130
const mockedNodemailer: {
117131
createTransport: jest.MockedFunction<typeof createTransport>;
118132
} = jest.requireMock("nodemailer");
119-
const mockedConsoleInfo = jest
120-
.spyOn(console, "info")
121-
.mockImplementation(() => undefined);
133+
const mockedLoggerModule = jest.requireMock<typeof loggerModule>(
134+
"../app/functions/server/logging",
135+
);
122136
const { initEmail, sendEmail } = await import("./email");
123137

124138
const sendMailInfo = { messageId: "test id", response: "test response" };
@@ -134,7 +148,10 @@ test("EmailUtils.init with empty host uses jsonTransport. logs messages", async
134148
expect(
135149
await sendEmail("[email protected]", "subject", "<html>test</html>"),
136150
).toBe(sendMailInfo);
137-
expect(mockedConsoleInfo).toHaveBeenCalledWith("sent_email", sendMailInfo);
151+
expect(mockedLoggerModule.logger.info).toHaveBeenCalledWith(
152+
"sent_email",
153+
sendMailInfo,
154+
);
138155
});
139156

140157
test("randomToken returns a random token of 2xlength (because of hex)", () => {

src/utils/email.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import crypto from "crypto";
77

88
import { SentMessageInfo } from "nodemailer/lib/smtp-transport/index.js";
99
import { getEnvVarsOrThrow } from "../envVars";
10+
import { logger } from "../app/functions/server/logging";
1011

1112
// The SMTP transport object. This is initialized to a nodemailer transport
1213
// object while reading SMTP credentials, or to a dummy function in debug mode.
@@ -17,7 +18,7 @@ const envVars = getEnvVarsOrThrow(["SMTP_URL", "EMAIL_FROM", "SES_CONFIG_SET"]);
1718
async function initEmail(smtpUrl = envVars.SMTP_URL) {
1819
// Allow a debug mode that will log JSON instead of sending emails.
1920
if (!smtpUrl) {
20-
console.info("smtpUrl-empty", {
21+
logger.info("smtpUrl-empty", {
2122
message: "EmailUtils will log a JSON response instead of sending emails.",
2223
});
2324
gTransporter = createTransport({ jsonTransport: true });
@@ -62,24 +63,24 @@ async function sendEmail(
6263
/* c8 ignore next 5 */
6364
if (gTransporter.transporter.name === "JSONTransport") {
6465
// @ts-ignore Added typing later, but it disagrees with actual use:
65-
console.info("JSONTransport", { message: info.message.toString() });
66+
logger.info("JSONTransport", { message: info.message.toString() });
6667
return info;
6768
}
6869

69-
console.info("sent_email", {
70+
logger.info("sent_email", {
7071
messageId: info.messageId,
7172
response: info.response,
7273
});
7374
return info;
7475
} catch (e) {
7576
if (e instanceof Error) {
76-
console.error("error_sending_email", {
77+
logger.error("error_sending_email", {
7778
message: e.message,
7879
stack: e.stack,
7980
});
8081
/* c8 ignore next 3 */
8182
} else {
82-
console.error("error_sending_email", { message: JSON.stringify(e) });
83+
logger.error("error_sending_email", { message: JSON.stringify(e) });
8384
}
8485
throw e;
8586
}

0 commit comments

Comments
 (0)