import { describe, expect, it } from "vitest"; import { evaluateCandidate, type ParsedRelease, parseRelease, parseRuleString, type QualityFacts, type RuleGroupSpec, rankForSlot, type SlotSpec, seasonReleaseKey, selectAcquisitionPolicy, selectSeasonRelease, } from "../src/index.ts"; const release = (title: string, facts: Partial = {}): ParsedRelease => ({ ...parseRelease(title), ...facts, }); const group = (name: string, rule: string): RuleGroupSpec => ({ name, layers: parseRuleString(rule).layers }); const current: QualityFacts = release("Show.S01E01.1080p.WEB-DL.H265-OTHER"); const target = release("Show.S01E02.1080p.WEB-DL.H265-CHD"); const consistent: SlotSpec = { name: "正片", hard: { resolutions: ["1080p", "2160p"] }, rules: { groups: [group("允许收录", "1080P | 4K")] }, upgrade: { enabled: true, consistency: "season_release" }, }; describe("按分类选择默认收录策略", () => { const policies = [ { id: "global", scope: {} }, { id: "series", scope: { mediaKinds: ["series" as const] } }, { id: "animation", scope: { categories: { tv: ["动漫剧集"] } } }, ]; it("订阅显式指定的策略覆盖类型和分类范围", () => { expect( selectAcquisitionPolicy(policies, { explicitId: "series", mediaKind: "movie", category: "动画电影" })?.id, ).toBe("series"); }); it("显式指定的策略不存在时不悄悄回退", () => { expect( selectAcquisitionPolicy(policies, { explicitId: "missing", mediaKind: "series", category: "动漫剧集" }), ).toBeUndefined(); }); it("分类匹配优先于类型默认,类型默认优先于全局默认", () => { expect(selectAcquisitionPolicy(policies, { mediaKind: "series", category: "动漫剧集" })?.id).toBe("animation"); expect(selectAcquisitionPolicy(policies, { mediaKind: "series", category: "华语剧集" })?.id).toBe("series"); expect(selectAcquisitionPolicy(policies, { mediaKind: "movie", category: "动画电影" })?.id).toBe("global"); }); it.each(["movie", "series", "variety"] as const)("%s 不串用另一类型的同名分类策略", (mediaKind) => { const scoped = [ { id: "variety", scope: { mediaKinds: ["variety" as const], categories: { tv: ["同名分类"] } } }, { id: "movie", scope: { categories: { movie: ["同名分类"] } } }, { id: "series", scope: { mediaKinds: ["series" as const], categories: { tv: ["同名分类"] } } }, ]; expect(selectAcquisitionPolicy(scoped, { mediaKind, category: "同名分类" })?.id).toBe(mediaKind); }); it("同一范围按传入的最新发布顺序取第一个", () => { const latest = { id: "latest", scope: { categories: { tv: ["动漫剧集"] } } }; const old = { id: "old", scope: { categories: { tv: ["动漫剧集"] } } }; expect(selectAcquisitionPolicy([latest, old], { mediaKind: "series", category: "动漫剧集" })).toBe(latest); expect(selectAcquisitionPolicy([old, latest], { mediaKind: "series", category: "动漫剧集" })).toBe(old); }); it("分类未知时跳过分类限定策略,仍可使用类型默认", () => { expect(selectAcquisitionPolicy(policies, { mediaKind: "series", category: null })?.id).toBe("series"); }); it("类型或分类没有匹配且无默认时返回 undefined", () => { const scoped = policies.slice(1); expect(selectAcquisitionPolicy(scoped, { mediaKind: "movie", category: "动漫剧集" })).toBeUndefined(); expect(selectAcquisitionPolicy([policies[2]!], { mediaKind: "series", category: null })).toBeUndefined(); expect(selectAcquisitionPolicy([], { mediaKind: "variety", category: "综艺" })).toBeUndefined(); }); it("空的分类列表仍按类型默认处理", () => { const typeDefault = { id: "type", scope: { mediaKinds: ["series" as const], categories: { movie: [], tv: [] } } }; expect(selectAcquisitionPolicy([policies[0]!, typeDefault], { mediaKind: "series", category: null })).toBe( typeDefault, ); }); }); describe("整季同套的规格与目标选择", () => { it("发布组大小写、联合组顺序、HDR 顺序不产生新的版本身份", () => { const first = { ...target, releaseGroup: { name: "CHD@FLUX", source: "suffix" as const }, hdr: ["DV", "HDR10"] as const, }; const second = { ...target, releaseGroup: { name: "flux@chd", source: "known" as const }, hdr: ["HDR10", "DV"] as const, }; expect(seasonReleaseKey({ ...first, hdr: [...first.hdr] })).toBe( seasonReleaseKey({ ...second, hdr: [...second.hdr] }), ); }); it.each(["resolution", "source", "videoCodec", "releaseGroup"] as const)( "缺少 %s 时不声称属于完整成套版本", (field) => { expect(seasonReleaseKey({ ...target, [field]: undefined })).toBeNull(); }, ); it("来源、分辨率、编码和剪辑不同分别是不同的成套版本", () => { for (const change of [ { source: "BluRay" as const }, { resolution: "2160p" as const }, { videoCodec: "H.264" as const }, { cut: "Extended" as const }, { hdr: ["DV" as const] }, ]) { expect(seasonReleaseKey({ ...target, ...change })).not.toBe(seasonReleaseKey(target)); } }); const unrestricted: SlotSpec = { name: "主版本", hard: {} }; it.each([ { name: "来源等级优先于更高分辨率和软偏好", spec: { ...unrestricted, soft: { groups: ["CHD"] } }, weaker: release("Show.S01E01.2160p.WEB-DL.H265-CHD"), better: release("Show.S01E02.1080p.BluRay.H264-OTHER"), }, { name: "同来源时分辨率优先于软偏好", spec: { ...unrestricted, soft: { groups: ["CHD"] } }, weaker: target, better: release("Show.S01E02.2160p.WEB-DL.H265-OTHER"), }, { name: "同档规格按发布组软偏好排序", spec: { ...unrestricted, soft: { groups: ["CHD", "OTHER"] } }, weaker: release("Show.S01E01.1080p.WEB-DL.H265-OTHER"), better: target, }, { name: "同档规格按 HDR 软偏好排序", spec: { ...unrestricted, soft: { hdr: ["DV" as const, "HDR10" as const] } }, weaker: release("Show.S01E01.1080p.WEB-DL.HDR.H265-CHD"), better: release("Show.S01E02.1080p.WEB-DL.DV.H265-CHD"), }, { name: "同档规格按编码软偏好排序", spec: { ...unrestricted, soft: { codecs: ["H.265" as const, "H.264" as const] } }, weaker: release("Show.S01E01.1080p.WEB-DL.H264-CHD"), better: target, }, ])("$name,成套选择与候选排序一致", ({ spec, weaker, better }) => { expect(rankForSlot(spec, [weaker, better])[0]?.candidate).toBe(better); expect(selectSeasonRelease(spec, [weaker, better])).toBe(better); expect(selectSeasonRelease(spec, [better, weaker])).toBe(better); }); it("组合优先级先于来源和分辨率等级", () => { const spec: SlotSpec = { ...unrestricted, rules: { groups: [group("来源偏好", "WEBDL > BLURAY")] } }; const blue = release("Show.S01E01.2160p.BluRay.H265-OTHER"); expect(rankForSlot(spec, [blue, target])[0]?.candidate).toBe(target); expect(selectSeasonRelease(spec, [blue, target])).toBe(target); }); it("规格不完整的版本不参与成套选择", () => { const incomplete = { ...target, videoCodec: undefined, pri: 1000 }; expect(selectSeasonRelease(unrestricted, [incomplete, target])).toBe(target); expect(selectSeasonRelease(unrestricted, [incomplete])).toBeUndefined(); }); it("同档没有偏好时目标不随数据库返回顺序反复变化", () => { const other = release("Show.S01E02.1080p.WEB-DL.H265-OTHER"); expect(selectSeasonRelease(unrestricted, [target, other])).toBe(selectSeasonRelease(unrestricted, [other, target])); }); }); describe("追更和洗版分别使用规则", () => { const phased: SlotSpec = { name: "主版本", hard: { resolutions: ["1080p", "2160p"] }, rules: { groups: [group("追更", "WEBDL")] }, upgradeRules: { groups: [group("洗版", "BLURAY > WEBDL")] }, upgrade: { enabled: true }, }; const blue = release("Show.S01E02.1080p.BluRay.H265-CHD"); it("补齐缺集按追更规则,洗版规则不提前过滤", () => { expect(evaluateCandidate(phased, target)).toMatchObject({ decision: "accept", pri: 100 }); expect(evaluateCandidate(phased, blue)).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ field: "rule", expected: "追更" })], }); }); it("已有当前资产时改用洗版规则,并按洗版规则重算当前档位", () => { expect(evaluateCandidate(phased, blue, current)).toMatchObject({ decision: "upgrade", pri: 100, fromPri: 99 }); expect(evaluateCandidate(phased, target, current)).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ code: "not_higher_priority" })], }); }); it("洗版专用硬条件不阻止追更,但在已有资产时生效", () => { const spec: SlotSpec = { ...phased, upgradeHard: { resolutions: ["2160p"] } }; expect(evaluateCandidate(spec, target).decision).toBe("accept"); expect(evaluateCandidate(spec, blue, current)).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ field: "resolution", code: "not_allowed" })], }); }); it.each(["1080p", "2160p"] as const)("允许分辨率 %s 在追更和洗版阶段均可使用", (resolution) => { const spec: SlotSpec = { name: "主版本", hard: phased.hard, upgrade: { enabled: true } }; const candidate = release(`Show.S01E02.${resolution}.BluRay.H265-CHD`); expect(evaluateCandidate(spec, candidate).decision).toBe("accept"); expect(evaluateCandidate(spec, candidate, current).decision).toBe("upgrade"); }); it.each(["720p", "4320p"] as const)("范围外的分辨率 %s 不因更优来源或整季对齐而放行", (resolution) => { const candidate = release(`Show.S01E02.${resolution}.BluRay.H265-CHD`); expect(evaluateCandidate(consistent, candidate).decision).toBe("reject"); expect(evaluateCandidate(consistent, candidate, current, { seasonReleaseTarget: candidate })).toMatchObject({ decision: "reject", violations: expect.arrayContaining([expect.objectContaining({ field: "resolution", code: "not_allowed" })]), }); }); it("分辨率未知时保持待补充,不把未知当作允许范围", () => { expect(evaluateCandidate(phased, { ...target, resolution: undefined })).toMatchObject({ decision: "unknown_field", missing: ["resolution"], }); }); it("组合条件的编码未知时,不能因 NOT 自动命中低档", () => { const spec: SlotSpec = { name: "主版本", hard: {}, rules: { groups: [group("编码", "H265 > !H265")] } }; expect(evaluateCandidate(spec, release("Show.S01E02.1080p.WEB-DL-CHD"))).toMatchObject({ decision: "unknown_field", missing: ["codec"], }); }); it("高档分辨率已明确不满足时,无关的未知编码不阻止已匹配的低档", () => { const spec: SlotSpec = { name: "主版本", hard: {}, rules: { groups: [group("组合档位", "4K & H265 > 1080P")] }, }; expect(evaluateCandidate(spec, release("Show.S01E02.1080p.WEB-DL-CHD"))).toMatchObject({ decision: "accept", pri: 99, }); }); }); describe("整季同套的同档对齐不绕过升级限制", () => { it("同档的不同发布组可以逐集对齐到选定的整季版本", () => { expect(evaluateCandidate(consistent, target, current, { seasonReleaseTarget: target })).toMatchObject({ decision: "upgrade", pri: 100, fromPri: 100, reasons: expect.arrayContaining(["season_release_aligned"]), }); }); it.each([ { name: "来源", source: "BluRay" as const, resolution: "1080p" as const }, { name: "分辨率", source: "WEB-DL" as const, resolution: "2160p" as const }, ])("同档对齐不能降低当前文件的$name", ({ source, resolution }) => { expect( evaluateCandidate(consistent, target, { ...current, source, resolution }, { seasonReleaseTarget: target }), ).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ code: "lower_tier" })] }); }); it("当前文件已经同套时不重复整理或下载", () => { expect(evaluateCandidate(consistent, target, target, { seasonReleaseTarget: target }).decision).toBe("reject"); const normalizedCurrent = { ...target, releaseGroup: { name: "chd", source: "known" as const } }; expect(evaluateCandidate(consistent, target, normalizedCurrent, { seasonReleaseTarget: target }).decision).toBe( "reject", ); }); it("另一套同档资源不能替换已选定的当季目标", () => { const other = release("Show.S01E02.1080p.WEB-DL.H265-FLUX"); expect(evaluateCandidate(consistent, other, current, { seasonReleaseTarget: target })).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ code: "different_season_release" })], }); }); it("关闭升级时,同档对齐也不能发起替换", () => { const spec = { ...consistent, upgrade: { ...consistent.upgrade!, enabled: false } }; expect(evaluateCandidate(spec, target, current, { seasonReleaseTarget: target })).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ code: "upgrade_disabled" })], }); }); it("全集洗版不能因同档对齐而接受单集或播出日期资源", () => { const spec: SlotSpec = { ...consistent, upgrade: { ...consistent.upgrade!, mode: "full_pack" } }; for (const candidate of [target, { ...target, episodes: undefined, airDate: "20261013" }]) { expect(evaluateCandidate(spec, candidate, current, { seasonReleaseTarget: candidate })).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ code: "not_full_pack" })], }); } const pack = release("Show.S01.1080p.WEB-DL.H265-CHD"); expect(pack.episodes).toBeUndefined(); expect(evaluateCandidate(spec, pack, current, { seasonReleaseTarget: pack }).decision).toBe("upgrade"); }); it("不合格蓝光压制组不能因同档对齐绕过名单", () => { const spec: SlotSpec = { ...consistent, upgrade: { ...consistent.upgrade!, encodeGroupAllowlist: ["CHD"] }, }; const old = release("Show.S01E01.1080p.BluRay.x265-OTHER"); const unqualified = release("Show.S01E02.1080p.BluRay.x265-BADGRP"); expect(unqualified.isEncode).toBe(true); expect(evaluateCandidate(spec, unqualified, old, { seasonReleaseTarget: unqualified })).toMatchObject({ decision: "reject", violations: [expect.objectContaining({ code: "encode_not_qualified" })], }); const qualified = release("Show.S01E02.1080p.BluRay.x265-chd"); expect(evaluateCandidate(spec, qualified, old, { seasonReleaseTarget: qualified }).decision).toBe("upgrade"); }); it.each([ { field: "resolution", missing: "resolution" }, { field: "source", missing: "source" }, { field: "videoCodec", missing: "codec" }, { field: "releaseGroup", missing: "group" }, ] as const)("候选缺少 $field 时不执行成套替换", ({ field, missing }) => { const candidate = { ...target, [field]: undefined }; expect(evaluateCandidate(consistent, candidate, current, { seasonReleaseTarget: target })).toMatchObject({ decision: "unknown_field", missing: expect.arrayContaining([missing]), }); }); it("季目标缺失或规格不完整时等待,不自行认定当前候选是目标", () => { expect(evaluateCandidate(consistent, target, current)).toMatchObject({ decision: "unknown_field", missing: ["current_asset"], }); expect( evaluateCandidate(consistent, target, current, { seasonReleaseTarget: { ...target, videoCodec: undefined } }), ).toMatchObject({ decision: "unknown_field", missing: ["current_asset"] }); }); });