test(plugins): cover kitchen sink clawhub cutover

This commit is contained in:
Vincent Koc
2026-05-02 08:30:20 -07:00
parent 4eedc4723f
commit 33eebc29c3
8 changed files with 122 additions and 11 deletions

View File

@@ -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}"

View File

@@ -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 = {

View File

@@ -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,
};

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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");
});
});

View File

@@ -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");