refactor(tests): share kitchen sink harness
This commit is contained in:
parent
dcf61bc18c
commit
da085688fd
@ -31,7 +31,7 @@ try {
|
||||
assert.equal(installedPackageJson.version, JSON.parse(readFileSync("package.json", "utf8")).version);
|
||||
|
||||
const probeFile = path.join(projectDir, "probe.mjs");
|
||||
writeFileSync(probeFile, consumerProbeSource());
|
||||
writeFileSync(probeFile, readFileSync(new URL("./fixtures/installed-consumer-probe.mjs", import.meta.url), "utf8"));
|
||||
run(process.execPath, [probeFile], { cwd: projectDir });
|
||||
|
||||
const inspectorBin = path.join(repoRoot, "node_modules", ".bin", "plugin-inspector");
|
||||
@ -66,67 +66,3 @@ function run(command, args, options = {}) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
function consumerProbeSource() {
|
||||
return String.raw`
|
||||
import assert from "node:assert/strict";
|
||||
import { plugin } from "@openclaw/kitchen-sink";
|
||||
import { createKitchenSinkRuntime } from "@openclaw/kitchen-sink/runtime";
|
||||
import { createKitchenSinkImageAsset, kitchenPromptGuidance } from "@openclaw/kitchen-sink/scenarios";
|
||||
import setup from "@openclaw/kitchen-sink/setup";
|
||||
|
||||
const registrations = {};
|
||||
const api = new Proxy(
|
||||
{ id: "consumer-install-smoke", registrationMode: "full", config: {}, logger: console },
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) return target[property];
|
||||
if (property === "on") {
|
||||
return (...args) => {
|
||||
registrations.on ??= [];
|
||||
registrations.on.push(args);
|
||||
};
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) return undefined;
|
||||
return (...args) => {
|
||||
registrations[property] ??= [];
|
||||
registrations[property].push(args);
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
plugin.register(api);
|
||||
assert.equal(plugin.id, "openclaw-kitchen-sink-fixture");
|
||||
assert.ok(registrations.registerImageGenerationProvider?.some(([provider]) => provider.id === "kitchen-sink-image"));
|
||||
assert.ok(registrations.registerProvider?.some(([provider]) => provider.id === "kitchen-sink-llm"));
|
||||
assert.ok(registrations.registerWebSearchProvider?.some(([provider]) => provider.id === "kitchen-sink-search"));
|
||||
assert.ok(registrations.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"));
|
||||
|
||||
const runtime = createKitchenSinkRuntime({
|
||||
delayMs: 10_000,
|
||||
sleep: async () => {},
|
||||
now: (() => {
|
||||
let tick = 0;
|
||||
return () => new Date(Date.UTC(2026, 3, 29, 12, 0, tick++));
|
||||
})(),
|
||||
});
|
||||
const image = await runtime.runImageJob({ prompt: "generate an image with kitchen sink" });
|
||||
assert.equal(image.job.status, "completed");
|
||||
assert.equal(image.image.metadata.assetName, "kitchen_sink_office.png");
|
||||
assert.equal(image.image.metadata.sha256, "e126064123bb13d8ee01a22c204e079bc22397c103ed1c3a191c60d5ae3319aa");
|
||||
|
||||
const directImage = createKitchenSinkImageAsset({
|
||||
prompt: "consumer import smoke",
|
||||
jobId: "ks_consumer_install_smoke",
|
||||
});
|
||||
assert.equal(directImage.mimeType, "image/png");
|
||||
assert.ok(directImage.dataUrl.startsWith("data:image/png;base64,"));
|
||||
assert.ok(kitchenPromptGuidance().some((line) => line.includes("kitchen_sink_image_job")));
|
||||
|
||||
assert.equal(setup.id, "openclaw-kitchen-sink-setup");
|
||||
assert.equal(typeof setup.setup, "function");
|
||||
const setupResult = await setup.setup({ config: {} });
|
||||
assert.equal(setupResult.configured, true);
|
||||
`;
|
||||
}
|
||||
|
||||
@ -3,32 +3,14 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { plugin } from "../src/index.js";
|
||||
import {
|
||||
capturePluginRegistration,
|
||||
createHookFinder,
|
||||
registrationSummary,
|
||||
} from "./lib/plugin-registration-harness.mjs";
|
||||
|
||||
const registrations = {};
|
||||
const api = new Proxy(
|
||||
{
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
registrationMode: "full",
|
||||
config: {},
|
||||
logger: console,
|
||||
},
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) {
|
||||
return target[property];
|
||||
}
|
||||
if (property === "on") {
|
||||
return (...args) => capture("on", args);
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) {
|
||||
return undefined;
|
||||
}
|
||||
return (...args) => capture(property, args);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
plugin.register(api);
|
||||
const registrations = capturePluginRegistration(plugin);
|
||||
const findHook = createHookFinder(registrations);
|
||||
|
||||
const beforeToolCall = findHook("before_tool_call");
|
||||
const llmInput = findHook("llm_input");
|
||||
@ -49,7 +31,7 @@ const probes = {
|
||||
end: await agentEnd(secretEvent("kitchen final answer"), secretContext()),
|
||||
},
|
||||
channel: await captureChannelProbe(),
|
||||
runtimeRegistrations: registrationSummary(),
|
||||
runtimeRegistrations: registrationSummary(registrations),
|
||||
};
|
||||
|
||||
assert.equal(probes.beforeToolCall.allow.decision, "allow");
|
||||
@ -90,50 +72,6 @@ console.log(
|
||||
`Kitchen contract probes OK: ${Object.keys(probes.runtimeRegistrations).length} registration methods, before_tool_call allow/block/approval, conversation privacy, channel envelope`,
|
||||
);
|
||||
|
||||
function capture(method, args) {
|
||||
registrations[method] ??= [];
|
||||
registrations[method].push(args);
|
||||
}
|
||||
|
||||
function findHook(name) {
|
||||
const entry = registrations.on?.find(([hookName]) => hookName === name);
|
||||
assert.ok(entry, `hook ${name} registered`);
|
||||
return entry[1];
|
||||
}
|
||||
|
||||
function registrationSummary() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(registrations)
|
||||
.filter(([method]) => method !== "on")
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([method, entries]) => [
|
||||
method,
|
||||
{
|
||||
count: entries.length,
|
||||
ids: entries.map((args) => idForRegistration(method, args)).filter(Boolean).sort(),
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function idForRegistration(method, args) {
|
||||
const [value, second] = args;
|
||||
if (method === "registerGatewayMethod" && typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
if (method === "registerCli" && second?.descriptors?.length > 0) {
|
||||
return second.descriptors.map((descriptor) => descriptor.name).join(", ");
|
||||
}
|
||||
if (value?.id || value?.name) {
|
||||
return value.id || value.name;
|
||||
}
|
||||
if (typeof second === "string") {
|
||||
return second;
|
||||
}
|
||||
const slug = method.slice("register".length).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
return `kitchen-sink-${slug}`;
|
||||
}
|
||||
|
||||
async function captureChannelProbe() {
|
||||
const channel = registrations.registerChannel?.map(([value]) => value).find((value) => value.id === "kitchen-sink-channel");
|
||||
assert.ok(channel, "kitchen-sink-channel registered");
|
||||
|
||||
@ -2,38 +2,16 @@
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { plugin } from "../src/index.js";
|
||||
import {
|
||||
capturePluginRegistration,
|
||||
createHookFinder,
|
||||
createRegistrationFinder,
|
||||
fixedNow,
|
||||
} from "./lib/plugin-registration-harness.mjs";
|
||||
|
||||
const registrations = {};
|
||||
const api = new Proxy(
|
||||
{
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
registrationMode: "full",
|
||||
config: {},
|
||||
logger: console,
|
||||
},
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) {
|
||||
return target[property];
|
||||
}
|
||||
if (property === "on") {
|
||||
return (...args) => {
|
||||
registrations.on ??= [];
|
||||
registrations.on.push(args);
|
||||
};
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) {
|
||||
return undefined;
|
||||
}
|
||||
return (...args) => {
|
||||
registrations[property] ??= [];
|
||||
registrations[property].push(args);
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
plugin.register(api);
|
||||
const registrations = capturePluginRegistration(plugin);
|
||||
const findRegistration = createRegistrationFinder(registrations);
|
||||
const findHook = createHookFinder(registrations);
|
||||
|
||||
const commands = registrations.registerCommand?.map(([command]) => command) ?? [];
|
||||
assert.ok(commands.some((command) => command.name === "kitchen"), "registers kitchen command");
|
||||
@ -400,7 +378,7 @@ assert.ok(
|
||||
),
|
||||
);
|
||||
|
||||
const conformance = capturePluginRegistration({ personality: "conformance" });
|
||||
const conformance = capturePluginRegistration(plugin, { personality: "conformance" });
|
||||
assert.ok(
|
||||
conformance.registerCommand?.some(([command]) => command.name === "kitchen"),
|
||||
"conformance registers usable commands",
|
||||
@ -415,7 +393,7 @@ assert.equal(
|
||||
false,
|
||||
);
|
||||
|
||||
const adversarial = capturePluginRegistration({ personality: "adversarial" });
|
||||
const adversarial = capturePluginRegistration(plugin, { personality: "adversarial" });
|
||||
assert.equal(
|
||||
adversarial.registerCommand?.some(([command]) => command.name === "kitchen"),
|
||||
false,
|
||||
@ -434,54 +412,3 @@ assert.ok(
|
||||
);
|
||||
|
||||
console.log("Kitchen runtime OK");
|
||||
|
||||
function capturePluginRegistration(config) {
|
||||
const captured = {};
|
||||
const captureApi = new Proxy(
|
||||
{
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
registrationMode: "full",
|
||||
config,
|
||||
logger: console,
|
||||
},
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) {
|
||||
return target[property];
|
||||
}
|
||||
if (property === "on") {
|
||||
return (...args) => {
|
||||
captured.on ??= [];
|
||||
captured.on.push(args);
|
||||
};
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) {
|
||||
return undefined;
|
||||
}
|
||||
return (...args) => {
|
||||
captured[property] ??= [];
|
||||
captured[property].push(args);
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
plugin.register(captureApi);
|
||||
return captured;
|
||||
}
|
||||
|
||||
function findRegistration(method, id) {
|
||||
const entry = registrations[method]?.map(([value]) => value).find((value) => value?.id === id);
|
||||
assert.ok(entry, `${method} ${id} registered`);
|
||||
return entry;
|
||||
}
|
||||
|
||||
function findHook(name) {
|
||||
const entry = registrations.on?.find(([hookName]) => hookName === name);
|
||||
assert.ok(entry, `hook ${name} registered`);
|
||||
return entry[1];
|
||||
}
|
||||
|
||||
function fixedNow() {
|
||||
let tick = 0;
|
||||
return () => new Date(Date.UTC(2026, 3, 28, 12, 0, tick++));
|
||||
}
|
||||
|
||||
59
scripts/fixtures/installed-consumer-probe.mjs
Normal file
59
scripts/fixtures/installed-consumer-probe.mjs
Normal file
@ -0,0 +1,59 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { plugin } from "@openclaw/kitchen-sink";
|
||||
import { createKitchenSinkRuntime } from "@openclaw/kitchen-sink/runtime";
|
||||
import { createKitchenSinkImageAsset, kitchenPromptGuidance } from "@openclaw/kitchen-sink/scenarios";
|
||||
import setup from "@openclaw/kitchen-sink/setup";
|
||||
|
||||
const registrations = {};
|
||||
const api = new Proxy(
|
||||
{ id: "consumer-install-smoke", registrationMode: "full", config: {}, logger: console },
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) return target[property];
|
||||
if (property === "on") {
|
||||
return (...args) => {
|
||||
registrations.on ??= [];
|
||||
registrations.on.push(args);
|
||||
};
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) return undefined;
|
||||
return (...args) => {
|
||||
registrations[property] ??= [];
|
||||
registrations[property].push(args);
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
plugin.register(api);
|
||||
assert.equal(plugin.id, "openclaw-kitchen-sink-fixture");
|
||||
assert.ok(registrations.registerImageGenerationProvider?.some(([provider]) => provider.id === "kitchen-sink-image"));
|
||||
assert.ok(registrations.registerProvider?.some(([provider]) => provider.id === "kitchen-sink-llm"));
|
||||
assert.ok(registrations.registerWebSearchProvider?.some(([provider]) => provider.id === "kitchen-sink-search"));
|
||||
assert.ok(registrations.registerChannel?.some(([channel]) => channel.id === "kitchen-sink-channel"));
|
||||
|
||||
const runtime = createKitchenSinkRuntime({
|
||||
delayMs: 10_000,
|
||||
sleep: async () => {},
|
||||
now: (() => {
|
||||
let tick = 0;
|
||||
return () => new Date(Date.UTC(2026, 3, 29, 12, 0, tick++));
|
||||
})(),
|
||||
});
|
||||
const image = await runtime.runImageJob({ prompt: "generate an image with kitchen sink" });
|
||||
assert.equal(image.job.status, "completed");
|
||||
assert.equal(image.image.metadata.assetName, "kitchen_sink_office.png");
|
||||
assert.equal(image.image.metadata.sha256, "e126064123bb13d8ee01a22c204e079bc22397c103ed1c3a191c60d5ae3319aa");
|
||||
|
||||
const directImage = createKitchenSinkImageAsset({
|
||||
prompt: "consumer import smoke",
|
||||
jobId: "ks_consumer_install_smoke",
|
||||
});
|
||||
assert.equal(directImage.mimeType, "image/png");
|
||||
assert.ok(directImage.dataUrl.startsWith("data:image/png;base64,"));
|
||||
assert.ok(kitchenPromptGuidance().some((line) => line.includes("kitchen_sink_image_job")));
|
||||
|
||||
assert.equal(setup.id, "openclaw-kitchen-sink-setup");
|
||||
assert.equal(typeof setup.setup, "function");
|
||||
const setupResult = await setup.setup({ config: {} });
|
||||
assert.equal(setupResult.configured, true);
|
||||
88
scripts/lib/plugin-registration-harness.mjs
Normal file
88
scripts/lib/plugin-registration-harness.mjs
Normal file
@ -0,0 +1,88 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
export function capturePluginRegistration(plugin, config = {}) {
|
||||
const captured = {};
|
||||
const api = new Proxy(
|
||||
{
|
||||
id: "openclaw-kitchen-sink-fixture",
|
||||
registrationMode: "full",
|
||||
config,
|
||||
logger: console,
|
||||
},
|
||||
{
|
||||
get(target, property) {
|
||||
if (property in target) {
|
||||
return target[property];
|
||||
}
|
||||
if (property === "on") {
|
||||
return (...args) => capture(captured, "on", args);
|
||||
}
|
||||
if (typeof property !== "string" || !property.startsWith("register")) {
|
||||
return undefined;
|
||||
}
|
||||
return (...args) => capture(captured, property, args);
|
||||
},
|
||||
},
|
||||
);
|
||||
plugin.register(api);
|
||||
return captured;
|
||||
}
|
||||
|
||||
export function createRegistrationFinder(registrations) {
|
||||
return (method, id) => {
|
||||
const entry = registrations[method]?.map(([value]) => value).find((value) => value?.id === id);
|
||||
assert.ok(entry, `${method} ${id} registered`);
|
||||
return entry;
|
||||
};
|
||||
}
|
||||
|
||||
export function createHookFinder(registrations) {
|
||||
return (name) => {
|
||||
const entry = registrations.on?.find(([hookName]) => hookName === name);
|
||||
assert.ok(entry, `hook ${name} registered`);
|
||||
return entry[1];
|
||||
};
|
||||
}
|
||||
|
||||
export function registrationSummary(registrations) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(registrations)
|
||||
.filter(([method]) => method !== "on")
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([method, entries]) => [
|
||||
method,
|
||||
{
|
||||
count: entries.length,
|
||||
ids: entries.map((args) => idForRegistration(method, args)).filter(Boolean).sort(),
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
export function fixedNow(start = Date.UTC(2026, 3, 28, 12, 0, 0)) {
|
||||
let tick = 0;
|
||||
return () => new Date(start + tick++ * 1000);
|
||||
}
|
||||
|
||||
function capture(registrations, method, args) {
|
||||
registrations[method] ??= [];
|
||||
registrations[method].push(args);
|
||||
}
|
||||
|
||||
function idForRegistration(method, args) {
|
||||
const [value, second] = args;
|
||||
if (method === "registerGatewayMethod" && typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
if (method === "registerCli" && second?.descriptors?.length > 0) {
|
||||
return second.descriptors.map((descriptor) => descriptor.name).join(", ");
|
||||
}
|
||||
if (value?.id || value?.name) {
|
||||
return value.id || value.name;
|
||||
}
|
||||
if (typeof second === "string") {
|
||||
return second;
|
||||
}
|
||||
const slug = method.slice("register".length).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
return `kitchen-sink-${slug}`;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user