Files
harness-tools/__archived/extensions/_workflows/sandbox.test.ts
T

136 lines
3.7 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { runWorkflowSandbox } from "./sandbox.ts";
function run(
source: string,
overrides: Partial<Parameters<typeof runWorkflowSandbox>[0]> = {},
) {
const abort = new AbortController();
return runWorkflowSandbox({
source,
args: undefined,
cwd: process.cwd(),
signal: abort.signal,
onAgent: async (prompt) => ({ ok: true, output: `reply:${prompt}` }),
onPhase: () => {},
...overrides,
});
}
test("sandbox exposes only workflow capabilities and validates results", async () => {
const phases: string[] = [];
const result = await run(
`
phase("Gather");
const replies = await parallel([
() => agent("one"),
() => agent("two"),
], { concurrency: 99 });
return {
replies: replies.map((reply) => reply.output),
processType: typeof process,
requireType: typeof require,
fetchType: typeof fetch,
};
`,
{ onPhase: (title) => phases.push(title) },
);
assert.deepEqual(result, {
replies: ["reply:one", "reply:two"],
processType: "undefined",
requireType: "undefined",
fetchType: "undefined",
});
assert.deepEqual(phases, ["Gather"]);
});
test("sandbox result serialization handles cycles and bigint", async () => {
const result = await run(`
const value = { count: 7n };
value.self = value;
return value;
`);
assert.deepEqual(result, { count: "7n", self: "[circular]" });
});
test("sandbox rejects unawaited agent calls", async () => {
let calls = 0;
await assert.rejects(
run(`agent("orphan"); return "done";`, {
onAgent: async () => {
calls++;
return { ok: true, output: "unexpected" };
},
}),
/unawaited agent/,
);
assert.equal(calls, 0);
});
test("sandbox source cannot escape the host accounting wrapper", async () => {
let calls = 0;
await assert.rejects(
run(
`}), agent("orphan"), Promise.resolve("bypass"); (async function () {`,
{
onAgent: async () => {
calls++;
return { ok: true, output: "unexpected" };
},
},
),
/unawaited agent/,
);
assert.equal(calls, 0);
});
test("sandbox VM still rejects non-yielding synchronous code", async () => {
await assert.rejects(run(`while (true) {}`), /timed out/);
});
test("workflow agent invocations have no per-request wall timer", async () => {
let signalAborted = false;
const result = await run(`return (await agent("delayed")).output;`, {
onAgent: async (_prompt, _options, signal) => {
await new Promise((resolve) => setTimeout(resolve, 30));
signalAborted = signal.aborted;
return { ok: true, output: "completed" };
},
});
assert.equal(result, "completed");
assert.equal(signalAborted, false);
});
test("workflow cancellation aborts a pending agent request", async () => {
const controller = new AbortController();
let startedResolve: (() => void) | undefined;
const started = new Promise<void>((resolve) => {
startedResolve = resolve;
});
let requestAborted = false;
const pending = run(`return await agent("pending");`, {
signal: controller.signal,
onAgent: async (_prompt, _options, signal) => {
startedResolve?.();
await new Promise<void>((resolve) => {
signal.addEventListener(
"abort",
() => {
requestAborted = true;
resolve();
},
{ once: true },
);
});
return { ok: false, output: "", error: "Agent was aborted" };
},
});
await started;
controller.abort(new Error("cancel fixture"));
await assert.rejects(pending, /Workflow was aborted/);
assert.equal(requestAborted, true);
});