Test a plug
Run a plug in isolation with the testing harness from @blaiseagent/runner/testing.
The harness runs one plug with the same rules as the real chain: next() exactly once, ctx.agent
frozen after it, onLoad before the other handlers, and persisted state validated and migrated
first. It uses an in-memory host and an in-memory state store.
import { z } from "zod";
import { definePlug } from "@blaiseagent/runner";
export default definePlug({
name: "acme/env-lines",
inputs: z.object({ file: z.string() }),
onSession: async (ctx, next) => {
const text = await ctx.host.fs.promises.readFile(ctx.inputs.file, "utf8");
ctx.setOutput("lines", String(text.split("\n").length));
await next();
},
});Test it with the harness:
import { describe, expect, test } from "vitest";
import { createPlugHarness, memoryHost } from "@blaiseagent/runner/testing";
import envLines from "./env-lines";
describe("env-lines", () => {
test("publishes the number of lines", async () => {
const host = memoryHost({ files: { "/workspace/.env": "A=1\nB=2" } });
const harness = createPlugHarness(envLines, {
inputs: { file: "/workspace/.env" },
host,
});
await harness.session();
expect(harness.outputs).toStrictEqual({ lines: "2" });
});
});A harness is one run of the plug. To test several runs, such as a start followed by a resume,
create a harness for each, and share the host and the store between them.
Test the pause and the teardown
session() takes the PauseInfo that next() should resolve with, and teardown() the
TeardownInfo. Pass inner to stand in for the inner plugs and the agent: if it throws, next()
resolves with reason: "failed", as it does in a real run.
await harness.session({ pause: { reason: "closed", mayResume: false } });
await harness.teardown({ info: { reason: "expired" } });Test message handling
message() runs onMessage for one message. forwarded lists what the plug passed on, and reply
provides the result of the rest of the chain.
const { result, forwarded } = await harness.message(message, {
reply: () => ({ stopReason: "end_turn" }),
});Test persisted state
Pass an id and, to start from stored state, a store from memoryStateStore:
const store = memoryStateStore([
{ id: "branch", plug: "acme/db-branch", value: { branch: "main" } },
]);
const harness = createPlugHarness(dbBranch, {
id: "branch",
store,
inputs: { project: "p" },
});
await harness.session(); // onMigrate runs if the stored value no longer validates