import { describe, it } from "node:test"; import assert from "node:assert"; import { resolveConfig } from "../src/config.js"; describe("resolveConfig", () => { it("returns empty config when no sources are present", () => { const config = resolveConfig({ existsSync: () => false, whichSync: () => undefined, }); assert.deepStrictEqual(config.paths, {}); assert.strictEqual(config.defaultClient, undefined); }); it("loads config from file", () => { const config = resolveConfig({ existsSync: () => true, readFileSync: () => JSON.stringify({ paths: { codex: "/file/codex", claude: "/file/claude" }, defaultClient: "claude", }), whichSync: () => undefined, }); assert.strictEqual(config.paths.codex, "/file/codex"); assert.strictEqual(config.paths.claude, "/file/claude"); assert.strictEqual(config.defaultClient, "claude"); }); it("overrides file config with env vars", () => { const config = resolveConfig({ env: { AI_CLI_CODEX_PATH: "/env/codex" }, existsSync: () => true, readFileSync: () => JSON.stringify({ paths: { codex: "/file/codex" } }), whichSync: () => undefined, }); assert.strictEqual(config.paths.codex, "/env/codex"); }); it("overrides env vars with CLI flags", () => { const config = resolveConfig({ flags: { "codex-path": "/flag/codex" }, env: { AI_CLI_CODEX_PATH: "/env/codex" }, existsSync: () => true, readFileSync: () => JSON.stringify({ paths: { codex: "/file/codex" } }), whichSync: () => undefined, }); assert.strictEqual(config.paths.codex, "/flag/codex"); }); it("falls back to PATH detection when config file is missing", () => { const config = resolveConfig({ existsSync: () => false, whichSync: (cmd) => cmd === "opencode" ? "/usr/bin/opencode" : undefined, }); assert.strictEqual(config.paths.opencode, "/usr/bin/opencode"); assert.strictEqual(config.paths.codex, undefined); assert.strictEqual(config.paths.claude, undefined); }); it("respects full priority ordering: flag > env > file > PATH", () => { const config = resolveConfig({ flags: { "codex-path": "/flag/codex", "claude-path": "/flag/claude", "opencode-path": "/flag/opencode", "default-client": "opencode", }, env: { AI_CLI_CODEX_PATH: "/env/codex", AI_CLI_CLAUDE_PATH: "/env/claude", AI_CLI_OPENCODE_PATH: "/env/opencode", AI_CLI_DEFAULT_CLIENT: "claude", }, existsSync: () => true, readFileSync: () => JSON.stringify({ paths: { codex: "/file/codex", claude: "/file/claude", opencode: "/file/opencode", }, defaultClient: "codex", }), whichSync: (cmd) => `/path/${cmd}`, }); assert.strictEqual(config.paths.codex, "/flag/codex"); assert.strictEqual(config.paths.claude, "/flag/claude"); assert.strictEqual(config.paths.opencode, "/flag/opencode"); assert.strictEqual(config.defaultClient, "opencode"); }); it("uses env var for default client when no flag is given", () => { const config = resolveConfig({ env: { AI_CLI_DEFAULT_CLIENT: "claude" }, existsSync: () => false, whichSync: () => undefined, }); assert.strictEqual(config.defaultClient, "claude"); }); it("uses file default client when no env var is given", () => { const config = resolveConfig({ existsSync: () => true, readFileSync: () => JSON.stringify({ defaultClient: "codex" }), whichSync: () => undefined, }); assert.strictEqual(config.defaultClient, "codex"); }); it("ignores invalid defaultClient from file", () => { const config = resolveConfig({ existsSync: () => true, readFileSync: () => JSON.stringify({ defaultClient: "foo" }), whichSync: () => undefined, }); assert.strictEqual(config.defaultClient, undefined); }); it("ignores invalid defaultClient from env var", () => { const config = resolveConfig({ env: { AI_CLI_DEFAULT_CLIENT: "bar" }, existsSync: () => false, whichSync: () => undefined, }); assert.strictEqual(config.defaultClient, undefined); }); it("ignores invalid defaultClient from flag", () => { const config = resolveConfig({ flags: { "default-client": "baz" }, existsSync: () => false, whichSync: () => undefined, }); assert.strictEqual(config.defaultClient, undefined); }); });