Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/modules/emails/services/MailjetEmailService.test.ts
T

180 lines
4.7 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from "vitest";
import { buildRetryJobOptions, buildTransactionalMailJob, queueNames, type TypedQueue } from "@hivegrid/queue";
import { sendEmailMailjet } from "./MailjetEmailService";
const runtimeQueue = {
add: vi.fn(async () => ({ id: "job_from_env" })),
close: vi.fn(async () => undefined),
};
vi.mock("@hivegrid/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<typeof queueNames.email> & {
add: ReturnType<typeof vi.fn>;
};
}
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: "<p>Hello</p>",
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: "<p>Hello</p>",
templateData: { firstName: "Ada" },
metadata: { alias: "registration-welcome" },
});
expect(queue.add).toHaveBeenCalledWith(
"send-transactional-email",
descriptor.payload,
buildRetryJobOptions(descriptor)
);
});
test("preserves portal tenant and source metadata for the final worker feature gate", async () => {
const queue = createQueueMock();
await sendEmailMailjet(
{
data: {
to: "customer@example.com",
subject: "Portal sign-in",
body: "<p>Sign in</p>",
alias: "magic-link",
source: "portal-auth-mail",
tenantId: "tenant_portal_1",
priorityOverride: "critical",
},
},
{ queue }
);
const descriptor = buildTransactionalMailJob({
to: ["customer@example.com"],
subject: "Portal sign-in",
html: "<p>Sign in</p>",
tenantId: "tenant_portal_1",
metadata: { alias: "magic-link", source: "portal-auth-mail" },
});
expect(queue.add).toHaveBeenCalledWith(
"send-transactional-email",
descriptor.payload,
expect.objectContaining({
...buildRetryJobOptions(descriptor),
priority: 1,
})
);
});
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: "<p>Hello</p>",
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: "<p>Hello</p>",
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: "<p>Hello</p>",
alias: "registration-welcome",
data: { firstName: "Ada" },
},
},
{
queue,
}
)
).rejects.toBe(error);
});
});