branch:
workers.test.ts
25973 bytesRaw
import { env } from "cloudflare:workers";
import { describe, expect, it } from "vitest";
import { createMemoryStateBackend } from "../memory";
import { createWorkspaceStateBackend } from "../workspace";
import { DynamicWorkerExecutor, resolveProvider } from "@cloudflare/codemode";
import { stateToolsFromBackend } from "../workers";

/** Resolve a state backend into executor-ready providers. */
function stateProviders(backend: ReturnType<typeof createMemoryStateBackend>) {
  return [resolveProvider(stateToolsFromBackend(backend))];
}

describe("stateTools", () => {
  it("executes code against the injected state runtime", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/src/app.ts": 'export const value = "foo";\n'
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const file = await state.readFile("/src/app.ts");
        await state.writeFile("/src/app.ts", file.replace("foo", "bar"));
        return await state.readFile("/src/app.ts");
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toBe('export const value = "bar";\n');
  });

  it("supports concurrent state calls and captures logs", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/a.txt": "A",
        "/b.txt": "B"
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const values = await Promise.all([
          state.readFile("/a.txt"),
          state.readFile("/b.txt")
        ]);
        console.log(values.join(","));
        return values;
      }`,
      stateProviders(backend)
    );

    expect(result.result).toEqual(["A", "B"]);
    expect(result.logs).toContain("A,B");
  });

  it("supports JSON helpers inside the sandbox", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/config.json": '{ "enabled": true, "count": 1 }\n'
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const config = await state.readJson("/config.json");
        config.count += 1;
        await state.writeJson("/config.json", config);
        return await state.readJson("/config.json");
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      enabled: true,
      count: 2
    });
    await expect(backend.readFile("/config.json")).resolves.toBe(
      '{\n  "enabled": true,\n  "count": 2\n}\n'
    );
  });

  it("supports search and replace helpers inside the sandbox", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/notes.txt": "alpha beta alpha\n"
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const matches = await state.searchText("/notes.txt", "alpha");
        const replacement = await state.replaceInFile(
          "/notes.txt",
          "alpha",
          "omega"
        );
        return { matches, replacement, next: await state.readFile("/notes.txt") };
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      matches: [
        {
          line: 1,
          column: 1,
          match: "alpha",
          lineText: "alpha beta alpha"
        },
        {
          line: 1,
          column: 12,
          match: "alpha",
          lineText: "alpha beta alpha"
        }
      ],
      replacement: {
        replaced: 2,
        content: "omega beta omega\n"
      },
      next: "omega beta omega\n"
    });
  });

  it("supports batch search, replace, and apply helpers inside the sandbox", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/src/a.ts": 'export const a = "foo";\n',
        "/src/b.ts": 'export const b = "foo";\n',
        "/src/c.ts": 'export const c = "nope";\n'
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const found = await state.searchFiles("/src/*.ts", "foo");
        const preview = await state.replaceInFiles(
          "/src/*.ts",
          "foo",
          "bar",
          { dryRun: true }
        );
        const applied = await state.applyEdits(
          [
            { path: "/src/a.ts", content: 'export const a = "baz";\\n' },
            { path: "/src/d.ts", content: 'export const d = "new";\\n' }
          ],
          { dryRun: true }
        );
        return { found, preview, applied };
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      found: [
        {
          path: "/src/a.ts",
          matches: [
            {
              line: 1,
              column: 19,
              match: "foo",
              lineText: 'export const a = "foo";'
            }
          ]
        },
        {
          path: "/src/b.ts",
          matches: [
            {
              line: 1,
              column: 19,
              match: "foo",
              lineText: 'export const b = "foo";'
            }
          ]
        }
      ],
      preview: {
        dryRun: true,
        files: [
          {
            path: "/src/a.ts",
            replaced: 1,
            content: 'export const a = "bar";\n',
            diff: expect.stringContaining("--- /src/a.ts")
          },
          {
            path: "/src/b.ts",
            replaced: 1,
            content: 'export const b = "bar";\n',
            diff: expect.stringContaining("--- /src/b.ts")
          }
        ],
        totalFiles: 2,
        totalReplacements: 2
      },
      applied: {
        dryRun: true,
        edits: [
          {
            path: "/src/a.ts",
            changed: true,
            content: 'export const a = "baz";\n',
            diff: expect.stringContaining("--- /src/a.ts")
          },
          {
            path: "/src/d.ts",
            changed: true,
            content: 'export const d = "new";\n',
            diff: expect.stringContaining("--- /src/d.ts")
          }
        ],
        totalChanged: 2
      }
    });

    await expect(backend.readFile("/src/a.ts")).resolves.toBe(
      'export const a = "foo";\n'
    );
    await expect(backend.exists("/src/d.ts")).resolves.toBe(false);
  });

  it("supports planning structured edits inside the sandbox", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/src/a.ts": 'export const a = "foo";\n',
        "/src/data.json": '{ "count": 1 }\n'
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const plan = await state.planEdits([
          {
            kind: "replace",
            path: "/src/a.ts",
            search: "foo",
            replacement: "bar"
          },
          {
            kind: "writeJson",
            path: "/src/data.json",
            value: { count: 2 }
          },
          {
            kind: "write",
            path: "/src/new.ts",
            content: 'export const created = true;\\n'
          }
        ]);

        const preview = await state.applyEditPlan(plan, { dryRun: true });
        const applied = await state.applyEditPlan(plan);
        return { plan, preview, applied, next: await state.readFile("/src/new.ts") };
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      plan: {
        edits: [
          {
            instruction: {
              kind: "replace",
              path: "/src/a.ts",
              search: "foo",
              replacement: "bar"
            },
            path: "/src/a.ts",
            changed: true,
            content: 'export const a = "bar";\n',
            diff: expect.stringContaining("--- /src/a.ts")
          },
          {
            instruction: {
              kind: "writeJson",
              path: "/src/data.json",
              value: { count: 2 }
            },
            path: "/src/data.json",
            changed: true,
            content: '{\n  "count": 2\n}\n',
            diff: expect.stringContaining("--- /src/data.json")
          },
          {
            instruction: {
              kind: "write",
              path: "/src/new.ts",
              content: "export const created = true;\n"
            },
            path: "/src/new.ts",
            changed: true,
            content: "export const created = true;\n",
            diff: expect.stringContaining("--- /src/new.ts")
          }
        ],
        totalChanged: 3,
        totalInstructions: 3
      },
      preview: {
        dryRun: true,
        edits: [
          {
            path: "/src/a.ts",
            changed: true,
            content: 'export const a = "bar";\n',
            diff: expect.stringContaining("--- /src/a.ts")
          },
          {
            path: "/src/data.json",
            changed: true,
            content: '{\n  "count": 2\n}\n',
            diff: expect.stringContaining("--- /src/data.json")
          },
          {
            path: "/src/new.ts",
            changed: true,
            content: "export const created = true;\n",
            diff: expect.stringContaining("--- /src/new.ts")
          }
        ],
        totalChanged: 3
      },
      applied: {
        dryRun: false,
        edits: [
          {
            path: "/src/a.ts",
            changed: true,
            content: 'export const a = "bar";\n',
            diff: expect.stringContaining("--- /src/a.ts")
          },
          {
            path: "/src/data.json",
            changed: true,
            content: '{\n  "count": 2\n}\n',
            diff: expect.stringContaining("--- /src/data.json")
          },
          {
            path: "/src/new.ts",
            changed: true,
            content: "export const created = true;\n",
            diff: expect.stringContaining("--- /src/new.ts")
          }
        ],
        totalChanged: 3
      },
      next: "export const created = true;\n"
    });
  });

  it("supports find, json query/update, archive, tree, hash, and file detection inside the sandbox", async () => {
    const backend = createMemoryStateBackend({
      files: {
        "/src/a.ts": 'export const a = "foo";\n',
        "/src/config.json": '{ "enabled": true }\n',
        "/src/docs/readme.txt": "hello"
      }
    });
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const found = await state.find("/src", {
          type: "file",
          pathPattern: "/src/**/*.json"
        });
        const before = await state.queryJson("/src/config.json", ".enabled");
        await state.updateJson("/src/config.json", [
          { op: "set", path: ".enabled", value: false }
        ]);
        await state.createArchive("/bundle.tar", ["/src"]);
        const archive = await state.listArchive("/bundle.tar");
        const summary = await state.summarizeTree("/src");
        const hash = await state.hashFile("/src/docs/readme.txt");
        const detected = await state.detectFile("/src/docs/readme.txt");
        return { found, before, archive, summary, hash, detected };
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      found: [
        {
          path: "/src/config.json",
          name: "config.json",
          type: "file",
          depth: 1,
          size: 20,
          mtime: expect.any(String)
        }
      ],
      before: true,
      archive: [
        { path: "src", type: "directory", size: 0 },
        { path: "src/a.ts", type: "file", size: 24 },
        { path: "src/config.json", type: "file", size: 23 },
        { path: "src/docs", type: "directory", size: 0 },
        { path: "src/docs/readme.txt", type: "file", size: 5 }
      ],
      summary: {
        files: 3,
        directories: 2,
        symlinks: 0,
        totalBytes: 52,
        maxDepth: 2
      },
      hash: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
      detected: {
        mime: "text/plain",
        extension: "txt",
        binary: false,
        description: "text/plain (txt)"
      }
    });
    await expect(backend.readJson("/src/config.json")).resolves.toEqual({
      enabled: false
    });
  });

  it("blocks external fetch by default", async () => {
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });
    const backend = createMemoryStateBackend();

    const result = await executor.execute(
      'async () => fetch("https://example.com").then((r) => r.status)',
      stateProviders(backend)
    );

    expect(result.error).toBeDefined();
  });

  it("supports custom modules inside the sandbox", async () => {
    const executor = new DynamicWorkerExecutor({
      loader: env.LOADER,
      modules: {
        "helpers.js": 'export function suffix(value) { return value + "-ok"; }'
      }
    });
    const backend = createMemoryStateBackend({
      files: {
        "/data.txt": "value"
      }
    });

    const result = await executor.execute(
      `async () => {
        const { suffix } = await import("helpers.js");
        return suffix(await state.readFile("/data.txt"));
      }`,
      stateProviders(backend)
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toBe("value-ok");
  });

  it("routes coarse operations through the workspace adapter", async () => {
    const files = new Map<string, string>([
      ["/workspace/a.ts", "const answer = 1;\n"],
      ["/workspace/b.ts", "const other = 2;\n"]
    ]);
    let globCalls = 0;

    const workspaceLike = {
      async readFile(path: string) {
        return files.get(path) ?? null;
      },
      async readFileBytes(path: string) {
        const v = files.get(path);
        return v === undefined ? null : new TextEncoder().encode(v);
      },
      async writeFile(path: string, content: string) {
        files.set(path, content);
      },
      async writeFileBytes(path: string, content: Uint8Array) {
        files.set(path, new TextDecoder().decode(content));
      },
      async appendFile(path: string, content: string) {
        files.set(path, (files.get(path) ?? "") + content);
      },
      exists(path: string) {
        return files.has(path);
      },
      stat(_path: string) {
        return null;
      },
      lstat(_path: string) {
        return null;
      },
      mkdir(_path: string) {},
      readDir(_path: string) {
        return [];
      },
      async rm(_path: string) {},
      async cp(_src: string, _dest: string) {},
      async mv(_src: string, _dest: string) {},
      symlink(_target: string, _linkPath: string) {},
      readlink(_path: string) {
        return "";
      },
      glob(pattern: string) {
        globCalls++;
        if (pattern === "/workspace/*.ts")
          return [fileInfo("/workspace/a.ts"), fileInfo("/workspace/b.ts")];
        return [];
      },
      async diff(_pathA: string, _pathB: string) {
        return "";
      }
    };

    const backend = createWorkspaceStateBackend(workspaceLike as never);
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const matches = await state.glob("/workspace/*.ts");
        const diff = await state.diffContent("/workspace/a.ts", "const answer = 2;\\n");
        return { matches, diff };
      }`,
      [resolveProvider(stateToolsFromBackend(backend))]
    );

    expect(result.error).toBeUndefined();
    expect((result.result as { matches: string[] }).matches).toEqual([
      "/workspace/a.ts",
      "/workspace/b.ts"
    ]);
    expect((result.result as { diff: string }).diff).toEqual(
      expect.stringContaining("-const answer = 1;")
    );
    expect((result.result as { diff: string }).diff).toEqual(
      expect.stringContaining("+const answer = 2;")
    );
    expect(globCalls).toBe(1);
  });

  it("runs JSON and replace helpers through the workspace adapter", async () => {
    const files = new Map<string, string>([
      ["/workspace/config.json", '{ "name": "demo", "feature": "alpha" }\n']
    ]);
    let writeCalls = 0;

    const workspaceLike = {
      async readFile(path: string) {
        return files.get(path) ?? null;
      },
      async readFileBytes(path: string) {
        const v = files.get(path);
        return v === undefined ? null : new TextEncoder().encode(v);
      },
      async writeFile(path: string, content: string) {
        writeCalls++;
        files.set(path, content);
      },
      async writeFileBytes(path: string, content: Uint8Array) {
        writeCalls++;
        files.set(path, new TextDecoder().decode(content));
      },
      async appendFile(path: string, content: string) {
        files.set(path, (files.get(path) ?? "") + content);
      },
      exists(path: string) {
        return files.has(path);
      },
      stat(_path: string) {
        return null;
      },
      lstat(_path: string) {
        return null;
      },
      mkdir(_path: string) {},
      readDir(_path: string) {
        return [];
      },
      async rm(_path: string) {},
      async cp(_src: string, _dest: string) {},
      async mv(_src: string, _dest: string) {},
      symlink(_target: string, _linkPath: string) {},
      readlink(_path: string) {
        return "";
      },
      glob(_pattern: string) {
        return [];
      },
      async diff(_pathA: string, _pathB: string) {
        return "";
      },
      async diffContent(_path: string, _newContent: string) {
        return "";
      }
    };

    const backend = createWorkspaceStateBackend(workspaceLike as never);
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const config = await state.readJson("/workspace/config.json");
        await state.replaceInFile("/workspace/config.json", "alpha", "beta");
        const next = await state.readJson("/workspace/config.json");
        return { config, next };
      }`,
      [resolveProvider(stateToolsFromBackend(backend))]
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      config: { name: "demo", feature: "alpha" },
      next: { name: "demo", feature: "beta" }
    });
    expect(writeCalls).toBe(1);
  });

  it("runs batch helpers through the workspace adapter", async () => {
    const files = new Map<string, string>([
      ["/workspace/a.ts", 'export const a = "foo";\n'],
      ["/workspace/b.ts", 'export const b = "foo";\n']
    ]);
    let writeCalls = 0;

    const workspaceLike = {
      async readFile(path: string) {
        return files.get(path) ?? null;
      },
      async readFileBytes(path: string) {
        const v = files.get(path);
        return v === undefined ? null : new TextEncoder().encode(v);
      },
      async writeFile(path: string, content: string) {
        writeCalls++;
        files.set(path, content);
      },
      async writeFileBytes(path: string, content: Uint8Array) {
        writeCalls++;
        files.set(path, new TextDecoder().decode(content));
      },
      async appendFile(path: string, content: string) {
        files.set(path, (files.get(path) ?? "") + content);
      },
      exists(path: string) {
        return files.has(path);
      },
      stat(_path: string) {
        return null;
      },
      lstat(path: string) {
        return files.has(path)
          ? {
              path,
              name: path.slice(path.lastIndexOf("/") + 1),
              type: "file" as const,
              mimeType: "text/plain",
              size: files.get(path)?.length ?? 0,
              createdAt: 0,
              updatedAt: 0
            }
          : null;
      },
      mkdir(_path: string) {},
      readDir(_path: string) {
        return [];
      },
      async rm(_path: string) {},
      async cp(_src: string, _dest: string) {},
      async mv(_src: string, _dest: string) {},
      symlink(_target: string, _linkPath: string) {},
      readlink(_path: string) {
        return "";
      },
      glob(pattern: string) {
        if (pattern === "/workspace/*.ts")
          return [fileInfo("/workspace/a.ts"), fileInfo("/workspace/b.ts")];
        return [];
      },
      async diff(_pathA: string, _pathB: string) {
        return "";
      },
      async diffContent(path: string, newContent: string) {
        const current = files.get(path) ?? "";
        return `--- ${path}\n+++ ${path}\n-${current.trim()}\n+${newContent.trim()}`;
      }
    };

    const backend = createWorkspaceStateBackend(workspaceLike as never);
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () => {
        const preview = await state.replaceInFiles(
          "/workspace/*.ts",
          "foo",
          "bar",
          { dryRun: true }
        );
        const applied = await state.applyEdits(
          [{ path: "/workspace/c.ts", content: 'export const c = "new";\\n' }],
          { dryRun: true }
        );
        return { preview, applied };
      }`,
      [resolveProvider(stateToolsFromBackend(backend))]
    );

    expect(result.error).toBeUndefined();
    expect(result.result).toEqual({
      preview: {
        dryRun: true,
        files: [
          {
            path: "/workspace/a.ts",
            replaced: 1,
            content: 'export const a = "bar";\n',
            diff: expect.stringContaining("--- /workspace/a.ts")
          },
          {
            path: "/workspace/b.ts",
            replaced: 1,
            content: 'export const b = "bar";\n',
            diff: expect.stringContaining("--- /workspace/b.ts")
          }
        ],
        totalFiles: 2,
        totalReplacements: 2
      },
      applied: {
        dryRun: true,
        edits: [
          {
            path: "/workspace/c.ts",
            changed: true,
            content: 'export const c = "new";\n',
            diff: expect.stringContaining("--- /workspace/c.ts")
          }
        ],
        totalChanged: 1
      }
    });
    expect(writeCalls).toBe(0);
  });

  it("rolls back failed batch writes through the isolate bridge", async () => {
    const files = new Map<string, string>([
      ["/workspace/a.ts", 'export const a = "foo";\n'],
      ["/workspace/b.ts", 'export const b = "foo";\n']
    ]);

    const workspaceLike = {
      async readFile(path: string) {
        return files.get(path) ?? null;
      },
      async readFileBytes(path: string) {
        const v = files.get(path);
        return v === undefined ? null : new TextEncoder().encode(v);
      },
      async writeFile(path: string, content: string) {
        if (path === "/workspace/b.ts")
          throw new Error(`simulated write failure: ${path}`);
        files.set(path, content);
      },
      async writeFileBytes(path: string, content: Uint8Array) {
        if (path === "/workspace/b.ts")
          throw new Error(`simulated write failure: ${path}`);
        files.set(path, new TextDecoder().decode(content));
      },
      async appendFile(path: string, content: string) {
        files.set(path, (files.get(path) ?? "") + content);
      },
      exists(path: string) {
        return files.has(path);
      },
      stat(_path: string) {
        return null;
      },
      lstat(path: string) {
        return files.has(path)
          ? {
              path,
              name: path.slice(path.lastIndexOf("/") + 1),
              type: "file" as const,
              mimeType: "text/plain",
              size: files.get(path)?.length ?? 0,
              createdAt: 0,
              updatedAt: 0
            }
          : null;
      },
      mkdir(_path: string) {},
      readDir(_path: string) {
        return [];
      },
      async rm(_path: string) {},
      async deleteFile(path: string) {
        return files.delete(path);
      },
      async cp(_src: string, _dest: string) {},
      async mv(_src: string, _dest: string) {},
      symlink(_target: string, _linkPath: string) {},
      readlink(_path: string) {
        return "";
      },
      glob(pattern: string) {
        if (pattern === "/workspace/*.ts")
          return [fileInfo("/workspace/a.ts"), fileInfo("/workspace/b.ts")];
        return [];
      },
      async diff(_pathA: string, _pathB: string) {
        return "";
      },
      async diffContent(path: string, newContent: string) {
        const current = files.get(path) ?? "";
        return `--- ${path}\n+++ ${path}\n-${current.trim()}\n+${newContent.trim()}`;
      }
    };

    const backend = createWorkspaceStateBackend(workspaceLike as never);
    const executor = new DynamicWorkerExecutor({ loader: env.LOADER });

    const result = await executor.execute(
      `async () =>
        state.replaceInFiles("/workspace/*.ts", "foo", "bar")`,
      [resolveProvider(stateToolsFromBackend(backend))]
    );

    expect(result.error).toContain("simulated write failure");
    expect(files.get("/workspace/a.ts")).toBe('export const a = "foo";\n');
    expect(files.get("/workspace/b.ts")).toBe('export const b = "foo";\n');
  });
});

function fileInfo(path: string) {
  const name = path.slice(path.lastIndexOf("/") + 1);
  return {
    path,
    name,
    type: "file" as const,
    mimeType: "text/plain",
    size: 0,
    createdAt: 0,
    updatedAt: 0
  };
}