201 lines · 6.5 KB · TypeScript
RawDownload
import { describe, it, expect } from "vitest";import { env } from "cloudflare:workers";import * as Data from "effect/Data";import * as Effect from "effect/Effect";import type { SandboxToolInvoker } from "@executor/codemode-core";import { ToolDispatcher } from "./executor";import { makeDynamicWorkerExecutor } from "./executor"; // ---------------------------------------------------------------------------// Helpers// --------------------------------------------------------------------------- class TestToolError extends Data.TaggedError("TestToolError")<{  readonly message: string;}> {} const makeInvoker = (  fn: (input: { path: string; args: unknown }) => unknown,): SandboxToolInvoker => ({  invoke: (input) => Effect.try(() => fn(input)),}); const failingInvoker = (message: string): SandboxToolInvoker => ({  invoke: () => Effect.fail(new TestToolError({ message })),}); // ---------------------------------------------------------------------------// ToolDispatcher// --------------------------------------------------------------------------- describe("ToolDispatcher", () => {  it("returns JSON result on successful tool call", async () => {    const invoker = makeInvoker(({ args }) => args);    const dispatcher = new ToolDispatcher(invoker);     const result = await dispatcher.call("test.tool", '{"key":"value"}');    expect(JSON.parse(result)).toEqual({ result: { key: "value" } });  });   it("returns JSON error when tool invocation fails", async () => {    const dispatcher = new ToolDispatcher(failingInvoker("tool broke"));     const result = await dispatcher.call("broken.tool", "{}");    expect(JSON.parse(result)).toEqual({ error: "tool broke" });  });   it("handles undefined args", async () => {    const invoker = makeInvoker(({ args }) => args);    const dispatcher = new ToolDispatcher(invoker);     const result = await dispatcher.call("test.tool", "");    expect(JSON.parse(result)).toEqual({ result: undefined });  });   it("passes the tool path correctly", async () => {    let capturedPath = "";    const invoker = makeInvoker(({ path }) => {      capturedPath = path;      return "ok";    });    const dispatcher = new ToolDispatcher(invoker);     await dispatcher.call("my.deep.tool.path", "{}");    expect(capturedPath).toBe("my.deep.tool.path");  });}); // ---------------------------------------------------------------------------// Full execution via makeDynamicWorkerExecutor// --------------------------------------------------------------------------- describe("makeDynamicWorkerExecutor", () => {  const loader = (env as { LOADER: WorkerLoader }).LOADER;   it("executes simple code and returns result", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = makeInvoker(() => null);     const result = await Effect.runPromise(executor.execute("async () => 42", invoker));     expect(result.error).toBeUndefined();    expect(result.result).toBe(42);  });   it("executes code that returns an object", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = makeInvoker(() => null);     const result = await Effect.runPromise(      executor.execute('async () => ({ hello: "world" })', invoker),    );     expect(result.error).toBeUndefined();    expect(result.result).toEqual({ hello: "world" });  });   it("captures console output in logs", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = makeInvoker(() => null);     const result = await Effect.runPromise(      executor.execute(        'async () => { console.log("hello"); console.warn("careful"); return 1; }',        invoker,      ),    );     expect(result.error).toBeUndefined();    expect(result.logs).toContain("hello");    expect(result.logs).toContain("[warn] careful");  });   it("returns error for throwing code", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = makeInvoker(() => null);     const result = await Effect.runPromise(      executor.execute('async () => { throw new Error("boom"); }', invoker),    );     expect(result.error).toBe("boom");    expect(result.result).toBeNull();  });   it("invokes tools via the proxy and returns results", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = makeInvoker(({ path, args }) => {      if (path === "math.add") {        const { a, b } = args as { a: number; b: number };        return a + b;      }      return null;    });     const result = await Effect.runPromise(      executor.execute(        "async () => { const sum = await tools.math.add({ a: 3, b: 4 }); return sum; }",        invoker,      ),    );     expect(result.error).toBeUndefined();    expect(result.result).toBe(7);  });   it("surfaces tool errors in execution result", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = failingInvoker("not authorized");     const result = await Effect.runPromise(      executor.execute("async () => { return await tools.secret.read({}); }", invoker),    );     expect(result.error).toBe("not authorized");  });   it("handles multiple tool calls in sequence", async () => {    const executor = makeDynamicWorkerExecutor({ loader });    const invoker = makeInvoker(({ path }) => {      if (path === "data.first") return 10;      if (path === "data.second") return 20;      return 0;    });     const result = await Effect.runPromise(      executor.execute(        `async () => {          const a = await tools.data.first({});          const b = await tools.data.second({});          return a + b;        }`,        invoker,      ),    );     expect(result.error).toBeUndefined();    expect(result.result).toBe(30);  });   it("respects timeout", async () => {    const executor = makeDynamicWorkerExecutor({ loader, timeoutMs: 500 });    const invoker = makeInvoker(() => null);     const result = await Effect.runPromise(      executor.execute("async () => { await new Promise(r => setTimeout(r, 5000)); }", invoker),    );     expect(result.error).toContain("timed out");  });   it("blocks fetch when globalOutbound is null", async () => {    const executor = makeDynamicWorkerExecutor({ loader, globalOutbound: null });    const invoker = makeInvoker(() => null);     const result = await Effect.runPromise(      executor.execute('async () => { await fetch("https://example.com"); }', invoker),    );     expect(result.error).toBeDefined();  });});