diff --git a/src/agents/shell-utils.e2e.test.ts b/src/agents/shell-utils.e2e.test.ts index 8bf9edc82e9..bcf9bc7d5e9 100644 --- a/src/agents/shell-utils.e2e.test.ts +++ b/src/agents/shell-utils.e2e.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { getShellConfig } from "./shell-utils.js"; +import { getShellConfig, resolveShellFromPath } from "./shell-utils.js"; const isWin = process.platform === "win32"; @@ -79,3 +79,59 @@ describe("getShellConfig", () => { expect(shell).toBe("sh"); }); }); + +describe("resolveShellFromPath", () => { + const originalPath = process.env.PATH; + const tempDirs: string[] = []; + + const createTempBin = (name: string, executable: boolean) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-path-")); + tempDirs.push(dir); + const filePath = path.join(dir, name); + fs.writeFileSync(filePath, ""); + if (executable) { + fs.chmodSync(filePath, 0o755); + } else { + fs.chmodSync(filePath, 0o644); + } + return dir; + }; + + afterEach(() => { + if (originalPath == null) { + delete process.env.PATH; + } else { + process.env.PATH = originalPath; + } + for (const dir of tempDirs.splice(0)) { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + + if (isWin) { + it("returns undefined on Windows for missing PATH entries in this test harness", () => { + process.env.PATH = ""; + expect(resolveShellFromPath("bash")).toBeUndefined(); + }); + return; + } + + it("returns undefined when PATH is empty", () => { + process.env.PATH = ""; + expect(resolveShellFromPath("bash")).toBeUndefined(); + }); + + it("returns the first executable match from PATH", () => { + const notExecutable = createTempBin("bash", false); + const executable = createTempBin("bash", true); + process.env.PATH = [notExecutable, executable].join(path.delimiter); + expect(resolveShellFromPath("bash")).toBe(path.join(executable, "bash")); + }); + + it("returns undefined when command does not exist", () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shell-empty-")); + tempDirs.push(dir); + process.env.PATH = dir; + expect(resolveShellFromPath("bash")).toBeUndefined(); + }); +}); diff --git a/src/commands/channels.add.test.ts b/src/commands/channels.add.test.ts index 7eefa8ac13a..e6d0c101d77 100644 --- a/src/commands/channels.add.test.ts +++ b/src/commands/channels.add.test.ts @@ -1,13 +1,13 @@ import { beforeEach, describe, expect, it } from "vitest"; import { setDefaultChannelPluginRegistryForTests } from "./channel-test-helpers.js"; -import { channelsAddCommand } from "./channels.js"; import { configMocks, offsetMocks } from "./channels.mock-harness.js"; import { baseConfigSnapshot, createTestRuntime } from "./test-runtime-config-helpers.js"; const runtime = createTestRuntime(); +let channelsAddCommand: typeof import("./channels.js").channelsAddCommand; describe("channelsAddCommand", () => { - beforeEach(() => { + beforeEach(async () => { configMocks.readConfigFileSnapshot.mockReset(); configMocks.writeConfigFile.mockClear(); offsetMocks.deleteTelegramUpdateOffset.mockClear(); @@ -15,6 +15,7 @@ describe("channelsAddCommand", () => { runtime.error.mockClear(); runtime.exit.mockClear(); setDefaultChannelPluginRegistryForTests(); + ({ channelsAddCommand } = await import("./channels.js")); }); it("clears telegram update offsets when the token changes", async () => {