tests / shellRun.test.ts
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { RunResult } from "../src/exec";
import { shellRun, type ShellRunDeps } from "../src/shellRun";
import { HAZARDS, type Assessment, type Hazard, type Settings } from "../src/types";
const settings: Settings = {
blockThreshold: 0.7,
reviewThreshold: 0.35,
severityBlock: 2,
defaultTimeoutSec: 30,
maxTimeoutSec: 120,
maxOutputChars: 20000,
};
function assessment(hazards: Partial<Record<Hazard, number>> = {}, severity = 0): Assessment {
const full = Object.fromEntries(HAZARDS.map(h => [h, 0])) as Record<Hazard, number>;
return { hazards: { ...full, ...hazards }, severity };
}
function ok(overrides: Partial<RunResult> = {}): RunResult {
return { output: "hi\n", exitCode: 0, timedOut: false, aborted: false, truncated: false, ...overrides };
}
function deps(overrides: Partial<ShellRunDeps> = {}) {
const assess = vi.fn().mockResolvedValue(assessment());
const run = vi.fn().mockResolvedValue(ok());
const merged: ShellRunDeps = { defaultCwd: os.tmpdir(), settings, assess, run, home: "/Users/tester", ...overrides };
return { deps: merged, assess: merged.assess as ReturnType<typeof vi.fn>, run: merged.run as ReturnType<typeof vi.fn> };
}
describe("shellRun", () => {
it("requires a command", async () => {
const { deps: d } = deps();
expect(await shellRun({ command: " " }, d)).toBe("Error: command is required");
});
it("rejects a cwd that is not a directory", async () => {
const { deps: d, assess } = deps();
expect(await shellRun({ command: "ls", cwd: "/definitely/not/here" }, d)).toBe(
"Error: not a directory: /definitely/not/here",
);
expect(assess).not.toHaveBeenCalled();
});
it("hard-denies without consulting jev or running", async () => {
const { deps: d, assess, run } = deps();
const result = await shellRun({ command: "sudo ls" }, d);
expect(result).toMatch(/^Blocked: 'sudo' is not allowed/);
expect(assess).not.toHaveBeenCalled();
expect(run).not.toHaveBeenCalled();
});
it("refuses when jev is unreachable, and does not run", async () => {
const { deps: d, run } = deps({ assess: vi.fn().mockRejectedValue(new Error("JEV_API_KEY is not set")) });
const result = await shellRun({ command: "ls" }, d);
expect(result).toContain("Blocked: safety check unavailable (JEV_API_KEY is not set)");
expect(run).not.toHaveBeenCalled();
});
it("refuses unsafe commands using the jev verdict", async () => {
const { deps: d, run } = deps({ assess: vi.fn().mockResolvedValue(assessment({ destroys_data: 0.95 }, 2.5)) });
const result = await shellRun({ command: "find . -delete" }, d);
expect(result).toContain("judged unsafe");
expect(run).not.toHaveBeenCalled();
});
it("refuses uncertain commands", async () => {
const { deps: d, run } = deps({ assess: vi.fn().mockResolvedValue(assessment({ touches_system: 0.5 })) });
expect(await shellRun({ command: "brew install x" }, d)).toContain("could not be confirmed safe");
expect(run).not.toHaveBeenCalled();
});
it("runs safe commands with the resolved cwd and settings, and returns output", async () => {
const { deps: d, run } = deps();
expect(await shellRun({ command: " echo hi ", cwd: "/" }, d)).toBe("hi");
expect(run).toHaveBeenCalledWith("echo hi", expect.objectContaining({ cwd: "/", timeoutMs: 30000, maxChars: 20000 }));
});
it("passes the assessed command and cwd to jev", async () => {
const { deps: d, assess } = deps();
await shellRun({ command: "ls", cwd: "/" }, d);
expect(assess).toHaveBeenCalledWith({ command: "ls", cwd: "/" }, undefined);
});
it("clamps the timeout to [1, max]", async () => {
const { deps: d, run } = deps();
await shellRun({ command: "ls", timeoutSec: 999 }, d);
await shellRun({ command: "ls", timeoutSec: 0 }, d);
expect(run.mock.calls[0][1].timeoutMs).toBe(120000);
expect(run.mock.calls[1][1].timeoutMs).toBe(1000);
});
it("reports status updates", async () => {
const status = vi.fn();
const { deps: d } = deps({ status });
await shellRun({ command: "ls" }, d);
expect(status.mock.calls.map(c => c[0])).toEqual(["Checking command safety…", "Running command…"]);
});
it.each([
[ok({ exitCode: 2, output: "boom\n" }), "Error (exit 2): boom"],
[ok({ exitCode: null, output: "" }), "Error (exit signal): (no output)"],
[ok({ output: "" }), "(no output)"],
[ok({ timedOut: true }), "Error: command timed out"],
[ok({ aborted: true }), "Error: command aborted"],
[ok({ output: "abc", truncated: true }), "abc\n[output truncated]"],
])("formats result %#", async (result, expected) => {
const { deps: d } = deps({ run: vi.fn().mockResolvedValue(result) });
expect(await shellRun({ command: "ls" }, d)).toBe(expected);
});
it("returns an error string when the process cannot start", async () => {
const { deps: d } = deps({ run: vi.fn().mockRejectedValue(new Error("spawn EACCES")) });
expect(await shellRun({ command: "ls" }, d)).toBe("Error: could not start command: spawn EACCES");
});
describe("relative cwd", () => {
let tempDir: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "shellrun-"));
fs.mkdirSync(path.join(tempDir, "sub"));
});
afterEach(() => fs.rmSync(tempDir, { recursive: true, force: true }));
it("resolves a relative cwd against defaultCwd", async () => {
const { deps: d, assess, run } = deps({ defaultCwd: tempDir });
await shellRun({ command: "ls", cwd: "sub" }, d);
const expected = path.join(tempDir, "sub");
expect(run.mock.calls[0][1].cwd).toBe(expected);
expect(assess.mock.calls[0][0].cwd).toBe(expected);
});
it("reports a missing relative cwd under defaultCwd", async () => {
const { deps: d } = deps({ defaultCwd: tempDir });
expect(await shellRun({ command: "ls", cwd: "missing" }, d)).toBe(
`Error: not a directory: ${path.join(tempDir, "missing")}`,
);
});
it("uses an absolute cwd as-is", async () => {
const { deps: d, run } = deps({ defaultCwd: tempDir });
await shellRun({ command: "ls", cwd: "/" }, d);
expect(run.mock.calls[0][1].cwd).toBe("/");
});
});
});
tests / shellRun.test.ts
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { RunResult } from "../src/exec";
import { shellRun, type ShellRunDeps } from "../src/shellRun";
import { HAZARDS, type Assessment, type Hazard, type Settings } from "../src/types";
const settings: Settings = {
blockThreshold: 0.7,
reviewThreshold: 0.35,
severityBlock: 2,
defaultTimeoutSec: 30,
maxTimeoutSec: 120,
maxOutputChars: 20000,
};
function assessment(hazards: Partial<Record<Hazard, number>> = {}, severity = 0): Assessment {
const full = Object.fromEntries(HAZARDS.map(h => [h, 0])) as Record<Hazard, number>;
return { hazards: { ...full, ...hazards }, severity };
}
function ok(overrides: Partial<RunResult> = {}): RunResult {
return { output: "hi\n", exitCode: 0, timedOut: false, aborted: false, truncated: false, ...overrides };
}
function deps(overrides: Partial<ShellRunDeps> = {}) {
const assess = vi.fn().mockResolvedValue(assessment());
const run = vi.fn().mockResolvedValue(ok());
const merged: ShellRunDeps = { defaultCwd: os.tmpdir(), settings, assess, run, home: "/Users/tester", ...overrides };
return { deps: merged, assess: merged.assess as ReturnType<typeof vi.fn>, run: merged.run as ReturnType<typeof vi.fn> };
}
describe("shellRun", () => {
it("requires a command", async () => {
const { deps: d } = deps();
expect(await shellRun({ command: " " }, d)).toBe("Error: command is required");
});
it("rejects a cwd that is not a directory", async () => {
const { deps: d, assess } = deps();
expect(await shellRun({ command: "ls", cwd: "/definitely/not/here" }, d)).toBe(
"Error: not a directory: /definitely/not/here",
);
expect(assess).not.toHaveBeenCalled();
});
it("hard-denies without consulting jev or running", async () => {
const { deps: d, assess, run } = deps();
const result = await shellRun({ command: "sudo ls" }, d);
expect(result).toMatch(/^Blocked: 'sudo' is not allowed/);
expect(assess).not.toHaveBeenCalled();
expect(run).not.toHaveBeenCalled();
});
it("refuses when jev is unreachable, and does not run", async () => {
const { deps: d, run } = deps({ assess: vi.fn().mockRejectedValue(new Error("JEV_API_KEY is not set")) });
const result = await shellRun({ command: "ls" }, d);
expect(result).toContain("Blocked: safety check unavailable (JEV_API_KEY is not set)");
expect(run).not.toHaveBeenCalled();
});
it("refuses unsafe commands using the jev verdict", async () => {
const { deps: d, run } = deps({ assess: vi.fn().mockResolvedValue(assessment({ destroys_data: 0.95 }, 2.5)) });
const result = await shellRun({ command: "find . -delete" }, d);
expect(result).toContain("judged unsafe");
expect(run).not.toHaveBeenCalled();
});
it("refuses uncertain commands", async () => {
const { deps: d, run } = deps({ assess: vi.fn().mockResolvedValue(assessment({ touches_system: 0.5 })) });
expect(await shellRun({ command: "brew install x" }, d)).toContain("could not be confirmed safe");
expect(run).not.toHaveBeenCalled();
});
it("runs safe commands with the resolved cwd and settings, and returns output", async () => {
const { deps: d, run } = deps();
expect(await shellRun({ command: " echo hi ", cwd: "/" }, d)).toBe("hi");
expect(run).toHaveBeenCalledWith("echo hi", expect.objectContaining({ cwd: "/", timeoutMs: 30000, maxChars: 20000 }));
});
it("passes the assessed command and cwd to jev", async () => {
const { deps: d, assess } = deps();
await shellRun({ command: "ls", cwd: "/" }, d);
expect(assess).toHaveBeenCalledWith({ command: "ls", cwd: "/" }, undefined);
});
it("clamps the timeout to [1, max]", async () => {
const { deps: d, run } = deps();
await shellRun({ command: "ls", timeoutSec: 999 }, d);
await shellRun({ command: "ls", timeoutSec: 0 }, d);
expect(run.mock.calls[0][1].timeoutMs).toBe(120000);
expect(run.mock.calls[1][1].timeoutMs).toBe(1000);
});
it("reports status updates", async () => {
const status = vi.fn();
const { deps: d } = deps({ status });
await shellRun({ command: "ls" }, d);
expect(status.mock.calls.map(c => c[0])).toEqual(["Checking command safety…", "Running command…"]);
});
it.each([
[ok({ exitCode: 2, output: "boom\n" }), "Error (exit 2): boom"],
[ok({ exitCode: null, output: "" }), "Error (exit signal): (no output)"],
[ok({ output: "" }), "(no output)"],
[ok({ timedOut: true }), "Error: command timed out"],
[ok({ aborted: true }), "Error: command aborted"],
[ok({ output: "abc", truncated: true }), "abc\n[output truncated]"],
])("formats result %#", async (result, expected) => {
const { deps: d } = deps({ run: vi.fn().mockResolvedValue(result) });
expect(await shellRun({ command: "ls" }, d)).toBe(expected);
});
it("returns an error string when the process cannot start", async () => {
const { deps: d } = deps({ run: vi.fn().mockRejectedValue(new Error("spawn EACCES")) });
expect(await shellRun({ command: "ls" }, d)).toBe("Error: could not start command: spawn EACCES");
});
describe("relative cwd", () => {
let tempDir: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "shellrun-"));
fs.mkdirSync(path.join(tempDir, "sub"));
});
afterEach(() => fs.rmSync(tempDir, { recursive: true, force: true }));
it("resolves a relative cwd against defaultCwd", async () => {
const { deps: d, assess, run } = deps({ defaultCwd: tempDir });
await shellRun({ command: "ls", cwd: "sub" }, d);
const expected = path.join(tempDir, "sub");
expect(run.mock.calls[0][1].cwd).toBe(expected);
expect(assess.mock.calls[0][0].cwd).toBe(expected);
});
it("reports a missing relative cwd under defaultCwd", async () => {
const { deps: d } = deps({ defaultCwd: tempDir });
expect(await shellRun({ command: "ls", cwd: "missing" }, d)).toBe(
`Error: not a directory: ${path.join(tempDir, "missing")}`,
);
});
it("uses an absolute cwd as-is", async () => {
const { deps: d, run } = deps({ defaultCwd: tempDir });
await shellRun({ command: "ls", cwd: "/" }, d);
expect(run.mock.calls[0][1].cwd).toBe("/");
});
});
});