import { createMockNexusPhpSite, createMockTmdb, type MockNexusPhpSite, type MockTmdb } from "@umt/testkit"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { refreshMetadata } from "../../src/modules/sources-identity/service/metadata.ts"; import { trackedSeasonsForAirDates } from "../../src/modules/sources-identity/service/metadata-totals.ts"; import { searchSubscription } from "../../src/modules/sources-identity/tasks.ts"; import { createHarness, type Harness, json, pgAvailable } from "../sources-identity/harness.ts"; // C1-A:订阅搜索关键词追加到主动搜索;元数据刷新按 TMDB 季详情给追踪中订阅的季写集播出日期(订阅日历数据源)。 const up = await pgAvailable(); describe.skipIf(!up)("订阅搜索关键词与集播出日期", () => { let h: Harness; let site: MockNexusPhpSite; let tmdb: MockTmdb; let siteId: string; beforeAll(async () => { site = await createMockNexusPhpSite(); tmdb = await createMockTmdb(); h = await createHarness("subsc1a", { tmdbUrl: tmdb.url }); const ok = await json( await h.req("/sites", { body: { key: "audiences", name: "观众", adapter: "nexusphp", baseUrl: `${site.baseUrl}/`, rateLimit: { minIntervalMs: 0, concurrency: 4 }, }, }), ); expect(ok.status).toBe(200); siteId = ok.body.data.id; await h.req("/site-accounts", { body: { siteId, label: "主号", authKind: "cookie", credential: site.cookie } }); }); afterAll(async () => { await h?.close(); await site?.close(); await tmdb?.close(); }); it("params.keywords 追加在标题之后(去重,不占标题检索词上限),每个站点每个词搜一次", async () => { const e = ( await h.db .insertInto("media_entity") .values({ kind: "movie", title: "Dune", original_title: "Dune", year: 2021, status: "active" }) .returning("id") .executeTakeFirstOrThrow() ).id; const sub = ( await h.db .insertInto("subscription") .values({ media_entity_id: e, media_kind: "movie", mode: "once", params: JSON.stringify({ keywords: ["Dune Part One", "dune", " "] }), }) .returning("id") .executeTakeFirstOrThrow() ).id; const r = await searchSubscription(h.rt, sub); expect(r).toMatchObject({ status: "ok", keywords: ["Dune", "Dune Part One"], sites: [siteId], searches: 2 }); const snaps = await h.db .selectFrom("site_snapshot") .select("query") .where("kind", "=", "live_search") .where("query", "=", "Dune Part One") .execute(); expect(snaps).toHaveLength(1); }); it("元数据刷新:追踪中订阅的季按 TMDB 季详情建集单元并写播出日期;已有集单元只补日期;没有追踪订阅不拉季详情", async () => { const e = ( await h.db .insertInto("media_entity") .values({ kind: "series", title: "The Bear", original_title: "The Bear", year: 2022, status: "active" }) .returning("id") .executeTakeFirstOrThrow() ).id; await h.db .insertInto("external_identity") .values({ media_entity_id: e, provider: "tmdb", entity_type: "tv", external_id: "136315", status: "accepted", }) .execute(); expect(await trackedSeasonsForAirDates(h.db, e, [1, 2, 3])).toEqual([]); const seasonRequests = () => tmdb.callsTo(/\/season\/\d+/).length; const before = seasonRequests(); await refreshMetadata(h.rt, e); expect(seasonRequests()).toBe(before); const s2 = ( await h.db .insertInto("content_unit") .values({ media_entity_id: e, kind: "season", season_number: 2, status: "confirmed" }) .returning("id") .executeTakeFirstOrThrow() ).id; // 已有的 E01(发布懒建,没有日期) const e1 = ( await h.db .insertInto("content_unit") .values({ media_entity_id: e, kind: "episode", season_number: 2, episode_number: 1, status: "provisional" }) .returning("id") .executeTakeFirstOrThrow() ).id; await h.db .insertInto("subscription") .values({ media_entity_id: e, media_kind: "series", mode: "continuous", season_unit_id: s2, season_scope: "following_seasons", }) .execute(); expect(await trackedSeasonsForAirDates(h.db, e, [1, 2, 3])).toEqual([2, 3]); const r = await refreshMetadata(h.rt, e); expect(r.status).toBe("refreshed"); const eps = await h.db .selectFrom("content_unit") .select(["id", "season_number", "episode_number", "status", "title"]) .select((eb) => eb.fn("to_char", [eb.ref("air_date"), eb.val("YYYY-MM-DD")]).as("air")) .where("media_entity_id", "=", e) .where("kind", "=", "episode") .orderBy("season_number") .orderBy("episode_number") .execute(); expect(eps.filter((x) => x.season_number === 2)).toHaveLength(10); expect(eps.filter((x) => x.season_number === 3)).toHaveLength(10); expect(eps.filter((x) => x.season_number === 1)).toHaveLength(0); const first = eps.find((x) => x.season_number === 2 && x.episode_number === 1)!; expect(first).toMatchObject({ id: e1, status: "provisional", air: "2023-06-22" }); expect(eps.find((x) => x.season_number === 2 && x.episode_number === 2)).toMatchObject({ status: "confirmed", air: "2023-06-29", }); // 再刷新幂等:不重复建 await refreshMetadata(h.rt, e); const n = await h.db .selectFrom("content_unit") .select((eb) => eb.fn.countAll().as("n")) .where("media_entity_id", "=", e) .where("kind", "=", "episode") .executeTakeFirstOrThrow(); expect(Number(n.n)).toBe(20); }); });