Skip to content
Write an extension

Test it

Test an extension with node:test and the in-memory host.

An extension’s tests are ordinary tests. memoryHost runs the real Agent over an in-memory database, and script stands in for the model, so they need no provider account and no network. This file uses Node’s built-in test runner.

bookmarks/checks.ts
import assert from "node:assert/strict";
import { test } from "node:test";
import { memoryHost } from "@fungi.computer/shiitake/testing";
import client from "./client.js";
import { bookmarks } from "./extension.js";
import { readOnlySave } from "./grants.js";
import { bookmarkRoundTrip } from "./run.js";
await test("the model and a person both save links the client can draw", async () => {
const rows = await bookmarkRoundTrip();
assert.deepEqual(
rows.map((row) => row.title),
["Fungi", "Shiitake docs"],
);
});
await test("a host without the commands grant refuses saves", async () => {
await assert.rejects(readOnlySave(), /Extension is unavailable/);
});
await test("each Session keeps its own bookmarks", async () => {
const host = await memoryHost({ extensions: [bookmarks] });
try {
for (const id of ["first", "second"]) {
const session = host.agent.session(id);
await session.send("Start.", { requestId: id });
await host.settle(id);
await session.extension(bookmarks.manifest.id).call("add", {
url: "https://fungi.computer/",
title: id,
});
}
for (const id of ["first", "second"]) {
const slot = (await host.agent.session(id).read()).extensions.bookmarks;
const rows = client.contribute?.(slot).panel ?? [];
assert.deepEqual(
rows.map((row) => row.title),
[id],
);
}
} finally {
await host.close();
}
});
await test("the client draws nothing from a broken projection", () => {
const contribution = client.contribute?.({
status: "running",
revision: "sha256:fixture",
value: [{ url: 42 }],
});
assert.deepEqual(contribution?.panel, []);
});

Each test checks one promise the extension makes:

  • The model and a person can both save links, and the client draws them.
  • A host that withholds commands refuses saves.
  • Each Session keeps its own bookmarks.
  • A broken projection renders no rows rather than invented ones.
TERMINAL
node --import tsx --test bookmarks/checks.ts

The summary at the end of its output reads:

TEXT
ℹ tests 4
ℹ pass 4
ℹ fail 0

The Shiitake package runs this same file in its own test suite.

The test host is not a sandbox: scripted tool calls run the admitted operation directly in your process, and no model-written code is evaluated. That makes it right for tests and wrong for production.

Next: Ship it.