Files
oh-my-pi/packages/utils/test/env.test.ts
T
2026-07-31 20:04:30 -07:00

144 lines
3.6 KiB
TypeScript

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[] = [];
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);
}
});
});
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",
});
});
});