import { describe, expect, it } from "vitest"; import { Api, EpisodeMappingPreview, EpisodeMappingPreviewInput, EpisodeMappingRuleCreate, EpisodeMappingRuleUpdate, EpisodeResolutionView, listEndpoints, } from "../src/index.ts"; // 集数映射接口(批次 4)的契约:端点登记、写入与预览的入参、解析结果结构。 describe("集数映射契约", () => { it("端点登记在 Api 注册表:读用 read,写用 organize.execute", () => { const keys = listEndpoints() .filter((e) => e.path.includes("episode-mapping")) .map((e) => `${e.method.toUpperCase()} ${e.path} ${e.scope}`) .sort(); expect(keys).toEqual([ "DELETE /episode-mapping-rules/{id} organize.execute", "GET /media-entities/{id}/episode-mapping-rules read", "PATCH /episode-mapping-rules/{id} organize.execute", "POST /media-entities/{id}/episode-mapping-rules organize.execute", "POST /media-entities/{id}/episode-mapping/preview read", ]); expect(Api.episodeMapping.preview.response).toBe(EpisodeMappingPreview); }); it("新建:三种规则都接受;季号不能为负,发布组去首尾空白", () => { expect(EpisodeMappingRuleCreate.parse({ kind: "expression", expression: "EP-12", targetSeason: 2 })).toEqual({ kind: "expression", expression: "EP-12", targetSeason: 2, }); expect( EpisodeMappingRuleCreate.safeParse({ season: 1, releaseGroup: "ANi", kind: "table", tableEntries: [{ from: { start: 27, end: 28 }, to: { season: 2, episode: 2 } }], }).success, ).toBe(true); expect( EpisodeMappingRuleCreate.parse({ kind: "special", special: "ignore", releaseGroup: " ANi " }).releaseGroup, ).toBe("ANi"); expect(EpisodeMappingRuleCreate.safeParse({ kind: "special", special: "skip" }).success).toBe(false); expect(EpisodeMappingRuleCreate.safeParse({ kind: "expression", season: -1 }).success).toBe(false); expect(EpisodeMappingRuleCreate.safeParse({ kind: "expression", expression: "E".repeat(65) }).success).toBe(false); }); it("修改:全部可选,可只改状态", () => { expect(EpisodeMappingRuleUpdate.parse({ status: "disabled" })).toEqual({ status: "disabled" }); expect(EpisodeMappingRuleUpdate.safeParse({ status: "deleted" }).success).toBe(false); }); it("预览入参:草稿可空,条数上限 300", () => { expect(EpisodeMappingPreviewInput.parse({})).toEqual({}); expect(EpisodeMappingPreviewInput.safeParse({ season: 2, releaseGroup: "ANi", draft: null }).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); }); });