/** * ID-2:站点资源标题更新(RSS 带分类前缀 / 内嵌描述的标题 → 列表标题)后按新标题重新解析并重新识别, * 不沿用旧解析;已识别的资源也不短路。 */ import type { SiteRelease } from "@umt/adapters"; import { PARSER_VERSION } from "@umt/domain"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { upsertSiteRelease } from "../../src/modules/sources-identity/service/snapshot.ts"; import { sourcesOutboxRoutes } from "../../src/modules/sources-identity/tasks.ts"; import { createHarness, type Harness, pgAvailable, seedSite } from "./harness.ts"; const up = await pgAvailable(); const rel = (title: string, subtitle?: string): SiteRelease => ({ siteId: "x", siteReleaseId: "9001", title, ...(subtitle ? { subtitle } : {}), category: { id: "402", name: "剧集" }, sizeBytes: null, seeders: null, leechers: null, snatched: null, promotion: { type: "none", downloadFactor: 1, uploadFactor: 1, expiresAt: null } as SiteRelease["promotion"], hr: false, uploadedAt: null, detailUrl: "https://hhan.example/details.php?id=9001", tags: [], raw: {}, observedAt: new Date().toISOString(), }); describe.skipIf(!up)("标题更新后重新解析 + 重新识别", () => { let h: Harness; let siteId: string; beforeAll(async () => { h = await createHarness("retitle"); siteId = (await seedSite(h, { baseUrl: "https://hhan.example/" })).site.id; }); afterAll(async () => { await h?.close(); }); it("RSS 标题 → 列表标题:parsed 按新标题重算,发 release.observed(isNew=false)并强制重新识别", async () => { const rss = "[电视剧]Irreplaceable S01 2026 1080p YK WEB-DL H.264 DTS5.1-HHWEB[无可替代 | 第03集 | 1080p | 类型: 剧情][510.37 MB]"; const first = await h.db.transaction().execute((trx) => upsertSiteRelease(h.rt, trx, { id: siteId }, rel(rss))); expect(first.inserted).toBe(true); let row = await h.db.selectFrom("source_release").selectAll().where("id", "=", first.id).executeTakeFirstOrThrow(); // 分类前缀不进片名;内嵌描述里的中文名补作中文名 expect(row.parsed).toMatchObject({ titleCn: "无可替代", titleEn: "Irreplaceable", category: "电视剧" }); const listTitle = "Irreplaceable S01 2026 1080p YK WEB-DL H.264 DTS5.1-HHWEB"; const again = await h.db .transaction() .execute((trx) => upsertSiteRelease(h.rt, trx, { id: siteId }, rel(listTitle, "无可替代 | 第04集 | 1080p"))); expect(again).toEqual({ id: first.id, inserted: false }); row = await h.db.selectFrom("source_release").selectAll().where("id", "=", first.id).executeTakeFirstOrThrow(); expect(row.title).toBe(listTitle); expect(row.parser_version).toBe(String(PARSER_VERSION)); expect(row.parsed).toMatchObject({ titleCn: "无可替代", episodes: { start: 4, end: 4 } }); const events = (await h.outbox("release.observed")).filter((e) => e.payload.sourceReleaseId === first.id); expect(events.map((e) => e.payload.isNew)).toEqual([true, false]); const route = sourcesOutboxRoutes["release.observed"]!({ payload: events[1]!.payload } as never); expect(route).toMatchObject({ task: "resolveObservation", input: { force: true, trigger: "observed" } }); // 同标题再次快照:不再发重新识别 await h.db .transaction() .execute((trx) => upsertSiteRelease(h.rt, trx, { id: siteId }, rel(listTitle, "无可替代 | 第04集 | 1080p"))); const after = (await h.outbox("release.observed")).filter((e) => e.payload.sourceReleaseId === first.id); expect(after).toHaveLength(2); }); });