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
123 lines
5.2 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|