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/list-files.test.ts41
-rw-r--r--packages/core/tests/tools/read-file.test.ts36
-rw-r--r--packages/core/tests/tools/registry.test.ts50
-rw-r--r--packages/core/tests/tools/write-file.test.ts45
4 files changed, 172 insertions, 0 deletions
diff --git a/packages/core/tests/tools/list-files.test.ts b/packages/core/tests/tools/list-files.test.ts
new file mode 100644
index 0000000..ead1df3
--- /dev/null
+++ b/packages/core/tests/tools/list-files.test.ts
@@ -0,0 +1,41 @@
+import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { createListFilesTool } from "../../src/tools/list-files.js";
+
+describe("list_files tool", () => {
+ let workDir: string;
+
+ beforeEach(async () => {
+ workDir = await mkdtemp(join(tmpdir(), "dispatch-test-"));
+ });
+
+ afterEach(async () => {
+ await rm(workDir, { recursive: true, force: true });
+ });
+
+ it("lists directory contents", async () => {
+ const tool = createListFilesTool(workDir);
+ await writeFile(join(workDir, "file1.txt"), "a");
+ await writeFile(join(workDir, "file2.txt"), "b");
+ await mkdir(join(workDir, "subdir"));
+ const result = await tool.execute({ path: "." });
+ expect(result).toContain("file1.txt");
+ expect(result).toContain("file2.txt");
+ expect(result).toContain("subdir/");
+ });
+
+ it("defaults to current directory when path is undefined", async () => {
+ const tool = createListFilesTool(workDir);
+ await writeFile(join(workDir, "hello.txt"), "hi");
+ const result = await tool.execute({});
+ expect(result).toContain("hello.txt");
+ });
+
+ it("blocks path traversal", async () => {
+ const tool = createListFilesTool(workDir);
+ const result = await tool.execute({ path: "../" });
+ expect(result).toMatch(/outside the working directory/i);
+ });
+});
diff --git a/packages/core/tests/tools/read-file.test.ts b/packages/core/tests/tools/read-file.test.ts
new file mode 100644
index 0000000..ce65b37
--- /dev/null
+++ b/packages/core/tests/tools/read-file.test.ts
@@ -0,0 +1,36 @@
+import { mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { createReadFileTool } from "../../src/tools/read-file.js";
+
+describe("read_file tool", () => {
+ let workDir: string;
+
+ beforeEach(async () => {
+ workDir = await mkdtemp(join(tmpdir(), "dispatch-test-"));
+ });
+
+ afterEach(async () => {
+ await rm(workDir, { recursive: true, force: true });
+ });
+
+ it("reads an existing file", async () => {
+ const tool = createReadFileTool(workDir);
+ await writeFile(join(workDir, "hello.txt"), "Hello, world!");
+ const result = await tool.execute({ path: "hello.txt" });
+ expect(result).toBe("Hello, world!");
+ });
+
+ it("returns error for non-existent file", async () => {
+ const tool = createReadFileTool(workDir);
+ const result = await tool.execute({ path: "missing.txt" });
+ expect(result).toMatch(/not found/i);
+ });
+
+ it("blocks path traversal", async () => {
+ const tool = createReadFileTool(workDir);
+ const result = await tool.execute({ path: "../etc/passwd" });
+ expect(result).toMatch(/outside the working directory/i);
+ });
+});
diff --git a/packages/core/tests/tools/registry.test.ts b/packages/core/tests/tools/registry.test.ts
new file mode 100644
index 0000000..b6f1fca
--- /dev/null
+++ b/packages/core/tests/tools/registry.test.ts
@@ -0,0 +1,50 @@
+import { describe, expect, it } from "vitest";
+import { z } from "zod";
+import { createToolRegistry } from "../../src/tools/registry.js";
+import type { ToolDefinition } from "../../src/types/index.js";
+
+const mockTool: ToolDefinition = {
+ name: "mock_tool",
+ description: "A mock tool for testing",
+ parameters: z.object({ input: z.string() }),
+ execute: async (_args) => "mock result",
+};
+
+const anotherTool: ToolDefinition = {
+ name: "another_tool",
+ description: "Another mock tool",
+ parameters: z.object({ value: z.number() }),
+ execute: async (_args) => "another result",
+};
+
+describe("createToolRegistry", () => {
+ it("returns all tools via getTools()", () => {
+ const registry = createToolRegistry([mockTool, anotherTool]);
+ const tools = registry.getTools();
+ expect(tools).toHaveLength(2);
+ expect(tools.map((t) => t.name)).toContain("mock_tool");
+ expect(tools.map((t) => t.name)).toContain("another_tool");
+ });
+
+ it("retrieves specific tool by name", () => {
+ const registry = createToolRegistry([mockTool, anotherTool]);
+ const tool = registry.getTool("mock_tool");
+ expect(tool).toBeDefined();
+ expect(tool?.name).toBe("mock_tool");
+ });
+
+ it("returns undefined for unknown tool", () => {
+ const registry = createToolRegistry([mockTool]);
+ expect(registry.getTool("nonexistent")).toBeUndefined();
+ });
+
+ it("getAISDKTools returns correct format", () => {
+ const registry = createToolRegistry([mockTool, anotherTool]);
+ const aiTools = registry.getAISDKTools();
+ expect(aiTools).toHaveProperty("mock_tool");
+ expect(aiTools).toHaveProperty("another_tool");
+ // Each should have description and parameters (AI SDK tool format)
+ expect(aiTools.mock_tool).toHaveProperty("description");
+ expect(aiTools.mock_tool).toHaveProperty("parameters");
+ });
+});
diff --git a/packages/core/tests/tools/write-file.test.ts b/packages/core/tests/tools/write-file.test.ts
new file mode 100644
index 0000000..01d7253
--- /dev/null
+++ b/packages/core/tests/tools/write-file.test.ts
@@ -0,0 +1,45 @@
+import { mkdtemp, readFile, rm } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { createWriteFileTool } from "../../src/tools/write-file.js";
+
+describe("write_file tool", () => {
+ let workDir: string;
+
+ beforeEach(async () => {
+ workDir = await mkdtemp(join(tmpdir(), "dispatch-test-"));
+ });
+
+ afterEach(async () => {
+ await rm(workDir, { recursive: true, force: true });
+ });
+
+ it("writes a new file", async () => {
+ const tool = createWriteFileTool(workDir);
+ const result = await tool.execute({
+ path: "output.txt",
+ content: "test content",
+ });
+ expect(result).toMatch(/successfully wrote/i);
+ const written = await readFile(join(workDir, "output.txt"), "utf8");
+ expect(written).toBe("test content");
+ });
+
+ it("creates parent directories", async () => {
+ const tool = createWriteFileTool(workDir);
+ const result = await tool.execute({
+ path: "nested/dir/file.txt",
+ content: "nested",
+ });
+ expect(result).toMatch(/successfully wrote/i);
+ const written = await readFile(join(workDir, "nested/dir/file.txt"), "utf8");
+ expect(written).toBe("nested");
+ });
+
+ it("blocks path traversal", async () => {
+ const tool = createWriteFileTool(workDir);
+ const result = await tool.execute({ path: "../evil.txt", content: "bad" });
+ expect(result).toMatch(/outside the working directory/i);
+ });
+});