/** * Unit tests for the email service's structured `{ ok, error? }` return. * * The shared `sendEmail` helper is private, but `sendWelcomeEmail` * calls it and is exported, so we exercise the happy path + the two * new error paths (no API key, Resend 4xx/5xx with JSON body) through * the public surface. */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; const originalFetch = global.fetch; const originalKey = process.env.RESEND_API_KEY; beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { global.fetch = originalFetch; if (originalKey === undefined) { delete process.env.RESEND_API_KEY; } else { process.env.RESEND_API_KEY = originalKey; } }); async function importFresh() { // Re-import the module after toggling RESEND_API_KEY / mocking fetch // so the module-level constants pick up the new env. vi.resetModules(); return await import("@/lib/email-service"); } describe("sendWelcomeEmail — structured error reporting", () => { it("returns ok=true when Resend accepts the message", async () => { process.env.RESEND_API_KEY = "re_test_key"; global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, json: async () => ({ id: "email-abc" }), } as Response) as unknown as typeof fetch; const { sendWelcomeEmail } = await importFresh(); const r = await sendWelcomeEmail({ to: "u@example.com", name: "U", role: "brand_admin", brandName: "Tuxedo", tempPassword: "TempPass123!", }); expect(r.ok).toBe(true); }); it("returns a clear error when RESEND_API_KEY is missing", async () => { delete process.env.RESEND_API_KEY; const { sendWelcomeEmail } = await importFresh(); const r = await sendWelcomeEmail({ to: "u@example.com", name: "U", role: "brand_admin", brandName: "Tuxedo", tempPassword: "TempPass123!", }); expect(r.ok).toBe(false); if (!r.ok) { expect(r.error).toMatch(/RESEND_API_KEY is not set/i); } }); it("surfaces Resend's actual error message on 4xx/5xx", async () => { process.env.RESEND_API_KEY = "re_test_key"; global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 422, json: async () => ({ name: "validation_error", message: "The gmail.com domain is not verified", }), } as Response) as unknown as typeof fetch; const { sendWelcomeEmail } = await importFresh(); const r = await sendWelcomeEmail({ to: "u@gmail.com", name: "U", role: "brand_admin", brandName: "Tuxedo", tempPassword: "TempPass123!", }); expect(r.ok).toBe(false); if (!r.ok) { expect(r.error).toContain("Resend 422"); expect(r.error).toContain("validation_error"); expect(r.error).toContain("gmail.com domain is not verified"); } }); it("surfaces the Resend message even when the response body is not JSON", async () => { process.env.RESEND_API_KEY = "re_test_key"; global.fetch = vi.fn().mockResolvedValue({ ok: false, status: 500, json: async () => { throw new Error("not json"); }, } as unknown as Response) as unknown as typeof fetch; const { sendWelcomeEmail } = await importFresh(); const r = await sendWelcomeEmail({ to: "u@example.com", name: "U", role: "brand_admin", brandName: "Tuxedo", tempPassword: "TempPass123!", }); expect(r.ok).toBe(false); if (!r.ok) { expect(r.error).toBe("Resend 500"); } }); it("surfaces network errors verbatim", async () => { process.env.RESEND_API_KEY = "re_test_key"; global.fetch = vi.fn().mockRejectedValue(new Error("fetch failed: ECONNREFUSED")) as unknown as typeof fetch; const { sendWelcomeEmail } = await importFresh(); const r = await sendWelcomeEmail({ to: "u@example.com", name: "U", role: "brand_admin", brandName: "Tuxedo", tempPassword: "TempPass123!", }); expect(r.ok).toBe(false); if (!r.ok) { expect(r.error).toBe("fetch failed: ECONNREFUSED"); } }); });