218 lines
7.9 KiB
TypeScript
218 lines
7.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { draftFromJson, draftToJson, type SlotRulesDraft, type SlotRulesJson } from "./slot-rules.tsx";
|
|
|
|
const existingRules = (): SlotRulesJson => ({
|
|
hardConditions: {
|
|
resolutions: ["1080p", "2160p"],
|
|
sources: ["WEB-DL", "BluRay"],
|
|
requireEncode: true,
|
|
minSizeBytes: 1024 ** 3,
|
|
maxSizeBytes: 10 * 1024 ** 3,
|
|
requiredSubtitles: ["zh", "en"],
|
|
cuts: ["default", "IMAX"],
|
|
excludeGroups: ["LOW", "BAD"],
|
|
contentTypes: ["main"],
|
|
codecs: ["H.265"],
|
|
hdr: { anyOf: ["HDR10"], noneOf: ["DV"] },
|
|
},
|
|
softPreferences: {
|
|
hdr: ["DV", "HDR10"],
|
|
groups: ["CHD", "ADE"],
|
|
sources: ["BluRay", "WEB-DL"],
|
|
codecs: ["H.265", "H.264"],
|
|
preferredSizeBytes: { min: 2 * 1024 ** 3, max: 8 * 1024 ** 3 },
|
|
tiers: [
|
|
{ name: "优先蓝光", sources: ["BluRay"], codecs: ["H.265"] },
|
|
{ name: "追更", sources: ["WEB-DL"], resolutions: ["1080p"] },
|
|
],
|
|
tiersRequired: true,
|
|
},
|
|
fallback: {
|
|
unknownPolicy: "hold",
|
|
retryLimit: 3,
|
|
investigation: { enabled: true, fields: ["codec", "hdr"] },
|
|
},
|
|
upgradeThreshold: {
|
|
enabled: true,
|
|
ruleGroupIds: ["quality", "release-group"],
|
|
consistency: "season_release",
|
|
minTierSteps: 2,
|
|
allowResolutionUpgrade: true,
|
|
encodeGroupAllowlist: ["CHD", "ADE"],
|
|
ceilingTier: 4,
|
|
mode: "full_pack",
|
|
basis: "priority",
|
|
targetPri: 98,
|
|
sourceTiers: { BluRay: 3, "WEB-DL": 2 },
|
|
},
|
|
completion: { kind: "movie", cuts: ["default", "IMAX"] },
|
|
});
|
|
|
|
function save(draft: SlotRulesDraft): SlotRulesJson {
|
|
const result = draftToJson(draft);
|
|
if (!result.ok) throw new Error(JSON.stringify(result.errors));
|
|
return result.value;
|
|
}
|
|
|
|
describe("收录规则编辑回写", () => {
|
|
it("逐版本修改洗版方式,关闭再开启保留模式,明确选默认才清除模式", () => {
|
|
const original = existingRules();
|
|
const disabled = save({ ...draftFromJson(original), upgradeEnabled: false });
|
|
expect(disabled.upgradeThreshold).toEqual({ ...original.upgradeThreshold, enabled: false });
|
|
const reopened = save({ ...draftFromJson(disabled), upgradeEnabled: true });
|
|
expect(reopened).toEqual(original);
|
|
const perUnit = save({ ...draftFromJson(original), upgradeMode: "per_unit" });
|
|
expect(perUnit.upgradeThreshold).toEqual({ ...original.upgradeThreshold, mode: "per_unit" });
|
|
const { mode: _mode, ...rest } = original.upgradeThreshold;
|
|
expect(save({ ...draftFromJson(original), upgradeMode: "" }).upgradeThreshold).toEqual(rest);
|
|
expect(save(draftFromJson({})).upgradeThreshold).toEqual({ enabled: false });
|
|
});
|
|
it("未修改保存时保留表单没有展示的各层 JSON 字段", () => {
|
|
const original = existingRules();
|
|
|
|
expect(save(draftFromJson(original))).toEqual(original);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "分辨率",
|
|
edit: { resolutions: ["2160p"] },
|
|
expected: (original: SlotRulesJson) => ({
|
|
...original,
|
|
hardConditions: { ...original.hardConditions, resolutions: ["2160p"] },
|
|
}),
|
|
},
|
|
{
|
|
name: "编码偏好",
|
|
edit: { softCodecs: ["AV1", "H.265"] },
|
|
expected: (original: SlotRulesJson) => ({
|
|
...original,
|
|
softPreferences: { ...original.softPreferences, codecs: ["AV1", "H.265"] },
|
|
}),
|
|
},
|
|
])("编辑$name只改写对应字段,不改硬编码限制或其他规则", ({ edit, expected }) => {
|
|
const original = existingRules();
|
|
const snapshot = structuredClone(original);
|
|
|
|
expect(save({ ...draftFromJson(original), ...edit })).toEqual(expected(original));
|
|
expect(original).toEqual(snapshot);
|
|
});
|
|
|
|
it("同时更新分辨率和编码偏好仍保留阶梯、洗版口径、完成版本及回退扩展", () => {
|
|
const original = existingRules();
|
|
const value = save({
|
|
...draftFromJson(original),
|
|
resolutions: ["720p", "1080p"],
|
|
softCodecs: ["AV1"],
|
|
});
|
|
|
|
expect(value).toEqual({
|
|
...original,
|
|
hardConditions: { ...original.hardConditions, resolutions: ["720p", "1080p"] },
|
|
softPreferences: { ...original.softPreferences, codecs: ["AV1"] },
|
|
});
|
|
});
|
|
|
|
it.each([false, true])("清空洗版规则和阈值不会从原 JSON 恢复,升级开关为 %s", (upgradeEnabled) => {
|
|
const original = existingRules();
|
|
const {
|
|
ruleGroupIds: _groups,
|
|
consistency: _consistency,
|
|
minTierSteps: _steps,
|
|
...remaining
|
|
} = original.upgradeThreshold;
|
|
const value = save({
|
|
...draftFromJson(original),
|
|
upgradeEnabled,
|
|
upgradeGroupIds: [],
|
|
seasonConsistency: false,
|
|
minTierSteps: "",
|
|
});
|
|
|
|
expect(value).toEqual({
|
|
...original,
|
|
upgradeThreshold: { ...remaining, enabled: upgradeEnabled },
|
|
});
|
|
expect(value.upgradeThreshold).not.toHaveProperty("ruleGroupIds");
|
|
expect(value.upgradeThreshold).not.toHaveProperty("consistency");
|
|
expect(value.upgradeThreshold).not.toHaveProperty("minTierSteps");
|
|
});
|
|
|
|
it("关闭升级并清空配置后,再加载并打开升级也不会复活旧规则", () => {
|
|
const disabled = save({
|
|
...draftFromJson(existingRules()),
|
|
upgradeEnabled: false,
|
|
upgradeGroupIds: [],
|
|
seasonConsistency: false,
|
|
minTierSteps: "",
|
|
});
|
|
const reopened = draftFromJson(disabled);
|
|
|
|
expect(reopened.upgradeGroupIds).toEqual([]);
|
|
expect(reopened.seasonConsistency).toBe(false);
|
|
expect(reopened.minTierSteps).toBe("");
|
|
const enabled = save({ ...reopened, upgradeEnabled: true });
|
|
expect(enabled.upgradeThreshold).toEqual({ ...disabled.upgradeThreshold, enabled: true });
|
|
expect(enabled.upgradeThreshold).not.toHaveProperty("ruleGroupIds");
|
|
expect(enabled.upgradeThreshold).not.toHaveProperty("consistency");
|
|
expect(enabled.upgradeThreshold).not.toHaveProperty("minTierSteps");
|
|
});
|
|
});
|
|
|
|
describe("按字段配置的未知值回退", () => {
|
|
const fieldFallback = () => ({
|
|
...existingRules(),
|
|
fallback: {
|
|
unknownPolicy: { codec: "hold", resolution: "reject", hdr: "accept" },
|
|
investigation: { enabled: true, fields: ["codec"] },
|
|
},
|
|
});
|
|
|
|
it("按字段对象回填为保留状态,未修改保存仍保留原配置", () => {
|
|
const original = fieldFallback();
|
|
const draft = draftFromJson(original);
|
|
|
|
expect(draft.unknownPolicy).toBe("fields");
|
|
expect(save(draft)).toEqual(original);
|
|
});
|
|
|
|
it("仅修改其他规则时也不会清除按字段回退对象及其扩展字段", () => {
|
|
const original = fieldFallback();
|
|
|
|
expect(save({ ...draftFromJson(original), resolutions: ["2160p"] })).toEqual({
|
|
...original,
|
|
hardConditions: { ...original.hardConditions, resolutions: ["2160p"] },
|
|
});
|
|
});
|
|
|
|
it("明确选择全局回退后替换原按字段对象,保留其他回退字段", () => {
|
|
const original = fieldFallback();
|
|
|
|
expect(save({ ...draftFromJson(original), unknownPolicy: "reject" })).toEqual({
|
|
...original,
|
|
fallback: { ...original.fallback, unknownPolicy: "reject" },
|
|
});
|
|
});
|
|
|
|
it("原按字段配置先改为全局拒绝,再明确选系统默认时清空回退,不恢复原对象", () => {
|
|
const original = fieldFallback();
|
|
const draft = draftFromJson(original);
|
|
const selectedGlobal = { ...draft, unknownPolicy: "reject" as const };
|
|
const cleared = { ...selectedGlobal, unknownPolicy: "" as const };
|
|
const { unknownPolicy: _policy, ...remaining } = original.fallback;
|
|
const value = save(cleared);
|
|
|
|
expect(value).toEqual({ ...original, fallback: remaining });
|
|
expect(value.fallback).not.toHaveProperty("unknownPolicy");
|
|
});
|
|
|
|
it("已有全局回退可以清空,保存后不会恢复原选择", () => {
|
|
const original = existingRules();
|
|
const { unknownPolicy: _policy, ...remaining } = original.fallback;
|
|
const value = save({ ...draftFromJson(original), unknownPolicy: "" });
|
|
|
|
expect(value).toEqual({ ...original, fallback: remaining });
|
|
expect(value.fallback).not.toHaveProperty("unknownPolicy");
|
|
});
|
|
});
|