test(plugins): cover malformed npm package metadata

This commit is contained in:
Vincent Koc
2026-05-16 09:20:33 +08:00
parent 3064d6181d
commit a70c90a52b
5 changed files with 66 additions and 5 deletions
+4 -3
View File
@@ -88,9 +88,10 @@ Important lanes:
local folder update skip behavior, local folders with preinstalled
dependencies, `file:` package installs, git installs with CLI execution, git
moving-ref updates, npm registry installs with hoisted transitive
dependencies, npm update no-ops, local ClawHub fixture installs and update
no-ops, marketplace update behavior, and Claude-bundle enable/inspect. Set
`OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to keep the ClawHub block hermetic/offline.
dependencies, npm update no-ops, malformed npm package metadata rejection,
local ClawHub fixture installs and update no-ops, marketplace update behavior,
and Claude-bundle enable/inspect. Set `OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to
keep the ClawHub block hermetic/offline.
- `test:docker:plugin-lifecycle-matrix` installs the candidate package in a bare
container, runs an npm plugin through install, inspect, disable, enable,
explicit upgrade, explicit downgrade, and uninstall after deleting the plugin
+1 -1
View File
@@ -799,7 +799,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
- MCP channel bridge (seeded Gateway + stdio bridge + raw Claude notification-frame smoke): `pnpm test:docker:mcp-channels` (script: `scripts/e2e/mcp-channels-docker.sh`)
- Pi bundle MCP tools (real stdio MCP server + embedded Pi profile allow/deny smoke): `pnpm test:docker:pi-bundle-mcp-tools` (script: `scripts/e2e/pi-bundle-mcp-tools-docker.sh`)
- Cron/subagent MCP cleanup (real Gateway + stdio MCP child teardown after isolated cron and one-shot subagent runs): `pnpm test:docker:cron-mcp-cleanup` (script: `scripts/e2e/cron-mcp-cleanup-docker.sh`)
- Plugins (install/update smoke for local path, `file:`, npm registry with hoisted dependencies, git moving refs, ClawHub kitchen-sink, marketplace updates, and Claude-bundle enable/inspect): `pnpm test:docker:plugins` (script: `scripts/e2e/plugins-docker.sh`)
- Plugins (install/update smoke for local path, `file:`, npm registry with hoisted dependencies, malformed npm package metadata, git moving refs, ClawHub kitchen-sink, marketplace updates, and Claude-bundle enable/inspect): `pnpm test:docker:plugins` (script: `scripts/e2e/plugins-docker.sh`)
Set `OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to skip the ClawHub block, or override the default kitchen-sink package/runtime pair with `OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC` and `OPENCLAW_PLUGINS_E2E_CLAWHUB_ID`. Without `OPENCLAW_CLAWHUB_URL`/`CLAWHUB_URL`, the test uses a hermetic local ClawHub fixture server.
- Plugin update unchanged smoke: `pnpm test:docker:plugin-update` (script: `scripts/e2e/plugin-update-unchanged-docker.sh`)
- Plugin lifecycle matrix smoke: `pnpm test:docker:plugin-lifecycle-matrix` installs the packed OpenClaw tarball in a bare container, installs an npm plugin, toggles enable/disable, upgrades and downgrades it through a local npm registry, deletes the installed code, then verifies uninstall still removes stale state while logging RSS/CPU metrics for each lifecycle phase.
+28
View File
@@ -616,6 +616,33 @@ function assertNpmPluginRemoved() {
}
}
function assertInvalidOpenClawExtensionsRejected() {
const pluginId = "demo-plugin-invalid-metadata";
const output = fs.readFileSync("/tmp/plugins-invalid-openclaw-extensions.log", "utf8");
for (const expected of ["openclaw.extensions[1]", "non-empty string"]) {
if (!output.includes(expected)) {
throw new Error(
`expected malformed metadata install output to include ${JSON.stringify(expected)}:\n${output}`,
);
}
}
const list = readJson("/tmp/plugins-invalid-openclaw-extensions-list.json");
if ((list.plugins || []).some((entry) => entry.id === pluginId)) {
throw new Error(`${pluginId} listed after rejected install`);
}
const installRecords = getInstallRecords();
if (installRecords[pluginId]) {
throw new Error(`${pluginId} install record persisted after rejected install`);
}
const managedInstallPath = path.join(process.env.HOME, ".openclaw", "extensions", pluginId);
if (fs.existsSync(managedInstallPath)) {
throw new Error(`${pluginId} managed install directory exists after rejected install`);
}
}
function assertMarketplaceUpdated() {
const data = readJson("/tmp/plugins-marketplace-updated.json");
const inspect = readJson("/tmp/plugins-marketplace-updated-inspect.json");
@@ -835,6 +862,7 @@ const commands = {
"plugin-npm": assertNpmPlugin,
"plugin-npm-update": assertNpmPluginUpdateUnchanged,
"plugin-npm-removed": assertNpmPluginRemoved,
"invalid-openclaw-extensions": assertInvalidOpenClawExtensionsRejected,
"bundle-disabled": assertClaudeBundleDisabled,
"bundle-inspect": assertClaudeBundleInspect,
"slash-install": assertSlashInstall,
+21
View File
@@ -86,6 +86,27 @@ pack_fixture_plugin() {
tar -czf "$output_tgz" -C "$pack_dir" package
}
pack_fixture_plugin_with_invalid_extension_entry() {
local pack_dir="$1"
local output_tgz="$2"
local id="$3"
local version="$4"
local method="$5"
local name="$6"
mkdir -p "$pack_dir/package"
write_fixture_plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
node --input-type=module - "$pack_dir/package/package.json" <<'NODE'
import fs from "node:fs";
const packageJsonPath = process.argv[2];
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
packageJson.openclaw.extensions = ["./index.js", " "];
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
NODE
tar -czf "$output_tgz" -C "$pack_dir" package
}
start_npm_fixture_registry() {
local package_name="$1"
local version="$2"
+12 -1
View File
@@ -88,10 +88,12 @@ node scripts/e2e/lib/plugins/assertions.mjs plugin-file-removed
echo "Testing install and update from npm registry..."
npm_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-npm-pack.XXXXXX")"
npm_dep_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-npm-dep-pack.XXXXXX")"
invalid_npm_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-invalid-metadata-pack.XXXXXX")"
npm_registry_dir="$(mktemp -d "/tmp/openclaw-plugin-npm-registry.XXXXXX")"
pack_fixture_plugin_with_cli_registry_dependency "$npm_pack_dir" /tmp/demo-plugin-npm.tgz demo-plugin-npm 0.0.1 demo.npm "Demo Plugin NPM" demo-npm "demo-plugin-npm:pong"
pack_fake_is_number_package "$npm_dep_pack_dir" /tmp/is-number-7.0.0.tgz
start_npm_fixture_registry "@openclaw/demo-plugin-npm" "0.0.1" /tmp/demo-plugin-npm.tgz "$npm_registry_dir" "is-number" "7.0.0" /tmp/is-number-7.0.0.tgz
pack_fixture_plugin_with_invalid_extension_entry "$invalid_npm_pack_dir" /tmp/demo-plugin-invalid-metadata.tgz demo-plugin-invalid-metadata 0.0.1 demo.invalid.metadata "Demo Plugin Invalid Metadata"
start_npm_fixture_registry "@openclaw/demo-plugin-npm" "0.0.1" /tmp/demo-plugin-npm.tgz "$npm_registry_dir" "is-number" "7.0.0" /tmp/is-number-7.0.0.tgz "@openclaw/demo-plugin-invalid-metadata" "0.0.1" /tmp/demo-plugin-invalid-metadata.tgz
run_logged install-npm node "$OPENCLAW_ENTRY" plugins install "npm:@openclaw/demo-plugin-npm@0.0.1"
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-npm.json
@@ -107,6 +109,15 @@ run_logged uninstall-npm node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-np
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-npm-uninstalled.json
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm-removed
echo "Testing npm install rejects malformed package metadata..."
if node "$OPENCLAW_ENTRY" plugins install "npm:@openclaw/demo-plugin-invalid-metadata@0.0.1" > /tmp/plugins-invalid-openclaw-extensions.log 2>&1; then
cat /tmp/plugins-invalid-openclaw-extensions.log
echo "Expected malformed package metadata install to fail." >&2
exit 1
fi
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-invalid-openclaw-extensions-list.json
node scripts/e2e/lib/plugins/assertions.mjs invalid-openclaw-extensions
echo "Testing install from git repo and plugin CLI execution..."
git_fixture_root="$(mktemp -d "/tmp/openclaw-plugin-git.XXXXXX")"
git_repo="$git_fixture_root/repo"