- adapters: overviewLanguages() limits overview/tagline chain to Chinese variants (zh-CN→zh-SG→zh-TW→zh-HK); titles keep full chain; season supplement fetch skips languages useless for overview - setting metadata.overviewNonChineseFallback (default off) + UI switch - media detail shows "暂无简介" for empty entity/episode overview - scripts/overview-recompute.ts: recompute stored non-Chinese overviews from kv_cache only (no TMDB requests), dry-run by default, --apply - tests: chain unit tests, Heart Signal (103147) fixture, server recompute test, MP differential for episode overview - runbook + CHANGELOG Co-Authored-By: Claude Opus 5.5 <[email protected]>
77 lines
3.5 KiB
TypeScript
77 lines
3.5 KiB
TypeScript
/**
|
|
* 每集简介差分(MD-2):MP `TmdbApi.get_tv_season_detail`(e6820a8,app/modules/themoviedb/tmdbapi.py)只按配置语言
|
|
* (TMDB_LOCALE = zh)请求季详情,episodes[].overview 原样使用,为空就为空;本项目 `fetchRichSeason` 按语言链补拉。
|
|
* 响应按《心动的信号》(TMDB tv/103147)各语言季详情的真实形态裁剪(每期标题有 zh-CN,简介只有 en-US)。
|
|
* 期望:简介与 MP 一致(中文变体都没有 → 空);标题允许按链回退(登记偏离 intentional:MD-1 占位集名按链补标题)。
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
import { fetchRichSeason } from "../../src/tmdb/index.ts";
|
|
|
|
type Ep = { episode_number: number; air_date: string; name: string; overview: string };
|
|
const season = (episodes: Ep[]) => ({
|
|
season_number: 1,
|
|
name: "第 1 季",
|
|
overview: "",
|
|
air_date: "2018-08-01",
|
|
episodes,
|
|
});
|
|
|
|
/** 各语言的 /tv/103147/season/1 响应(裁剪) */
|
|
const RECORDED: Record<string, ReturnType<typeof season>> = {
|
|
"zh-CN": season([
|
|
{ episode_number: 1, air_date: "2018-08-01", name: "心动不如行动,大胆亮出底牌!", overview: "" },
|
|
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
|
]),
|
|
"zh-SG": season([
|
|
{ episode_number: 1, air_date: "2018-08-01", name: "第 1 集", overview: "" },
|
|
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
|
]),
|
|
"zh-TW": season([
|
|
{ episode_number: 1, air_date: "2018-08-01", name: "第 1 集", overview: "" },
|
|
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
|
]),
|
|
"zh-HK": season([
|
|
{ episode_number: 1, air_date: "2018-08-01", name: "第 1 集", overview: "" },
|
|
{ episode_number: 2, air_date: "2018-08-08", name: "第 2 集", overview: "" },
|
|
]),
|
|
"en-US": season([
|
|
{
|
|
episode_number: 1,
|
|
air_date: "2018-08-01",
|
|
name: "Episode 1",
|
|
overview: "If Your Heart Skips a Beat, Play Your Cards Openly!",
|
|
},
|
|
{ episode_number: 2, air_date: "2018-08-08", name: "Who Is the One?", overview: "Who Is the One?" },
|
|
]),
|
|
};
|
|
|
|
const client = {
|
|
getJson: <T>(_path: string, params: Record<string, string | undefined> = {}) =>
|
|
Promise.resolve(RECORDED[params.language ?? "en-US"] as T),
|
|
};
|
|
|
|
/** MP 侧:只取配置语言(zh-CN)的季详情,集名 / 简介原样(空串 = 没有) */
|
|
function mpEpisodes(lang = "zh-CN") {
|
|
return RECORDED[lang]!.episodes.map((e) => ({ ep: e.episode_number, name: e.name, overview: e.overview || null }));
|
|
}
|
|
|
|
const CHAIN = ["zh-CN", "zh-SG", "zh-TW", "zh-HK", "en-US"];
|
|
const DEVIATIONS: Record<string, { kind: "intentional"; note: string }> = {
|
|
"E2.name": { kind: "intentional", note: "MD-1:zh-CN 是占位名「第 2 集」时按链补标题(可到 en-US)" },
|
|
};
|
|
|
|
describe("每集简介差分(MP get_tv_season_detail ↔ fetchRichSeason)", () => {
|
|
it("简介与 MP 一致:中文变体都没有时为空,不回退英文;标题偏离已登记", async () => {
|
|
const ours = await fetchRichSeason(client, 103147, 1, CHAIN, { now: new Date("2026-01-01") });
|
|
const mismatches: string[] = [];
|
|
for (const mp of mpEpisodes()) {
|
|
const e = ours.episodes.find((x) => x.episodeNumber === mp.ep)!;
|
|
if (e.overview !== mp.overview) mismatches.push(`E${mp.ep}.overview`);
|
|
if (e.name !== mp.name) mismatches.push(`E${mp.ep}.name`);
|
|
}
|
|
expect(mismatches.filter((m) => !DEVIATIONS[m])).toEqual([]);
|
|
expect(Object.keys(DEVIATIONS).every((k) => mismatches.includes(k))).toBe(true);
|
|
expect(ours.episodes.map((e) => e.overview)).toEqual([null, null]);
|
|
});
|
|
});
|