/** * 一次性 E2E 验证:authorize -> ingest 发送带 HTML 正文的邮件, * 等 webhook 投递后检查 receiver 收到的 payload 是否包含 markdown 字段。 */ import { signWorkerRequest, generateWmailKey } from "../shared/src/index.ts"; const CONSOLE = "http://127.0.0.1:18787"; /** # 函数 * 登录拿会话 cookie */ async function login(): Promise { const resp = await fetch(`${CONSOLE}/api/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "admin", password: "admin" }), }); const setCookie = resp.headers.get("Set-Cookie") ?? ""; return /wmh_session=[^;]+/.exec(setCookie)?.[0] ?? ""; } const cookie = await login(); if (!cookie) throw new Error("login failed"); /** 取 worker secret(签名用) */ const secretResp = await fetch(`${CONSOLE}/api/accounts`, { headers: { Cookie: cookie } }); const accounts = (await secretResp.json()) as { items: { id: string; status: string }[] }; const account = accounts.items.find((a) => a.status === "active"); if (!account) throw new Error("no active account"); const wsResp = await fetch(`${CONSOLE}/api/accounts/${account.id}/worker-secret`, { headers: { Cookie: cookie } }); const { worker_id, secret } = (await wsResp.json()) as { worker_id: string; secret: string }; console.log("worker:", worker_id, "secret:", secret.slice(0, 8) + "…"); /** 带结构化 HTML 的测试邮件(标题/加粗/链接/列表/表格) */ const mime = [ "From: E2E ", "To: hello@example.test", "Subject: Markdown 全链测试", "Message-ID: ", "MIME-Version: 1.0", 'Content-Type: text/html; charset="utf-8"', "", "

升级通知

您的配额已 用尽,请前往 账单页 处理。

", "
  • 方案 A
  • 方案 B
", "
项目用量
存储98%
", "", ].join("\r\n"); const recipient = "hello@example.test"; const sender = "e2e@example.org"; // Phase 1: authorize const auth = await signWorkerRequest(secret, worker_id, "POST", `${CONSOLE}/internal/email/authorize`, JSON.stringify({ recipient, sender, message_id: "md-e2e-1@example.org" })); const authResp = await fetch(`${CONSOLE}/internal/email/authorize`, { method: "POST", headers: { ...auth, "Content-Type": "application/json" }, body: JSON.stringify({ recipient, sender, message_id: "md-e2e-1@example.org" }), }); const decision = (await authResp.json()) as { decision: string }; console.log("authorize:", authResp.status, JSON.stringify(decision)); if (decision.decision !== "accept") throw new Error("not accepted"); // Phase 2: ingest(raw MIME) const ingestAuth = await signWorkerRequest(secret, worker_id, "POST", `${CONSOLE}/internal/email/ingest`, mime); const ingestResp = await fetch(`${CONSOLE}/internal/email/ingest`, { method: "POST", headers: { ...ingestAuth, "Content-Type": "message/rfc822", "X-WmailHub-Recipient": recipient, "X-WmailHub-Sender": sender, }, body: mime, }); const ingestData = (await ingestResp.json()) as { ok: boolean; id: string; webhook: string | null }; console.log("ingest:", ingestResp.status, JSON.stringify(ingestData)); // 触发 cron 投递 const cron = await fetch(`${CONSOLE}/api/admin/cron/run`, { method: "POST", headers: { Cookie: cookie } }); console.log("cron:", cron.status, JSON.stringify(await cron.json())); // 检查 receiver 侧收到的 payload(直接查库里这条邮件的 markdown_body + receiver 日志) const detail = await fetch(`${CONSOLE}/api/emails/${ingestData.id}`, { headers: { Cookie: cookie } }); const emailDetail = (await detail.json()) as { email: { markdown_body: string | null; subject: string } }; console.log("markdown_body from DB:", JSON.stringify(emailDetail.email.markdown_body)); if (!emailDetail.email.markdown_body?.includes("**用尽**")) throw new Error("markdown missing bold"); // 等 receiver 落日志后抓最后一条校验 markdown 字段 await Bun.write("/tmp/last_payload_check.txt", "pending"); const tail = await Bun.file("/tmp/receiver.log").text(); const lines = tail.trim().split("\n").filter(Boolean); const last = lines[lines.length - 1]; const payload = JSON.parse(last) as Record; console.log("receiver last:", JSON.stringify(payload).slice(0, 300)); const md = String(payload.markdown ?? ""); if (!md.includes("**用尽**") || !md.includes("98%")) { console.log("FULL MARKDOWN:", md); throw new Error("receiver payload missing markdown"); } console.log("\n=== E2E OK: webhook payload 含完整 Markdown ===");