plugin-inspector/test/synthetic-probes.test.js
Vincent Koc 06cc55ce51
fix(report): accept min host version floors (#17)
* fix(report): accept min host version floors

* fix(inspector): treat bundled channel entries as channel coverage

* fix(inspector): classify bundled channel probes

* fix(policy): allow wildcard seam rules
2026-05-02 18:20:13 -07:00

333 lines
11 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtemp, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "node:test";
import {
buildSyntheticProbePlan,
captureEntrypoint,
renderSyntheticProbeMarkdown,
runCapturedSyntheticProbes,
validateSyntheticProbePlan,
} from "../src/advanced.js";
import { buildSyntheticProbePlanFromReport } from "../src/synthetic-probe-suite.js";
test("synthetic probe plan maps capture inventory to executable probes", () => {
const plan = buildSyntheticProbePlan({
capture: {
generatedAt: "test",
summary: { fixtureCount: 1 },
fixtures: [
{
id: "fixture",
hooks: [
{
id: "hook.before_tool_call:fixture:index",
hook: "before_tool_call",
ref: "src/index.js",
assertions: ["synthetic hook payload is accepted"],
syntheticEvent: { toolName: "fixture_tool" },
},
],
registrations: [
{
id: "registration.registerTool:fixture:index",
registrar: "registerTool",
ref: "src/index.js",
assertions: ["tool schema is captured"],
syntheticArguments: [{ name: "fixture_tool" }],
},
],
},
],
},
});
assert.deepEqual(validateSyntheticProbePlan(plan), []);
assert.equal(plan.summary.probeCount, 2);
assert.equal(plan.summary.readyCount, 2);
assert.match(renderSyntheticProbeMarkdown(plan), /registerTool/);
});
test("synthetic probe plan can be built from a compatibility report", () => {
const plan = buildSyntheticProbePlanFromReport({
generatedAt: "test",
targetOpenClaw: {
capturedRegistrars: ["registerTool"],
sdkExports: [],
},
summary: {},
fixtures: [
{
id: "fixture",
priority: "high",
hookDetails: [{ name: "before_tool_call", ref: "src/index.js:1" }],
registrationDetails: [{ name: "registerTool", ref: "src/index.js:2" }],
sdkImportDetails: [],
packages: [],
},
],
contractProbes: [],
});
assert.equal(plan.generatedAt, "test");
assert.equal(plan.summary.probeCount, 2);
assert.equal(plan.summary.readyCount, 2);
assert.deepEqual(validateSyntheticProbePlan(plan), []);
});
test("synthetic probe plan blocks unclassified registrars", () => {
const plan = buildSyntheticProbePlan({
capture: {
generatedAt: "test",
summary: { fixtureCount: 1 },
fixtures: [
{
id: "fixture",
hooks: [],
registrations: [
{
id: "registration.registerMystery:fixture:index",
registrar: "registerMystery",
ref: "src/index.js",
assertions: ["mystery registration is classified"],
syntheticArguments: [{}],
},
],
},
],
},
});
assert.equal(plan.summary.blockedCount, 1);
assert.match(validateSyntheticProbePlan(plan).join("\n"), /not been classified/);
});
test("synthetic probe plan classifies generated kitchen-sink registrars", () => {
const kitchenSinkRegistrars = [
"createChatChannelPlugin",
"registerAgentEventSubscription",
"registerAgentHarness",
"registerAgentToolResultMiddleware",
"registerAutoEnableProbe",
"registerChannel",
"defineBundledChannelEntry",
"registerCli",
"registerCliBackend",
"registerCodexAppServerExtensionFactory",
"registerCommand",
"registerCompactionProvider",
"registerConfigMigration",
"registerContextEngine",
"registerControlUiDescriptor",
"registerDetachedTaskRuntime",
"registerGatewayDiscoveryService",
"registerGatewayMethod",
"registerHook",
"registerHttpRoute",
"registerImageGenerationProvider",
"registerInteractiveHandler",
"registerMediaUnderstandingProvider",
"registerMemoryCapability",
"registerMemoryCorpusSupplement",
"registerMemoryEmbeddingProvider",
"registerMemoryFlushPlan",
"registerMemoryPromptSection",
"registerMemoryPromptSupplement",
"registerMemoryRuntime",
"registerMigrationProvider",
"registerMusicGenerationProvider",
"registerNodeHostCommand",
"registerNodeInvokePolicy",
"registerProvider",
"registerRealtimeTranscriptionProvider",
"registerRealtimeVoiceProvider",
"registerReload",
"registerRuntimeLifecycle",
"registerSecurityAuditCollector",
"registerService",
"registerSessionExtension",
"registerSessionSchedulerJob",
"registerSpeechProvider",
"registerTextTransforms",
"registerTool",
"registerToolMetadata",
"registerTrustedToolPolicy",
"registerVideoGenerationProvider",
"registerWebFetchProvider",
"registerWebSearchProvider",
];
const plan = buildSyntheticProbePlan({
capture: {
generatedAt: "test",
summary: { fixtureCount: 1 },
fixtures: [
{
id: "kitchen-sink",
hooks: [],
registrations: kitchenSinkRegistrars.map((registrar) => ({
id: `registration.${registrar}:kitchen-sink:index`,
registrar,
ref: "src/generated-registrars.js",
assertions: [`${registrar} is classified`],
syntheticArguments: [{}],
})),
},
],
},
});
assert.equal(plan.summary.probeCount, kitchenSinkRegistrars.length);
assert.equal(plan.summary.blockedCount, 0);
assert.deepEqual(validateSyntheticProbePlan(plan), []);
});
test("synthetic probes invoke retained hook and tool handlers", async () => {
const capture = await captureLocalFixture([
"export function register(api) {",
" api.on('before_tool_call', (event, ctx) => ({ seen: event.toolName, ctxTool: ctx.toolName }));",
" api.registerTool({",
" name: 'fixture_tool',",
" execute(toolCallId, params) { return { toolCallId, sawParams: typeof params === 'object' }; },",
" });",
"}",
]);
const result = await runCapturedSyntheticProbes(capture);
assert.equal(result.summary.failCount, 0);
assert.equal(result.summary.blockedCount, 0);
assert.deepEqual(
result.results.map((item) => `${item.status}:${item.kind}:${item.label}`),
["pass:hook:before_tool_call", "pass:registration:registerTool.execute"],
);
});
test("synthetic probes pass registrar-specific handler inputs", async () => {
const capture = await captureLocalFixture([
"export function register(api) {",
" api.registerTool({",
" name: 'fixture_tool',",
" run(params, ctx) { return { sawParams: typeof params === 'object', toolName: ctx.toolName }; },",
" });",
" api.registerHttpRoute({",
" method: 'POST',",
" path: '/fixture',",
" handler(req, ctx) { return { method: req.method, hasLogger: Boolean(ctx.logger) }; },",
" });",
"}",
]);
const result = await runCapturedSyntheticProbes(capture);
assert.equal(result.summary.failCount, 0);
assert.deepEqual(
result.results.map((item) => `${item.status}:${item.label}`),
["pass:registerTool.run", "pass:registerHttpRoute.handler"],
);
});
test("synthetic probes pass channel envelopes and gateway responders", async () => {
const capture = await captureLocalFixture([
"export function register(api) {",
" api.registerChannel({",
" id: 'fixture_channel',",
" async send(ctx) { return { messageId: ctx.replyToId, to: ctx.to }; },",
" async receive(ctx) { return { messageId: ctx.message.id, peer: ctx.route.peer.id }; },",
" });",
" api.registerGatewayMethod('fixture.ping', ({ respond, params }) => respond(true, { sawParams: typeof params === 'object' }));",
"}",
]);
const blocked = await runCapturedSyntheticProbes(capture);
assert.equal(blocked.summary.blockedCount, 1);
const result = await runCapturedSyntheticProbes(capture, { includeChannelRuntime: true });
assert.equal(result.summary.failCount, 0);
assert.deepEqual(
result.results.map((item) => `${item.status}:${item.label}`),
[
"pass:registerChannel.send",
"pass:registerChannel.receive",
"pass:registerGatewayMethod.handler",
"pass:registerGatewayMethod.run",
"pass:registerGatewayMethod.execute",
],
);
});
test("synthetic probes can execute string plus handler registrations", async () => {
const capture = await captureLocalFixture([
"export function register(api) {",
" api.registerGatewayMethod('fixture.ping', (event) => ({ method: event.registrar, property: event.property }));",
"}",
]);
const result = await runCapturedSyntheticProbes(capture);
assert.equal(result.summary.failCount, 0);
assert.equal(result.summary.blockedCount, 0);
assert.deepEqual(
result.results.map((item) => `${item.status}:${item.label}`),
[
"pass:registerGatewayMethod.handler",
"pass:registerGatewayMethod.run",
"pass:registerGatewayMethod.execute",
],
);
});
test("synthetic probes keep opt-in registrations guarded", async () => {
const capture = await captureLocalFixture([
"export function register(api) {",
" api.registerService({ name: 'fixture_service', start() { return { started: true }; } });",
"}",
]);
const blocked = await runCapturedSyntheticProbes(capture);
assert.equal(blocked.summary.blockedCount, 1);
assert.match(blocked.results[0].reason, /includeLifecycle=true/);
const executed = await runCapturedSyntheticProbes(capture, { includeLifecycle: true });
assert.equal(executed.summary.passCount, 1);
assert.equal(executed.results[0].label, "registerService.start");
});
test("mock SDK capture preserves retained registration metadata across subprocesses", async () => {
const dir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-probes-mock-sdk-"));
const entrypoint = path.join(dir, "fixture.mjs");
await writeFile(
entrypoint,
[
'import { definePluginEntry } from "openclaw/plugin-sdk";',
"",
"export default definePluginEntry((api) => {",
" api.registerTool({ name: 'fixture_tool', run(params) { return { sawParams: typeof params === 'object' }; } });",
"});",
].join("\n"),
"utf8",
);
const capture = await captureEntrypoint("fixture.mjs", {
cwd: dir,
pluginRoot: dir,
mockSdk: true,
apiOptions: { retainHandlers: true },
});
const result = await runCapturedSyntheticProbes(capture);
assert.equal(capture.retained.length, 1);
assert.equal(result.summary.blockedCount, 1);
assert.match(result.results[0].reason, /no supported callable probe/);
});
async function captureLocalFixture(lines) {
const dir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-probes-"));
const entrypoint = path.join(dir, "fixture.mjs");
await writeFile(entrypoint, `${lines.join("\n")}\n`, "utf8");
return captureEntrypoint(entrypoint, {
apiOptions: { retainHandlers: true },
});
}