import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import nodemailer from "nodemailer"; const { findUnique, sendMail, createTransport } = vi.hoisted(() => { const sendMail = vi.fn().mockResolvedValue({ messageId: "test" }); return { findUnique: vi.fn(), sendMail, createTransport: vi.fn(() => ({ sendMail })), }; }); vi.mock("@capakraken/db", () => ({ prisma: { systemSettings: { findUnique }, }, })); vi.mock("nodemailer", () => ({ default: { createTransport }, })); vi.mock("../lib/logger.js", () => ({ logger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() }, })); // Re-import after mocks are set up const { sendEmail } = await import("../lib/email.js"); describe("SMTP ENV overrides", () => { beforeEach(() => { vi.clearAllMocks(); // Default DB row — used as fallback findUnique.mockResolvedValue({ smtpHost: "db-smtp.example.com", smtpPort: 587, smtpUser: "db-user@example.com", smtpPassword: "db-password", smtpFrom: "db-from@example.com", smtpTls: true, }); sendMail.mockResolvedValue({ messageId: "ok" }); }); afterEach(() => { vi.unstubAllEnvs(); }); it("SMTP_HOST env overrides DB smtpHost", async () => { vi.stubEnv("SMTP_HOST", "env-smtp.example.com"); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ host: "env-smtp.example.com" }), ); }); it("SMTP_PORT env overrides DB smtpPort (parsed as integer)", async () => { vi.stubEnv("SMTP_PORT", "1025"); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ port: 1025 }), ); }); it("SMTP_TLS=false sets secure: false", async () => { vi.stubEnv("SMTP_TLS", "false"); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ secure: false }), ); }); it("SMTP_TLS=true sets secure: true", async () => { vi.stubEnv("SMTP_TLS", "true"); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ secure: true }), ); }); it("no SMTP_USER → auth uses DB user; with SMTP_USER env → auth uses env user", async () => { vi.stubEnv("SMTP_USER", "env-user@example.com"); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ auth: expect.objectContaining({ user: "env-user@example.com" }), }), ); }); it("no SMTP_USER and no SMTP_PASSWORD → auth is undefined (Mailhog scenario)", async () => { // Clear all user/password from both ENV and DB vi.stubEnv("SMTP_HOST", "mailhog"); vi.stubEnv("SMTP_PORT", "1025"); vi.stubEnv("SMTP_TLS", "false"); // Explicitly no SMTP_USER / SMTP_PASSWORD findUnique.mockResolvedValue({ smtpHost: null, smtpPort: null, smtpUser: null, smtpPassword: null, smtpFrom: "noreply@capakraken.app", smtpTls: null, }); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ auth: undefined }), ); }); it("DB values are used when no ENV overrides are set", async () => { await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith({ host: "db-smtp.example.com", port: 587, secure: true, auth: { user: "db-user@example.com", pass: "db-password" }, }); }); it("ENV overrides take priority over DB for all SMTP fields simultaneously", async () => { vi.stubEnv("SMTP_HOST", "mailhog"); vi.stubEnv("SMTP_PORT", "1025"); vi.stubEnv("SMTP_USER", ""); // empty string = no override vi.stubEnv("SMTP_TLS", "false"); vi.stubEnv("SMTP_PASSWORD", ""); // empty = no override findUnique.mockResolvedValue({ smtpHost: "db-smtp.example.com", smtpPort: 587, smtpUser: "db-user@example.com", smtpPassword: "db-password", smtpFrom: "from@example.com", smtpTls: true, }); await sendEmail({ to: "user@test.com", subject: "Test", text: "Hello" }); expect(nodemailer.createTransport).toHaveBeenCalledWith( expect.objectContaining({ host: "mailhog", port: 1025, secure: false, }), ); }); });