import { afterEach, describe, expect, test, vi } from "vitest"; import { buildRetryJobOptions, buildTransactionalMailJob, queueNames, type TypedQueue } from "@toolctrl/queue"; import { sendEmailMailjet } from "./MailjetEmailService"; const runtimeQueue = { add: vi.fn(async () => ({ id: "job_from_env" })), close: vi.fn(async () => undefined), }; vi.mock("@toolctrl/queue/runtime", () => ({ createQueue: vi.fn(() => runtimeQueue), })); afterEach(() => { vi.clearAllMocks(); delete process.env.BULLMQ_REDIS_URL; delete process.env.REDIS_URL; }); function createQueueMock() { return { add: vi.fn(async () => ({ id: "job_123" })), } as unknown as TypedQueue & { add: ReturnType; }; } describe("sendEmailMailjet", () => { test("enqueues transactional email through the default queue provider", async () => { const queue = createQueueMock(); const result = await sendEmailMailjet( { data: { to: "user@example.com", subject: "Welcome", body: "

Hello

", alias: "registration-welcome", data: { firstName: "Ada" }, }, }, { queue, } ); expect(result).toEqual({ provider: "queue", status: "queued", messageId: "job_123", warnings: { queueName: queueNames.email, jobName: "send-transactional-email", }, }); const descriptor = buildTransactionalMailJob({ to: ["user@example.com"], subject: "Welcome", html: "

Hello

", templateData: { firstName: "Ada" }, metadata: { alias: "registration-welcome" }, }); expect(queue.add).toHaveBeenCalledWith( "send-transactional-email", descriptor.payload, buildRetryJobOptions(descriptor) ); }); test("fails closed when Redis is missing without falling back to synchronous mail", async () => { await expect(sendEmailMailjet( { data: { to: "user@example.com", subject: "Welcome", body: "

Hello

", alias: "registration-welcome", data: { firstName: "Ada" }, }, }, { env: {}, } )).rejects.toMatchObject({ provider: "queue", message: "Set BULLMQ_REDIS_URL or REDIS_URL to enqueue transactional email jobs.", responseBody: { code: "missing_redis_connection", message: "Set BULLMQ_REDIS_URL or REDIS_URL to enqueue transactional email jobs.", requiredEnv: ["BULLMQ_REDIS_URL", "REDIS_URL"], }, }); }); test("uses process Redis configuration by default", async () => { process.env.BULLMQ_REDIS_URL = "redis://localhost:6379/2"; const result = await sendEmailMailjet({ data: { to: "user@example.com", subject: "Welcome", body: "

Hello

", alias: "registration-welcome", data: { firstName: "Ada" }, }, }); expect(result).toEqual({ provider: "queue", status: "queued", messageId: "job_from_env", warnings: { queueName: queueNames.email, jobName: "send-transactional-email", }, }); expect(runtimeQueue.add).toHaveBeenCalledOnce(); expect(runtimeQueue.close).toHaveBeenCalledOnce(); }); test("propagates enqueue failures to the caller", async () => { const queue = createQueueMock(); const error = new Error("queue add failed"); queue.add.mockRejectedValueOnce(error); await expect( sendEmailMailjet( { data: { to: "user@example.com", subject: "Welcome", body: "

Hello

", alias: "registration-welcome", data: { firstName: "Ada" }, }, }, { queue, } ) ).rejects.toBe(error); }); });