/* Tests for builtins/config module behavior. */ import { describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { readBindings } from "../config/bindings.ts"; import { resolveAppConfigPath, writeAppConfigFile } from "../config/file.ts"; import { Cli, type CliProgram } from "../index.ts"; function configFixture(): CliProgram { return { key: "cfg-app", version: "1.0.0", description: "Config builtin test.", appConfig: { entries: { apiToken: { description: "Token.", env: "API_TOKEN", sensitive: true }, port: { description: "Port.", required: false }, }, }, commands: [{ key: "run", description: "Run.", handler: () => {} }], }; } /** Tests for builtins/config. */ describe("builtins/config", () => { /** Tests that config get redacts sensitive values. */ test("config get redacts sensitive values", async () => { const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-")); const prevTestHome = process.env.TEST_USER_HOME; process.env.TEST_USER_HOME = dir; const prev = process.env.API_TOKEN; delete process.env.API_TOKEN; try { const program = configFixture(); const configPath = resolveAppConfigPath(program); mkdirSync(dirname(configPath), { recursive: true }); writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`); const result = await new Cli(program).invoke(["configure", "get", "apiToken"]); expect(result.exitCode).toBe(0); expect(result.stdout.trim()).toBe("REDACTED"); } finally { if (prevTestHome === undefined) delete process.env.TEST_USER_HOME; else process.env.TEST_USER_HOME = prevTestHome; if (prev !== undefined) process.env.API_TOKEN = prev; rmSync(dir, { recursive: true, force: true }); } }); /** Tests that config get --json redacts sensitive as { set: true }. */ test("config get --json redacts sensitive as { set: true }", async () => { const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-")); const prevTestHome = process.env.TEST_USER_HOME; process.env.TEST_USER_HOME = dir; const prev = process.env.API_TOKEN; delete process.env.API_TOKEN; try { const program = configFixture(); const configPath = resolveAppConfigPath(program); mkdirSync(dirname(configPath), { recursive: true }); writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`); const result = await new Cli(program).invoke(["configure", "get", "apiToken", "--json"]); expect(result.exitCode).toBe(0); expect(JSON.parse(result.stdout)).toEqual({ set: true }); } finally { if (prevTestHome === undefined) delete process.env.TEST_USER_HOME; else process.env.TEST_USER_HOME = prevTestHome; if (prev !== undefined) process.env.API_TOKEN = prev; rmSync(dir, { recursive: true, force: true }); } }); /** Tests that config set writes and resolves without required exit. */ test("config set writes and resolves without required exit", async () => { const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-")); const prevTestHome = process.env.TEST_USER_HOME; process.env.TEST_USER_HOME = dir; const prev = process.env.API_TOKEN; delete process.env.API_TOKEN; try { const program = configFixture(); writeAppConfigFile(program, { apiToken: "seed" }); const result = await new Cli(program).invoke(["configure", "set", "port", "9090"]); expect(result.exitCode).toBe(0); const get = await new Cli(program).invoke(["configure", "get", "port"]); expect(get.stdout.trim()).toBe("9090"); } finally { if (prevTestHome === undefined) delete process.env.TEST_USER_HOME; else process.env.TEST_USER_HOME = prevTestHome; if (prev !== undefined) process.env.API_TOKEN = prev; rmSync(dir, { recursive: true, force: true }); } }); test("config set --from-env stores binding without literal", async () => { const dir = mkdtempSync(join(tmpdir(), "cfg-from-env-")); const prevTestHome = process.env.TEST_USER_HOME; process.env.TEST_USER_HOME = dir; const prev = process.env.API_TOKEN; process.env.API_TOKEN = "from-env"; try { const program = configFixture(); writeAppConfigFile(program, { apiToken: "seed" }, { partial: true }); const result = await new Cli(program).invoke(["configure", "set", "apiToken", "--from-env"]); expect(result.exitCode).toBe(0); const raw = await new Cli(program).invoke(["configure", "get", "apiToken"]); expect(raw.stdout.trim()).toBe("REDACTED"); const configPath = resolveAppConfigPath(program); const { readFileSync } = await import("node:fs"); const onDisk = JSON.parse(readFileSync(configPath, "utf8")) as Record; expect(onDisk.apiToken).toBeUndefined(); expect(readBindings(onDisk).apiToken).toBe("env"); } finally { if (prevTestHome === undefined) delete process.env.TEST_USER_HOME; else process.env.TEST_USER_HOME = prevTestHome; if (prev === undefined) delete process.env.API_TOKEN; else process.env.API_TOKEN = prev; rmSync(dir, { recursive: true, force: true }); } }); });