Files
UHD_Media_Tool/packages/contracts/test/episode-mapping.test.ts
T
rootandClaude Opus 5.5 63e94b308e feat(identify-words): 识别词——全局识别词(MP 语法)+ 作品识别词(季集换算),规则页「识别词」页签
- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-08 17:03:00 +08:00

55 lines
2.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
Api,
EpisodeMappingPreview,
EpisodeMappingPreviewInput,
EpisodeResolutionView,
listEndpoints,
} from "../src/index.ts";
// 集数映射与识别词接口的契约:端点登记、预览入参、解析结果结构。
describe("集数映射契约", () => {
it("端点登记在 Api 注册表:读和试算用 read,作品识别词写 organize.execute,全局写 admin", () => {
const keys = listEndpoints()
.filter((e) => e.path.includes("episode-mapping") || e.path.includes("identify-words"))
.map((e) => `${e.method.toUpperCase()} ${e.path} ${e.scope}`)
.sort();
expect(keys).toEqual([
"GET /identify-words/entities read",
"GET /identify-words/global read",
"GET /media-entities/{id}/identify-words read",
"POST /identify-words/global/dry-run read",
"POST /identify-words/try read",
"POST /media-entities/{id}/episode-mapping/preview read",
"POST /media-entities/{id}/identify-words/dry-run read",
"PUT /identify-words/global admin",
"PUT /media-entities/{id}/identify-words organize.execute",
]);
expect(Api.episodeMapping.preview.response).toBe(EpisodeMappingPreview);
});
it("预览入参:草稿是整块识别词原文,条数上限 300", () => {
expect(EpisodeMappingPreviewInput.parse({})).toEqual({});
expect(
EpisodeMappingPreviewInput.safeParse({ season: 2, releaseGroup: "ANi", words: "S02 >> S01 EP+24" }).success,
).toBe(true);
expect(EpisodeMappingPreviewInput.safeParse({ limit: 301 }).success).toBe(false);
});
it("解析结果:与 source_release.episode_resolution 同结构", () => {
const v = {
method: "absolute_cross_season",
source: { season: 1, episodes: { start: 25, end: 25 } },
canonical: { season: 2, episodes: { start: 13, end: 13 } },
evidence: ["conversion:cross_season"],
};
expect(EpisodeResolutionView.parse(v)).toEqual(v);
expect(EpisodeResolutionView.safeParse({ ...v, method: "guess" }).success).toBe(false);
expect(
EpisodeResolutionView.safeParse({ method: "rule", source: {}, canonical: null, special: "ignore", evidence: [] })
.success,
).toBe(true);
});
});