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(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); } }); });