feat: package Pi extensions as Bun workspaces
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
parseScriptAssignmentPayload,
|
||||
runScriptSandbox,
|
||||
ScriptRequestBudget,
|
||||
validateScriptIpcEnvelope,
|
||||
} from "./script-runtime.ts";
|
||||
|
||||
function run(
|
||||
source: string,
|
||||
overrides: Partial<Parameters<typeof runScriptSandbox>[0]> = {},
|
||||
) {
|
||||
const abort = new AbortController();
|
||||
return runScriptSandbox({
|
||||
source,
|
||||
args: undefined,
|
||||
cwd: process.cwd(),
|
||||
signal: abort.signal,
|
||||
onAssign: async (role, prompt) => ({
|
||||
ok: true,
|
||||
output: `${role}:${prompt}`,
|
||||
}),
|
||||
onStage: () => {},
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
test("script sandbox exposes only stage, assign, parallel, and immutable args", async () => {
|
||||
const stages: string[] = [];
|
||||
const result = await run(
|
||||
`
|
||||
stage("Audit");
|
||||
const replies = await parallel([
|
||||
() => assign("review", "one"),
|
||||
() => assign("review", "two"),
|
||||
], { concurrency: 99 });
|
||||
return {
|
||||
replies: replies.map((reply) => reply.output),
|
||||
processType: typeof process,
|
||||
requireType: typeof require,
|
||||
fetchType: typeof fetch,
|
||||
nestedArgsFrozen: Object.isFrozen(args.nested),
|
||||
};
|
||||
`,
|
||||
{
|
||||
args: { nested: { value: "immutable" } },
|
||||
onStage: (title) => stages.push(title),
|
||||
},
|
||||
);
|
||||
assert.deepEqual(result, {
|
||||
replies: ["review:one", "review:two"],
|
||||
processType: "undefined",
|
||||
requireType: "undefined",
|
||||
fetchType: "undefined",
|
||||
nestedArgsFrozen: true,
|
||||
});
|
||||
assert.deepEqual(stages, ["Audit"]);
|
||||
});
|
||||
|
||||
test("script sandbox rejects unawaited assignments and synchronous loops", async () => {
|
||||
let calls = 0;
|
||||
await assert.rejects(
|
||||
run(`assign("review", "orphan"); return "done";`, {
|
||||
onAssign: async () => {
|
||||
calls++;
|
||||
return { ok: true, output: "unexpected" };
|
||||
},
|
||||
}),
|
||||
/unawaited assign/,
|
||||
);
|
||||
assert.equal(calls, 0);
|
||||
await assert.rejects(run(`while (true) {}`), /timed out/);
|
||||
});
|
||||
|
||||
test("script cancellation aborts active assignments and terminates execution", 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 assign("review", "pending");`, {
|
||||
signal: controller.signal,
|
||||
onAssign: async (_role, _prompt, signal) => {
|
||||
startedResolve?.();
|
||||
await new Promise<void>((resolve) => {
|
||||
signal.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
requestAborted = true;
|
||||
resolve();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
return { ok: false, output: "", error: "Assignment was aborted" };
|
||||
},
|
||||
});
|
||||
await started;
|
||||
controller.abort(new Error("cancel fixture"));
|
||||
await assert.rejects(pending, /Script was aborted/);
|
||||
assert.equal(requestAborted, true);
|
||||
});
|
||||
|
||||
test("script IPC rejects unauthenticated, malformed, oversized, duplicate, and over-budget requests", () => {
|
||||
assert.throws(
|
||||
() => validateScriptIpcEnvelope({ token: "wrong", kind: "assign" }, "right"),
|
||||
/invalid IPC message/,
|
||||
);
|
||||
assert.throws(() => parseScriptAssignmentPayload("{"), /malformed assignment JSON/);
|
||||
assert.throws(
|
||||
() => parseScriptAssignmentPayload("x".repeat(512 * 1024 + 1)),
|
||||
/oversized assignment request/,
|
||||
);
|
||||
assert.throws(
|
||||
() => parseScriptAssignmentPayload(JSON.stringify({ id: 1, role: "", prompt: "x" })),
|
||||
/invalid assignment request/,
|
||||
);
|
||||
const duplicate = new ScriptRequestBudget();
|
||||
duplicate.accept(1);
|
||||
assert.throws(() => duplicate.accept(1), /duplicate assignment request/);
|
||||
const budget = new ScriptRequestBudget();
|
||||
for (let id = 1; id <= 32; id++) budget.accept(id);
|
||||
assert.throws(() => budget.accept(33), /request budget/);
|
||||
});
|
||||
|
||||
test("script stage updates and total runtime are bounded", async () => {
|
||||
await assert.rejects(
|
||||
run(`for (let index = 0; index < 65; index++) stage("flood-" + index);`),
|
||||
/stage update budget/,
|
||||
);
|
||||
await assert.rejects(
|
||||
run(`while (true) { await Promise.resolve(); }`, { runtimeTimeoutMs: 20 }),
|
||||
/wall-clock time limit/,
|
||||
);
|
||||
});
|
||||
|
||||
test("script source and args are bounded", async () => {
|
||||
await assert.rejects(run("x".repeat(512 * 1024 + 1)), /source exceeds/);
|
||||
await assert.rejects(
|
||||
run("return null", { args: "x".repeat(256 * 1024 + 1) }),
|
||||
/args exceed/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user