fix(e2e): harden Windows plugin assertions

This commit is contained in:
Vincent Koc
2026-05-24 19:09:57 +02:00
parent 5f0315467b
commit 8edc671eb4
6 changed files with 226 additions and 139 deletions

View File

@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
- Channels/iMessage: advance the startup catchup cursor from live-handled rows after a completed catchup pass, including rows received while catchup is still running, so restarts do not replay them. (#85475) Thanks @TurboTheTurtle.
- Tests: mount the shared Windows command helper into bare Docker E2E harness containers so published upgrade-survivor config walks can start on Linux.
- Tests: let the generic plugin install E2E assertions use a configurable temp root and Windows home-relative install paths.
- Tests: keep kitchen-sink plugin assertion fixtures on a configurable temp root so native Windows runs no longer skip full-surface diagnostic coverage.
- Tests: fail Gateway startup benchmarks when a child startup never produces ready probes or process metrics instead of reporting all `n/a` samples as passing.
- Config/secrets: allow exec SecretRef ids to include `#` selectors so AWS-style `secret#json_key` ids validate consistently. (#80731) Thanks @TurboTheTurtle.

View File

@@ -1,8 +1,21 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const command = process.argv[2];
const scratchRoot = process.env.OPENCLAW_PLUGINS_TMP_DIR || os.tmpdir();
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const scratchFile = (name) => path.join(scratchRoot, name);
function resolveHomePath(value) {
if (value === "~") {
return process.env.HOME;
}
if (value?.startsWith("~/") || value?.startsWith("~\\")) {
return path.join(process.env.HOME, value.slice(2));
}
return value;
}
function getInstallRecords() {
const indexPath = path.join(process.env.HOME, ".openclaw", "plugins", "installs.json");
@@ -59,7 +72,7 @@ function rememberPluginInstallPath(params) {
`unexpected source path for ${params.pluginId}: ${record.sourcePath}, expected ${params.sourcePath}`,
);
}
const installPath = record.installPath?.replace(/^~(?=$|\/)/u, process.env.HOME);
const installPath = resolveHomePath(record.installPath);
if (!installPath || !fs.existsSync(installPath)) {
throw new Error(`${params.pluginId} install path missing on disk: ${installPath}`);
}
@@ -128,8 +141,8 @@ function recordFixturePluginTrust() {
}
function assertDemoPlugin() {
const data = readJson("/tmp/plugins.json");
const inspect = readJson("/tmp/plugins-inspect.json");
const data = readJson(scratchFile("plugins.json"));
const inspect = readJson(scratchFile("plugins-inspect.json"));
const plugin = (data.plugins || []).find((entry) => entry.id === "demo-plugin");
if (!plugin) {
throw new Error("plugin not found");
@@ -183,7 +196,7 @@ function assertUpdateOutput(logFile, expectedSnippet) {
}
function assertClaudeBundleDisabled() {
const data = readJson("/tmp/plugins-bundle-disabled.json");
const data = readJson(scratchFile("plugins-bundle-disabled.json"));
const plugin = (data.plugins || []).find((entry) => entry.id === "claude-bundle-e2e");
if (!plugin) {
throw new Error("Claude bundle plugin not found");
@@ -194,7 +207,7 @@ function assertClaudeBundleDisabled() {
}
function assertClaudeBundleInspect() {
const inspect = readJson("/tmp/plugins-bundle-inspect.json");
const inspect = readJson(scratchFile("plugins-bundle-inspect.json"));
if (inspect.plugin?.bundleFormat !== "claude") {
throw new Error(`expected Claude bundle format, got ${inspect.plugin?.bundleFormat}`);
}
@@ -206,7 +219,7 @@ function assertClaudeBundleInspect() {
}
function assertSlashInstall() {
const inspect = readJson("/tmp/plugin-command-install-show.json");
const inspect = readJson(scratchFile("plugin-command-install-show.json"));
if (inspect.plugin?.status !== "loaded") {
throw new Error(`expected loaded status after install, got ${inspect.plugin?.status}`);
}
@@ -226,7 +239,7 @@ function parseClawHubPackageName(rawSpec) {
}
function assertMarketplaceList() {
const data = readJson("/tmp/marketplace-list.json");
const data = readJson(scratchFile("marketplace-list.json"));
const names = (data.plugins || []).map((entry) => entry.name).toSorted();
if (data.name !== "Fixture Marketplace") {
throw new Error(`unexpected marketplace name: ${data.name}`);
@@ -237,9 +250,9 @@ function assertMarketplaceList() {
}
function assertMarketplaceInstalled() {
const data = readJson("/tmp/plugins-marketplace.json");
const shortcutInspect = readJson("/tmp/plugins-marketplace-shortcut-inspect.json");
const directInspect = readJson("/tmp/plugins-marketplace-direct-inspect.json");
const data = readJson(scratchFile("plugins-marketplace.json"));
const shortcutInspect = readJson(scratchFile("plugins-marketplace-shortcut-inspect.json"));
const directInspect = readJson(scratchFile("plugins-marketplace-direct-inspect.json"));
const getPlugin = (id) => {
const plugin = (data.plugins || []).find((entry) => entry.id === id);
if (!plugin) {
@@ -292,14 +305,14 @@ function assertMarketplaceRecords() {
function assertPluginTgz() {
assertSimplePlugin(
"/tmp/plugins2.json",
"/tmp/plugins2-inspect.json",
scratchFile("plugins2.json"),
scratchFile("plugins2-inspect.json"),
"demo-plugin-tgz",
"demo.tgz",
);
rememberPluginInstallPath({
pluginId: "demo-plugin-tgz",
installPathFile: "/tmp/plugins2-install-path.txt",
installPathFile: scratchFile("plugins2-install-path.txt"),
source: "archive",
});
}
@@ -307,23 +320,23 @@ function assertPluginTgz() {
function assertPluginTgzRemoved() {
assertManagedInstallRemoved({
pluginId: "demo-plugin-tgz",
listFile: "/tmp/plugins2-uninstalled.json",
installPathFile: "/tmp/plugins2-install-path.txt",
listFile: scratchFile("plugins2-uninstalled.json"),
installPathFile: scratchFile("plugins2-install-path.txt"),
});
}
function assertPluginDir() {
const sourceDir = process.argv[3];
assertSimplePlugin(
"/tmp/plugins3.json",
"/tmp/plugins3-inspect.json",
scratchFile("plugins3.json"),
scratchFile("plugins3-inspect.json"),
"demo-plugin-dir",
"demo.dir",
);
rememberPluginInstallPath({
pluginId: "demo-plugin-dir",
installPathFile: "/tmp/plugins3-install-path.txt",
sourcePathFile: "/tmp/plugins3-source-path.txt",
installPathFile: scratchFile("plugins3-install-path.txt"),
sourcePathFile: scratchFile("plugins3-source-path.txt"),
source: "path",
sourcePath: sourceDir,
});
@@ -332,9 +345,9 @@ function assertPluginDir() {
function assertPluginDirRemoved() {
assertManagedInstallRemoved({
pluginId: "demo-plugin-dir",
listFile: "/tmp/plugins3-uninstalled.json",
installPathFile: "/tmp/plugins3-install-path.txt",
sourcePathFile: "/tmp/plugins3-source-path.txt",
listFile: scratchFile("plugins3-uninstalled.json"),
installPathFile: scratchFile("plugins3-install-path.txt"),
sourcePathFile: scratchFile("plugins3-source-path.txt"),
});
}
@@ -342,18 +355,18 @@ function assertGitPlugin() {
const repoUrl = process.argv[3];
const gitRef = process.argv[4];
assertSimplePlugin(
"/tmp/plugins-git.json",
"/tmp/plugins-git-inspect.json",
scratchFile("plugins-git.json"),
scratchFile("plugins-git-inspect.json"),
"demo-plugin-git",
"demo.git",
);
const inspect = readJson("/tmp/plugins-git-inspect.json");
const inspect = readJson(scratchFile("plugins-git-inspect.json"));
if (!Array.isArray(inspect.cliCommands) || !inspect.cliCommands.includes("demo-git")) {
throw new Error(`expected demo-git cli command, got ${inspect.cliCommands?.join(", ")}`);
}
const cliOutput = fs.readFileSync("/tmp/plugins-git-cli.txt", "utf8");
const cliOutput = fs.readFileSync(scratchFile("plugins-git-cli.txt"), "utf8");
if (!cliOutput.includes("demo-plugin-git:pong")) {
throw new Error(`unexpected git plugin cli output: ${cliOutput.trim()}`);
}
@@ -378,7 +391,7 @@ function assertGitPlugin() {
throw new Error(`unexpected git spec: ${record.spec}`);
}
const installPath = record.installPath?.replace(/^~(?=$|\/)/u, process.env.HOME);
const installPath = resolveHomePath(record.installPath);
if (!installPath || !fs.existsSync(installPath)) {
throw new Error(`git install path missing on disk: ${installPath}`);
}
@@ -392,16 +405,16 @@ function assertGitPlugin() {
throw new Error(`missing git plugin installed dependency: ${dependencyPackagePath}`);
}
assertRealPathInside(installPath, dependencyPackagePath, "git plugin installed dependency");
fs.writeFileSync("/tmp/plugins-git-install-path.txt", installPath, "utf8");
fs.writeFileSync("/tmp/plugins-git-install-parent.txt", path.dirname(installPath), "utf8");
fs.writeFileSync(scratchFile("plugins-git-install-path.txt"), installPath, "utf8");
fs.writeFileSync(scratchFile("plugins-git-install-parent.txt"), path.dirname(installPath), "utf8");
}
function assertGitPluginRemoved() {
const installPath = fs.readFileSync("/tmp/plugins-git-install-path.txt", "utf8").trim();
const installParent = fs.readFileSync("/tmp/plugins-git-install-parent.txt", "utf8").trim();
const installPath = fs.readFileSync(scratchFile("plugins-git-install-path.txt"), "utf8").trim();
const installParent = fs.readFileSync(scratchFile("plugins-git-install-parent.txt"), "utf8").trim();
assertPluginRemoved({
pluginId: "demo-plugin-git",
listFile: "/tmp/plugins-git-uninstalled.json",
listFile: scratchFile("plugins-git-uninstalled.json"),
});
if (fs.existsSync(installPath)) {
throw new Error(`git managed repo still exists after uninstall: ${installPath}`);
@@ -470,8 +483,8 @@ function assertClawHubArtifactMetadata(record, pluginId) {
function assertPluginDirDeps() {
const sourceDir = process.argv[3];
assertSimplePlugin(
"/tmp/plugins-dir-deps.json",
"/tmp/plugins-dir-deps-inspect.json",
scratchFile("plugins-dir-deps.json"),
scratchFile("plugins-dir-deps-inspect.json"),
"demo-plugin-dir-deps",
"demo.dir.deps",
);
@@ -486,7 +499,7 @@ function assertPluginDirDeps() {
if (record.sourcePath !== sourceDir) {
throw new Error(`unexpected local dependency plugin source path: ${record.sourcePath}`);
}
const installPath = record.installPath?.replace(/^~(?=$|\/)/u, process.env.HOME);
const installPath = resolveHomePath(record.installPath);
if (!installPath || !fs.existsSync(installPath)) {
throw new Error(`local dependency plugin install path missing on disk: ${installPath}`);
}
@@ -497,8 +510,8 @@ function assertPluginDirDeps() {
assertRealPathInside(installPath, dependencyPackagePath, "local plugin copied dependency");
rememberPluginInstallPath({
pluginId: "demo-plugin-dir-deps",
installPathFile: "/tmp/plugins-dir-deps-install-path.txt",
sourcePathFile: "/tmp/plugins-dir-deps-source-path.txt",
installPathFile: scratchFile("plugins-dir-deps-install-path.txt"),
sourcePathFile: scratchFile("plugins-dir-deps-source-path.txt"),
source: "path",
sourcePath: sourceDir,
});
@@ -507,30 +520,33 @@ function assertPluginDirDeps() {
function assertPluginDirDepsRemoved() {
assertManagedInstallRemoved({
pluginId: "demo-plugin-dir-deps",
listFile: "/tmp/plugins-dir-deps-uninstalled.json",
installPathFile: "/tmp/plugins-dir-deps-install-path.txt",
sourcePathFile: "/tmp/plugins-dir-deps-source-path.txt",
listFile: scratchFile("plugins-dir-deps-uninstalled.json"),
installPathFile: scratchFile("plugins-dir-deps-install-path.txt"),
sourcePathFile: scratchFile("plugins-dir-deps-source-path.txt"),
});
}
function assertLocalPathUpdateSkipped() {
assertUpdateOutput("/tmp/plugins-dir-update.log", 'Skipping "demo-plugin-dir" (source: path).');
assertUpdateOutput(
scratchFile("plugins-dir-update.log"),
'Skipping "demo-plugin-dir" (source: path).',
);
}
function assertNpmPlugin() {
assertSimplePlugin(
"/tmp/plugins-npm.json",
"/tmp/plugins-npm-inspect.json",
scratchFile("plugins-npm.json"),
scratchFile("plugins-npm-inspect.json"),
"demo-plugin-npm",
"demo.npm",
);
const inspect = readJson("/tmp/plugins-npm-inspect.json");
const inspect = readJson(scratchFile("plugins-npm-inspect.json"));
if (!Array.isArray(inspect.cliCommands) || !inspect.cliCommands.includes("demo-npm")) {
throw new Error(`expected demo-npm cli command, got ${inspect.cliCommands?.join(", ")}`);
}
const cliOutput = fs.readFileSync("/tmp/plugins-npm-cli.txt", "utf8");
const cliOutput = fs.readFileSync(scratchFile("plugins-npm-cli.txt"), "utf8");
if (!cliOutput.includes("demo-plugin-npm:pong")) {
throw new Error(`unexpected npm plugin cli output: ${cliOutput.trim()}`);
}
@@ -551,7 +567,7 @@ function assertNpmPlugin() {
if (record.resolvedVersion !== "0.0.1") {
throw new Error(`unexpected npm resolved version: ${record.resolvedVersion}`);
}
const installPath = record.installPath?.replace(/^~(?=$|\/)/u, process.env.HOME);
const installPath = resolveHomePath(record.installPath);
if (!installPath || !fs.existsSync(installPath)) {
throw new Error(`npm install path missing on disk: ${installPath}`);
}
@@ -562,27 +578,27 @@ function assertNpmPlugin() {
throw new Error(`missing npm plugin installed dependency: ${dependencyPackagePath}`);
}
assertRealPathInside(npmRoot, dependencyPackagePath, "npm plugin installed dependency");
fs.writeFileSync("/tmp/plugins-npm-install-path.txt", installPath, "utf8");
fs.writeFileSync("/tmp/plugins-npm-dependency-path.txt", dependencyPackagePath, "utf8");
fs.writeFileSync(scratchFile("plugins-npm-install-path.txt"), installPath, "utf8");
fs.writeFileSync(scratchFile("plugins-npm-dependency-path.txt"), dependencyPackagePath, "utf8");
}
function assertNpmPluginUpdateUnchanged() {
assertUpdateOutput("/tmp/plugins-npm-update.log", "demo-plugin-npm is up to date (0.0.1).");
assertUpdateOutput(scratchFile("plugins-npm-update.log"), "demo-plugin-npm is up to date (0.0.1).");
assertNpmPlugin();
}
function assertPluginFile() {
const sourceDir = process.argv[3];
assertSimplePlugin(
"/tmp/plugins4.json",
"/tmp/plugins4-inspect.json",
scratchFile("plugins4.json"),
scratchFile("plugins4-inspect.json"),
"demo-plugin-file",
"demo.file",
);
rememberPluginInstallPath({
pluginId: "demo-plugin-file",
installPathFile: "/tmp/plugins4-install-path.txt",
sourcePathFile: "/tmp/plugins4-source-path.txt",
installPathFile: scratchFile("plugins4-install-path.txt"),
sourcePathFile: scratchFile("plugins4-source-path.txt"),
source: "path",
sourcePath: sourceDir,
});
@@ -591,20 +607,20 @@ function assertPluginFile() {
function assertPluginFileRemoved() {
assertManagedInstallRemoved({
pluginId: "demo-plugin-file",
listFile: "/tmp/plugins4-uninstalled.json",
installPathFile: "/tmp/plugins4-install-path.txt",
sourcePathFile: "/tmp/plugins4-source-path.txt",
listFile: scratchFile("plugins4-uninstalled.json"),
installPathFile: scratchFile("plugins4-install-path.txt"),
sourcePathFile: scratchFile("plugins4-source-path.txt"),
});
}
function assertNpmPluginRemoved() {
const installPath = fs.readFileSync("/tmp/plugins-npm-install-path.txt", "utf8").trim();
const installPath = fs.readFileSync(scratchFile("plugins-npm-install-path.txt"), "utf8").trim();
const dependencyPackagePath = fs
.readFileSync("/tmp/plugins-npm-dependency-path.txt", "utf8")
.readFileSync(scratchFile("plugins-npm-dependency-path.txt"), "utf8")
.trim();
assertPluginRemoved({
pluginId: "demo-plugin-npm",
listFile: "/tmp/plugins-npm-uninstalled.json",
listFile: scratchFile("plugins-npm-uninstalled.json"),
});
if (fs.existsSync(installPath)) {
throw new Error(`npm managed package still exists after uninstall: ${installPath}`);
@@ -618,7 +634,7 @@ function assertNpmPluginRemoved() {
function assertInvalidOpenClawExtensionsRejected() {
const pluginId = "demo-plugin-invalid-metadata";
const output = fs.readFileSync("/tmp/plugins-invalid-openclaw-extensions.log", "utf8");
const output = fs.readFileSync(scratchFile("plugins-invalid-openclaw-extensions.log"), "utf8");
for (const expected of ["openclaw.extensions[1]", "non-empty string"]) {
if (!output.includes(expected)) {
throw new Error(
@@ -627,7 +643,7 @@ function assertInvalidOpenClawExtensionsRejected() {
}
}
const list = readJson("/tmp/plugins-invalid-openclaw-extensions-list.json");
const list = readJson(scratchFile("plugins-invalid-openclaw-extensions-list.json"));
if ((list.plugins || []).some((entry) => entry.id === pluginId)) {
throw new Error(`${pluginId} listed after rejected install`);
}
@@ -644,8 +660,8 @@ function assertInvalidOpenClawExtensionsRejected() {
}
function assertMarketplaceUpdated() {
const data = readJson("/tmp/plugins-marketplace-updated.json");
const inspect = readJson("/tmp/plugins-marketplace-updated-inspect.json");
const data = readJson(scratchFile("plugins-marketplace-updated.json"));
const inspect = readJson(scratchFile("plugins-marketplace-updated-inspect.json"));
const plugin = (data.plugins || []).find((entry) => entry.id === "marketplace-shortcut");
if (!plugin) {
throw new Error("updated marketplace plugin not found");
@@ -661,18 +677,18 @@ function assertMarketplaceUpdated() {
function assertGitPluginUpdated() {
const beforeCommit = process.argv[3];
assertSimplePlugin(
"/tmp/plugins-git-update.json",
"/tmp/plugins-git-update-inspect.json",
scratchFile("plugins-git-update.json"),
scratchFile("plugins-git-update-inspect.json"),
"demo-plugin-git-update",
"demo.git.update.v2",
);
const inspect = readJson("/tmp/plugins-git-update-inspect.json");
const inspect = readJson(scratchFile("plugins-git-update-inspect.json"));
if (!Array.isArray(inspect.cliCommands) || !inspect.cliCommands.includes("demo-git-update")) {
throw new Error(`expected demo-git-update cli command, got ${inspect.cliCommands?.join(", ")}`);
}
const cliOutput = fs.readFileSync("/tmp/plugins-git-update-cli.txt", "utf8");
const cliOutput = fs.readFileSync(scratchFile("plugins-git-update-cli.txt"), "utf8");
if (!cliOutput.includes("demo-plugin-git-update:pong-v2")) {
throw new Error(`unexpected updated git plugin cli output: ${cliOutput.trim()}`);
}
@@ -696,7 +712,7 @@ function assertGitPluginUpdated() {
throw new Error(`unexpected git update version: ${record.version}`);
}
assertUpdateOutput(
"/tmp/plugins-git-update.log",
scratchFile("plugins-git-update.log"),
"Updated demo-plugin-git-update: 0.0.1 -> 0.0.2.",
);
}
@@ -744,8 +760,8 @@ function assertClawHubInstalled() {
const pluginId = process.env.CLAWHUB_PLUGIN_ID;
const spec = process.env.CLAWHUB_PLUGIN_SPEC;
const packageName = parseClawHubPackageName(spec);
const list = readJson("/tmp/plugins-clawhub-installed.json");
const inspect = readJson("/tmp/plugins-clawhub-inspect.json");
const list = readJson(scratchFile("plugins-clawhub-installed.json"));
const inspect = readJson(scratchFile("plugins-clawhub-inspect.json"));
const plugin = (list.plugins || []).find((entry) => entry.id === pluginId);
if (!plugin) {
throw new Error(`ClawHub plugin not found after install: ${pluginId}`);
@@ -788,7 +804,7 @@ function assertClawHubInstalled() {
}
assertClawHubArtifactMetadata(record, pluginId);
const installPath = record.installPath.replace(/^~(?=$|\/)/u, process.env.HOME);
const installPath = resolveHomePath(record.installPath);
const extensionsRoot = path.join(process.env.HOME, ".openclaw", "extensions");
if (!installPath.startsWith(`${extensionsRoot}${path.sep}`)) {
throw new Error(`ClawHub install path is outside managed extensions root: ${installPath}`);
@@ -799,13 +815,13 @@ function assertClawHubInstalled() {
if (record.artifactKind === "npm-pack") {
assertClawHubExternalInstallContract(installPath);
}
fs.writeFileSync("/tmp/plugins-clawhub-install-path.txt", installPath, "utf8");
fs.writeFileSync(scratchFile("plugins-clawhub-install-path.txt"), installPath, "utf8");
}
function assertClawHubRemoved() {
const pluginId = process.env.CLAWHUB_PLUGIN_ID;
const installPath = fs.readFileSync("/tmp/plugins-clawhub-install-path.txt", "utf8").trim();
const list = readJson("/tmp/plugins-clawhub-uninstalled.json");
const installPath = fs.readFileSync(scratchFile("plugins-clawhub-install-path.txt"), "utf8").trim();
const list = readJson(scratchFile("plugins-clawhub-uninstalled.json"));
if ((list.plugins || []).some((entry) => entry.id === pluginId)) {
throw new Error(`ClawHub plugin still listed after uninstall: ${pluginId}`);
}
@@ -840,7 +856,7 @@ function assertClawHubRemoved() {
}
function assertClawHubUpdated() {
const output = fs.readFileSync("/tmp/plugins-clawhub-update.log", "utf8");
const output = fs.readFileSync(scratchFile("plugins-clawhub-update.log"), "utf8");
if (!output.includes(`${process.env.CLAWHUB_PLUGIN_ID} already at `)) {
throw new Error(`expected ClawHub update to report already-at version:\n${output}`);
}

View File

@@ -44,26 +44,26 @@ run_plugins_clawhub_scenario() {
echo "Ignoring ambient ClawHub URL for fixture-mode plugin E2E; set OPENCLAW_PLUGINS_E2E_LIVE_CLAWHUB=1 for live ClawHub."
fi
unset OPENCLAW_CLAWHUB_URL CLAWHUB_URL
clawhub_fixture_dir="$(mktemp -d "/tmp/openclaw-clawhub-fixture.XXXXXX")"
clawhub_fixture_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-clawhub-fixture.XXXXXX")"
start_clawhub_fixture_server "$clawhub_fixture_dir"
fi
node scripts/e2e/lib/plugins/assertions.mjs clawhub-preflight
run_logged install-clawhub node "$OPENCLAW_ENTRY" plugins install "$CLAWHUB_PLUGIN_SPEC"
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-clawhub-installed.json
node "$OPENCLAW_ENTRY" plugins inspect "$CLAWHUB_PLUGIN_ID" --json >/tmp/plugins-clawhub-inspect.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-installed.json"
node "$OPENCLAW_ENTRY" plugins inspect "$CLAWHUB_PLUGIN_ID" --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs clawhub-installed
node "$OPENCLAW_ENTRY" plugins update "$CLAWHUB_PLUGIN_ID" >/tmp/plugins-clawhub-update.log 2>&1
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-clawhub-updated.json
node "$OPENCLAW_ENTRY" plugins inspect "$CLAWHUB_PLUGIN_ID" --json >/tmp/plugins-clawhub-updated-inspect.json
node "$OPENCLAW_ENTRY" plugins update "$CLAWHUB_PLUGIN_ID" >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-update.log" 2>&1
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-updated.json"
node "$OPENCLAW_ENTRY" plugins inspect "$CLAWHUB_PLUGIN_ID" --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-updated-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs clawhub-updated
run_logged uninstall-clawhub node "$OPENCLAW_ENTRY" plugins uninstall "$CLAWHUB_PLUGIN_SPEC" --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-clawhub-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-clawhub-uninstalled.json"
node scripts/e2e/lib/plugins/assertions.mjs clawhub-removed
fi

View File

@@ -16,15 +16,15 @@ run_plugins_marketplace_scenario() {
"Marketplace Direct"
node scripts/e2e/lib/fixture.mjs marketplace "$marketplace_root"
node "$OPENCLAW_ENTRY" plugins marketplace list claude-fixtures --json >/tmp/marketplace-list.json
node "$OPENCLAW_ENTRY" plugins marketplace list claude-fixtures --json >"$OPENCLAW_PLUGINS_TMP_DIR/marketplace-list.json"
node scripts/e2e/lib/plugins/assertions.mjs marketplace-list
run_logged install-marketplace-shortcut node "$OPENCLAW_ENTRY" plugins install marketplace-shortcut@claude-fixtures
run_logged install-marketplace-direct node "$OPENCLAW_ENTRY" plugins install marketplace-direct --marketplace claude-fixtures
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-marketplace.json
node "$OPENCLAW_ENTRY" plugins inspect marketplace-shortcut --runtime --json >/tmp/plugins-marketplace-shortcut-inspect.json
node "$OPENCLAW_ENTRY" plugins inspect marketplace-direct --runtime --json >/tmp/plugins-marketplace-direct-inspect.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace.json"
node "$OPENCLAW_ENTRY" plugins inspect marketplace-shortcut --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-shortcut-inspect.json"
node "$OPENCLAW_ENTRY" plugins inspect marketplace-direct --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-direct-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs marketplace-installed
@@ -38,8 +38,8 @@ run_plugins_marketplace_scenario() {
"Marketplace Shortcut"
run_logged update-marketplace-shortcut-dry-run node "$OPENCLAW_ENTRY" plugins update marketplace-shortcut --dry-run
run_logged update-marketplace-shortcut node "$OPENCLAW_ENTRY" plugins update marketplace-shortcut
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-marketplace-updated.json
node "$OPENCLAW_ENTRY" plugins inspect marketplace-shortcut --runtime --json >/tmp/plugins-marketplace-updated-inspect.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-updated.json"
node "$OPENCLAW_ENTRY" plugins inspect marketplace-shortcut --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-marketplace-updated-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs marketplace-updated
}

View File

@@ -5,6 +5,8 @@ source scripts/lib/openclaw-e2e-instance.sh
source scripts/lib/docker-e2e-logs.sh
OPENCLAW_ENTRY="$(openclaw_e2e_resolve_entrypoint)"
export OPENCLAW_ENTRY
export OPENCLAW_PLUGINS_TMP_DIR="${OPENCLAW_PLUGINS_TMP_DIR:-/tmp}"
mkdir -p "$OPENCLAW_PLUGINS_TMP_DIR"
PACKAGE_VERSION="$(node -p 'require("./package.json").version')"
OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT="$(node scripts/e2e/lib/package-compat.mjs "$PACKAGE_VERSION")"
export OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT
@@ -21,105 +23,105 @@ demo_plugin_root="$OPENCLAW_PLUGIN_HOME/$demo_plugin_id"
write_demo_fixture_plugin "$demo_plugin_root"
record_fixture_plugin_trust "$demo_plugin_id" "$demo_plugin_root" 1
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin --runtime --json >/tmp/plugins-inspect.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs demo-plugin
echo "Testing tgz install flow..."
pack_dir="$(mktemp -d "/tmp/openclaw-plugin-pack.XXXXXX")"
pack_fixture_plugin "$pack_dir" /tmp/demo-plugin-tgz.tgz demo-plugin-tgz 0.0.1 demo.tgz "Demo Plugin TGZ"
pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-pack.XXXXXX")"
pack_fixture_plugin "$pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-tgz.tgz" demo-plugin-tgz 0.0.1 demo.tgz "Demo Plugin TGZ"
run_logged install-tgz node "$OPENCLAW_ENTRY" plugins install /tmp/demo-plugin-tgz.tgz
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins2.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-tgz --runtime --json >/tmp/plugins2-inspect.json
run_logged install-tgz node "$OPENCLAW_ENTRY" plugins install "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-tgz.tgz"
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins2.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-tgz --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins2-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-tgz
run_logged uninstall-tgz node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-tgz --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins2-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins2-uninstalled.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-tgz-removed
echo "Testing install from local folder (plugins.load.paths)..."
dir_plugin="$(mktemp -d "/tmp/openclaw-plugin-dir.XXXXXX")"
dir_plugin="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-dir.XXXXXX")"
write_fixture_plugin "$dir_plugin" demo-plugin-dir 0.0.1 demo.dir "Demo Plugin DIR"
run_logged install-dir node "$OPENCLAW_ENTRY" plugins install "$dir_plugin"
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins3.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-dir --runtime --json >/tmp/plugins3-inspect.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins3.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-dir --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins3-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir "$dir_plugin"
node "$OPENCLAW_ENTRY" plugins update demo-plugin-dir >/tmp/plugins-dir-update.log 2>&1
node "$OPENCLAW_ENTRY" plugins update demo-plugin-dir >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-update.log" 2>&1
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-update-skipped
run_logged uninstall-dir node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-dir --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins3-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins3-uninstalled.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-removed
echo "Testing install from local folder with preinstalled dependencies..."
dir_deps_plugin="$(mktemp -d "/tmp/openclaw-plugin-dir-deps.XXXXXX")"
dir_deps_plugin="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/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 "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-dir-deps --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-deps "$dir_deps_plugin"
run_logged uninstall-dir-deps node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-dir-deps --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-dir-deps-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-dir-deps-uninstalled.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-dir-deps-removed
echo "Testing install from npm spec (file:)..."
file_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-filepack.XXXXXX")"
file_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-filepack.XXXXXX")"
write_fixture_plugin "$file_pack_dir/package" demo-plugin-file 0.0.1 demo.file "Demo Plugin FILE"
run_logged install-file node "$OPENCLAW_ENTRY" plugins install "file:$file_pack_dir/package"
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins4.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-file --runtime --json >/tmp/plugins4-inspect.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins4.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-file --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins4-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-file "$file_pack_dir/package"
run_logged uninstall-file node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-file --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins4-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins4-uninstalled.json"
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
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
npm_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-npm-pack.XXXXXX")"
npm_dep_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-npm-dep-pack.XXXXXX")"
invalid_npm_pack_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-invalid-metadata-pack.XXXXXX")"
npm_registry_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-npm-registry.XXXXXX")"
pack_fixture_plugin_with_cli_registry_dependency "$npm_pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/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" "$OPENCLAW_PLUGINS_TMP_DIR/is-number-7.0.0.tgz"
pack_fixture_plugin_with_invalid_extension_entry "$invalid_npm_pack_dir" "$OPENCLAW_PLUGINS_TMP_DIR/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" "$OPENCLAW_PLUGINS_TMP_DIR/demo-plugin-npm.tgz" "$npm_registry_dir" "is-number" "7.0.0" "$OPENCLAW_PLUGINS_TMP_DIR/is-number-7.0.0.tgz" "@openclaw/demo-plugin-invalid-metadata" "0.0.1" "$OPENCLAW_PLUGINS_TMP_DIR/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
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-npm --runtime --json >/tmp/plugins-npm-inspect.json
run_logged exec-npm-plugin-cli bash -c 'node "$OPENCLAW_ENTRY" demo-npm ping >/tmp/plugins-npm-cli.txt'
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-npm --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-inspect.json"
run_logged exec-npm-plugin-cli bash -c 'node "$OPENCLAW_ENTRY" demo-npm ping >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-cli.txt"'
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm
node "$OPENCLAW_ENTRY" plugins update demo-plugin-npm >/tmp/plugins-npm-update.log 2>&1
node "$OPENCLAW_ENTRY" plugins update demo-plugin-npm >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-npm-update.log" 2>&1
node scripts/e2e/lib/plugins/assertions.mjs plugin-npm-update
run_logged uninstall-npm node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-npm --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-npm-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/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
if node "$OPENCLAW_ENTRY" plugins install "npm:@openclaw/demo-plugin-invalid-metadata@0.0.1" >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-invalid-openclaw-extensions.log" 2>&1; then
cat "$OPENCLAW_PLUGINS_TMP_DIR/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 "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/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_fixture_root="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-git.XXXXXX")"
git_repo="$git_fixture_root/repo"
git_repo_url="file://$git_repo"
write_fixture_plugin_with_cli "$git_repo" demo-plugin-git 0.0.1 demo.git "Demo Plugin Git" demo-git "demo-plugin-git:pong"
@@ -131,18 +133,18 @@ git -C "$git_repo" commit -qm "test fixture"
git_ref="$(git -C "$git_repo" rev-parse HEAD)"
run_logged install-git node "$OPENCLAW_ENTRY" plugins install "git:$git_repo_url@$git_ref"
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-git.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-git --runtime --json >/tmp/plugins-git-inspect.json
run_logged exec-git-plugin-cli bash -c 'node "$OPENCLAW_ENTRY" demo-git ping >/tmp/plugins-git-cli.txt'
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-git --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-inspect.json"
run_logged exec-git-plugin-cli bash -c 'node "$OPENCLAW_ENTRY" demo-git ping >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-cli.txt"'
node scripts/e2e/lib/plugins/assertions.mjs plugin-git "$git_repo_url" "$git_ref"
run_logged uninstall-git node "$OPENCLAW_ENTRY" plugins uninstall demo-plugin-git --force
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-git-uninstalled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-uninstalled.json"
node scripts/e2e/lib/plugins/assertions.mjs plugin-git-removed
echo "Testing git plugin update from moving ref..."
git_update_fixture_root="$(mktemp -d "/tmp/openclaw-plugin-git-update.XXXXXX")"
git_update_fixture_root="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-git-update.XXXXXX")"
git_update_repo="$git_update_fixture_root/repo"
git_update_repo_url="file://$git_update_repo"
write_fixture_plugin_with_cli "$git_update_repo" demo-plugin-git-update 0.0.1 demo.git.update.v1 "Demo Plugin Git Update" demo-git-update "demo-plugin-git-update:pong-v1"
@@ -159,10 +161,10 @@ write_fixture_plugin_with_cli "$git_update_repo" demo-plugin-git-update 0.0.2 de
git -C "$git_update_repo" add -A
git -C "$git_update_repo" commit -qm "test fixture v2"
node "$OPENCLAW_ENTRY" plugins update demo-plugin-git-update >/tmp/plugins-git-update.log 2>&1
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-git-update.json
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-git-update --runtime --json >/tmp/plugins-git-update-inspect.json
run_logged exec-updated-git-plugin-cli bash -c 'node "$OPENCLAW_ENTRY" demo-git-update ping >/tmp/plugins-git-update-cli.txt'
node "$OPENCLAW_ENTRY" plugins update demo-plugin-git-update >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update.log" 2>&1
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update.json"
node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-git-update --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update-inspect.json"
run_logged exec-updated-git-plugin-cli bash -c 'node "$OPENCLAW_ENTRY" demo-git-update ping >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-git-update-cli.txt"'
node scripts/e2e/lib/plugins/assertions.mjs plugin-git-updated "$git_update_ref_v1"
@@ -172,19 +174,19 @@ bundle_root="$OPENCLAW_PLUGIN_HOME/$bundle_plugin_id"
write_claude_bundle_fixture "$bundle_root"
record_fixture_plugin_trust "$bundle_plugin_id" "$bundle_root" 0
node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins-bundle-disabled.json
node "$OPENCLAW_ENTRY" plugins list --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-bundle-disabled.json"
node scripts/e2e/lib/plugins/assertions.mjs bundle-disabled
run_logged enable-claude-bundle node "$OPENCLAW_ENTRY" plugins enable claude-bundle-e2e
node "$OPENCLAW_ENTRY" plugins inspect claude-bundle-e2e --json >/tmp/plugins-bundle-inspect.json
node "$OPENCLAW_ENTRY" plugins inspect claude-bundle-e2e --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugins-bundle-inspect.json"
node scripts/e2e/lib/plugins/assertions.mjs bundle-inspect
echo "Testing plugin install visible after explicit restart..."
slash_install_dir="$(mktemp -d "/tmp/openclaw-plugin-slash-install.XXXXXX")"
slash_install_dir="$(mktemp -d "$OPENCLAW_PLUGINS_TMP_DIR/openclaw-plugin-slash-install.XXXXXX")"
write_fixture_plugin "$slash_install_dir" slash-install-plugin 0.0.1 demo.slash.install "Slash Install Plugin"
run_logged install-slash-plugin node "$OPENCLAW_ENTRY" plugins install "$slash_install_dir"
node "$OPENCLAW_ENTRY" plugins inspect slash-install-plugin --runtime --json >/tmp/plugin-command-install-show.json
node "$OPENCLAW_ENTRY" plugins inspect slash-install-plugin --runtime --json >"$OPENCLAW_PLUGINS_TMP_DIR/plugin-command-install-show.json"
node scripts/e2e/lib/plugins/assertions.mjs slash-install
run_plugins_marketplace_scenario

View File

@@ -0,0 +1,68 @@
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
const ASSERTIONS_SCRIPT = "scripts/e2e/lib/plugins/assertions.mjs";
function writeJson(filePath: string, value: unknown) {
mkdirSync(path.dirname(filePath), { recursive: true });
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
describe("plugins Docker assertions", () => {
it("keeps sweep artifact paths aligned with the assertion scratch root", () => {
const scripts = [
"scripts/e2e/lib/plugins/sweep.sh",
"scripts/e2e/lib/plugins/marketplace.sh",
"scripts/e2e/lib/plugins/clawhub.sh",
];
for (const scriptPath of scripts) {
const script = readFileSync(scriptPath, "utf8");
expect(script).toContain("OPENCLAW_PLUGINS_TMP_DIR");
expect(script).not.toMatch(
/\/tmp\/(?:plugins|marketplace|demo-plugin|is-number|openclaw-plugin|openclaw-clawhub)/,
);
}
});
it("uses the configured scratch root and resolves Windows home-relative install paths", () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugins-assertions-"));
const home = path.join(root, "home");
const scratchRoot = path.join(root, "scratch");
const installPath = path.join(home, "managed-plugin");
mkdirSync(installPath, { recursive: true });
try {
writeJson(path.join(scratchRoot, "plugins2.json"), {
plugins: [{ id: "demo-plugin-tgz", status: "loaded" }],
});
writeJson(path.join(scratchRoot, "plugins2-inspect.json"), {
gatewayMethods: ["demo.tgz"],
});
writeJson(path.join(home, ".openclaw", "plugins", "installs.json"), {
installRecords: {
"demo-plugin-tgz": {
source: "archive",
installPath: String.raw`~\managed-plugin`,
},
},
});
const result = spawnSync(process.execPath, [ASSERTIONS_SCRIPT, "plugin-tgz"], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
OPENCLAW_PLUGINS_TMP_DIR: scratchRoot,
},
});
expect(result.status).toBe(0);
} finally {
rmSync(root, { force: true, recursive: true });
}
});
});