- 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
115 lines
5.5 KiB
TypeScript
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.");
|
|
});
|
|
});
|