summaryrefslogtreecommitdiffhomepage
path: root/packages/core/tests/tools
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/tests/tools')
-rw-r--r--packages/core/tests/tools/bash-arity.test.ts36
-rw-r--r--packages/core/tests/tools/run-shell.test.ts78
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");
+ });
+});