fix(identity): 季范围资源只建第一季;刷新时退役空的临时季;v0.3.42

药屋少女的呢喃:一条「S01-S24 季度全24集」的资源按季范围建出 S04–S24 二十一个空季。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-08 15:27:21 +08:00
1 parent 1ade49fe8e
commit 12f8fc59c3
17 files changed
+136 -20

No files matched your search

@@ -235,6 +235,36 @@ describe.skipIf(!up)("DH-1 内容单元数据治理", () => {
expect((await units()).filter((u) => label(u) === "S1E20")).toHaveLength(1);
});
it("季范围资源(把全 24 集写成 S01-S24)只建第一季、挂已有的季;刷新时退役空的临时季", async () => {
const seasonsNow = async () =>
(
await h.db
.selectFrom("content_unit")
.select(["id", "season_number", "status"])
.where("media_entity_id", "=", id)
.where("kind", "=", "season")
.where("status", "<>", "retired")
.orderBy("season_number")
.execute()
).map((s) => s.season_number);
const before = await seasonsNow();
const parsed = { season: 1, seasonEnd: 24 } as ParsedRelease;
const r = await h.db.transaction().execute((trx) => ensureUnits(trx, id, "series", parsed));
expect(await seasonsNow()).toEqual(before);
expect(r.unitIds.length).toBe(before.filter((n) => n !== null && n >= 1).length);
// 空的临时季:S7 没人引用 → 退役;S8 被资源引用 → 留着;S3 下面有集 → 留着
const s7 = await unit("season", 7, null);
const s8 = await unit("season", 8, null);
await release("s8-pack", s8);
await refreshMetadata(h.rt, id);
const status = async (uid: string) =>
(await h.db.selectFrom("content_unit").select("status").where("id", "=", uid).executeTakeFirstOrThrow()).status;
expect(await status(s7)).toBe("retired");
expect(await status(s8)).toBe("provisional");
expect(await status(ids.s3!)).toBe("provisional");
});
it("identity-cleanup --merge-duplicates:默认 dry-run 列清单不改库,--apply 只合并重复、不删孤立单元", async () => {
const schema = (await sql<{ s: string }>`select current_schema() as s`.execute(h.db)).rows[0]!.s;
const u = new URL(PG_URL);
@@ -305,10 +305,10 @@ describe.skipIf(!pg)("剧集组", () => {
const u14 = await unit(unitE14);
expect(u14).toMatchObject({ season_number: 1, episode_number: 14, status: "confirmed" });
expect(u14.metadata).not.toHaveProperty("tmdbSeason");
// 组里的第 2 季在默认结构里没有,切回后成了没有集、没被引用的空季:随刷新退役,不再挂着临时季
expect(await seasonTotals()).toEqual([
[0, null, "confirmed"],
[1, 25, "confirmed"],
[2, null, "provisional"],
]);
expect((await loadSeasonTable(h.db, entityId)).origins).toBeUndefined();
expect((await release(abs13)).episode_resolution).toMatchObject({