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("email"), sendEmail: jest.fn().mockResolvedValue("msg-123"), }) as unknown as jest.Mocked; 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", "email"); }); 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 = { 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" })); }); }); });