Files
UHD_Media_Tool/packages/domain/test/identity-evidence.test.ts
T
rootandClaude Opus 5.5 b61335eaf6 test: 测试按行为命名、server 测试统一到 test/、PG 测试分组并行
- server 的 src/modules/*/__tests__ 挪到 test/<模块>/;e2e 去掉 eN- 前缀,
  w5a2 / v2 / search2 / *-dh1 / *-md1 / *-md2、web 的 agui3 / search2、domain 的 id2 / id3 改为按行为命名
- vitest 分三组:unit、pg(真实 PG + 假引擎,文件并行,最多 6 个)、serial(真实引擎 / worker / e2e,串行);
  pg 组用例超时放宽到 60s(迁移类用例并行时排队等 schema 锁)。server 测试 615s → 约 350s
- CLAUDE.md 补测试约定;待办补 harness 去重与 continuous-series 偶发超时(疑似 Slot 评估竞争)
- ADR-004 补充:维持 Deno,记录实际只用到 --allow-run 白名单

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 16:54:04 +08:00

174 lines
8.1 KiB
TypeScript

/** ID-2:副标题 / RSS 内嵌描述片名、分类前缀、续作序号、通用词守卫、评分权重与同级取舍 */
import { describe, expect, it } from "vitest";
import {
DEFAULT_MATCH_WEIGHTS,
type IdentityCandidate,
isGenericAliasName,
normalizeTitle,
parseRelease,
queryTitles,
resolveIdentity,
scoreCandidate,
sequelNumbers,
} from "../src/index.ts";
const tv = (id: string, titles: string[], year: number, over: Partial<IdentityCandidate> = {}): IdentityCandidate => ({
provider: "tmdb",
entityType: "tv",
externalId: id,
titles,
year,
aliases: [],
...over,
});
describe("解析:分类前缀、内嵌描述、副标题片名", () => {
it("RSS `[动漫]英文名 … -GRP[中文名 / 别名 | 第N集 | …][体积]`", () => {
const p = parseRelease(
"[动漫]Martial Master S01 2020 1080p WEB-DL H.264 AAC2.0-HHWEB[武神主宰 / 武神主宰 第一季 / The God of War Dominates | 第685集 | 1080p | 类型: 动画/奇幻/冒险 | 导演: 老鹰][180.86 MB]",
);
expect(p).toMatchObject({
category: "动漫",
titleCn: "武神主宰",
titleEn: "Martial Master",
year: 2020,
season: 1,
episodes: { start: 685, end: 685 },
altTitles: ["武神主宰 第一季", "The God of War Dominates"],
});
expect(p.evidence).toContain("subtitle:embedded");
});
it("分类词不当片名:[电视剧] / [音乐] / [综艺]", () => {
for (const c of ["电视剧", "音乐", "综艺", "纪录片", "短剧", "游戏", "软件", "学习", "其他"]) {
const p = parseRelease(`[${c}]Some Title S01 2026 1080p WEB-DL-GRP`);
expect(p.category, c).toBe(c);
expect(p.titleCn, c).toBeUndefined();
}
});
it("数字紧跟普通单词是片名的一部分,不是集号(`Travis - 12 Memories`)", () => {
const p = parseRelease("[音乐]Travis - 12 Memories (2003) - WEB-DL - 16bit ALAC-HHWEB");
expect(p.episodes).toBeUndefined();
expect(p.titleEn).toContain("12 Memories");
expect(parseRelease("[Sub] Some Anime - 12 [1080p]").episodes).toEqual({ start: 12, end: 12 });
expect(parseRelease("Some Anime - 05 END [1080p]").episodes).toEqual({ start: 5, end: 5 });
});
it("副标题第一段:主标题没有中文名时取第一个中文名,其余进 altTitles;字幕/简介字段不算片名", () => {
const p = parseRelease("Archives S01 2009 1080p WEB-DL H.264 AAC-HHWEB", {
subtitle: "档案 / 档案解密 | 第152集 | 1080p | 类型: 纪录片/历史 | 主演: 石凉 [内嵌中字]",
});
expect(p).toMatchObject({ titleCn: "档案", titleEn: "Archives", altTitles: ["档案解密"], year: 2009 });
const q = parseRelease("Interstellar 2014 2160p UHD Blu-ray-GRP", {
subtitle: "星际穿越 IMAX版/星際啟示錄(港)/英语DTS-HD MA5.1/原盘英简繁&双语特效SUP字幕/章节",
});
expect(q.titleCn).toBe("星际穿越");
expect(q.altTitles).toEqual(["星際啟示錄"]);
expect(parseRelease("Some Movie 2020 1080p-GRP", { subtitle: "官方中字 | 特效字幕" }).titleCn).toBeUndefined();
});
it("通用词不进比对名称", () => {
expect(isGenericAliasName(normalizeTitle("电视剧"))).toBe(true);
expect(isGenericAliasName("剧")).toBe(true);
expect(isGenericAliasName("tv")).toBe(true);
expect(isGenericAliasName(normalizeTitle("档案"))).toBe(false);
const p = parseRelease("Lanterns 2026 1080p-GRP");
expect(queryTitles({ ...p, titleCn: "电视剧" })).toEqual(["lanterns"]);
});
});
describe("续作 / 章节序号", () => {
it("Chapter / Part / 第N章 / 罗马数字符号", () => {
expect(sequelNumbers("Mononoke The Movie Chapter III The Curse of the Serpent")).toEqual([3]);
expect(sequelNumbers("剧场版物怪:第 3 章:蛇神")).toEqual([3]);
expect(sequelNumbers("劇場版モノノ怪 第三章 蛇神")).toEqual([3]);
expect(sequelNumbers("Mononoke the Movie: Chapter Ⅱ")).toEqual([2]);
expect(sequelNumbers("Dune Part Two")).toEqual([2]);
expect(sequelNumbers("Blade Runner 2049")).toEqual([]);
});
it("序号冲突直接拒绝,一致加分", () => {
const p = parseRelease("Mononoke The Movie Chapter III The Curse of the Serpent 2026 1080p NF WEB-DL-HHWEB");
const c3: IdentityCandidate = {
provider: "tmdb",
entityType: "movie",
externalId: "3",
titles: ["剧场版物怪:第 3 章:蛇神"],
aliases: ["Mononoke the Movie: Chapter III - The Curse of the Serpent"],
year: 2026,
};
const c2 = {
...c3,
externalId: "2",
titles: ["剧场版物怪:第 2 章:火鼠"],
aliases: ["Mononoke the Movie: Chapter II"],
};
expect(scoreCandidate(p, c3).signals.sequel).toBe("match");
expect(scoreCandidate(p, c2).level).toBe("rejected");
expect(resolveIdentity(p, [c2, c3]).best?.candidate.externalId).toBe("3");
});
});
describe("证据:中英名、通用词、别名佐证、同级取舍", () => {
it("中文名精确 + 年份精确 → rule_confirmed;唯一候选 → auto", () => {
const p = parseRelease("Martial Master S01 2020 1080p WEB-DL-HHWEB", { subtitle: "武神主宰 | 第685集" });
const r = resolveIdentity(p, [tv("110181", ["武神主宰"], 2020), tv("228707", ["武神主宰"], 2023)]);
expect(r).toMatchObject({ status: "auto", best: { level: "rule_confirmed" } });
expect(r.best?.candidate.externalId).toBe("110181");
});
it("通用英文词只靠别名命中且池里撞名 → 封顶 needs_confirm;中文名命中则确认", () => {
const noCn = parseRelease("Archives S01 2009 1080p WEB-DL-HHWEB");
const pool = [
tv("201401", ["档案"], 2009, { aliases: ["Archives"] }),
tv("1", ["Archives secrètes"], 2021),
tv("2", ["Mystères d'archives"], 2009),
tv("3", ["The Magnus Archives"], 2016),
];
const r = resolveIdentity(noCn, pool);
expect(r.status).toBe("ambiguous");
expect(r.best?.evidence).toContain("generic_name");
const withCn = parseRelease("Archives S01 2009 1080p WEB-DL-HHWEB", { subtitle: "档案 | 第152集" });
expect(resolveIdentity(withCn, pool)).toMatchObject({
status: "auto",
best: { candidate: { externalId: "201401" } },
});
});
it("别名命中需要年份或第二种文字的名称佐证", () => {
const p = parseRelease("人中之龙第一季 1080p WEB-DL H265 AAC-HHWEB", {
subtitle: "人中之龙第一季 | 全98集 | 1080p",
});
const r = scoreCandidate(p, tv("256116", ["如龙", "龍が如く"], 2024, { aliases: ["人中之龙"] }));
expect(r.level).toBe("needs_confirm");
expect(r.evidence).toContain("alias_uncorroborated");
});
it("同级多候选:年份精确优先,再按命中的名称文字种数", () => {
const p = parseRelease("A Prophet S01 2026 2160p TX WEB-DL-HHWEB", { subtitle: "我不是大师 / 长风起 | 第05集" });
const right = tv("289078", ["我不是大师"], 2026, { aliases: ["A Prophet"] });
const other = tv("224006", ["预言者", "Un prophète", "A Prophet"], 2026);
const r = resolveIdentity(p, [other, right]);
expect(r.status).toBe("auto");
expect(r.best?.candidate.externalId).toBe("289078");
expect(r.reason).toBe("rule_confirmed:tie_break");
// 完全对称 → 仍进池
expect(resolveIdentity(p, [tv("1", ["我不是大师"], 2026), tv("2", ["我不是大师"], 2026)]).status).toBe("ambiguous");
});
it("季号不在候选季列表里扣分", () => {
const p = parseRelease("Heart Signal S09 2018 1080p WEB-DL-HHWEB");
const r = scoreCandidate(p, tv("72976", ["Heart Signal"], 2018, { seasonNumbers: [1, 2, 3, 4] }));
expect(r.evidence).toContain("season:missing");
expect(r.level).toBe("needs_confirm");
});
it("权重表可覆盖", () => {
const p = parseRelease("Oppenheimer 2023 1080p-GRP");
const c: IdentityCandidate = {
provider: "tmdb",
entityType: "movie",
externalId: "1",
titles: ["Oppenheimer"],
year: 2023,
aliases: [],
};
expect(scoreCandidate(p, c).level).toBe("rule_confirmed");
expect(
scoreCandidate(p, c, { weights: { thresholds: { ...DEFAULT_MATCH_WEIGHTS.thresholds, rule_confirmed: 9 } } })
.level,
).toBe("multi_evidence");
});
});