/** Markdown 转换与 webhook markdown 字段测试(v2.1.0) */ import { describe, expect, test } from "bun:test"; import { htmlToMarkdown } from "../shared/src/markdown.ts"; import { ingestEmail } from "../console/src/services/email_ingest.ts"; import { buildWebhookPayload } from "../console/src/services/webhook_delivery.ts"; import { loginCookie, makeTestEnv, TEST_MAIL, authedFetch } from "./helpers.ts"; /** # 成员变量: 纯文本邮件样例(无 HTML part) **/ const TEST_MAIL_PLAIN = `From: Bob \r To: hello@example.test\r Subject: Plain Mail\r Content-Type: text/plain\r \r plain body\r `; /** # 成员变量: HTML 邮件样例(text/html part 带 p/b 结构) **/ const TEST_MAIL_HTML = `From: Carl \r To: hello@example.test\r Subject: Html Mail\r MIME-Version: 1.0\r Content-Type: text/html\r \r

Hello world and link

\r `; describe("htmlToMarkdown(mailhook 同思路)", () => { test("标题/加粗/链接/行内代码", () => { expect(htmlToMarkdown("

Hello

World bold link cfg

")).toBe( "## Hello\n\nWorld **bold** [link](https://x.com) `cfg`", ); }); test("列表与嵌套列表", () => { expect(htmlToMarkdown("")).toBe( "- one\n\n- two\n\n 1. nested", ); }); test("表格转管道表(| 转义)", () => { expect(htmlToMarkdown("
A
1|2
")).toBe( "| A |\n| --- |\n| 1\\|2 |", ); }); test("pre 代码块保留换行;blockquote 引用;hr 分隔线", () => { expect(htmlToMarkdown("
line1\nline2
")).toBe("```\nline1\nline2\n```"); expect(htmlToMarkdown("
quoted
")).toBe("> quoted"); expect(htmlToMarkdown("
")).toBe("---"); }); test("script/style 丢弃;空内容返回空串", () => { expect(htmlToMarkdown("")).toBe(""); expect(htmlToMarkdown("")).toBe(""); }); }); describe("邮件入库 markdown_body 与 webhook markdown 字段", () => { test("HTML 邮件入库时存 Markdown;纯文本邮件 markdown=纯文本", async () => { const t = await makeTestEnv(); const html = await ingestEmail(t.env, { raw: new TextEncoder().encode(TEST_MAIL_HTML), mailFrom: "a@b.c", rcptTo: "hello@example.test", accountId: t.accountId, }); const row = await t.env.DB.prepare("SELECT markdown_body, text_body, html_body FROM emails WHERE id = ?") .bind(html.id) .first<{ markdown_body: string | null; text_body: string | null; html_body: string | null }>(); expect(row?.html_body).toBeTruthy(); expect(row?.markdown_body).toContain("**"); expect(row?.markdown_body?.trim().length ?? 0).toBeGreaterThan(0); const plain = await ingestEmail(t.env, { raw: new TextEncoder().encode(TEST_MAIL_PLAIN), mailFrom: "a@b.c", rcptTo: "hello@example.test", accountId: t.accountId, }); const row2 = await t.env.DB.prepare("SELECT markdown_body FROM emails WHERE id = ?") .bind(plain.id) .first<{ markdown_body: string | null }>(); expect(row2?.markdown_body).toBeTruthy(); }); test("webhook payload 含 markdown 字段(优先存量 markdown_body)", () => { const payload = buildWebhookPayload({ id: "msg_x", mailboxAddress: "hello@example.test", fromAddress: "a@b.c", toAddresses: '["hello@example.test"]', subject: "s", textBody: "plain body", htmlBody: "

hello world

", markdownBody: null, receivedAt: new Date().toISOString(), }); expect(payload.markdown).toBe("hello **world**"); const payload2 = buildWebhookPayload({ id: "msg_y", mailboxAddress: null, fromAddress: null, toAddresses: "[]", subject: null, textBody: null, htmlBody: null, markdownBody: "stored md", receivedAt: new Date().toISOString(), }); expect(payload2.markdown).toBe("stored md"); }); test("设置接口返回 api_key 元信息;生成接口明文 32 位 wmh_", async () => { const t = await makeTestEnv(); const cookie = await loginCookie(t); const before = (await (await authedFetch(t, cookie, "http://localhost/api/settings")).json()) as { api_key: { has_key: boolean; key_last4: string }; }; expect(before.api_key.has_key).toBe(false); const created = (await ( await authedFetch(t, cookie, "http://localhost/api/settings/api-key", { method: "POST" }) ).json()) as { key: string }; expect(created.key.startsWith("wmh_")).toBe(true); expect(created.key.length).toBe(32); const after = (await (await authedFetch(t, cookie, "http://localhost/api/settings")).json()) as { api_key: { has_key: boolean; key_last4: string }; }; expect(after.api_key.has_key).toBe(true); expect(created.key.endsWith(after.api_key.key_last4)).toBe(true); }); });