import { describe, expect, test, beforeEach, afterEach } from "bun:test"; import { isEnvTruthy, isEnvDefinedFalsy, parseEnvVars, hasNodeOption, getAWSRegion, getDefaultVertexRegion, getVertexRegionForModel, isBareMode, shouldMaintainProjectWorkingDir, getClaudeConfigHomeDir, } from "../envUtils"; // ─── isEnvTruthy ─────────────────────────────────────────────────────── describe("isEnvTruthy", () => { test("returns true for '1'", () => { expect(isEnvTruthy("1")).toBe(true); }); test("returns true for 'true'", () => { expect(isEnvTruthy("true")).toBe(true); }); test("returns true for 'TRUE'", () => { expect(isEnvTruthy("TRUE")).toBe(true); }); test("returns true for 'yes'", () => { expect(isEnvTruthy("yes")).toBe(true); }); test("returns true for 'on'", () => { expect(isEnvTruthy("on")).toBe(true); }); test("returns true for boolean true", () => { expect(isEnvTruthy(true)).toBe(true); }); test("returns false for '0'", () => { expect(isEnvTruthy("0")).toBe(false); }); test("returns false for 'false'", () => { expect(isEnvTruthy("false")).toBe(false); }); test("returns false for empty string", () => { expect(isEnvTruthy("")).toBe(false); }); test("returns false for undefined", () => { expect(isEnvTruthy(undefined)).toBe(false); }); test("returns false for boolean false", () => { expect(isEnvTruthy(false)).toBe(false); }); test("returns true for ' true ' (trimmed)", () => { expect(isEnvTruthy(" true ")).toBe(true); }); }); // ─── isEnvDefinedFalsy ───────────────────────────────────────────────── describe("isEnvDefinedFalsy", () => { test("returns true for '0'", () => { expect(isEnvDefinedFalsy("0")).toBe(true); }); test("returns true for 'false'", () => { expect(isEnvDefinedFalsy("false")).toBe(true); }); test("returns true for 'no'", () => { expect(isEnvDefinedFalsy("no")).toBe(true); }); test("returns true for 'off'", () => { expect(isEnvDefinedFalsy("off")).toBe(true); }); test("returns true for boolean false", () => { expect(isEnvDefinedFalsy(false)).toBe(true); }); test("returns false for undefined", () => { expect(isEnvDefinedFalsy(undefined)).toBe(false); }); test("returns false for '1'", () => { expect(isEnvDefinedFalsy("1")).toBe(false); }); test("returns false for 'true'", () => { expect(isEnvDefinedFalsy("true")).toBe(false); }); test("returns false for empty string", () => { expect(isEnvDefinedFalsy("")).toBe(false); }); }); // ─── parseEnvVars ────────────────────────────────────────────────────── describe("parseEnvVars", () => { test("parses KEY=VALUE pairs", () => { const result = parseEnvVars(["FOO=bar", "BAZ=qux"]); expect(result).toEqual({ FOO: "bar", BAZ: "qux" }); }); test("handles value with equals sign", () => { const result = parseEnvVars(["URL=http://host?a=1&b=2"]); expect(result).toEqual({ URL: "http://host?a=1&b=2" }); }); test("returns empty object for undefined", () => { expect(parseEnvVars(undefined)).toEqual({}); }); test("returns empty object for empty array", () => { expect(parseEnvVars([])).toEqual({}); }); test("throws for missing value", () => { expect(() => parseEnvVars(["NOVALUE"])).toThrow("Invalid environment variable format"); }); test("throws for empty key", () => { expect(() => parseEnvVars(["=value"])).toThrow("Invalid environment variable format"); }); }); // ─── hasNodeOption ───────────────────────────────────────────────────── describe("hasNodeOption", () => { const saved = process.env.NODE_OPTIONS; afterEach(() => { if (saved === undefined) delete process.env.NODE_OPTIONS; else process.env.NODE_OPTIONS = saved; }); test("returns true when flag present", () => { process.env.NODE_OPTIONS = "--max-old-space-size=4096 --inspect"; expect(hasNodeOption("--inspect")).toBe(true); }); test("returns false when flag absent", () => { process.env.NODE_OPTIONS = "--max-old-space-size=4096"; expect(hasNodeOption("--inspect")).toBe(false); }); test("returns false when NODE_OPTIONS not set", () => { delete process.env.NODE_OPTIONS; expect(hasNodeOption("--inspect")).toBe(false); }); test("does not match partial flags", () => { process.env.NODE_OPTIONS = "--inspect-brk"; expect(hasNodeOption("--inspect")).toBe(false); }); }); // ─── getAWSRegion ────────────────────────────────────────────────────── describe("getAWSRegion", () => { const savedRegion = process.env.AWS_REGION; const savedDefault = process.env.AWS_DEFAULT_REGION; afterEach(() => { if (savedRegion === undefined) delete process.env.AWS_REGION; else process.env.AWS_REGION = savedRegion; if (savedDefault === undefined) delete process.env.AWS_DEFAULT_REGION; else process.env.AWS_DEFAULT_REGION = savedDefault; }); test("uses AWS_REGION when set", () => { process.env.AWS_REGION = "eu-west-1"; expect(getAWSRegion()).toBe("eu-west-1"); }); test("falls back to AWS_DEFAULT_REGION", () => { delete process.env.AWS_REGION; process.env.AWS_DEFAULT_REGION = "ap-northeast-1"; expect(getAWSRegion()).toBe("ap-northeast-1"); }); test("defaults to us-east-1", () => { delete process.env.AWS_REGION; delete process.env.AWS_DEFAULT_REGION; expect(getAWSRegion()).toBe("us-east-1"); }); }); // ─── getDefaultVertexRegion ──────────────────────────────────────────── describe("getDefaultVertexRegion", () => { const saved = process.env.CLOUD_ML_REGION; afterEach(() => { if (saved === undefined) delete process.env.CLOUD_ML_REGION; else process.env.CLOUD_ML_REGION = saved; }); test("uses CLOUD_ML_REGION when set", () => { process.env.CLOUD_ML_REGION = "europe-west4"; expect(getDefaultVertexRegion()).toBe("europe-west4"); }); test("defaults to us-east5", () => { delete process.env.CLOUD_ML_REGION; expect(getDefaultVertexRegion()).toBe("us-east5"); }); }); // ─── getVertexRegionForModel ─────────────────────────────────────────── describe("getVertexRegionForModel", () => { const envKeys = [ "VERTEX_REGION_CLAUDE_HAIKU_4_5", "VERTEX_REGION_CLAUDE_4_0_SONNET", "VERTEX_REGION_CLAUDE_4_6_SONNET", "CLOUD_ML_REGION", ]; const saved: Record = {}; beforeEach(() => { for (const k of envKeys) saved[k] = process.env[k]; }); afterEach(() => { for (const k of envKeys) { if (saved[k] === undefined) delete process.env[k]; else process.env[k] = saved[k]; } }); test("returns model-specific override when set", () => { process.env.VERTEX_REGION_CLAUDE_HAIKU_4_5 = "us-central1"; expect(getVertexRegionForModel("claude-haiku-4-5-20251001")).toBe("us-central1"); }); test("falls back to default vertex region when override not set", () => { delete process.env.VERTEX_REGION_CLAUDE_4_0_SONNET; delete process.env.CLOUD_ML_REGION; expect(getVertexRegionForModel("claude-sonnet-4-some-variant")).toBe("us-east5"); }); test("returns default region for unknown model prefix", () => { delete process.env.CLOUD_ML_REGION; expect(getVertexRegionForModel("unknown-model-123")).toBe("us-east5"); }); test("returns default region for undefined model", () => { delete process.env.CLOUD_ML_REGION; expect(getVertexRegionForModel(undefined)).toBe("us-east5"); }); }); // ─── isBareMode ──────────────────────────────────────────────────────── describe("isBareMode", () => { const saved = process.env.CLAUDE_CODE_SIMPLE; const originalArgv = [...process.argv]; afterEach(() => { if (saved === undefined) delete process.env.CLAUDE_CODE_SIMPLE; else process.env.CLAUDE_CODE_SIMPLE = saved; process.argv.length = 0; process.argv.push(...originalArgv); }); test("returns true when CLAUDE_CODE_SIMPLE=1", () => { process.env.CLAUDE_CODE_SIMPLE = "1"; expect(isBareMode()).toBe(true); }); test("returns true when --bare in argv", () => { process.argv.push("--bare"); expect(isBareMode()).toBe(true); }); test("returns false when neither set", () => { delete process.env.CLAUDE_CODE_SIMPLE; // argv doesn't have --bare by default expect(isBareMode()).toBe(false); }); }); // ─── shouldMaintainProjectWorkingDir ─────────────────────────────────── describe("shouldMaintainProjectWorkingDir", () => { const saved = process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR; afterEach(() => { if (saved === undefined) delete process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR; else process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR = saved; }); test("returns true when set to truthy", () => { process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR = "1"; expect(shouldMaintainProjectWorkingDir()).toBe(true); }); test("returns false when not set", () => { delete process.env.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR; expect(shouldMaintainProjectWorkingDir()).toBe(false); }); }); // ─── getClaudeConfigHomeDir ──────────────────────────────────────────── describe("getClaudeConfigHomeDir", () => { const saved = process.env.CLAUDE_CONFIG_DIR; afterEach(() => { if (saved === undefined) delete process.env.CLAUDE_CONFIG_DIR; else process.env.CLAUDE_CONFIG_DIR = saved; }); test("uses CLAUDE_CONFIG_DIR when set", () => { process.env.CLAUDE_CONFIG_DIR = "/tmp/test-claude"; // Memoized by CLAUDE_CONFIG_DIR key, so changing env gives fresh value expect(getClaudeConfigHomeDir()).toBe("/tmp/test-claude"); }); test("returns a string ending with .claude by default", () => { delete process.env.CLAUDE_CONFIG_DIR; const result = getClaudeConfigHomeDir(); expect(result).toMatch(/\.claude$/); }); });