Files
UHD_Media_Tool/packages/contracts/test/settings-sections.test.ts
T
rootandClaude Opus 5.5 93a6c8a910 feat(settings): 设置分区按接入 / 流程 / 系统三组重排
SETTINGS_SECTIONS 改为 12 个分区(下载器、媒体服务器、站点与网络、元数据、获取、媒体库与目录、
整理与刮削、智能体、系统、缓存、账户与安全、界面),加分组、节别名(搜索用)、旧 hash 别名
SETTINGS_HASH_ALIASES 与 resolveSettingsHash;retention.logRetentionDays 无人读取,登记为弃用。
不改存储结构与缺省值。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-06 18:01:19 +08:00

123 lines
5.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
DEFAULT_SETTINGS,
DEPRECATED_SETTINGS_FIELDS,
resolveSettingsHash,
SETTINGS_GROUPS,
SETTINGS_HASH_ALIASES,
SETTINGS_SECTIONS,
SettingsSectionId,
settingsSectionForLegacyTab,
} from "../src/settings.ts";
describe("SETTINGS_SECTIONS", () => {
it("分区顺序与 SettingsSectionId 一致,按接入 → 流程 → 系统三组排", () => {
expect(SETTINGS_SECTIONS.map((s) => s.id)).toEqual(SettingsSectionId.options);
const groups = SETTINGS_SECTIONS.map((s) => s.group);
const order = SETTINGS_GROUPS.map((g) => g.id);
expect([...groups].sort((a, b) => order.indexOf(a) - order.indexOf(b))).toEqual(groups);
for (const s of SETTINGS_SECTIONS) {
expect(s.description, s.id).not.toBe("");
expect(s.anchors.length, s.id).toBeGreaterThan(0);
expect(new Set(s.anchors.map((a) => a.id)).size, s.id).toBe(s.anchors.length);
}
});
it("每个存储 key 的每个字段(弃用字段除外)恰好归属一个分区", () => {
const deprecated = new Set<string>(DEPRECATED_SETTINGS_FIELDS);
const all = Object.entries(DEFAULT_SETTINGS)
.flatMap(([key, v]) => Object.keys(v).map((f) => `${key}.${f}`))
.filter((f) => !deprecated.has(f));
const owned = SETTINGS_SECTIONS.flatMap((s) => s.fields);
expect(new Set(owned).size).toBe(owned.length);
expect([...owned].sort()).toEqual([...all].sort());
});
it("近期新增的设置归到用户找得到的分区", () => {
const owner = (f: string) => SETTINGS_SECTIONS.find((s) => s.fields.includes(f))?.id;
expect(owner("organize.scrapeMode")).toBe("organize");
expect(owner("organize.scrapeAfterImport")).toBe("organize");
expect(owner("organize.minSizeBytes")).toBe("organize");
expect(owner("agent.autoIdentifyOrganize")).toBe("agent");
expect(owner("agent.maxToolCalls")).toBe("agent");
expect(owner("metadata.includeSpecials")).toBe("metadata");
expect(owner("cache.hardThresholdPercent")).toBe("downloaders");
expect(owner("retention.seedRetentionDays")).toBe("downloaders");
expect(owner("budget.maxConcurrentDownloads")).toBe("acquire");
expect(owner("sites.proxyUrl")).toBe("sites");
expect(owner("storage.cacheTtl")).toBe("cache");
expect(owner("cache.tmdbMaxSize")).toBe("cache");
expect(DEPRECATED_SETTINGS_FIELDS).toContain("retention.logRetentionDays");
});
it("旧 ?tab= 值都能落到分区", () => {
for (const tab of [
"automation",
"downloaders",
"media-servers",
"library-roots",
"path-mappings",
"site-credentials",
"api-keys",
"pi",
"storage",
]) {
expect(settingsSectionForLegacyTab(tab), tab).not.toBeNull();
}
expect(settingsSectionForLegacyTab("storage")).toEqual({ section: "downloaders", anchor: "cache" });
expect(settingsSectionForLegacyTab("api-keys")).toEqual({ section: "account", anchor: "api-keys" });
expect(settingsSectionForLegacyTab("nope")).toBeNull();
});
});
describe("resolveSettingsHash", () => {
it("新 hash 原样解析", () => {
expect(resolveSettingsHash("#agent/budget")).toEqual({ section: "agent", anchor: "budget", moved: false });
expect(resolveSettingsHash("#downloaders")).toEqual({ section: "downloaders", anchor: null, moved: false });
expect(resolveSettingsHash("")).toEqual({ section: null, anchor: null, moved: false });
expect(resolveSettingsHash("#nope")).toEqual({ section: null, anchor: null, moved: false });
// 不认识的节丢掉
expect(resolveSettingsHash("#agent/zzz")).toEqual({ section: "agent", anchor: null, moved: false });
});
it("旧锚点跳到新位置", () => {
const cases: [string, string, string | null][] = [
["#agent", "agent", null],
["#downloaders", "downloaders", null],
["#automation", "system", null],
["#automation/cache", "downloaders", "cache"],
["#automation/budget", "acquire", "budget"],
["#automation/retention", "downloaders", "seeding"],
["#automation/advanced", "system", "readonly"],
["#automation/storage-ttl", "cache", "ttl"],
["#automation/whatever", "system", null],
["#library/organize", "organize", "mode"],
["#library/scrape", "organize", "scrape"],
["#metadata/identity", "acquire", "identity"],
["#metadata/tmdb-cache", "cache", "tmdb-cache"],
["#metadata/images", "cache", "images"],
["#api-keys", "account", "api-keys"],
["#api-keys/keys", "account", "api-keys"],
["#account/shortcuts", "interface", "shortcuts"],
];
for (const [hash, section, anchor] of cases) {
const r = resolveSettingsHash(hash);
expect({ section: r.section, anchor: r.anchor }, hash).toEqual({ section, anchor });
}
expect(resolveSettingsHash("#automation/cache").moved).toBe(true);
});
it("别名的目标都是存在的分区与节", () => {
for (const [from, to] of Object.entries(SETTINGS_HASH_ALIASES)) {
const [sid, anchor] = to.split("/");
const s = SETTINGS_SECTIONS.find((x) => x.id === sid);
expect(s, from).toBeDefined();
if (anchor)
expect(
s?.anchors.map((a) => a.id),
from,
).toContain(anchor);
}
});
});