import { afterEach, describe, expect, it } from "vitest"; import nodemailer from "nodemailer"; import { buildVerifyLinkParts, resolveMailFrom, } from "../../lib/server/mail"; const VERIFY_URL = "https://staging.communityrule.info/api/auth/magic-link/verify?token=5IdE_BHowaw-QJj7Rwue7CbB8wDXvYITvnxRb1FGqxA"; function decodeQuotedPrintable(value: string): string { return value .replace(/=\r?\n/g, "") .replace(/=([0-9A-Fa-f]{2})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)), ); } const MAIL_FROM_KEYS = ["SMTP_FROM", "CLOUDRON_MAIL_FROM"] as const; const ORIGINAL_FROM = Object.fromEntries( MAIL_FROM_KEYS.map((key) => [key, process.env[key]]), ) as Record<(typeof MAIL_FROM_KEYS)[number], string | undefined>; afterEach(() => { for (const key of MAIL_FROM_KEYS) { const original = ORIGINAL_FROM[key]; if (original === undefined) delete process.env[key]; else process.env[key] = original; } }); describe("buildVerifyLinkParts", () => { it("puts the exact verify URL in both text and the HTML href", () => { const { text, html } = buildVerifyLinkParts( VERIFY_URL, "Open this link to sign in (it expires in 15 minutes):", "If you did not request this, you can ignore this email.", "Sign in", ); expect(text).toContain(VERIFY_URL); expect(html).toContain(`href="${VERIFY_URL}"`); expect(html).toContain(">Sign in"); }); it("escapes HTML in the intro and href", () => { const { html } = buildVerifyLinkParts( 'https://example.test/verify?token=a&b="c"', 'View "Rule "', "Ignore if unexpected.", "Open", ); expect(html).toContain("View "Rule <beta>""); expect(html).toContain( 'href="https://example.test/verify?token=a&b="c""', ); }); }); describe("MIME encoding of verify-link mail", () => { it("keeps a clickable href after quoted-printable encoding", async () => { const { text, html } = buildVerifyLinkParts( VERIFY_URL, "Open this link to sign in (it expires in 15 minutes):", "If you did not request this, you can ignore this email.", "Sign in", ); const transporter = nodemailer.createTransport({ streamTransport: true, buffer: true, newline: "unix", }); const info = await transporter.sendMail({ from: "Community Rule ", to: "member@example.com", subject: "Sign in to Community Rule", text, html, }); const raw = Buffer.isBuffer(info.message) ? info.message.toString("utf8") : String(info.message); const decoded = decodeQuotedPrintable(raw); expect(decoded).toContain(`href="${VERIFY_URL}"`); expect(decoded).toContain(VERIFY_URL); expect(decoded).not.toContain("token=3D"); }); }); describe("resolveMailFrom", () => { it("prefers SMTP_FROM, then CLOUDRON_MAIL_FROM", () => { delete process.env.SMTP_FROM; delete process.env.CLOUDRON_MAIL_FROM; expect(resolveMailFrom()).toBe("noreply@localhost"); process.env.CLOUDRON_MAIL_FROM = "staging.app@communityrule.info"; expect(resolveMailFrom()).toBe("staging.app@communityrule.info"); process.env.SMTP_FROM = "Community Rule "; expect(resolveMailFrom()).toBe( "Community Rule ", ); }); });