diff options
Diffstat (limited to 'packages/core/tests/tools')
| -rw-r--r-- | packages/core/tests/tools/bash-arity.test.ts | 36 | ||||
| -rw-r--r-- | packages/core/tests/tools/run-shell.test.ts | 78 |
2 files changed, 114 insertions, 0 deletions
diff --git a/packages/core/tests/tools/bash-arity.test.ts b/packages/core/tests/tools/bash-arity.test.ts new file mode 100644 index 0000000..a01a6a5 --- /dev/null +++ b/packages/core/tests/tools/bash-arity.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import { prefix } from "../../src/tools/bash-arity.js"; + +describe("BashArity.prefix", () => { + it("returns arity-2 prefix for known command 'git'", () => { + expect(prefix(["git", "checkout", "main"])).toEqual(["git", "checkout"]); + }); + + it("returns arity-3 prefix for npm", () => { + expect(prefix(["npm", "run", "dev"])).toEqual(["npm", "run", "dev"]); + }); + + it("returns arity-2 prefix for bun", () => { + expect(prefix(["bun", "install", "--frozen-lockfile"])).toEqual(["bun", "install"]); + }); + + it("returns just the command for unknown command", () => { + expect(prefix(["unknowncmd", "arg1", "arg2"])).toEqual(["unknowncmd"]); + }); + + it("returns empty array for empty tokens", () => { + expect(prefix([])).toEqual([]); + }); + + it("handles single token for unknown command", () => { + expect(prefix(["ls"])).toEqual(["ls"]); + }); + + it("handles git with fewer tokens than arity", () => { + expect(prefix(["git"])).toEqual(["git"]); + }); + + it("handles case-insensitive matching", () => { + expect(prefix(["GIT", "checkout", "main"])).toEqual(["GIT", "checkout"]); + }); +}); diff --git a/packages/core/tests/tools/run-shell.test.ts b/packages/core/tests/tools/run-shell.test.ts new file mode 100644 index 0000000..cb66d1c --- /dev/null +++ b/packages/core/tests/tools/run-shell.test.ts @@ -0,0 +1,78 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createRunShellTool } from "../../src/tools/run-shell.js"; + +describe("run_shell tool", () => { + let workDir: string; + + beforeEach(async () => { + workDir = await mkdtemp(join(tmpdir(), "dispatch-test-")); + }); + + afterEach(async () => { + await rm(workDir, { recursive: true, force: true }); + }); + + it("executes a simple echo command", async () => { + const tool = createRunShellTool(workDir); + const raw = await tool.execute({ command: "echo hello" }); + const result = JSON.parse(raw); + expect(result.stdout.trim()).toBe("hello"); + expect(result.exitCode).toBe(0); + }); + + it("returns non-zero exit code on failure", async () => { + const tool = createRunShellTool(workDir); + const raw = await tool.execute({ command: "exit 42" }); + const result = JSON.parse(raw); + expect(result.exitCode).toBe(42); + }); + + it("captures stderr", async () => { + const tool = createRunShellTool(workDir); + const raw = await tool.execute({ command: "echo errormsg >&2" }); + const result = JSON.parse(raw); + expect(result.stderr.trim()).toBe("errormsg"); + }); + + it("handles timeout", async () => { + const tool = createRunShellTool(workDir); + const raw = await tool.execute({ command: "sleep 10", timeout: 100 }); + const result = JSON.parse(raw); + // Either times out (non-zero exit) or returns an error + expect(result.exitCode !== 0 || result.error !== undefined).toBe(true); + }, 5000); + + it("executes in the working directory", async () => { + const tool = createRunShellTool(workDir); + const raw = await tool.execute({ command: "pwd" }); + const result = JSON.parse(raw); + // On macOS /tmp is symlinked; use includes check + expect(result.stdout.trim()).toContain(workDir.replace(/^\/private/, "")); + }); + + it("calls onOutput callback with stdout chunks", async () => { + const tool = createRunShellTool(workDir); + const onOutput = vi.fn(); + const raw = await tool.execute({ command: "echo streaming" }, { onOutput }); + const result = JSON.parse(raw); + expect(result.stdout.trim()).toBe("streaming"); + expect(onOutput).toHaveBeenCalledWith(expect.stringContaining("streaming"), "stdout"); + }); + + it("calls onOutput callback with stderr chunks", async () => { + const tool = createRunShellTool(workDir); + const onOutput = vi.fn(); + await tool.execute({ command: "echo errdata >&2" }, { onOutput }); + expect(onOutput).toHaveBeenCalledWith(expect.stringContaining("errdata"), "stderr"); + }); + + it("works without context (backward compatible)", async () => { + const tool = createRunShellTool(workDir); + const raw = await tool.execute({ command: "echo nocontext" }); + const result = JSON.parse(raw); + expect(result.stdout.trim()).toBe("nocontext"); + }); +}); |
