import { createMockTmdb, type MockTmdb, SPLIT_COUR_FIXTURE, TMDB_FIXTURES } from "@umt/testkit"; import { afterEach, describe, expect, it } from "vitest"; import { createTmdbClient, fetchEpisodeGroup, fetchEpisodeGroups, groupSeasonAsRichSeason } from "./index.ts"; let mock: MockTmdb | null = null; afterEach(async () => { await mock?.close(); mock = null; }); async function setup() { mock = await createMockTmdb({ fixtures: [...TMDB_FIXTURES, SPLIT_COUR_FIXTURE] }); const tmdb = createTmdbClient({ baseUrl: `${mock.url}/3`, apiKey: "test-tmdb-key", retries: 0, timeoutMs: 1000 }); return { mock, tmdb }; } describe("TMDB 剧集组", () => { it("列表:id、名称、类型、组数、集数、播出平台", async () => { const { tmdb } = await setup(); const groups = await fetchEpisodeGroups(tmdb, 990001, "zh-CN"); expect(groups).toEqual([ { id: "grp-production", name: "制作季", type: 6, description: null, groupCount: 2, episodeCount: 25, network: "TOKYO MX", }, { id: "grp-aired", name: "播出顺序", type: 1, description: null, groupCount: 1, episodeCount: 25, network: null, }, ]); }); it("详情:组序号 = 季号,组内顺序从 1 编集号,保留 TMDB 默认季集号、标题、播出日期", async () => { const { tmdb } = await setup(); const g = await fetchEpisodeGroup(tmdb, "grp-production", "zh-CN"); expect(g.name).toBe("制作季"); expect(g.seasons.map((s) => [s.seasonNumber, s.name, s.episodes.length, s.airDate])).toEqual([ [1, "第一部", 12, "2024-01-07"], [2, "第二部", 13, "2024-07-07"], ]); const s2e1 = g.seasons[1]!.episodes[0]!; expect(s2e1).toMatchObject({ seasonNumber: 2, episodeNumber: 1, tmdbSeason: 1, tmdbEpisode: 13, name: "下篇第1话", nameLanguage: "zh-CN", airDate: "2024-07-07", }); expect(g.seasons[1]!.episodes.at(-1)).toMatchObject({ episodeNumber: 13, tmdbEpisode: 25 }); }); it("组内按 order 排序;重复的组序号顺延;占位名按无标题处理", async () => { const calls: string[] = []; const client = { getJson: async (path: string) => { calls.push(path); return { id: "x", name: "X", type: 3, groups: [ { id: "b", name: "B", order: 1, episodes: [{ order: 0, season_number: 2, episode_number: 1, name: "Opening", air_date: "2020-01-01" }], }, { id: "a", name: "A", order: 1, episodes: [ { order: 1, season_number: 1, episode_number: 5, name: "第 5 集", air_date: "2019-02-01" }, { order: 0, season_number: 1, episode_number: 9, name: "Nine", air_date: "2019-01-01" }, ], }, ], } as T; }, }; const g = await fetchEpisodeGroup(client, "x", "zh-CN"); expect(calls).toEqual(["/tv/episode_group/x"]); expect(g.seasons.map((s) => [s.seasonNumber, s.name])).toEqual([ [1, "B"], [2, "A"], ]); expect(g.seasons[1]!.episodes.map((e) => [e.episodeNumber, e.tmdbSeason, e.tmdbEpisode, e.name])).toEqual([ [1, 1, 9, "Nine"], [2, 1, 5, null], ]); const rich = groupSeasonAsRichSeason(42, g.seasons[1]!, "zh-CN", "/p.jpg"); expect(rich).toMatchObject({ tvId: "42", seasonNumber: 2, name: "A", posterPath: "/p.jpg", airDate: "2019-01-01" }); }); it("剧集组不存在抛 notfound;请求走缓存", async () => { const { tmdb, mock } = await setup(); await expect(fetchEpisodeGroup(tmdb, "nope")).rejects.toMatchObject({ kind: "notfound" }); await fetchEpisodeGroups(tmdb, 990001, "zh-CN"); const before = mock.calls.length; await fetchEpisodeGroups(tmdb, 990001, "zh-CN"); expect(mock.calls.length).toBe(before); }); });