Files

135 lines
5.4 KiB
TypeScript

import { normalizeTitle } from "@umt/domain";
import { createMockTmdb, type MockTmdb, type TmdbFixture } from "@umt/testkit";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { siteAliasGuard } from "../../src/modules/sources-identity/service/alias-guard.ts";
import { resolveRelease } from "../../src/modules/sources-identity/service/resolve.ts";
import { createHarness, type Harness, pgAvailable, seedSite } from "./harness.ts";
// FIX-5 别名守卫:站点标题里另一部作品的英文名不能挂到按中文名命中的作品上。
const up = await pgAvailable();
const cnTv = (id: number, zh: string, en: string | null, date: string): TmdbFixture => ({
type: "tv",
id,
original_title: zh,
original_language: "zh",
origin_country: ["CN"],
date,
titles: en ? { "zh-CN": zh, "en-US": en } : { "zh-CN": zh },
popularity: 5,
alternative_titles: [],
external_ids: {},
});
describe("siteAliasGuard(纯函数)", () => {
const base = {
level: "rule_confirmed",
yearMatch: "exact",
parsedYear: 2026,
entityYear: 2026,
normalized: "spark of the long march",
script: "latin" as const,
externalNames: null,
ownedByOther: false,
};
it("证据等级非最高、年份不相容、与外部标题冲突、已属于别的作品都不登记", () => {
expect(siteAliasGuard(base)).toBeNull();
expect(siteAliasGuard({ ...base, level: "multi_evidence" })).toBe("low_evidence");
expect(siteAliasGuard({ ...base, parsedYear: 2019 })).toBe("year_conflict");
expect(siteAliasGuard({ ...base, parsedYear: 2019, seasonYears: [2019] })).toBeNull();
expect(
siteAliasGuard({
...base,
externalNames: { han: new Set(["东北有个周东北"]), latin: new Set(["zhou dongbei"]) },
}),
).toBe("conflicts_external_title");
// 外部标题只有中文时,英文名不按外部标题判冲突
expect(siteAliasGuard({ ...base, externalNames: { han: new Set(["长河"]), latin: new Set() } })).toBeNull();
expect(siteAliasGuard({ ...base, ownedByOther: true })).toBe("owned_by_other");
expect(siteAliasGuard({ ...base, normalized: "电视剧" })).toBe("generic");
});
});
describe.skipIf(!up)("别名守卫(resolveObservation)", () => {
let h: Harness;
let tmdb: MockTmdb;
let siteId: string;
let n = 0;
beforeAll(async () => {
tmdb = await createMockTmdb({
fixtures: [
cnTv(990001, "东北有个周东北", "Zhou Dongbei in the Northeast", "2026-01-10"),
cnTv(990003, "长河", null, "2026-02-01"),
],
});
h = await createHarness("aliasguard", { tmdbUrl: tmdb.url });
siteId = (await seedSite(h, { baseUrl: "https://guard.example/" })).site.id;
await h.db
.insertInto("settings")
.values({ key: "identity", value: JSON.stringify({ eagerTmdbLookup: true }) } as never)
.execute();
});
afterAll(async () => {
await h?.close();
await tmdb?.close();
});
const release = (title: string) =>
h.db
.insertInto("source_release")
.values({ site_id: siteId, site_release_id: String(880000 + ++n), title })
.returningAll()
.executeTakeFirstOrThrow();
const aliasesOf = async (entityId: string) =>
(
await h.db.selectFrom("title_alias").select(["alias", "source"]).where("media_entity_id", "=", entityId).execute()
).map((a) => a.alias);
const lastAttempt = (releaseId: string) =>
h.db
.selectFrom("resolution_attempt")
.select(["best"])
.where("source_release_id", "=", releaseId)
.orderBy("created_at", "desc")
.executeTakeFirstOrThrow();
it("与作品已确认的英文名对不上的站点英文名不登记,记入 resolution_attempt", async () => {
const r = await release("东北有个周东北.Spark.of.the.Long.March.2026.S01E38.1080p.WEB-DL.H264-GRP");
const res = await resolveRelease(h.rt, { sourceReleaseId: r.id });
expect(res.outcome).toBe("auto");
const aliases = await aliasesOf(res.mediaEntityId!);
expect(aliases).toContain("东北有个周东北");
expect(aliases).not.toContain("Spark of the Long March");
const best = (await lastAttempt(r.id)).best as { aliases?: { skipped: { alias: string; reason: string }[] } };
expect(best.aliases?.skipped).toEqual([{ alias: "Spark of the Long March", reason: "conflicts_external_title" }]);
});
it("已属于别的作品的站点名称不登记", async () => {
const other = await h.db
.insertInto("media_entity")
.values({ kind: "series", title: "逆流", status: "active" } as never)
.returning("id")
.executeTakeFirstOrThrow();
await h.db
.insertInto("title_alias")
.values({
media_entity_id: other.id,
alias: "Against the Current",
normalized: normalizeTitle("Against the Current"),
kind: "other",
source: "user",
} as never)
.execute();
const r = await release("长河.Against.the.Current.2026.S01E01.1080p.WEB-DL.H264-GRP");
const res = await resolveRelease(h.rt, { sourceReleaseId: r.id });
expect(res.outcome).toBe("auto");
expect(res.mediaEntityId).not.toBe(other.id);
expect(await aliasesOf(res.mediaEntityId!)).not.toContain("Against the Current");
const best = (await lastAttempt(r.id)).best as { aliases?: { skipped: { alias: string; reason: string }[] } };
expect(best.aliases?.skipped).toEqual([{ alias: "Against the Current", reason: "owned_by_other" }]);
});
});