test: harden plugin update validation

This commit is contained in:
Peter Steinberger
2026-05-01 23:19:30 +01:00
parent 34b40b007c
commit bcd6499abd
18 changed files with 610 additions and 22 deletions

View File

@@ -5,6 +5,15 @@ function writePluginManifest(file, id) {
writeJson(file, { id, configSchema: { type: "object", properties: {} } });
}
function writeFakeIsNumberPackage(dir) {
writeJson(path.join(dir, "package.json"), {
name: "is-number",
version: "7.0.0",
main: "index.js",
});
write(path.join(dir, "index.js"), "module.exports = (value) => typeof value === 'number';\n");
}
function writePluginDemo([dir]) {
write(
path.join(requireArg(dir, "dir"), "index.js"),
@@ -35,6 +44,22 @@ function writePlugin([dir, id, version, method, name]) {
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
}
function writePluginWithVendoredDependency([dir, id, version, method, name]) {
writePlugin([dir, id, version, method, name]);
const packageJsonPath = path.join(dir, "package.json");
writeJson(packageJsonPath, {
name: `@openclaw/${id}`,
version,
dependencies: { "is-number": "7.0.0" },
openclaw: { extensions: ["./index.js"] },
});
write(
path.join(dir, "index.js"),
`const isNumber = require("is-number");\nmodule.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: isNumber(42) })); }, };\n`,
);
writeFakeIsNumberPackage(path.join(dir, "node_modules", "is-number"));
}
function writePluginWithCli([dir, id, version, method, name, cliRoot, cliOutput]) {
for (const [value, label] of [
[dir, "dir"],
@@ -50,11 +75,13 @@ function writePluginWithCli([dir, id, version, method, name, cliRoot, cliOutput]
writeJson(path.join(dir, "package.json"), {
name: `@openclaw/${id}`,
version,
dependencies: { "is-number": "file:./deps/is-number" },
openclaw: { extensions: ["./index.js"] },
});
writeFakeIsNumberPackage(path.join(dir, "deps", "is-number"));
write(
path.join(dir, "index.js"),
`module.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: true })); api.registerCli(({ program }) => { const root = program.command(${JSON.stringify(cliRoot)}).description(${JSON.stringify(`${name} fixture command`)}); root.command("ping").description("Print fixture ping output").action(() => { console.log(${JSON.stringify(cliOutput)}); }); }, { descriptors: [{ name: ${JSON.stringify(cliRoot)}, description: ${JSON.stringify(`${name} fixture command`)}, hasSubcommands: true }] }); }, };\n`,
`const isNumber = require("is-number");\nmodule.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: isNumber(42) })); api.registerCli(({ program }) => { const root = program.command(${JSON.stringify(cliRoot)}).description(${JSON.stringify(`${name} fixture command`)}); root.command("ping").description("Print fixture ping output").action(() => { console.log(${JSON.stringify(cliOutput)}); }); }, { descriptors: [{ name: ${JSON.stringify(cliRoot)}, description: ${JSON.stringify(`${name} fixture command`)}, hasSubcommands: true }] }); }, };\n`,
);
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
}
@@ -99,6 +126,7 @@ function writePluginMarketplace([root]) {
export const pluginCommands = {
"plugin-demo": writePluginDemo,
plugin: writePlugin,
"plugin-vendored-dep": writePluginWithVendoredDependency,
"plugin-cli": writePluginWithCli,
"plugin-manifest": ([file, id]) =>
writePluginManifest(requireArg(file, "file"), requireArg(id, "id")),

View File

@@ -258,6 +258,11 @@ function assertGitPlugin() {
throw new Error(`git install path should point at cloned repo root: ${installPath}`);
}
assertRealPathInside(gitRoot, installPath, "git install path");
const dependencyPackagePath = path.join(installPath, "node_modules", "is-number", "package.json");
if (!fs.existsSync(dependencyPackagePath)) {
throw new Error(`missing git plugin installed dependency: ${dependencyPackagePath}`);
}
assertRealPathInside(installPath, dependencyPackagePath, "git plugin installed dependency");
}
function assertRealPathInside(parentPath, childPath, label) {
@@ -292,6 +297,36 @@ function assertClawHubExternalInstallContract(installPath) {
assertRealPathInside(installPath, dependencyPackagePath, "ClawHub isolated dependency");
}
function assertPluginDirDeps() {
const sourceDir = process.argv[3];
assertSimplePlugin(
"/tmp/plugins-dir-deps.json",
"/tmp/plugins-dir-deps-inspect.json",
"demo-plugin-dir-deps",
"demo.dir.deps",
);
const record = getInstallRecords()["demo-plugin-dir-deps"];
if (!record) {
throw new Error("missing local dependency plugin install record");
}
if (record.source !== "path") {
throw new Error(`unexpected local dependency plugin source: ${record.source}`);
}
if (record.sourcePath !== sourceDir) {
throw new Error(`unexpected local dependency plugin source path: ${record.sourcePath}`);
}
const installPath = record.installPath?.replace(/^~(?=$|\/)/u, process.env.HOME);
if (!installPath || !fs.existsSync(installPath)) {
throw new Error(`local dependency plugin install path missing on disk: ${installPath}`);
}
const dependencyPackagePath = path.join(installPath, "node_modules", "is-number", "package.json");
if (!fs.existsSync(dependencyPackagePath)) {
throw new Error(`missing copied local plugin dependency: ${dependencyPackagePath}`);
}
assertRealPathInside(installPath, dependencyPackagePath, "local plugin copied dependency");
}
function assertMarketplaceUpdated() {
const data = readJson("/tmp/plugins-marketplace-updated.json");
const inspect = readJson("/tmp/plugins-marketplace-updated-inspect.json");
@@ -459,6 +494,7 @@ const commands = {
"demo-plugin-dir",
"demo.dir",
),
"plugin-dir-deps": assertPluginDirDeps,
"plugin-file": () =>
assertSimplePlugin(
"/tmp/plugins4.json",

View File

@@ -32,6 +32,16 @@ write_fixture_plugin_with_cli() {
node scripts/e2e/lib/fixture.mjs plugin-cli "$dir" "$id" "$version" "$method" "$name" "$cli_root" "$cli_output"
}
write_fixture_plugin_with_vendored_dependency() {
local dir="$1"
local id="$2"
local version="$3"
local method="$4"
local name="$5"
node scripts/e2e/lib/fixture.mjs plugin-vendored-dep "$dir" "$id" "$version" "$method" "$name"
}
write_fixture_manifest() {
local file="$1"
local id="$2"

View File

@@ -46,6 +46,16 @@ node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-dir --runtime --json >/tmp/pl
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir
echo "Testing install from local folder with preinstalled dependencies..."
dir_deps_plugin="$(mktemp -d "/tmp/openclaw-plugin-dir-deps.XXXXXX")"
write_fixture_plugin_with_vendored_dependency "$dir_deps_plugin" demo-plugin-dir-deps 0.0.1 demo.dir.deps "Demo Plugin DIR Deps"
run_logged install-dir-deps node "$OPENCLAW_ENTRY" plugins install "$dir_deps_plugin"
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-dir-deps.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-dir-deps --runtime --json >/tmp/plugins-dir-deps-inspect.json
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-deps "$dir_deps_plugin"
echo "Testing install from npm spec (file:)..."
file_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-filepack.XXXXXX")"
write_fixture_plugin "$file_pack_dir/package" demo-plugin-file 0.0.1 demo.file "Demo Plugin FILE"