Skip to content

Commit a70c90a

Browse files
committed
test(plugins): cover malformed npm package metadata
1 parent 3064d61 commit a70c90a

5 files changed

Lines changed: 66 additions & 5 deletions

File tree

docs/help/testing-updates-plugins.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,10 @@ Important lanes:
8888
local folder update skip behavior, local folders with preinstalled
8989
dependencies, `file:` package installs, git installs with CLI execution, git
9090
moving-ref updates, npm registry installs with hoisted transitive
91-
dependencies, npm update no-ops, local ClawHub fixture installs and update
92-
no-ops, marketplace update behavior, and Claude-bundle enable/inspect. Set
93-
`OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to keep the ClawHub block hermetic/offline.
91+
dependencies, npm update no-ops, malformed npm package metadata rejection,
92+
local ClawHub fixture installs and update no-ops, marketplace update behavior,
93+
and Claude-bundle enable/inspect. Set `OPENCLAW_PLUGINS_E2E_CLAWHUB=0` to
94+
keep the ClawHub block hermetic/offline.
9495
- `test:docker:plugin-lifecycle-matrix` installs the candidate package in a bare
9596
container, runs an npm plugin through install, inspect, disable, enable,
9697
explicit upgrade, explicit downgrade, and uninstall after deleting the plugin

docs/help/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
799799
- MCP channel bridge (seeded Gateway + stdio bridge + raw Claude notification-frame smoke): `pnpm test:docker:mcp-channels` (script: `scripts/e2e/mcp-channels-docker.sh`)
800800
- 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`)
801801
- 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`)
802-
- 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`)
802+
- 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`)
803803
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.
804804
- Plugin update unchanged smoke: `pnpm test:docker:plugin-update` (script: `scripts/e2e/plugin-update-unchanged-docker.sh`)
805805
- 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.

scripts/e2e/lib/plugins/assertions.mjs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,33 @@ function assertNpmPluginRemoved() {
616616
}
617617
}
618618

619+
function assertInvalidOpenClawExtensionsRejected() {
620+
const pluginId = "demo-plugin-invalid-metadata";
621+
const output = fs.readFileSync("/tmp/plugins-invalid-openclaw-extensions.log", "utf8");
622+
for (const expected of ["openclaw.extensions[1]", "non-empty string"]) {
623+
if (!output.includes(expected)) {
624+
throw new Error(
625+
`expected malformed metadata install output to include ${JSON.stringify(expected)}:\n${output}`,
626+
);
627+
}
628+
}
629+
630+
const list = readJson("/tmp/plugins-invalid-openclaw-extensions-list.json");
631+
if ((list.plugins || []).some((entry) => entry.id === pluginId)) {
632+
throw new Error(`${pluginId} listed after rejected install`);
633+
}
634+
635+
const installRecords = getInstallRecords();
636+
if (installRecords[pluginId]) {
637+
throw new Error(`${pluginId} install record persisted after rejected install`);
638+
}
639+
640+
const managedInstallPath = path.join(process.env.HOME, ".openclaw", "extensions", pluginId);
641+
if (fs.existsSync(managedInstallPath)) {
642+
throw new Error(`${pluginId} managed install directory exists after rejected install`);
643+
}
644+
}
645+
619646
function assertMarketplaceUpdated() {
620647
const data = readJson("/tmp/plugins-marketplace-updated.json");
621648
const inspect = readJson("/tmp/plugins-marketplace-updated-inspect.json");
@@ -835,6 +862,7 @@ const commands = {
835862
"plugin-npm": assertNpmPlugin,
836863
"plugin-npm-update": assertNpmPluginUpdateUnchanged,
837864
"plugin-npm-removed": assertNpmPluginRemoved,
865+
"invalid-openclaw-extensions": assertInvalidOpenClawExtensionsRejected,
838866
"bundle-disabled": assertClaudeBundleDisabled,
839867
"bundle-inspect": assertClaudeBundleInspect,
840868
"slash-install": assertSlashInstall,

scripts/e2e/lib/plugins/fixtures.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ pack_fixture_plugin() {
8686
tar -czf "$output_tgz" -C "$pack_dir" package
8787
}
8888

89+
pack_fixture_plugin_with_invalid_extension_entry() {
90+
local pack_dir="$1"
91+
local output_tgz="$2"
92+
local id="$3"
93+
local version="$4"
94+
local method="$5"
95+
local name="$6"
96+
97+
mkdir -p "$pack_dir/package"
98+
write_fixture_plugin "$pack_dir/package" "$id" "$version" "$method" "$name"
99+
node --input-type=module - "$pack_dir/package/package.json" <<'NODE'
100+
import fs from "node:fs";
101+
102+
const packageJsonPath = process.argv[2];
103+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
104+
packageJson.openclaw.extensions = ["./index.js", " "];
105+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
106+
NODE
107+
tar -czf "$output_tgz" -C "$pack_dir" package
108+
}
109+
89110
start_npm_fixture_registry() {
90111
local package_name="$1"
91112
local version="$2"

scripts/e2e/lib/plugins/sweep.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,12 @@ node scripts/e2e/lib/plugins/assertions.mjs plugin-file-removed
8888
echo "Testing install and update from npm registry..."
8989
npm_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-npm-pack.XXXXXX")"
9090
npm_dep_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-npm-dep-pack.XXXXXX")"
91+
invalid_npm_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-invalid-metadata-pack.XXXXXX")"
9192
npm_registry_dir="$(mktemp -d "/tmp/openclaw-plugin-npm-registry.XXXXXX")"
9293
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"
9394
pack_fake_is_number_package "$npm_dep_pack_dir" /tmp/is-number-7.0.0.tgz
94-
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
95+
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"
96+
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
9597

9698
run_logged install-npm node "$OPENCLAW_ENTRY" plugins install "npm:@openclaw/[email protected]"
9799
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
107109
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-npm-uninstalled.json
108110
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm-removed
109111

112+
echo "Testing npm install rejects malformed package metadata..."
113+
if node "$OPENCLAW_ENTRY" plugins install "npm:@openclaw/[email protected]" > /tmp/plugins-invalid-openclaw-extensions.log 2>&1; then
114+
cat /tmp/plugins-invalid-openclaw-extensions.log
115+
echo "Expected malformed package metadata install to fail." >&2
116+
exit 1
117+
fi
118+
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-invalid-openclaw-extensions-list.json
119+
node scripts/e2e/lib/plugins/assertions.mjs invalid-openclaw-extensions
120+
110121
echo "Testing install from git repo and plugin CLI execution..."
111122
git_fixture_root="$(mktemp -d "/tmp/openclaw-plugin-git.XXXXXX")"
112123
git_repo="$git_fixture_root/repo"

0 commit comments

Comments
 (0)