import { afterEach, beforeEach, 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 { _resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { executeBash } from "@oh-my-pi/pi-coding-agent/exec/bash-executor"; function makeTempDir(): string { return fs.mkdtempSync(path.join(os.tmpdir(), "omp-bash-exec-")); } describe("executeBash", () => { let tempDir: string; beforeEach(async () => { tempDir = makeTempDir(); _resetSettingsForTest(); await Settings.init({ inMemory: true, cwd: tempDir }); }); afterEach(() => { _resetSettingsForTest(); if (fs.existsSync(tempDir)) { fs.rmSync(tempDir, { recursive: true }); } }); it("returns non-zero exit codes without cancellation", async () => { const result = await executeBash("exit 7", { cwd: tempDir, timeout: 5000 }); expect(result.exitCode).toBe(7); expect(result.cancelled).toBe(false); }); it("honors cwd", async () => { const result = await executeBash("pwd", { cwd: tempDir, timeout: 5000 }); expect(result.output.trim()).toBe(tempDir); }); it("passes env vars", async () => { const result = await executeBash("echo $OMP_TEST_ENV", { cwd: tempDir, timeout: 5000, env: { OMP_TEST_ENV: "hello" }, }); expect(result.output.trim()).toBe("hello"); }); it("times out commands", async () => { if (process.platform === "win32") { return; } const result = await executeBash("sleep 10", { cwd: tempDir, timeout: 50 }); expect(result.cancelled).toBe(true); expect(result.output).toContain("timed out"); }); it("aborts commands", async () => { if (process.platform === "win32") { return; } const controller = new AbortController(); const promise = executeBash("sleep 10", { cwd: tempDir, timeout: 5000, signal: controller.signal, }); await Bun.sleep(50); controller.abort(); const result = await promise; expect(result.cancelled).toBe(true); expect(result.output).toContain("Command cancelled"); }); it("streams output chunks", async () => { const chunks: string[] = []; const result = await executeBash("i=1; while [ $i -le 20 ]; do echo line$i; i=$((i+1)); done", { cwd: tempDir, timeout: 5000, onChunk: chunk => chunks.push(chunk), }); const combined = chunks.join(""); expect(chunks.length).toBeGreaterThan(0); expect(combined).toContain("line1"); expect(combined).toContain("line20"); expect(result.output).toContain("line1"); expect(result.output).toContain("line20"); }); it("does not allow exec to replace the host", async () => { const result = await executeBash("exec echo hi", { cwd: tempDir, timeout: 5000 }); expect(result.cancelled).toBe(false); expect(result.exitCode).not.toBeUndefined(); if (!result.output.includes("hi")) { expect(result.output.toLowerCase()).toContain("exec"); } }); });