This commit is contained in:
Stefan Lazic committed 2026-08-03 21:15:24 +01:00
1 parent ec5583f972
commit 546613e677
189 files changed
+9873 -655

No files matched your search

@@ -0,0 +1,75 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const sendMail = vi.fn();
vi.mock("nodemailer", () => ({
default: { createTransport: vi.fn(() => ({ sendMail })) },
}));
const { BrevoEmailProvider } = await import("../brevo-email-provider");
/**
* Exercises BrevoEmailProvider's retry classification (see isRetryable in
* brevo-email-provider.ts) with a mocked nodemailer transporter — no real
* SMTP connection is ever made in this test file, matching the Phase 7
* brief's "no real API calls in automated tests" requirement.
*/
describe("BrevoEmailProvider", () => {
const provider = new BrevoEmailProvider({
host: "smtp-relay.brevo.com",
port: 587,
username: "test-user",
password: "test-pass",
fromAddress: "noreply@example.com",
fromName: "Test Business",
});
beforeEach(() => {
sendMail.mockReset();
});
const params = { to: { email: "customer@example.com" }, subject: "Hi", html: "<p>Hi</p>", text: "Hi" };
it("reports success with the provider message id on a clean send", async () => {
sendMail.mockResolvedValueOnce({ messageId: "<abc123@smtp-relay.brevo.com>" });
const result = await provider.sendEmail(params);
expect(result).toEqual({ status: "sent", providerMessageId: "<abc123@smtp-relay.brevo.com>" });
});
it("classifies a connection timeout as retryable", async () => {
sendMail.mockRejectedValueOnce({ code: "ETIMEDOUT", message: "Connection timed out" });
const result = await provider.sendEmail(params);
expect(result).toEqual({ status: "failed", retryable: true, reason: "Connection timed out" });
});
it("classifies a 4xx SMTP response (rate limit / temporary) as retryable", async () => {
sendMail.mockRejectedValueOnce({ responseCode: 421, message: "Too many connections" });
const result = await provider.sendEmail(params);
expect(result).toEqual({ status: "failed", retryable: true, reason: "Too many connections" });
});
it("classifies a 5xx SMTP response (invalid recipient / rejected) as permanent, not retryable", async () => {
sendMail.mockRejectedValueOnce({ responseCode: 550, message: "Mailbox unavailable" });
const result = await provider.sendEmail(params);
expect(result).toEqual({ status: "failed", retryable: false, reason: "Mailbox unavailable" });
});
it("classifies an auth failure (535, permanent per SMTP) as not retryable", async () => {
sendMail.mockRejectedValueOnce({ responseCode: 535, message: "Authentication failed" });
const result = await provider.sendEmail(params);
expect(result.status).toBe("failed");
expect((result as { retryable: boolean }).retryable).toBe(false);
});
it("defaults an unrecognized error to retryable, bounded later by the background processor's max-attempt cap", async () => {
sendMail.mockRejectedValueOnce({ message: "Something unexpected" });
const result = await provider.sendEmail(params);
expect(result).toEqual({ status: "failed", retryable: true, reason: "Something unexpected" });
});
it("never includes the SMTP username/password in a failure result", async () => {
sendMail.mockRejectedValueOnce({ responseCode: 535, message: "Authentication failed" });
const result = await provider.sendEmail(params);
expect(JSON.stringify(result)).not.toContain("test-pass");
expect(JSON.stringify(result)).not.toContain("test-user");
});
});