test: stabilize bash e2e suites with explicit exec approvals mode
This commit is contained in:
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-events.js";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
import { getFinishedSession, resetProcessRegistryForTests } from "./bash-process-registry.js";
|
||||
import { createExecTool, createProcessTool, execTool, processTool } from "./bash-tools.js";
|
||||
import { createExecTool, createProcessTool } from "./bash-tools.js";
|
||||
import { buildDockerExecArgs } from "./bash-tools.shared.js";
|
||||
import { resolveShellFromPath, sanitizeBinaryOutput } from "./shell-utils.js";
|
||||
|
||||
@@ -16,6 +16,12 @@ const shortDelayCmd = isWin ? "Start-Sleep -Milliseconds 20" : "sleep 0.02";
|
||||
const yieldDelayCmd = isWin ? "Start-Sleep -Milliseconds 90" : "sleep 0.09";
|
||||
const longDelayCmd = isWin ? "Start-Sleep -Milliseconds 700" : "sleep 0.7";
|
||||
const POLL_INTERVAL_MS = 15;
|
||||
const TEST_EXEC_DEFAULTS = { security: "full" as const, ask: "off" as const };
|
||||
const createTestExecTool = (
|
||||
defaults?: Parameters<typeof createExecTool>[0],
|
||||
): ReturnType<typeof createExecTool> => createExecTool({ ...TEST_EXEC_DEFAULTS, ...defaults });
|
||||
const execTool = createTestExecTool();
|
||||
const processTool = createProcessTool();
|
||||
// Both PowerShell and bash use ; for command separation
|
||||
const joinCommands = (commands: string[]) => commands.join("; ");
|
||||
const echoAfterDelay = (message: string) => joinCommands([shortDelayCmd, `echo ${message}`]);
|
||||
@@ -144,7 +150,7 @@ describe("exec tool backgrounding", () => {
|
||||
});
|
||||
|
||||
it("uses default timeout when timeout is omitted", async () => {
|
||||
const customBash = createExecTool({ timeoutSec: 0.1, backgroundMs: 10 });
|
||||
const customBash = createTestExecTool({ timeoutSec: 0.1, backgroundMs: 10 });
|
||||
const customProcess = createProcessTool();
|
||||
|
||||
const result = await customBash.execute("call1", {
|
||||
@@ -168,7 +174,7 @@ describe("exec tool backgrounding", () => {
|
||||
});
|
||||
|
||||
it("rejects elevated requests when not allowed", async () => {
|
||||
const customBash = createExecTool({
|
||||
const customBash = createTestExecTool({
|
||||
elevated: { enabled: true, allowed: false, defaultLevel: "off" },
|
||||
messageProvider: "telegram",
|
||||
sessionKey: "agent:main:main",
|
||||
@@ -183,7 +189,7 @@ describe("exec tool backgrounding", () => {
|
||||
});
|
||||
|
||||
it("does not default to elevated when not allowed", async () => {
|
||||
const customBash = createExecTool({
|
||||
const customBash = createTestExecTool({
|
||||
elevated: { enabled: true, allowed: false, defaultLevel: "on" },
|
||||
backgroundMs: 1000,
|
||||
timeoutSec: 5,
|
||||
@@ -270,9 +276,9 @@ describe("exec tool backgrounding", () => {
|
||||
});
|
||||
|
||||
it("scopes process sessions by scopeKey", async () => {
|
||||
const bashA = createExecTool({ backgroundMs: 10, scopeKey: "agent:alpha" });
|
||||
const bashA = createTestExecTool({ backgroundMs: 10, scopeKey: "agent:alpha" });
|
||||
const processA = createProcessTool({ scopeKey: "agent:alpha" });
|
||||
const bashB = createExecTool({ backgroundMs: 10, scopeKey: "agent:beta" });
|
||||
const bashB = createTestExecTool({ backgroundMs: 10, scopeKey: "agent:beta" });
|
||||
const processB = createProcessTool({ scopeKey: "agent:beta" });
|
||||
|
||||
const resultA = await bashA.execute("call1", {
|
||||
@@ -332,7 +338,7 @@ describe("exec exit codes", () => {
|
||||
|
||||
describe("exec notifyOnExit", () => {
|
||||
it("enqueues a system event when a backgrounded exec exits", async () => {
|
||||
const tool = createExecTool({
|
||||
const tool = createTestExecTool({
|
||||
allowBackground: true,
|
||||
backgroundMs: 0,
|
||||
notifyOnExit: true,
|
||||
@@ -372,7 +378,7 @@ describe("exec notifyOnExit", () => {
|
||||
});
|
||||
|
||||
it("skips no-op completion events when command succeeds without output", async () => {
|
||||
const tool = createExecTool({
|
||||
const tool = createTestExecTool({
|
||||
allowBackground: true,
|
||||
backgroundMs: 0,
|
||||
notifyOnExit: true,
|
||||
@@ -392,7 +398,7 @@ describe("exec notifyOnExit", () => {
|
||||
});
|
||||
|
||||
it("can re-enable no-op completion events via notifyOnExitEmptySuccess", async () => {
|
||||
const tool = createExecTool({
|
||||
const tool = createTestExecTool({
|
||||
allowBackground: true,
|
||||
backgroundMs: 0,
|
||||
notifyOnExit: true,
|
||||
@@ -434,13 +440,15 @@ describe("exec PATH handling", () => {
|
||||
const prepend = isWin ? ["C:\\custom\\bin", "C:\\oss\\bin"] : ["/custom/bin", "/opt/oss/bin"];
|
||||
process.env.PATH = basePath;
|
||||
|
||||
const tool = createExecTool({ pathPrepend: prepend });
|
||||
const tool = createTestExecTool({ pathPrepend: prepend });
|
||||
const result = await tool.execute("call1", {
|
||||
command: isWin ? "Write-Output $env:PATH" : "echo $PATH",
|
||||
});
|
||||
|
||||
const text = normalizeText(result.content.find((c) => c.type === "text")?.text);
|
||||
expect(text).toBe([...prepend, basePath].join(path.delimiter));
|
||||
const entries = text.split(path.delimiter);
|
||||
expect(entries.slice(0, prepend.length)).toEqual(prepend);
|
||||
expect(entries).toContain(basePath);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,14 @@ const ABORT_WAIT_TIMEOUT_MS = process.platform === "win32" ? 1_500 : 450;
|
||||
const POLL_INTERVAL_MS = 15;
|
||||
const FINISHED_WAIT_TIMEOUT_MS = process.platform === "win32" ? 8_000 : 1_200;
|
||||
const BACKGROUND_TIMEOUT_SEC = process.platform === "win32" ? 0.2 : 0.12;
|
||||
const TEST_EXEC_DEFAULTS = {
|
||||
security: "full" as const,
|
||||
ask: "off" as const,
|
||||
};
|
||||
|
||||
const createTestExecTool = (
|
||||
defaults?: Parameters<typeof createExecTool>[0],
|
||||
): ReturnType<typeof createExecTool> => createExecTool({ ...TEST_EXEC_DEFAULTS, ...defaults });
|
||||
|
||||
afterEach(() => {
|
||||
resetProcessRegistryForTests();
|
||||
@@ -106,7 +114,7 @@ async function expectBackgroundSessionTimesOut(params: {
|
||||
}
|
||||
|
||||
test("background exec is not killed when tool signal aborts", async () => {
|
||||
const tool = createExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
await expectBackgroundSessionSurvivesAbort({
|
||||
tool,
|
||||
executeParams: { command: BACKGROUND_HOLD_CMD, background: true },
|
||||
@@ -114,7 +122,7 @@ test("background exec is not killed when tool signal aborts", async () => {
|
||||
});
|
||||
|
||||
test("pty background exec is not killed when tool signal aborts", async () => {
|
||||
const tool = createExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
await expectBackgroundSessionSurvivesAbort({
|
||||
tool,
|
||||
executeParams: { command: BACKGROUND_HOLD_CMD, background: true, pty: true },
|
||||
@@ -122,7 +130,7 @@ test("pty background exec is not killed when tool signal aborts", async () => {
|
||||
});
|
||||
|
||||
test("background exec still times out after tool signal abort", async () => {
|
||||
const tool = createExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 0 });
|
||||
await expectBackgroundSessionTimesOut({
|
||||
tool,
|
||||
executeParams: {
|
||||
@@ -135,7 +143,7 @@ test("background exec still times out after tool signal abort", async () => {
|
||||
});
|
||||
|
||||
test("yielded background exec is not killed when tool signal aborts", async () => {
|
||||
const tool = createExecTool({ allowBackground: true, backgroundMs: 10 });
|
||||
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 10 });
|
||||
await expectBackgroundSessionSurvivesAbort({
|
||||
tool,
|
||||
executeParams: { command: BACKGROUND_HOLD_CMD, yieldMs: 5 },
|
||||
@@ -143,7 +151,7 @@ test("yielded background exec is not killed when tool signal aborts", async () =
|
||||
});
|
||||
|
||||
test("yielded background exec still times out", async () => {
|
||||
const tool = createExecTool({ allowBackground: true, backgroundMs: 10 });
|
||||
const tool = createTestExecTool({ allowBackground: true, backgroundMs: 10 });
|
||||
await expectBackgroundSessionTimesOut({
|
||||
tool,
|
||||
executeParams: {
|
||||
|
||||
@@ -16,7 +16,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
test("exec falls back when PTY spawn fails", async () => {
|
||||
const tool = createExecTool({ allowBackground: false });
|
||||
const tool = createExecTool({ allowBackground: false, security: "full", ask: "off" });
|
||||
const result = await tool.execute("toolcall", {
|
||||
command: "printf ok",
|
||||
pty: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
test("exec supports pty output", async () => {
|
||||
const tool = createExecTool({ allowBackground: false });
|
||||
const tool = createExecTool({ allowBackground: false, security: "full", ask: "off" });
|
||||
const result = await tool.execute("toolcall", {
|
||||
command: 'node -e "process.stdout.write(String.fromCharCode(111,107))"',
|
||||
pty: true,
|
||||
|
||||
@@ -8,7 +8,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
async function startPtySession(command: string) {
|
||||
const execTool = createExecTool();
|
||||
const execTool = createExecTool({ security: "full", ask: "off" });
|
||||
const processTool = createProcessTool();
|
||||
const result = await execTool.execute("toolcall", {
|
||||
command,
|
||||
|
||||
Reference in New Issue
Block a user