118 lines
2.9 KiB
TypeScript
118 lines
2.9 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import nodemailer from "nodemailer";
|
|
import { logger } from "../lib/logger.js";
|
|
import { testSmtpConnection } from "../lib/email.js";
|
|
|
|
const { findUnique, verify, createTransport } = vi.hoisted(() => {
|
|
const verify = vi.fn();
|
|
return {
|
|
findUnique: vi.fn(),
|
|
verify,
|
|
createTransport: vi.fn(() => ({
|
|
verify,
|
|
sendMail: vi.fn(),
|
|
})),
|
|
};
|
|
});
|
|
|
|
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(),
|
|
},
|
|
}));
|
|
|
|
describe("email runtime config hardening", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
findUnique.mockResolvedValue({
|
|
smtpHost: "smtp.secret.internal",
|
|
smtpPort: 587,
|
|
smtpUser: "alice@example.com",
|
|
smtpPassword: "super-secret",
|
|
smtpTls: true,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
it("returns a classified SMTP auth error without leaking diagnostics", async () => {
|
|
verify.mockRejectedValueOnce(
|
|
new Error("Invalid login for alice@example.com against smtp.secret.internal"),
|
|
);
|
|
|
|
const result = await testSmtpConnection();
|
|
|
|
expect(result).toEqual({
|
|
ok: false,
|
|
error: "SMTP authentication failed — check username and password.",
|
|
});
|
|
|
|
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
host: "smtp.secret.internal",
|
|
port: 587,
|
|
secure: true,
|
|
auth: {
|
|
user: "alice@example.com",
|
|
pass: "super-secret",
|
|
},
|
|
});
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
diagnostic: expect.any(String),
|
|
}),
|
|
"SMTP connection test failed",
|
|
);
|
|
|
|
const diagnostic = vi.mocked(logger.warn).mock.calls[0]?.[0];
|
|
expect(JSON.stringify(diagnostic)).toContain("<redacted-email>");
|
|
expect(JSON.stringify(diagnostic)).toContain("<redacted-host>");
|
|
expect(JSON.stringify(diagnostic)).not.toContain("alice@example.com");
|
|
expect(JSON.stringify(diagnostic)).not.toContain("smtp.secret.internal");
|
|
});
|
|
|
|
it("prefers SMTP_PASSWORD from the environment at runtime", async () => {
|
|
vi.stubEnv("SMTP_PASSWORD", "env-smtp-password");
|
|
findUnique.mockResolvedValue({
|
|
smtpHost: "smtp.secret.internal",
|
|
smtpPort: 587,
|
|
smtpUser: "alice@example.com",
|
|
smtpPassword: "db-password",
|
|
smtpTls: true,
|
|
});
|
|
verify.mockResolvedValueOnce(true);
|
|
|
|
const result = await testSmtpConnection();
|
|
|
|
expect(result).toEqual({ ok: true });
|
|
expect(nodemailer.createTransport).toHaveBeenCalledWith({
|
|
host: "smtp.secret.internal",
|
|
port: 587,
|
|
secure: true,
|
|
auth: {
|
|
user: "alice@example.com",
|
|
pass: "env-smtp-password",
|
|
},
|
|
});
|
|
});
|
|
});
|