Files
sass-mohfarawati/tests/unit/mail.test.ts
T
Moh e2e06be86e test: add comprehensive automated test coverage
- Vitest multi-project setup (unit / integration / component)
- Real-Postgres integration harness via in-process PGlite (TEST_DATABASE_URL
  override), migrations applied per worker; production code untouched
- Unit: routing, locale, validation/Zod schemas, metadata, mail, site-theme,
  marquee, media, portfolio helpers, plus architecture-rule tests
- Integration: Prisma data layer, API routes, and all server actions
- Component: UI primitives and form components (jsdom + Testing Library)
- 371 tests passing
2026-08-06 02:27:20 +02:00

115 lines
5.5 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type { MailSettings } from "@/lib/mail-settings";
import { createSmtpTransport, sendContactMessage, sendMail, sendTestEmail } from "@/lib/mail";
function settings(overrides: Partial<MailSettings> = {}): MailSettings {
return {
smtp: { host: "smtp.example.com", port: 587, secure: false, username: "mailer", password: "secret", ...overrides.smtp },
sender: { email: "hello@example.com", name: "Studio", ...overrides.sender },
recipients: { contact: "contact@example.com", test: "test@example.com", ...overrides.recipients },
};
}
function mockTransport() {
const sendMailMock = vi.fn().mockResolvedValue({});
const createTransport = vi.fn().mockReturnValue({ sendMail: sendMailMock });
return { sendMailMock, createTransport };
}
describe("createSmtpTransport", () => {
it("builds the transport with host, port, secure, and auth", () => {
const { createTransport } = mockTransport();
createSmtpTransport(settings({ smtp: { host: "h", port: 465, secure: true, username: "u", password: "p" } }), createTransport);
expect(createTransport).toHaveBeenCalledWith({
host: "h",
port: 465,
secure: true,
auth: { user: "u", pass: "p" },
});
});
it("requires host, username and password", () => {
const { createTransport } = mockTransport();
expect(() => createSmtpTransport(settings({ smtp: { host: "", port: 587, secure: false, username: "u", password: "p" } }), createTransport)).toThrow(/host/i);
expect(() => createSmtpTransport(settings({ smtp: { host: "h", port: 587, secure: false, username: "", password: "p" } }), createTransport)).toThrow(/username/i);
expect(() => createSmtpTransport(settings({ smtp: { host: "h", port: 587, secure: false, username: "u", password: "" } }), createTransport)).toThrow(/password/i);
});
});
describe("sendMail", () => {
it("formats the from header with the sender name", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendMail({ to: "x@y.z", subject: "S", text: "T" }, { settings: settings(), createTransport });
expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ from: "Studio <hello@example.com>", to: "x@y.z" }));
});
it("omits the display name when sender name is blank", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendMail({ to: "x@y.z", subject: "S", text: "T" }, { settings: settings({ sender: { email: "hello@example.com", name: "" } }), createTransport });
expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ from: "hello@example.com" }));
});
it("requires a from email", async () => {
const { createTransport } = mockTransport();
await expect(
sendMail({ to: "x@y.z", subject: "S", text: "T" }, { settings: settings({ sender: { email: "", name: "" } }), createTransport }),
).rejects.toThrow(/from email/i);
});
});
describe("sendContactMessage", () => {
it("targets the contact recipient with reply-to and full body", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendContactMessage(
{ locale: "en", name: "Jane", email: "jane@x.z", phone: "123", company: "Acme", message: "Hi there team." },
{ settings: settings(), createTransport },
);
const call = sendMailMock.mock.calls[0][0];
expect(call).toMatchObject({ to: "contact@example.com", subject: "New contact message", replyTo: "jane@x.z" });
expect(call.text).toContain("Name: Jane");
expect(call.text).toContain("Phone: 123");
expect(call.text).toContain("Company: Acme");
expect(call.text).toContain("Hi there team.");
});
it("falls back to the test recipient when contact is empty", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendContactMessage(
{ locale: "de", name: "Jane", email: "jane@x.z", message: "Fallback works fine." },
{ settings: settings({ recipients: { contact: "", test: "fallback@x.z" } }), createTransport },
);
expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ to: "fallback@x.z" }));
});
it("renders dashes for missing optional fields", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendContactMessage(
{ locale: "en", name: "Jane", email: "jane@x.z", message: "No phone or company." },
{ settings: settings(), createTransport },
);
const call = sendMailMock.mock.calls[0][0];
expect(call.text).toContain("Phone: -");
expect(call.text).toContain("Company: -");
});
});
describe("sendTestEmail", () => {
it("sends to the test recipient", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendTestEmail({ settings: settings(), createTransport });
expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ to: "test@example.com", subject: "SMTP test email" }));
});
it("falls back to the contact recipient when test is empty", async () => {
const { sendMailMock, createTransport } = mockTransport();
await sendTestEmail({ settings: settings({ recipients: { contact: "c@x.z", test: "" } }), createTransport });
expect(sendMailMock).toHaveBeenCalledWith(expect.objectContaining({ to: "c@x.z" }));
});
it("propagates transport failures", async () => {
const createTransport = vi.fn().mockReturnValue({ sendMail: vi.fn().mockRejectedValue(new Error("Auth failed.")) });
await expect(sendTestEmail({ settings: settings(), createTransport })).rejects.toThrow("Auth failed.");
});
});