import { afterEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { filterProcessEnv, getDbBusyTimeoutMs, parseEnvFile, setInteractiveHost } from "@oh-my-pi/pi-utils/env"; const tempDirs: string[] = []; const runtimeProbePath = path.join(import.meta.dir, "fixtures", "test-runtime-probe.ts"); afterEach(() => { for (const dir of tempDirs.splice(0)) { fs.rmSync(dir, { force: true, recursive: true }); } }); function writeTempEnv(content: string): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-utils-env-")); tempDirs.push(dir); const filePath = path.join(dir, ".env"); fs.writeFileSync(filePath, content); return filePath; } describe("getDbBusyTimeoutMs", () => { it("defaults to the bounded headless timeout", () => { const previous = setInteractiveHost(false); try { expect(getDbBusyTimeoutMs()).toBe(1000); } finally { setInteractiveHost(previous); } }); it("keeps the interactive timeout for interactive hosts", () => { const previous = setInteractiveHost(true); try { expect(getDbBusyTimeoutMs()).toBe(5000); } finally { setInteractiveHost(previous); } }); }); async function runRuntimeProbe( env: Record, probePath = runtimeProbePath, ): Promise { const cwd = path.dirname(writeTempEnv("")); const proc = Bun.spawn([process.execPath, probePath], { cwd, env: { ...process.env, ...env }, stdout: "pipe", stderr: "pipe", }); const [stdout, stderr, exitCode] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited, ]); expect(exitCode, stderr).toBe(0); return JSON.parse(stdout) as boolean; } describe("parseEnvFile", () => { it("ignores malformed names and nul-containing values", () => { const filePath = writeTempEnv( [ "GOOD=value", "_ALSO_GOOD='quoted value'", "1BAD=value", "BAD-NAME=value", "BAD NAME=value", "BAD_VALUE=before\0after", "# comment", "NO_EQUALS", ].join("\n"), ); expect(parseEnvFile(filePath)).toEqual({ GOOD: "value", _ALSO_GOOD: "quoted value", }); }); it("mirrors valid OMP_ variables to PI_ variables", () => { const filePath = writeTempEnv("OMP_FEATURE=enabled\nOMP_BAD=before\0after\n"); expect(parseEnvFile(filePath)).toEqual({ OMP_FEATURE: "enabled", PI_FEATURE: "enabled", }); }); it("matches Bun dotenv syntax for export prefixes and inline comments", () => { const filePath = writeTempEnv( [ "export EXPORTED=value", "COMMENTED=secret # trailing comment", 'QUOTED_HASH="keep # this"', "NO_SPACE=http://host/path#frag", ].join("\n"), ); expect(parseEnvFile(filePath)).toEqual({ EXPORTED: "value", COMMENTED: "secret", QUOTED_HASH: "keep # this", NO_SPACE: "http://host/path#frag", }); }); it("keeps escaped quotes inside quoted values literal, matching Bun", () => { const filePath = writeTempEnv(['JSON="{\\"a\\":1}"', "SINGLE='it\\'s'"].join("\n")); expect(parseEnvFile(filePath)).toEqual({ JSON: '{\\"a\\":1}', SINGLE: "it\\'s", }); }); }); describe("filterProcessEnv", () => { it("drops entries that cannot be passed to process spawn env", () => { expect( filterProcessEnv({ GOOD: "value", EMPTY: "", "BAD=NAME": "value", BAD_VALUE: "before\0after", MISSING: undefined, }), ).toEqual({ GOOD: "value", EMPTY: "", }); }); it("drops macOS malloc stack logging toggles instead of forwarding disabled values", () => { expect( filterProcessEnv({ GOOD: "value", MallocStackLogging: "0", MallocStackLoggingNoCompact: "0", }), ).toEqual({ GOOD: "value", }); }); it("preserves Windows-style variable names containing parentheses", () => { // `ProgramFiles(x86)` and friends are standard on Windows and must // survive the scrub so Git Bash discovery in procmgr.ts can resolve // 32-bit Program Files installations. expect( filterProcessEnv({ "ProgramFiles(x86)": "C:\\Program Files (x86)", "CommonProgramFiles(x86)": "C:\\Program Files (x86)\\Common Files", }), ).toEqual({ "ProgramFiles(x86)": "C:\\Program Files (x86)", "CommonProgramFiles(x86)": "C:\\Program Files (x86)\\Common Files", }); }); }); describe("isBunTestRuntime", () => { it("does not treat shared application env names as a test runner signal", async () => { expect(await runRuntimeProbe({ NODE_ENV: "test", BUN_ENV: undefined, PI_TEST_RUNTIME: undefined })).toBe(false); expect(await runRuntimeProbe({ NODE_ENV: undefined, BUN_ENV: "test", PI_TEST_RUNTIME: undefined })).toBe(false); }); it("honors the private test runner signal", async () => { expect(await runRuntimeProbe({ NODE_ENV: undefined, BUN_ENV: undefined, PI_TEST_RUNTIME: "1" })).toBe(true); }); it("recognizes Bun's underscore test entrypoints", async () => { const dir = path.dirname(writeTempEnv("")); const underscoreProbePath = path.join(dir, "runtime_test.ts"); const envModulePath = path.join(import.meta.dir, "..", "src", "env.ts"); fs.writeFileSync( underscoreProbePath, `import { isBunTestRuntime } from ${JSON.stringify(envModulePath)};\nprocess.stdout.write(JSON.stringify(isBunTestRuntime()));\n`, ); expect( await runRuntimeProbe( { NODE_ENV: "test", BUN_ENV: undefined, PI_TEST_RUNTIME: undefined }, underscoreProbePath, ), ).toBe(true); }); });