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.

108 lines
3.4 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 { matchSenderCustomer } from "@/services/customer/resolve-sender";
import {
isRecentlyCreated,
TIMEOUT_VERIFY_RECENT_MS,
} from "@/services/import/compensation";
import { isPastRetention } from "@/services/retention/cleanup";
import {
assertTransition,
canTransition,
} from "@/services/state-machine";
import { classifyMail } from "@/services/parse/classify";
import { mapChannel } from "@/services/parse/channel-map";
import { isValidContainerNo } from "@/utils/iso6346";
/**
* Resilience A-* 可测采样(docx/异常场景与系统韧性设计 §12)
* 依赖 DB/HTTP 的链路见 force-import UI + worker compensation/retention。
*/
describe("resilience A-*", () => {
describe("A-1 input", () => {
it("A-1.1 empty subject continues classify", () => {
const r = classifyMail({ subject: "", body: "转仓", filenames: [] });
// 无主题不阻断;正文「转仓」现行口径为 TRANSFER
expect(["TRANSFER", "WORK_ORDER"]).toContain(r.mail_type);
});
it("A-1.6 invalid container format", () => {
expect(isValidContainerNo("BAD")).toBe(false);
});
it("A-1.9 unmapped channel still returns transporter", () => {
const m = mapChannel("神秘渠道XYZ");
expect(m.unmapped).toBe(true);
expect(m.transporter).toBeTruthy();
});
});
describe("A-3 timeout verify window", () => {
it("A-3.x recent createDate within 10min is recently created", () => {
const now = Date.now();
expect(
isRecentlyCreated(new Date(now - 60_000).toISOString(), now),
).toBe(true);
expect(
isRecentlyCreated(
new Date(now - TIMEOUT_VERIFY_RECENT_MS - 1).toISOString(),
now,
),
).toBe(false);
});
});
describe("A-5 / A-6 state machine", () => {
it("A-5.8 reparse allowed from PARSE_FAILED", () => {
expect(canTransition("PARSE_FAILED", "PARSING")).toBe(true);
expect(canTransition("SUCCESS", "PARSING")).toBe(false);
});
it("A-6.6 illegal transition rejected", () => {
expect(canTransition("SUCCESS", "PENDING_CONFIRM")).toBe(false);
expect(() => assertTransition("IMPORTING", "PARSED")).toThrow();
});
});
describe("retention + sender_customer_map", () => {
it("DATA_RETENTION cutoff marks old mail as past", () => {
const now = new Date("2026-07-22T00:00:00Z");
const old = new Date("2026-06-01T00:00:00Z");
expect(isPastRetention(old, old, 30, now)).toBe(true);
expect(isPastRetention(now, now, 30, now)).toBe(false);
});
it("sender map EMAIL beats DOMAIN", () => {
const hit = matchSenderCustomer(
{ fromAddr: "demo-customer@example.com" },
[
{
matchType: "DOMAIN",
matchValue: "example.com",
customerCode: "DOM",
},
{
matchType: "EMAIL",
matchValue: "demo-customer@example.com",
customerCode: "MAIL",
},
],
);
expect(hit?.customerCode).toBe("MAIL");
});
it("sender map DOMAIN match", () => {
const hit = matchSenderCustomer(
{ fromAddr: "ops@partner.example.com" },
[
{
matchType: "DOMAIN",
matchValue: "partner.example.com",
customerCode: "PARTNER01",
},
],
);
expect(hit?.customerCode).toBe("PARTNER01");
});
});
});