summaryrefslogtreecommitdiffhomepage
path: root/packages/core/tests/tools/write-file.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/tests/tools/write-file.test.ts')
-rw-r--r--packages/core/tests/tools/write-file.test.ts152
1 files changed, 0 insertions, 152 deletions
diff --git a/packages/core/tests/tools/write-file.test.ts b/packages/core/tests/tools/write-file.test.ts
deleted file mode 100644
index 0dedbfc..0000000
--- a/packages/core/tests/tools/write-file.test.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import { access, mkdtemp, readdir, readFile, rm, symlink } 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);
- });
-
- // Regression for `resolve(join(workingDirectory, filePath))` — when filePath
- // is absolute, `join` does NOT short-circuit, it concatenates. The old code
- // silently rewrote `/etc/foo` to `<workdir>/etc/foo` and "succeeded" by
- // writing to the wrong location. After the fix, absolute paths resolve
- // to themselves and the workdir gate behaves correctly.
- describe("absolute path handling", () => {
- it("writes an absolute path that lives under the workdir to the expected location", async () => {
- const tool = createWriteFileTool(workDir);
- const absoluteTarget = join(workDir, "abs.txt");
- const result = await tool.execute({ path: absoluteTarget, content: "abs content" });
- expect(result).toMatch(/successfully wrote/i);
- // File must exist at exactly `absoluteTarget`, NOT at
- // `<workdir>/<workdir>/abs.txt` (the old mangled location).
- const written = await readFile(absoluteTarget, "utf8");
- expect(written).toBe("abs content");
- });
-
- it("rejects absolute paths outside the workdir instead of silently mangling them", async () => {
- const tool = createWriteFileTool(workDir);
- // Pick a path under tmpdir that's definitely not under workDir.
- // Under the bug, this got rewritten to `<workdir>/tmp/...` and the
- // write "succeeded" at the wrong location.
- const evilPath = join(tmpdir(), `dispatch-evil-${Date.now()}.txt`);
- const result = await tool.execute({ path: evilPath, content: "should not land" });
- expect(result).toMatch(/outside the working directory/i);
- });
- });
-
- // Symlink containment: even when the *leaf* doesn't exist yet (the
- // common case for write_file creating a new file), `canonicalize`
- // must walk up to the nearest existing ancestor and resolve symlinks
- // there. Otherwise, a directory symlink inside workdir pointing
- // outside lets a write escape the workspace.
- describe("symlink handling", () => {
- let externalDir: string;
-
- beforeEach(async () => {
- externalDir = await mkdtemp(join(tmpdir(), "dispatch-external-"));
- });
-
- afterEach(async () => {
- await rm(externalDir, { recursive: true, force: true });
- });
-
- it("blocks writes that escape through a parent symlink (leaf does not exist yet)", async () => {
- const tool = createWriteFileTool(workDir);
- // `escape` is a symlink *inside* workdir to a directory *outside*.
- await symlink(externalDir, join(workDir, "escape"));
- const result = await tool.execute({
- path: "escape/payload.txt",
- content: "malicious payload",
- });
- expect(result).toMatch(/outside the working directory/i);
- // And the file must NOT exist in externalDir.
- await expect(access(join(externalDir, "payload.txt"))).rejects.toThrow();
- // And externalDir should be empty (nothing leaked through).
- const entries = await readdir(externalDir);
- expect(entries).toEqual([]);
- });
- });
-
- describe("onAfterWrite hook", () => {
- it("appends the hook's returned string to a successful write", async () => {
- const tool = createWriteFileTool(workDir, async (abs) => `DIAGNOSTICS for ${abs}`);
- const result = await tool.execute({ path: "a.luau", content: "local x = 1" });
- expect(result).toMatch(/successfully wrote/i);
- expect(result).toContain("DIAGNOSTICS for");
- expect(result).toContain(join(workDir, "a.luau"));
- });
-
- it("does not append when the hook returns empty string", async () => {
- const tool = createWriteFileTool(workDir, async () => "");
- const result = await tool.execute({ path: "a.luau", content: "local x = 1" });
- expect(result.trim()).toMatch(/^Successfully wrote to "a\.luau"\.$/);
- });
-
- it("does not run the hook when the write is blocked (traversal)", async () => {
- let called = false;
- const tool = createWriteFileTool(workDir, async () => {
- called = true;
- return "should not appear";
- });
- const result = await tool.execute({ path: "../evil.txt", content: "bad" });
- expect(result).toMatch(/outside the working directory/i);
- expect(called).toBe(false);
- });
-
- it("swallows hook errors so a throwing hook never fails the write", async () => {
- const tool = createWriteFileTool(workDir, async () => {
- throw new Error("lsp blew up");
- });
- const result = await tool.execute({ path: "a.luau", content: "local x = 1" });
- expect(result).toMatch(/successfully wrote/i);
- expect(result).not.toContain("lsp blew up");
- });
-
- it("passes the canonical absolute path to the hook", async () => {
- let seen = "";
- const tool = createWriteFileTool(workDir, async (abs) => {
- seen = abs;
- return "";
- });
- await tool.execute({ path: "nested/b.luau", content: "x" });
- expect(seen).toBe(join(workDir, "nested/b.luau"));
- });
- });
-});