From 33eebc29c31f3c53cbe560d6e85295548054e269 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 08:30:20 -0700 Subject: [PATCH] test(plugins): cover kitchen sink clawhub cutover --- scripts/e2e/kitchen-sink-plugin-docker.sh | 5 +- scripts/e2e/lib/clawhub-fixture-server.cjs | 2 +- .../lib/kitchen-sink-plugin/assertions.mjs | 34 +++++++++++ scripts/e2e/lib/kitchen-sink-plugin/sweep.sh | 15 ++++- scripts/e2e/lib/plugins/clawhub.sh | 2 +- test/plugin-clawhub-release.test.ts | 61 ++++++++++++++++++- test/scripts/docker-build-helper.test.ts | 1 + .../plugin-prerelease-test-plan.test.ts | 13 +++- 8 files changed, 122 insertions(+), 11 deletions(-) diff --git a/scripts/e2e/kitchen-sink-plugin-docker.sh b/scripts/e2e/kitchen-sink-plugin-docker.sh index d4716410706..380d8116261 100644 --- a/scripts/e2e/kitchen-sink-plugin-docker.sh +++ b/scripts/e2e/kitchen-sink-plugin-docker.sh @@ -14,8 +14,9 @@ npm-latest-full|npm:@openclaw/kitchen-sink@latest|openclaw-kitchen-sink-fixture| npm-latest-conformance|npm:@openclaw/kitchen-sink@latest|openclaw-kitchen-sink-fixture|npm|success|conformance|conformance npm-latest-adversarial|npm:@openclaw/kitchen-sink@latest|openclaw-kitchen-sink-fixture|npm|success|adversarial|adversarial npm-beta|npm:@openclaw/kitchen-sink@beta|openclaw-kitchen-sink-fixture|npm|failure|none -clawhub-latest|clawhub:openclaw-kitchen-sink@latest|openclaw-kitchen-sink-fixture|clawhub|success|basic -clawhub-beta|clawhub:openclaw-kitchen-sink@beta|openclaw-kitchen-sink-fixture|clawhub|failure|none +clawhub-latest|clawhub:@openclaw/kitchen-sink@latest|openclaw-kitchen-sink-fixture|clawhub|success|basic +clawhub-beta|clawhub:@openclaw/kitchen-sink@beta|openclaw-kitchen-sink-fixture|clawhub|failure|none +npm-to-clawhub|clawhub:@openclaw/kitchen-sink@latest|openclaw-kitchen-sink-fixture|clawhub|success|basic||npm:@openclaw/kitchen-sink@latest SCENARIOS )" KITCHEN_SINK_SCENARIOS="${OPENCLAW_KITCHEN_SINK_PLUGIN_SCENARIOS:-$DEFAULT_KITCHEN_SINK_SCENARIOS}" diff --git a/scripts/e2e/lib/clawhub-fixture-server.cjs b/scripts/e2e/lib/clawhub-fixture-server.cjs index 0c5e8a7de46..ca2b53a4860 100644 --- a/scripts/e2e/lib/clawhub-fixture-server.cjs +++ b/scripts/e2e/lib/clawhub-fixture-server.cjs @@ -8,7 +8,7 @@ const profile = process.argv[2]; const portFile = process.argv[3]; const requireFromApp = createRequire(path.join(process.cwd(), "package.json")); const JSZip = requireFromApp("jszip"); -const packageName = "openclaw-kitchen-sink"; +const packageName = "@openclaw/kitchen-sink"; const pluginId = "openclaw-kitchen-sink-fixture"; const profiles = { diff --git a/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs b/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs index bd63bc77301..f2b9785629f 100644 --- a/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs +++ b/scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs @@ -213,6 +213,39 @@ function assertClawHubExternalInstallContract(installPath) { } } +function inferInstallSource(spec) { + if (spec?.startsWith("npm:")) { + return "npm"; + } + if (spec?.startsWith("clawhub:")) { + return "clawhub"; + } + return null; +} + +function assertCutoverPreinstalled() { + const pluginId = process.env.KITCHEN_SINK_ID; + const preinstallSpec = process.env.KITCHEN_SINK_PREINSTALL_SPEC; + const source = inferInstallSource(preinstallSpec); + if (!pluginId || !preinstallSpec || !source) { + throw new Error(`invalid kitchen-sink cutover preinstall spec: ${preinstallSpec}`); + } + + const indexPath = path.join(process.env.HOME, ".openclaw", "plugins", "installs.json"); + const index = readJson(indexPath); + const record = (index.installRecords ?? index.records ?? {})[pluginId]; + if (!record) { + throw new Error(`missing kitchen-sink cutover preinstall record for ${pluginId}`); + } + if (record.source !== source) { + throw new Error(`expected kitchen-sink preinstall source=${source}, got ${record.source}`); + } + const expectedSpec = source === "npm" ? preinstallSpec.replace(/^npm:/u, "") : preinstallSpec; + if (record.spec !== expectedSpec) { + throw new Error(`expected kitchen-sink preinstall spec ${expectedSpec}, got ${record.spec}`); + } +} + function assertInstalled() { const pluginId = process.env.KITCHEN_SINK_ID; const spec = process.env.KITCHEN_SINK_SPEC; @@ -412,6 +445,7 @@ const commands = { "scan-logs": scanLogs, "configure-runtime": configureRuntime, "remove-channel-config": removeChannelConfig, + "assert-cutover-preinstalled": assertCutoverPreinstalled, "assert-installed": assertInstalled, "assert-removed": assertRemoved, }; diff --git a/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh b/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh index c686d788aae..39cd3122430 100644 --- a/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh +++ b/scripts/e2e/lib/kitchen-sink-plugin/sweep.sh @@ -75,9 +75,19 @@ assert_kitchen_sink_removed() { node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-removed } +assert_kitchen_sink_cutover_preinstalled() { + node scripts/e2e/lib/kitchen-sink-plugin/assertions.mjs assert-cutover-preinstalled +} + run_success_scenario() { echo "Testing ${KITCHEN_SINK_LABEL} install from ${KITCHEN_SINK_SPEC}..." - run_logged_print "kitchen-sink-install-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins install "$KITCHEN_SINK_SPEC" + local install_args=("$KITCHEN_SINK_SPEC") + if [ -n "${KITCHEN_SINK_PREINSTALL_SPEC:-}" ]; then + run_logged_print "kitchen-sink-preinstall-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins install "$KITCHEN_SINK_PREINSTALL_SPEC" + assert_kitchen_sink_cutover_preinstalled + install_args+=("--force") + fi + run_logged_print "kitchen-sink-install-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins install "${install_args[@]}" configure_kitchen_sink_runtime run_logged_print "kitchen-sink-enable-${KITCHEN_SINK_LABEL}" node "$OPENCLAW_ENTRY" plugins enable "$KITCHEN_SINK_ID" node "$OPENCLAW_ENTRY" plugins list --json >"/tmp/kitchen-sink-${KITCHEN_SINK_LABEL}-plugins.json" @@ -110,7 +120,7 @@ if [[ "$KITCHEN_SINK_SCENARIOS" == *"clawhub:"* ]] && fi scenario_count=0 -while IFS='|' read -r label spec plugin_id source expectation surface_mode personality; do +while IFS='|' read -r label spec plugin_id source expectation surface_mode personality preinstall_spec; do if [ -z "${label:-}" ] || [[ "$label" == \#* ]]; then continue fi @@ -122,6 +132,7 @@ while IFS='|' read -r label spec plugin_id source expectation surface_mode perso export KITCHEN_SINK_SURFACE_MODE="$surface_mode" export KITCHEN_SINK_PERSONALITY="${personality:-}" export OPENCLAW_KITCHEN_SINK_PERSONALITY="${personality:-}" + export KITCHEN_SINK_PREINSTALL_SPEC="${preinstall_spec:-}" case "$expectation" in success) run_success_scenario diff --git a/scripts/e2e/lib/plugins/clawhub.sh b/scripts/e2e/lib/plugins/clawhub.sh index c77c8688076..53392ebd73a 100644 --- a/scripts/e2e/lib/plugins/clawhub.sh +++ b/scripts/e2e/lib/plugins/clawhub.sh @@ -3,7 +3,7 @@ run_plugins_clawhub_scenario() { echo "Skipping ClawHub plugin install and uninstall (OPENCLAW_PLUGINS_E2E_CLAWHUB=0)." else echo "Testing ClawHub kitchen-sink plugin install and uninstall..." - CLAWHUB_PLUGIN_SPEC="${OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC:-clawhub:openclaw-kitchen-sink}" + CLAWHUB_PLUGIN_SPEC="${OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC:-clawhub:@openclaw/kitchen-sink}" CLAWHUB_PLUGIN_ID="${OPENCLAW_PLUGINS_E2E_CLAWHUB_ID:-openclaw-kitchen-sink-fixture}" export CLAWHUB_PLUGIN_SPEC CLAWHUB_PLUGIN_ID diff --git a/test/plugin-clawhub-release.test.ts b/test/plugin-clawhub-release.test.ts index 2c645e7238e..ebfbaecd2af 100644 --- a/test/plugin-clawhub-release.test.ts +++ b/test/plugin-clawhub-release.test.ts @@ -1,5 +1,5 @@ import { execFileSync } from "node:child_process"; -import { mkdirSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { @@ -11,7 +11,10 @@ import { resolveSelectedClawHubPublishablePluginPackages, type PublishablePluginPackage, } from "../scripts/lib/plugin-clawhub-release.ts"; -import { OPENCLAW_PLUGIN_NPM_REPOSITORY_URL } from "../scripts/lib/plugin-npm-release.ts"; +import { + collectPublishablePluginPackages, + OPENCLAW_PLUGIN_NPM_REPOSITORY_URL, +} from "../scripts/lib/plugin-npm-release.ts"; import { cleanupTempDirs, makeTempRepoRoot } from "./helpers/temp-repo.js"; const tempDirs: string[] = []; @@ -101,6 +104,60 @@ describe("collectClawHubPublishablePluginPackages", () => { }); }); +describe("OpenClaw ClawHub-preferred plugin metadata", () => { + const clawHubPreferredPlugins = [ + { + extensionId: "diagnostics-otel", + packageName: "@openclaw/diagnostics-otel", + }, + { + extensionId: "diagnostics-prometheus", + packageName: "@openclaw/diagnostics-prometheus", + }, + ] as const; + + it("keeps diagnostics plugins selectable through both ClawHub and npm release paths", () => { + const packageNames = clawHubPreferredPlugins.map((plugin) => plugin.packageName); + const clawHubPublishable = collectClawHubPublishablePluginPackages(undefined, { + packageNames, + }); + const npmPublishable = collectPublishablePluginPackages(undefined, { + packageNames, + }); + + expect(clawHubPublishable.map((plugin) => plugin.packageName)).toEqual(packageNames); + expect(npmPublishable.map((plugin) => plugin.packageName)).toEqual(packageNames); + + for (const plugin of clawHubPreferredPlugins) { + const packageJson = JSON.parse( + readFileSync(`extensions/${plugin.extensionId}/package.json`, "utf8"), + ) as { + openclaw?: { + install?: { + clawhubSpec?: string; + defaultChoice?: string; + npmSpec?: string; + }; + release?: { + publishToClawHub?: boolean; + publishToNpm?: boolean; + }; + }; + }; + + expect(packageJson.openclaw?.install).toMatchObject({ + clawhubSpec: `clawhub:${plugin.packageName}`, + defaultChoice: "clawhub", + npmSpec: plugin.packageName, + }); + expect(packageJson.openclaw?.release).toMatchObject({ + publishToClawHub: true, + publishToNpm: true, + }); + } + }); +}); + describe("collectClawHubVersionGateErrors", () => { it("requires a version bump when a publishable plugin changes", () => { const repoDir = createTempPluginRepo(); diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 3a5bf5ff105..3ae234a3049 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -305,6 +305,7 @@ describe("docker build helper", () => { expect(clawhub).toContain('plugins install "$CLAWHUB_PLUGIN_SPEC"'); expect(clawhub).toContain('plugins update "$CLAWHUB_PLUGIN_ID"'); + expect(clawhub).toContain("clawhub:@openclaw/kitchen-sink"); expect(assertions).toContain("clawhub-updated"); }); }); diff --git a/test/scripts/plugin-prerelease-test-plan.test.ts b/test/scripts/plugin-prerelease-test-plan.test.ts index 74acd2e3cfe..e971ab3412e 100644 --- a/test/scripts/plugin-prerelease-test-plan.test.ts +++ b/test/scripts/plugin-prerelease-test-plan.test.ts @@ -102,10 +102,16 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { expect(script).toContain("npm-latest-conformance"); expect(script).toContain("npm-latest-adversarial"); expect(script).toContain("npm:@openclaw/kitchen-sink@beta"); - expect(script).toContain("clawhub:openclaw-kitchen-sink@latest"); - expect(script).toContain("clawhub:openclaw-kitchen-sink@beta"); + expect(script).toContain("clawhub:@openclaw/kitchen-sink@latest"); + expect(script).toContain("clawhub:@openclaw/kitchen-sink@beta"); + expect(script).toContain( + "npm-to-clawhub|clawhub:@openclaw/kitchen-sink@latest|openclaw-kitchen-sink-fixture|clawhub|success|basic||npm:@openclaw/kitchen-sink@latest", + ); expect(script).toContain("scripts/e2e/lib/kitchen-sink-plugin/sweep.sh"); expect(sweepScript).toContain('plugins install "$KITCHEN_SINK_SPEC"'); + expect(sweepScript).toContain('plugins install "$KITCHEN_SINK_PREINSTALL_SPEC"'); + expect(sweepScript).toContain("assert-cutover-preinstalled"); + expect(sweepScript).toContain('install_args+=("--force")'); expect(sweepScript).toContain("KITCHEN_SINK_PERSONALITY"); expect(sweepScript).toContain("OPENCLAW_KITCHEN_SINK_PERSONALITY"); expect(sweepScript).toContain('plugins uninstall "$KITCHEN_SINK_SPEC" --force'); @@ -113,7 +119,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { sweepScript.indexOf("run_success_scenario()"), sweepScript.indexOf("run_failure_scenario()"), ); - expect(successScenario.indexOf('plugins install "$KITCHEN_SINK_SPEC"')).toBeLessThan( + expect(successScenario.indexOf('plugins install "${install_args[@]}"')).toBeLessThan( successScenario.indexOf("configure_kitchen_sink_runtime"), ); expect(successScenario.indexOf("configure_kitchen_sink_runtime")).toBeLessThan( @@ -122,6 +128,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => { expect(successScenario).toContain('plugins inspect "$KITCHEN_SINK_ID" --runtime --json'); expect(successScenario).toContain("plugins inspect --all --runtime --json"); expect(sweepScript).toContain("run_failure_scenario"); + expect(assertionsScript).toContain("assertCutoverPreinstalled"); expect(assertionsScript).toContain("record.source !== source"); expect(assertionsScript).toContain("record.clawhubPackage !== packageName"); expect(assertionsScript).toContain("assertClawHubExternalInstallContract");