import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { ConfigSwitchStore } from "./config-switch-store.mjs"; async function makeTempRoot() { return fs.mkdtemp(path.join(os.tmpdir(), "config-switch-store-test-")); } function makeProviderDefinitions(root) { return { codex: { provider: "codex", label: "Codex 配置", targetPaths: [ { key: "authJsonText", label: "auth.json", path: path.join(root, "home", ".codex", "auth.json"), kind: "json", }, { key: "configTomlText", label: "config.toml", path: path.join(root, "home", ".codex", "config.toml"), kind: "toml", }, ], }, "claude-code": { provider: "claude-code", label: "Claude Code 配置", targetPaths: [ { key: "settingsJsonText", label: "settings.json", path: path.join(root, "home", ".claude", "settings.json"), kind: "json", }, ], }, }; } async function makeStore() { const root = await makeTempRoot(); const dataDir = path.join(root, "data"); const store = new ConfigSwitchStore(dataDir, { providerDefinitions: makeProviderDefinitions(root), }); await store.load(); return { root, dataDir, store }; } test("ConfigSwitchStore initializes empty providers", async () => { const { store } = await makeStore(); const result = await store.getConfigSwitchData(); assert.deepEqual(Object.keys(result.providers), ["codex", "claude-code"]); assert.equal(result.providers.codex.presets.length, 0); assert.equal(result.providers["claude-code"].presets.length, 0); }); test("ConfigSwitchStore persists codex presets to local data dir", async () => { const { dataDir, store } = await makeStore(); const result = await store.upsertPreset("codex", { name: "主配置", payload: { authJsonText: '{"OPENAI_API_KEY":"sk-main"}', configTomlText: 'model_provider = "OpenAI"\nmodel = "gpt-5.4"', }, }); assert.equal(result.providers.codex.presets.length, 1); const written = JSON.parse( await fs.readFile(path.join(dataDir, "config-switch.json"), "utf8"), ); assert.equal(written.presetsByProvider.codex[0].name, "主配置"); }); test("ConfigSwitchStore copies and deletes claude presets", async () => { const { store } = await makeStore(); const created = await store.upsertPreset("claude-code", { name: "Claude 主配置", payload: { settingsJsonText: '{"env":{"ANTHROPIC_AUTH_TOKEN":"sk-claude"}}', }, }); const presetId = created.providers["claude-code"].presets[0].id; const copied = await store.copyPreset("claude-code", presetId); assert.equal(copied.providers["claude-code"].presets.length, 2); assert.match(copied.providers["claude-code"].presets[0].name, /副本/); const afterDelete = await store.deletePreset( "claude-code", copied.providers["claude-code"].presets[1].id, ); assert.equal(afterDelete.providers["claude-code"].presets.length, 1); }); test("ConfigSwitchStore rejects invalid JSON payloads", async () => { const { store } = await makeStore(); await assert.rejects( store.upsertPreset("claude-code", { name: "非法配置", payload: { settingsJsonText: "{" }, }), (error) => error?.statusCode === 400, ); }); test("ConfigSwitchStore activation writes codex files", async () => { const { root, store } = await makeStore(); const authPath = path.join(root, "home", ".codex", "auth.json"); const configPath = path.join(root, "home", ".codex", "config.toml"); await fs.mkdir(path.dirname(authPath), { recursive: true }); await fs.writeFile(authPath, '{"OPENAI_API_KEY":"old"}\n', "utf8"); await fs.writeFile(configPath, 'model = "old"\n', "utf8"); const created = await store.upsertPreset("codex", { name: "HF 代理", payload: { authJsonText: '{"OPENAI_API_KEY":"hf-key"}', configTomlText: 'model_provider = "OpenAI"\nmodel = "gpt-5.4"', }, }); const presetId = created.providers.codex.presets[0].id; const activated = await store.activatePreset("codex", presetId, { confirmed: true }); assert.equal(activated.providers.codex.presets[0].status, "active"); assert.match(await fs.readFile(authPath, "utf8"), /hf-key/); assert.match(await fs.readFile(configPath, "utf8"), /gpt-5.4/); }); test("ConfigSwitchStore activation requires confirmation", async () => { const { store } = await makeStore(); const created = await store.upsertPreset("codex", { name: "主配置", payload: { authJsonText: '{"OPENAI_API_KEY":"sk-main"}', configTomlText: 'model_provider = "OpenAI"', }, }); await assert.rejects( store.activatePreset("codex", created.providers.codex.presets[0].id, { confirmed: false }), (error) => error?.statusCode === 409, ); }); test("ConfigSwitchStore marks last applied preset as drifted after local change", async () => { const { root, store } = await makeStore(); const settingsPath = path.join(root, "home", ".claude", "settings.json"); const created = await store.upsertPreset("claude-code", { name: "Claude 代理", payload: { settingsJsonText: '{"env":{"ANTHROPIC_AUTH_TOKEN":"sk-claude","ANTHROPIC_MODEL":"claude-sonnet"}}', }, }); const presetId = created.providers["claude-code"].presets[0].id; await store.activatePreset("claude-code", presetId, { confirmed: true }); await fs.writeFile( settingsPath, '{\n "env": {\n "ANTHROPIC_AUTH_TOKEN": "changed"\n }\n}\n', "utf8", ); const result = await store.getConfigSwitchData(); assert.equal(result.providers["claude-code"].presets[0].status, "drifted"); assert.match( result.providers["claude-code"].presets[0].currentPayload.settingsJsonText, /changed/, ); });