You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mail-yubao/tests/unit/split-instructions.test.ts

204 lines
7.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { describe, expect, it } from "vitest";
import {
detectUiKindsFromText,
extractForecastInstructionText,
extractMailInstructions,
splitBodySegments,
stripMobileMailChrome,
extractForwardChromeMeta,
} from "@/services/parse/split-instructions";
import { matchKeywordRules } from "@/services/parse/instruction-lexicon";
const MAIL4_SUBJECT =
"Fw: \u8f6c\u53d1\uff1a\u65b0\u589e\u9884\u62a5136\uff1a\u65b0\u8fb0\u6cfd+WHLC027G597465+WHSU8127240+\u6d1b\u6749\u77f6+40HQ+EDT2026.04-25 ETA2026.05-15\u8239\u540d\u822a\u6b21HMM EMERALD 013E+\u63d0\u62c6\u6d3e\u7ec4\u5408\u67dc-\u4e0d\u5e26\u6258\u67b6";
const MAIL4_BODY = [
"Dear,",
"\u539f\u7bb1\u53f7YT2604021091=FBA199R49LD6-MDW2=76\u4ef6\u64cd\u4f5c\u6307\u4ee4",
"\u6362\u6807\u540e\u5355\u53f7\uff1aFBA19HW52S0L-2G1MCB6X-HIA1=76\u4ef6",
"1\uff1a\u8986\u76d6\u8d34FBA\u6807\u7b7e\uff0c\u4e00\u7bb1\u8d34\u4e24\u5f20",
"3\uff1a\u8d34\u597d\u62cd\u7167\u56de\u4f20",
].join("\n");
const MAIL4_FILES = [
"YT2604021091=76\u4ef6\u6362\u6807\u8d70HIA1\u5361\u6d3e\u51fa\u5e93\u64cd\u4f5c\u6307\u4ee4.xlsx",
"FBA19HW52S0L-2G1MCB6X-HIA1.pdf",
];
const MULTI_DATE_BODY = [
"\u65b0\u589e\u8f6c\u4ed3\uff0c\u8bf7\u7559\u610f",
"\u67dc\u53f7\uff1aMATU2745683",
"------------------ \u539f\u59cb\u7535\u5b50\u90ae\u4ef6 ------------------",
"\u53d1\u4ef6\u4eba\uff1aa@b.com",
"\u4e3b\u9898\uff1a\u65b0\u589e\u9884\u62a5136\uff1a\u65b0\u8fb0\u6cfd+WHSU8127240",
"\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5",
].join("\n");
describe("splitBodySegments", () => {
it("splits on quote header", () => {
const segs = splitBodySegments(MULTI_DATE_BODY);
expect(segs.length).toBeGreaterThanOrEqual(2);
expect(segs[0]).toContain("\u65b0\u589e\u8f6c\u4ed3");
});
});
describe("matchKeywordRules", () => {
it("maps hard words to ui kinds", () => {
expect(matchKeywordRules("\u8bf7\u8d34\u6807\u5e76\u62cd\u7167", "body").map((h) => h.uiKind)).toEqual(
expect.arrayContaining(["work_order"]),
);
expect(matchKeywordRules("\u65b0\u589e\u9884\u62a5", "subject").map((h) => h.uiKind)).toContain(
"forecast",
);
expect(matchKeywordRules("\u65b0\u589e\u8f6c\u4ed3", "body").map((h) => h.uiKind)).toContain(
"transfer",
);
});
});
describe("detectUiKindsFromText", () => {
it("subject forecast", () => {
expect(detectUiKindsFromText(MAIL4_SUBJECT, "", [])).toContain("forecast");
});
it("body work_order", () => {
expect(detectUiKindsFromText("", MAIL4_BODY, [])).toContain("work_order");
});
it("DO filename", () => {
expect(detectUiKindsFromText("", "", ["WHSU8127240-DO.pdf"])).toContain(
"do_upload",
);
});
});
describe("extractMailInstructions multi-date", () => {
it("mail4: forecast + work_order as separate units", () => {
const units = extractMailInstructions({
subject: MAIL4_SUBJECT,
body: MAIL4_BODY,
filenames: MAIL4_FILES,
});
const kinds = units.map((u) => u.uiKind);
expect(kinds).toContain("forecast");
expect(kinds).toContain("work_order");
const wo = units.find((u) => u.uiKind === "work_order");
expect(wo?.segmentText).toMatch(/操作指令|换标/);
});
it("keeps transfer(current) + forecast(history) as two units", () => {
const units = extractMailInstructions({
subject: "MATU2745683",
body: MULTI_DATE_BODY,
filenames: [],
});
expect(units.some((u) => u.uiKind === "transfer" && u.isCurrent)).toBe(true);
expect(units.some((u) => u.uiKind === "forecast")).toBe(true);
expect(units.length).toBeGreaterThanOrEqual(2);
});
it("does not duplicate subject forecast when body already has forecast", () => {
const units = extractMailInstructions({
subject: MAIL4_SUBJECT,
body: "\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5\uff1a\u65b0\u8fb0\u6cfd+WHSU8127240+\u63d0\u62c6\u6d3e",
filenames: [],
});
const forecasts = units.filter((u) => u.uiKind === "forecast");
expect(forecasts).toHaveLength(1);
expect(forecasts[0].source).toBe("body_segment");
});
it("mail4 current unit is work_order not forecast", () => {
const units = extractMailInstructions({
subject: MAIL4_SUBJECT,
body: MAIL4_BODY,
filenames: MAIL4_FILES,
});
const current = units.filter((u) => u.isCurrent);
expect(current.some((u) => u.uiKind === "work_order")).toBe(true);
expect(current.every((u) => u.uiKind !== "forecast")).toBe(true);
});
});
describe("extractForecastInstructionText", () => {
it("isolates forecast from label body", () => {
const t = extractForecastInstructionText({
subject: MAIL4_SUBJECT,
body: MAIL4_BODY,
});
expect(t).not.toMatch(/\u8986\u76d6\u8d34/);
});
it("extracts 时间/发件人/主题 from QQ forward chrome, ignores 收件人", () => {
const body = [
"Dear, 请处理。",
"",
"---- 转发的邮件 ----",
"发件人cs3<cs3@xinfenginc.com>",
"日期 2026年07月09日 15:32",
"收件人fancy<fancy@link-ever.com>、receiving<receiving@usasinogroup.com>、hui",
"<hui@usasinogroup.com>、luo<luo@usasinogroup.com>",
"主题 TIIU8073522-90022",
"",
"贴标后拍照回传",
].join("\n");
const meta = extractForwardChromeMeta(body);
expect(meta.from).toMatch(/cs3.*cs3@xinfenginc\.com/);
expect(meta.at).toMatch(/2026年07月09日\s*15:32/);
expect(meta.subject).toBe("TIIU8073522-90022");
// 收件人不得进 from/subject
expect(meta.from).not.toMatch(/fancy|receiving|hui@/);
expect(meta.subject).not.toMatch(/fancy|收件人/);
const units = extractMailInstructions({
subject: "Fw: TIIU8073522-90022",
body,
});
const cur = units.find((u) => u.isCurrent) || units[0];
expect(cur?.segmentSubject).toBe("TIIU8073522-90022");
expect(cur?.roundFrom).toMatch(/cs3/);
expect(cur?.roundAt).toMatch(/2026年07月09日/);
expect(cur?.segmentText || "").not.toMatch(/收件人fancy|转发的邮件/);
});
it("strips QQ desktop 转发的邮件 header block", () => {
const body = [
"Dear,",
"请贴标后拍照回传,谢谢。",
"",
"---- 转发的邮件 ----",
"发件人cs3<cs3@xinfenginc.com>",
"日期 2026年07月09日 15:32",
"收件人fancy<fancy@link-ever.com>、receiving<receiving@usasinogroup.com>、hui",
"<hui@usasinogroup.com>、luo<luo@usasinogroup.com>、date<date@usasi",
"nogroup.com>、op10<op10@xinfenginc.com>、cs1<cs1@xinfenginc.com>",
"主题 TIIU8073522-90022",
"",
"原业务说明可在此继续",
].join("\n");
const t = stripMobileMailChrome(body);
expect(t).not.toMatch(/转发的邮件/);
expect(t).not.toMatch(/发件人cs3|收件人fancy|usasinogroup|主题 TIIU|日期 2026/);
expect(t).toMatch(/请贴标后拍照回传/);
expect(t).toMatch(/原业务说明可在此继续/);
});
it("strips QQ/iPhone mobile forward chrome from instruction", () => {
const body = [
"Fw: 转发:新增预报136:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ",
"船名航次HMM EMERALD 013E+提拆派组合柜-不带托架",
"【发件人75440098<75440098@qq.com>",
"收件人3457189498<3457189498@qq.com>",
"大小 1.4M",
"时间 2026年7月14日 16:00",
"发自我的iPhone】",
"",
"请查收新增预报资料",
].join("\n");
const t = extractForecastInstructionText({
subject: "Fw: 转发:新增预报136:新辰泽+WHSU8127240",
body,
});
expect(t).not.toMatch(/发件人75440098|收件人3457189498|1\.4M|发自我的iPhone|2026年7月14日/);
expect(t).toMatch(/新增预报|船名航次|WHSU8127240|请查收新增预报/);
});
});