Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
452 changes: 452 additions & 0 deletions tests/cli-spawn-env-isolation.test.ts

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions tests/dispatch-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { findProjectContext } from "../src/lib/project.ts";
import { upsertProjectRegistration } from "../src/lib/projects-registry.ts";
import { restoreEnv } from "./helpers/env.ts";
import { privateCliEnv } from "./helpers/private-cli-env.ts";

const shouldRun = process.env.HACK_DISPATCH_E2E === "1";
const runTest = shouldRun ? test : test.skip;
Expand Down Expand Up @@ -138,13 +139,11 @@ runTest(
const result = await runCommand({
cmd: command,
cwd: resolve(import.meta.dir, ".."),
env: {
...process.env,
HOME: tempHome,
env: childEnv({
HACK_GLOBAL_CONFIG_PATH: "",
[nodeTokenEnv]: nodeToken,
NO_COLOR: "1",
},
}),
});
if (result.exitCode !== 0) {
throw new Error(
Expand Down Expand Up @@ -301,15 +300,13 @@ runTest(
const result = await runCommand({
cmd: command,
cwd: resolve(import.meta.dir, ".."),
env: {
...process.env,
HOME: tempHome,
env: childEnv({
HACK_GLOBAL_CONFIG_PATH: "",
[nodeTokenEnv]: nodeToken,
PATH: `${mutagenBinDir}:${process.env.PATH ?? ""}`,
HACK_TEST_MUTAGEN_LOG_PATH: mutagenLogPath,
NO_COLOR: "1",
},
}),
});
if (result.exitCode !== 0) {
throw new Error(
Expand Down Expand Up @@ -421,13 +418,11 @@ runTest(
const result = await runCommand({
cmd: command,
cwd: resolve(import.meta.dir, ".."),
env: {
...process.env,
HOME: tempHome,
env: childEnv({
HACK_GLOBAL_CONFIG_PATH: "",
[nodeTokenEnv]: nodeToken,
NO_COLOR: "1",
},
}),
});
expect(result.exitCode).toBe(1);
expect(result.stderr).toContain("Workspace ensure failed (500)");
Expand Down Expand Up @@ -506,14 +501,27 @@ async function initializeGitRepo(input: {
});
}

/**
* A private env for a child of the current test: HOME is the test's temporary
* home, and no other runner variable is inherited beyond PATH and TMPDIR.
*/
function childEnv(
env: Readonly<Record<string, string>>
): Record<string, string> {
if (tempHome === null) {
throw new Error("dispatch E2E child env requires a temporary HOME");
}
return privateCliEnv({ root: tempHome, home: tempHome, env });
}

async function runGit(input: {
readonly cwd: string;
readonly args: readonly string[];
}): Promise<void> {
const result = await runCommand({
cmd: ["git", ...input.args],
cwd: input.cwd,
env: process.env,
env: childEnv({}),
});
if (result.exitCode !== 0) {
throw new Error(
Expand Down
52 changes: 33 additions & 19 deletions tests/doctor-browser-network.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { expect, test } from "bun:test";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildDoctorJsonData } from "../src/commands/doctor.ts";
import {
checkBrowserLocalNetwork,
parseBrowserNetworkOptions,
probeBrowserTarget,
} from "../src/lib/doctor-browser-network.ts";
import { privateCliEnv } from "./helpers/private-cli-env.ts";

const url = "https://app.hack/";

Expand Down Expand Up @@ -152,25 +156,35 @@ test("curl invocation validates TLS, disables config, and bounds transfer withou
});

test("invalid browser arguments fail before Doctor invokes tools", async () => {
const child = Bun.spawn(
[
process.execPath,
"index.ts",
"doctor",
"--browser-result",
"works",
"--json",
],
{ stdout: "pipe", stderr: "pipe", timeout: 5000 }
);
const [stdout, stderr, code] = await Promise.all([
new Response(child.stdout).text(),
new Response(child.stderr).text(),
child.exited,
]);
expect(code).not.toBe(0);
expect(stdout).not.toContain("docker");
expect(stdout + stderr).toContain("requires --browser-url");
const root = await mkdtemp(join(tmpdir(), "hack-doctor-browser-"));
try {
const child = Bun.spawn(
[
process.execPath,
"index.ts",
"doctor",
"--browser-result",
"works",
"--json",
],
{
env: privateCliEnv({ root }),
stdout: "pipe",
stderr: "pipe",
timeout: 5000,
}
);
const [stdout, stderr, code] = await Promise.all([
new Response(child.stdout).text(),
new Response(child.stderr).text(),
child.exited,
]);
expect(code).not.toBe(0);
expect(stdout).not.toContain("docker");
expect(stdout + stderr).toContain("requires --browser-url");
} finally {
await rm(root, { recursive: true, force: true });
}
});

test("JSON and summary retain the unknown browser warning separately", async () => {
Expand Down
29 changes: 17 additions & 12 deletions tests/doctor-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
buildRecoveryNextSteps,
buildRecoveryWorkflowLines,
} from "../src/commands/recovery-guidance.ts";
import { privateCliEnv } from "./helpers/private-cli-env.ts";

async function createDoctorTestProject(opts: {
readonly modern?: boolean;
Expand Down Expand Up @@ -74,18 +75,20 @@ test("doctor refuses native input before probing or repairing global runtime", a
[process.execPath, cli, "doctor", "--path", project, ...flags],
{
cwd: project,
env: {
...process.env,
HOME: root,
HACK_HOME: join(root, "state"),
HACK_GLOBAL_CONFIG_PATH: join(root, "state", "hack.config.json"),
HACK_DAEMON_DISABLE: "1",
HACK_NO_INTERACTIVE: "1",
HACK_LOGGER: "console",
XDG_CACHE_HOME: cache,
PATH: `${tools}:/usr/bin:/bin`,
RUNTIME_SENTINEL: sentinel,
},
env: privateCliEnv({
root,
home: root,
env: {
HACK_HOME: join(root, "state"),
HACK_GLOBAL_CONFIG_PATH: join(root, "state", "hack.config.json"),
HACK_DAEMON_DISABLE: "1",
HACK_NO_INTERACTIVE: "1",
HACK_LOGGER: "console",
XDG_CACHE_HOME: cache,
PATH: `${tools}:/usr/bin:/bin`,
RUNTIME_SENTINEL: sentinel,
},
}),
stdout: "pipe",
stderr: "pipe",
}
Expand All @@ -104,8 +107,10 @@ test("doctor refuses native input before probing or repairing global runtime", a
});
}
expect(await Bun.file(sentinel).exists()).toBe(false);
// `docker-client` is the test's isolated Docker client config.
expect((await readdir(root)).sort()).toEqual([
"cache",
"docker-client",
"project",
"tools",
]);
Expand Down
15 changes: 9 additions & 6 deletions tests/doctor-domain-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { privateCliEnv } from "./helpers/private-cli-env.ts";

const directories: string[] = [];
const sentinel = "PRIVATE_CONFIG_MUST_NOT_APPEAR";
Expand Down Expand Up @@ -60,12 +61,14 @@ async function invoke(
...extra,
],
{
env: {
...process.env,
HACK_HOME: f.home,
HACK_NO_INTERACTIVE: "1",
PATH: join(f.root, "no-tools"),
},
env: privateCliEnv({
root: f.root,
env: {
HACK_HOME: f.home,
HACK_NO_INTERACTIVE: "1",
PATH: join(f.root, "no-tools"),
},
}),
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
Expand Down
19 changes: 11 additions & 8 deletions tests/e2e/run-dependency-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { privateCliEnv } from "../helpers/private-cli-env.ts";

const roots: string[] = [];
const cli = resolve(import.meta.dir, "../../index.ts");
Expand Down Expand Up @@ -129,14 +130,16 @@ else if(args.includes("run")) console.log("fixture-command-output");
{
cwd: primary,
timeout: 10_000,
env: {
...process.env,
HOME: home,
HACK_HOME: join(home, ".hack"),
PATH: `${bin}:${process.env.PATH ?? ""}`,
HACK_NO_INTERACTIVE: "1",
HACK_LOGGER: "console",
},
env: privateCliEnv({
root,
home,
env: {
HACK_HOME: join(home, ".hack"),
PATH: `${bin}:${process.env.PATH ?? ""}`,
HACK_NO_INTERACTIVE: "1",
HACK_LOGGER: "console",
},
}),
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
Expand Down
Loading
Loading