Files
Checkmate/server/test/unit/providers/notifications/emailProvider.test.ts
2026-06-09 12:34:30 -07:00

150 lines
7.8 KiB
TypeScript

import { describe, expect, it, jest, beforeEach } from "@jest/globals";
import { EmailProvider } from "../../../../src/domain/notifications/providers/email.ts";
import { createMockLogger } from "../../../helpers/createMockLogger.ts";
import { makeNotification, makeMessage } from "../../../helpers/notificationMessage.ts";
import { testNotificationProviderContract } from "../../../helpers/notificationProviderContract.ts";
import type { IEmailService } from "../../../../src/service/emailService.ts";
// ── Helpers ──────────────────────────────────────────────────────────────────
const createMockEmailService = () =>
({
buildEmail: jest.fn().mockResolvedValue("<html>email</html>"),
sendEmail: jest.fn().mockResolvedValue("msg-123"),
}) as unknown as jest.Mocked<IEmailService>;
const createProvider = () => {
const logger = createMockLogger();
const emailService = createMockEmailService();
const provider = new EmailProvider(emailService, logger as any);
return { provider, logger, emailService };
};
testNotificationProviderContract("EmailProvider", {
create: () => createProvider().provider,
makeNotification: () => makeNotification(),
});
// ── Tests ────────────────────────────────────────────────────────────────────
describe("EmailProvider", () => {
describe("sendTestAlert", () => {
it("builds test email and sends it", async () => {
const { provider, emailService } = createProvider();
const result = await provider.sendTestAlert(makeNotification());
expect(result).toBe(true);
expect(emailService.buildEmail).toHaveBeenCalledWith("testEmailTemplate", { testName: "Monitoring System" });
expect(emailService.sendEmail).toHaveBeenCalledWith("https://hooks.example.com/webhook", "Test notification", "<html>email</html>");
});
it("returns false when address is missing", async () => {
const { provider, logger } = createProvider();
expect(await provider.sendTestAlert(makeNotification({ address: "" }))).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Missing address" }));
});
it("returns false when buildEmail returns undefined", async () => {
const { provider, emailService, logger } = createProvider();
(emailService.buildEmail as jest.Mock).mockResolvedValue(undefined);
expect(await provider.sendTestAlert(makeNotification())).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Failed to build test email content" }));
});
it("returns false when sendEmail returns falsy", async () => {
const { provider, emailService, logger } = createProvider();
(emailService.sendEmail as jest.Mock).mockResolvedValue(false);
expect(await provider.sendTestAlert(makeNotification())).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Email test alert failed" }));
});
});
describe("sendMessage", () => {
it("builds email from message and sends it", async () => {
const { provider, emailService } = createProvider();
const result = await provider.sendMessage(makeNotification() as any, makeMessage());
expect(result).toBe(true);
expect(emailService.buildEmail).toHaveBeenCalledWith(
"unifiedNotificationTemplate",
expect.objectContaining({ title: "Monitor Down: Test Monitor" })
);
expect(emailService.sendEmail).toHaveBeenCalled();
});
it("returns false when address is missing", async () => {
const { provider } = createProvider();
expect(await provider.sendMessage(makeNotification({ address: "" }) as any, makeMessage())).toBe(false);
});
it("returns false when buildEmail returns undefined", async () => {
const { provider, emailService, logger } = createProvider();
(emailService.buildEmail as jest.Mock).mockResolvedValue(undefined);
expect(await provider.sendMessage(makeNotification() as any, makeMessage())).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Failed to build email content" }));
});
it("returns false when sendEmail returns falsy", async () => {
const { provider, emailService, logger } = createProvider();
(emailService.sendEmail as jest.Mock).mockResolvedValue(undefined);
expect(await provider.sendMessage(makeNotification() as any, makeMessage())).toBe(false);
expect(logger.warn).toHaveBeenCalledWith(expect.objectContaining({ message: "Email notification failed" }));
});
it("builds correct subject for monitor_down", async () => {
const { provider, emailService } = createProvider();
await provider.sendMessage(makeNotification() as any, makeMessage({ type: "monitor_down" }));
expect(emailService.sendEmail).toHaveBeenCalledWith(expect.anything(), "Monitor Test Monitor is down", expect.anything());
});
it("builds correct subject for monitor_up", async () => {
const { provider, emailService } = createProvider();
await provider.sendMessage(makeNotification() as any, makeMessage({ type: "monitor_up" }));
expect(emailService.sendEmail).toHaveBeenCalledWith(expect.anything(), "Monitor Test Monitor is back up", expect.anything());
});
it("builds correct subject for threshold_breach", async () => {
const { provider, emailService } = createProvider();
await provider.sendMessage(makeNotification() as any, makeMessage({ type: "threshold_breach" }));
expect(emailService.sendEmail).toHaveBeenCalledWith(expect.anything(), "Monitor Test Monitor threshold exceeded", expect.anything());
});
it("builds correct subject for threshold_resolved", async () => {
const { provider, emailService } = createProvider();
await provider.sendMessage(makeNotification() as any, makeMessage({ type: "threshold_resolved" }));
expect(emailService.sendEmail).toHaveBeenCalledWith(expect.anything(), "Monitor Test Monitor thresholds resolved", expect.anything());
});
it("builds default subject for unknown type", async () => {
const { provider, emailService } = createProvider();
await provider.sendMessage(makeNotification() as any, makeMessage({ type: "test" }));
expect(emailService.sendEmail).toHaveBeenCalledWith(expect.anything(), "Alert: Test Monitor", expect.anything());
});
it("passes incident url in context when present", async () => {
const { provider, emailService } = createProvider();
const msg = makeMessage();
msg.content.incident = { id: "inc-1", url: "https://app.example.com/incidents/inc-1", createdAt: new Date() };
await provider.sendMessage(makeNotification() as any, msg);
expect(emailService.buildEmail).toHaveBeenCalledWith(
"unifiedNotificationTemplate",
expect.objectContaining({ incidentUrl: "https://app.example.com/incidents/inc-1" })
);
});
it("maps all severity levels to colors", async () => {
const { provider, emailService } = createProvider();
const severityColors: Record<string, string> = { critical: "red", warning: "#f59e0b", info: "#3b82f6", success: "green" };
for (const [severity, color] of Object.entries(severityColors)) {
(emailService.buildEmail as jest.Mock).mockClear();
await provider.sendMessage(makeNotification() as any, makeMessage({ severity: severity as any }));
expect(emailService.buildEmail).toHaveBeenCalledWith("unifiedNotificationTemplate", expect.objectContaining({ headerColor: color }));
}
});
it("uses default color for unknown severity", async () => {
const { provider, emailService } = createProvider();
await provider.sendMessage(makeNotification() as any, makeMessage({ severity: "unknown" as any }));
expect(emailService.buildEmail).toHaveBeenCalledWith("unifiedNotificationTemplate", expect.objectContaining({ headerColor: "#3b82f6" }));
});
});
});