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); }); });