summaryrefslogtreecommitdiffhomepage
path: root/packages/tool-read-file/src/read-file.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-05 21:20:12 +0900
committerAdam Malczewski <[email protected]>2026-06-05 21:20:12 +0900
commit7fb3269c698ae583ea7997ce206c4ae252fd3218 (patch)
tree247d03408ecccd633290ea56b1b08811ebe460ec /packages/tool-read-file/src/read-file.test.ts
parent4283d1f8a0bc3953e65962a2364c903d0015f047 (diff)
downloaddispatch-7fb3269c698ae583ea7997ce206c4ae252fd3218.tar.gz
dispatch-7fb3269c698ae583ea7997ce206c4ae252fd3218.zip
feat(backend): credential-store + model selection/catalog (GET /models) + per-turn cwd through orchestrator/transport/host-bin
Diffstat (limited to 'packages/tool-read-file/src/read-file.test.ts')
-rw-r--r--packages/tool-read-file/src/read-file.test.ts68
1 files changed, 67 insertions, 1 deletions
diff --git a/packages/tool-read-file/src/read-file.test.ts b/packages/tool-read-file/src/read-file.test.ts
index f995b09..2725a05 100644
--- a/packages/tool-read-file/src/read-file.test.ts
+++ b/packages/tool-read-file/src/read-file.test.ts
@@ -11,7 +11,7 @@ import {
validateArgs,
} from "./read-file.js";
-function stubCtx(): ToolExecuteContext {
+function stubCtx(overrides?: Partial<ToolExecuteContext>): ToolExecuteContext {
return {
toolCallId: "test-call-1",
onOutput: () => {},
@@ -21,6 +21,7 @@ function stubCtx(): ToolExecuteContext {
{ emit: () => {} },
{ now: () => 0, newId: () => "id" },
),
+ ...overrides,
};
}
@@ -250,4 +251,69 @@ describe("createReadFileTool", () => {
expect(tool.parameters.required).toEqual(["path"]);
expect(tool.parameters.properties?.path?.type).toBe("string");
});
+
+ it("reads file under ctx.cwd when set (not baked workdir)", async () => {
+ const ctxDir = await mkdtemp(join(tmpdir(), "ctx-cwd-test-"));
+ try {
+ const filePath = join(ctxDir, "ctx-file.txt");
+ await writeFile(filePath, "from ctx cwd", "utf8");
+
+ const tool = createReadFileTool(workdir); // baked workdir is different
+ const result = await tool.execute({ path: "ctx-file.txt" }, stubCtx({ cwd: ctxDir }));
+
+ expect(result.isError).toBeUndefined();
+ expect(result.content).toContain("1: from ctx cwd");
+ } finally {
+ await rm(ctxDir, { recursive: true, force: true });
+ }
+ });
+
+ it("rejects path escaping ctx.cwd via ..", async () => {
+ const ctxDir = await mkdtemp(join(tmpdir(), "ctx-escape-test-"));
+ try {
+ const tool = createReadFileTool(workdir);
+ const result = await tool.execute({ path: "../escape.txt" }, stubCtx({ cwd: ctxDir }));
+
+ expect(result.isError).toBe(true);
+ expect(result.content).toContain("outside the working directory");
+ } finally {
+ await rm(ctxDir, { recursive: true, force: true });
+ }
+ });
+
+ it("rejects symlink escaping ctx.cwd", async () => {
+ const ctxDir = await mkdtemp(join(tmpdir(), "ctx-symlink-test-"));
+ const outsideDir = await mkdtemp(join(tmpdir(), "ctx-outside-"));
+ try {
+ const outsideFile = join(outsideDir, "secret.txt");
+ await writeFile(outsideFile, "secret data", "utf8");
+
+ const symlinkPath = join(ctxDir, "link.txt");
+ const { symlink } = await import("node:fs/promises");
+ await symlink(outsideFile, symlinkPath);
+
+ const tool = createReadFileTool(workdir);
+ const result = await tool.execute({ path: "link.txt" }, stubCtx({ cwd: ctxDir }));
+
+ expect(result.isError).toBe(true);
+ expect(result.content).toContain("outside the working directory");
+ } finally {
+ await rm(ctxDir, { recursive: true, force: true });
+ await rm(outsideDir, { recursive: true, force: true });
+ }
+ });
+
+ it("falls back to baked workdir when ctx.cwd is omitted", async () => {
+ const filePath = join(workdir, "baked-file.txt");
+ await writeFile(filePath, "from baked workdir", "utf8");
+
+ const tool = createReadFileTool(workdir);
+ const ctx = stubCtx();
+ // Ensure cwd is undefined
+ expect(ctx.cwd).toBeUndefined();
+ const result = await tool.execute({ path: "baked-file.txt" }, ctx);
+
+ expect(result.isError).toBeUndefined();
+ expect(result.content).toContain("1: from baked workdir");
+ });
});