126 lines
5.1 KiB
TypeScript
126 lines
5.1 KiB
TypeScript
import { createMockTmdb, type MockTmdb } from "@umt/testkit";
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
import { registerScheduledJob, scheduleRegistry } from "../../src/modules/schedules/registry.ts";
|
|
import { syncDefinitions } from "../../src/modules/schedules/store.ts";
|
|
import { SourcesRuntime } from "../../src/modules/sources-identity/runtime.ts";
|
|
import {
|
|
BACKFILL_LANGUAGES_JOB,
|
|
backfillLanguages,
|
|
} from "../../src/modules/sources-identity/service/backfill-languages.ts";
|
|
import { createEntityFromTmdbCandidate } from "../../src/modules/sources-identity/service/entity-from-candidate.ts";
|
|
import { createHarness, type Harness, pgAvailable } from "./harness.ts";
|
|
|
|
// FIX-5 存量元数据回刷(metadata.backfill_languages)
|
|
|
|
const up = await pgAvailable();
|
|
|
|
describe.skipIf(!up)("元数据语言回刷", () => {
|
|
let h: Harness;
|
|
let tmdb: MockTmdb;
|
|
const ids: string[] = [];
|
|
const actor = { type: "user" as const, id: "tester" };
|
|
|
|
beforeAll(async () => {
|
|
tmdb = await createMockTmdb();
|
|
h = await createHarness("backfill", { tmdbUrl: tmdb.url });
|
|
for (const [externalId, kind] of [
|
|
["95396", "tv"],
|
|
["872585", "movie"],
|
|
] as const) {
|
|
const r = await createEntityFromTmdbCandidate(h.db, () => h.rt.tmdb(), { externalId, kind }, actor);
|
|
ids.push(r.mediaEntityId);
|
|
}
|
|
// 没有 TMDB 身份的作品不参与
|
|
await h.db
|
|
.insertInto("media_entity")
|
|
.values({ kind: "series", title: "本地短剧", status: "provisional" } as never)
|
|
.execute();
|
|
});
|
|
afterAll(async () => {
|
|
await h?.close();
|
|
await tmdb?.close();
|
|
});
|
|
|
|
it("登记为维护组任务,首次登记即到期;跑完按新语言链刷新并自动停用", async () => {
|
|
registerScheduledJob(h.ctx, {
|
|
key: BACKFILL_LANGUAGES_JOB,
|
|
defaults: {
|
|
name: "元数据语言回刷",
|
|
group: "maintain",
|
|
schedule: { type: "interval", everyMs: 86_400_000 },
|
|
firstRun: "now",
|
|
},
|
|
run: async () => undefined,
|
|
});
|
|
const now = new Date();
|
|
await syncDefinitions(h.db, scheduleRegistry(h.ctx).list(), now);
|
|
const before = await h.db
|
|
.selectFrom("scheduled_job")
|
|
.selectAll()
|
|
.where("id", "=", BACKFILL_LANGUAGES_JOB)
|
|
.executeTakeFirstOrThrow();
|
|
expect(before).toMatchObject({ enabled: true, board_group: "maintain" });
|
|
expect(before.next_run_at!.getTime()).toBeLessThanOrEqual(now.getTime());
|
|
|
|
const r = await backfillLanguages(h.rt, { jobId: BACKFILL_LANGUAGES_JOB, disableWhenDone: true, minIntervalMs: 0 });
|
|
expect(r).toMatchObject({ status: "ok", total: 2, refreshed: 2, failed: 0 });
|
|
expect(r.summary).toContain("2 部作品:刷新 2");
|
|
const tv = await h.db.selectFrom("media_entity").selectAll().where("id", "=", ids[0]!).executeTakeFirstOrThrow();
|
|
expect(tv.title).toBe("人生切割术");
|
|
const eps = await h.db
|
|
.selectFrom("content_unit")
|
|
.select(["episode_number", "title"])
|
|
.where("media_entity_id", "=", ids[0]!)
|
|
.where("kind", "=", "episode")
|
|
.where("season_number", "=", 1)
|
|
.orderBy("episode_number")
|
|
.execute();
|
|
expect(eps.length).toBeGreaterThan(0);
|
|
|
|
const after = await h.db
|
|
.selectFrom("scheduled_job")
|
|
.selectAll()
|
|
.where("id", "=", BACKFILL_LANGUAGES_JOB)
|
|
.executeTakeFirstOrThrow();
|
|
expect(after).toMatchObject({ enabled: false, customized: true });
|
|
// 重启后同步定义不会重新打开
|
|
await syncDefinitions(h.db, scheduleRegistry(h.ctx).list(), new Date());
|
|
const synced = await h.db
|
|
.selectFrom("scheduled_job")
|
|
.select(["enabled"])
|
|
.where("id", "=", BACKFILL_LANGUAGES_JOB)
|
|
.executeTakeFirstOrThrow();
|
|
expect(synced.enabled).toBe(false);
|
|
});
|
|
|
|
it("按设置建的客户端:网络请求限速,走 PG 请求缓存,第二次不再请求 TMDB", async () => {
|
|
await h.db
|
|
.insertInto("settings")
|
|
.values({ key: "metadata", value: JSON.stringify({ tmdbBaseUrl: `${tmdb.url}/3` }) } as never)
|
|
.onConflict((oc) => oc.column("key").doUpdateSet({ value: JSON.stringify({ tmdbBaseUrl: `${tmdb.url}/3` }) }))
|
|
.execute();
|
|
const prev = Deno.env.get("TMDB_API_KEY");
|
|
Deno.env.set("TMDB_API_KEY", "test-tmdb-key");
|
|
try {
|
|
const rt = new SourcesRuntime(h.ctx, {});
|
|
const t0 = performance.now();
|
|
const first = await backfillLanguages(rt, { minIntervalMs: 100 });
|
|
const elapsed = performance.now() - t0;
|
|
expect(first).toMatchObject({ status: "ok", total: 2, refreshed: 2 });
|
|
expect(first.requests).toBeGreaterThan(2);
|
|
expect(elapsed).toBeGreaterThanOrEqual((first.requests! - 1) * 90);
|
|
const second = await backfillLanguages(rt, { minIntervalMs: 100 });
|
|
expect(second).toMatchObject({ refreshed: 2, requests: 0 });
|
|
} finally {
|
|
if (prev === undefined) Deno.env.delete("TMDB_API_KEY");
|
|
else Deno.env.set("TMDB_API_KEY", prev);
|
|
}
|
|
});
|
|
|
|
it("TMDB 未配置:跳过,不停用", async () => {
|
|
const rt = new SourcesRuntime(h.ctx, { tmdb: () => null });
|
|
const r = await backfillLanguages(rt, { jobId: "x", disableWhenDone: true });
|
|
expect(r.status).toBe("skipped");
|
|
});
|
|
});
|