Forwarded mails (e.g. Yandex webmail) arrive as a single text/html part with no <html>/<body> wrapper and no text/plain alternative. Two bugs made their codes unextractable: - stripHtmlTags dropped href attributes, so button-only mails (Netflix 'Get Code') reached the LLM with no URL at all → codeExist=0. Anchors now render as 'label [url]' with & decoded. - extractMailBodies only recognised single-part HTML wrapped in <html>/<body>, so htmlBody was null and the Rendered preview was empty. A bare fragment with Content-Type text/html now counts as the html body. Verified against the real forwarded Netflix mail from production raw_mails: content now carries the travel/verify link and classifies 5/5 as login_code.
124 lines
4.5 KiB
TypeScript
124 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
decodeMimeHeader,
|
|
decodeQuotedPrintable,
|
|
extractMailBodies,
|
|
isPromotionalEmail,
|
|
stripHtmlTags,
|
|
} from "../src/services/mime";
|
|
|
|
describe("mime decoding", () => {
|
|
it("decodes UTF-8 base64 body parts as Unicode text", () => {
|
|
const rawEmail = [
|
|
'Content-Type: multipart/alternative; boundary="mail-boundary"',
|
|
"",
|
|
"--mail-boundary",
|
|
"Content-Type: text/plain; charset=UTF-8",
|
|
"Content-Transfer-Encoding: base64",
|
|
"",
|
|
"5Lit5paH6aqM6K+B56CB77yaMTIzNDU2",
|
|
"--mail-boundary--",
|
|
"",
|
|
].join("\r\n");
|
|
|
|
expect(extractMailBodies(rawEmail).textBody).toBe("中文验证码:123456");
|
|
});
|
|
|
|
it("decodes single-part base64 HTML emails", () => {
|
|
const rawEmail = [
|
|
"Content-Type: text/html; charset=utf-8",
|
|
"Content-Transfer-Encoding: base64",
|
|
"",
|
|
"PGh0bWw+PGJvZHk+PHA+5Lit5paH6aqM6K+B56CB77yaMTIzNDU2PC9wPjwvYm9keT48L2h0bWw+",
|
|
"",
|
|
].join("\r\n");
|
|
|
|
const bodies = extractMailBodies(rawEmail);
|
|
expect(bodies.htmlBody).toContain("中文验证码:123456");
|
|
expect(bodies.textBody).toBe("中文验证码:123456");
|
|
});
|
|
|
|
it("decodes UTF-8 quoted-printable body parts as Unicode text", () => {
|
|
expect(decodeQuotedPrintable("=E4=B8=AD=E6=96=87=E9=AA=8C=E8=AF=81=E7=A0=81=EF=BC=9A123456")).toBe(
|
|
"中文验证码:123456",
|
|
);
|
|
});
|
|
|
|
it("decodes RFC 2047 encoded subject headers", () => {
|
|
expect(decodeMimeHeader("=?UTF-8?B?6aqM6K+B56CB?=")).toBe("验证码");
|
|
});
|
|
});
|
|
|
|
describe("isPromotionalEmail", () => {
|
|
it("does not flag transactional mail that carries List-Unsubscribe (Gmail/Yahoo bulk sender rules)", () => {
|
|
const headers = new Headers({
|
|
"List-Unsubscribe": "<mailto:x@unsubscribe.netflix.com>",
|
|
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
|
|
Subject: "Your Netflix temporary access code",
|
|
});
|
|
expect(isPromotionalEmail(headers, "Subject: x\r\n\r\nbody")).toBe(false);
|
|
});
|
|
|
|
it("exempts RFC 2047 encoded CJK verification subjects", () => {
|
|
const headers = new Headers({
|
|
"List-Unsubscribe": "<mailto:x@example.com>",
|
|
Subject: "=?UTF-8?B?6aqM6K+B56CB?=", // 验证码
|
|
});
|
|
expect(isPromotionalEmail(headers, "Subject: x\r\n\r\nbody")).toBe(false);
|
|
});
|
|
|
|
it("still flags List-Unsubscribe mail with a promotional subject", () => {
|
|
const headers = new Headers({
|
|
"List-Unsubscribe": "<mailto:x@example.com>",
|
|
Subject: "Summer sale: 50% off everything this weekend!",
|
|
});
|
|
expect(isPromotionalEmail(headers, "Subject: x\r\n\r\nbody")).toBe(true);
|
|
});
|
|
|
|
it("keeps strong signals unconditional even when the subject looks transactional", () => {
|
|
const listMail = new Headers({ "List-ID": "<dev.lists.example.com>", Subject: "your code" });
|
|
expect(isPromotionalEmail(listMail, "Subject: x\r\n\r\nbody")).toBe(true);
|
|
|
|
const bulkMail = new Headers({ Precedence: "bulk", Subject: "your verification code" });
|
|
expect(isPromotionalEmail(bulkMail, "Subject: x\r\n\r\nbody")).toBe(true);
|
|
|
|
const campaignMail = new Headers({ Subject: "your login code" });
|
|
expect(
|
|
isPromotionalEmail(campaignMail, "X-Campaign-ID: 42\r\nSubject: x\r\n\r\nbody"),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("keeps plain mail without any bulk signal non-promotional", () => {
|
|
const headers = new Headers({ Subject: "Hello" });
|
|
expect(isPromotionalEmail(headers, "Subject: Hello\r\n\r\nbody")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("stripHtmlTags", () => {
|
|
it("preserves anchor hrefs so button-only mails keep their link", () => {
|
|
const html = '<p>Hi Thomas</p><a class="h5" href="https://www.netflix.com/verify?a=1&b=2" style="x">Get Code</a>';
|
|
expect(stripHtmlTags(html)).toContain("Get Code [https://www.netflix.com/verify?a=1&b=2]");
|
|
});
|
|
|
|
it("drops non-http hrefs but keeps the label", () => {
|
|
expect(stripHtmlTags('<a href="mailto:x@y.com">contact us</a>')).toBe("contact us");
|
|
});
|
|
});
|
|
|
|
describe("extractMailBodies single-part html", () => {
|
|
it("treats a bare text/html fragment (no <html> wrapper) as the html body", () => {
|
|
const rawEmail = [
|
|
"Subject: code",
|
|
"Content-Type: text/html; charset=utf-8",
|
|
"Content-Transfer-Encoding: base64",
|
|
"",
|
|
Buffer.from('<div>Your code: <a href="https://x.com/v?t=1">Get Code</a></div>').toString("base64"),
|
|
"",
|
|
].join("\r\n");
|
|
|
|
const bodies = extractMailBodies(rawEmail);
|
|
expect(bodies.htmlBody).toContain("https://x.com/v?t=1");
|
|
expect(bodies.textBody).toContain("Get Code [https://x.com/v?t=1]");
|
|
});
|
|
});
|