144 lines
3.6 KiB
TypeScript
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",
|
|
});
|
|
});
|
|
});
|