Files
UHD_Media_Tool/packages/domain/test/rules-slot.test.ts
T
rootandClaude Opus 5.5 4480fc346d refactor(copy): 术语统一——收录版本、模型服务、智能体等(DEAI-2)
- 版本目标 → 收录版本(前后端用户可见字符串、能力描述、给模型的提示)
- Agent 上游 / 上游 → 模型服务;界面上的 Agent / Agent 会话 → 智能体 / 智能体会话
- 身份歧义 / 识别有歧义 / 无法确定作品 → 没识别出作品;待识别(池) → 未识别
- 建档 → 加入媒体库;资产 → 文件;库根 → 媒体库目录;纳管 → 本工具 / 外部任务;
  哨兵 → 标记文件 / 检查目录;站点快照 → 抓取;Feed / RSS 订阅 / 来源健康 → RSS
- 代码标识符、API 字段名、迁移文件不改;glossary.md 同步;smoke 选择器跟随

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-29 20:35:43 +08:00

95 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
currentPri,
evaluateCandidate,
parseRelease,
parseRuleString,
type RuleGroupSpec,
rankForSlot,
type SlotSpec,
} from "../src/index.ts";
const group = (name: string, s: string, extra: Partial<RuleGroupSpec> = {}): RuleGroupSpec => ({
name,
layers: s ? parseRuleString(s).layers : [],
...extra,
});
const base = (groups: RuleGroupSpec[], extra: Partial<SlotSpec> = {}): SlotSpec => ({
name: "main",
hard: {},
rules: { groups },
...extra,
});
const rel = (t: string, subtitle?: string) => parseRelease(t, subtitle ? { subtitle } : {});
describe("收录版本接入规则组", () => {
it("硬排除命中 → reject(rule.hard_exclude),trace 带组名与修订号", () => {
const g = group("不要原盘", "", {
id: "g1",
revision: 3,
hardExcludes: parseRuleString("BLU | REMUX").layers.map((l) => l.expr),
});
const ev = evaluateCandidate(base([g]), rel("Movie.2023.1080p.BluRay.REMUX.AVC-EPSiLON"));
expect(ev.decision).toBe("reject");
if (ev.decision !== "reject") return;
expect(ev.violations).toEqual([
{ field: "rule", code: "hard_exclude", expected: "不要原盘", actual: ["BLU | REMUX"] },
]);
expect(ev.trace?.join("\n")).toContain("规则组「不要原盘」@r3 硬排除 BLU | REMUX 命中");
});
it("多组串联取交集,pri 取最后一个带层的组", () => {
const spec = base([group("分辨率", "4K > 1080P"), group("字幕", "CNSUB > !CNSUB"), group("空组", "")]);
const a = evaluateCandidate(spec, rel("Dune.2024.1080p.WEB-DL.H264-HHWEB", "中字"));
expect(a).toMatchObject({ decision: "accept", pri: 100 });
const b = evaluateCandidate(spec, rel("Dune.2024.2160p.WEB-DL.H265-FLUX"));
expect(b).toMatchObject({ decision: "accept", pri: 99 });
const c = evaluateCandidate(spec, rel("Dune.2024.720p.WEBRip.x264-GRP"));
expect(c.decision).toBe("reject");
if (c.decision === "reject")
expect(c.violations[0]).toMatchObject({ code: "no_layer_matched", expected: "分辨率" });
});
it("ladderRequired=false 的软偏好组不中只降 pri,不拒绝", () => {
const spec = base([group("偏好", "SPECSUB", { ladderRequired: false })]);
const r = rel("X.2024.1080p.WEB-DL.H264-G", "特效中字");
expect(evaluateCandidate(spec, r, null, { subtitle: "特效中字" })).toMatchObject({ pri: 100 });
expect(evaluateCandidate(spec, rel("X.2024.1080p.WEB-DL.H264-G"))).toMatchObject({ decision: "accept", pri: 0 });
});
it("有规则组阶梯时升级按 pri 严格提升(洗版)", () => {
const spec = base([group("洗版", "REMUX > BLURAY > WEBDL")], { upgrade: { enabled: true } });
const up = evaluateCandidate(spec, rel("M.2023.1080p.BluRay.REMUX.AVC-E"), {
source: "WEB-DL",
resolution: "1080p",
pri: 98,
});
expect(up).toMatchObject({ decision: "upgrade", pri: 100, fromPri: 98 });
const same = evaluateCandidate(spec, rel("M.2023.1080p.WEB-DL.H264-G"), { source: "WEB-DL", pri: 98 });
expect(same.decision).toBe("reject");
expect(currentPri(spec, { source: "REMUX", resolution: "1080p" })).toBe(100);
});
it("作品原语言中文时 CNSUB 放行(MP tmdb 条件)", () => {
const spec = base([group("中字", "CNSUB")]);
const r = rel("Movie.2023.1080p.WEB-DL.H264-G");
expect(evaluateCandidate(spec, r).decision).toBe("reject");
expect(evaluateCandidate(spec, r, null, { originalLanguage: "zh" })).toMatchObject({ decision: "accept" });
});
it("rankForSlot 按规则组 pri 排序", () => {
const spec = base([group("阶梯", "4K > 1080P > 720P")]);
const ranked = rankForSlot(spec, [
rel("A.720p.WEB-DL.x264-G"),
rel("A.2160p.WEB-DL.x265-G"),
rel("A.1080p.WEB-DL.x264-G"),
]);
expect(ranked.map((x) => x.evaluation.decision === "accept" && x.evaluation.pri)).toEqual([100, 99, 98]);
});
it("没有规则组时行为不变", () => {
const r = rel("Movie.2023.1080p.WEB-DL.H264-G");
expect(evaluateCandidate(base([]), r)).toEqual(evaluateCandidate({ name: "main", hard: {} }, r));
});
});