From a6d25c1c2e977ffa6603d44fcea2b07f43b2a869 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 23:57:45 -0700 Subject: [PATCH] test(plugins): assert local install uninstall cleanup --- scripts/e2e/lib/plugins/assertions.mjs | 162 +++++++++++++++++++++---- scripts/e2e/lib/plugins/sweep.sh | 20 ++- 2 files changed, 159 insertions(+), 23 deletions(-) diff --git a/scripts/e2e/lib/plugins/assertions.mjs b/scripts/e2e/lib/plugins/assertions.mjs index 9643233c67f..5c043caeaaf 100644 --- a/scripts/e2e/lib/plugins/assertions.mjs +++ b/scripts/e2e/lib/plugins/assertions.mjs @@ -46,6 +46,50 @@ function assertPluginRemoved(params) { } } +function rememberPluginInstallPath(params) { + const record = getInstallRecords()[params.pluginId]; + if (!record) { + throw new Error(`missing install record for ${params.pluginId}`); + } + if (params.source && record.source !== params.source) { + throw new Error(`unexpected source for ${params.pluginId}: ${record.source}`); + } + if (params.sourcePath && record.sourcePath !== params.sourcePath) { + throw new Error( + `unexpected source path for ${params.pluginId}: ${record.sourcePath}, expected ${params.sourcePath}`, + ); + } + const installPath = record.installPath?.replace(/^~(?=$|\/)/u, process.env.HOME); + if (!installPath || !fs.existsSync(installPath)) { + throw new Error(`${params.pluginId} install path missing on disk: ${installPath}`); + } + fs.writeFileSync(params.installPathFile, installPath, "utf8"); + if (params.sourcePathFile && params.sourcePath) { + fs.writeFileSync(params.sourcePathFile, params.sourcePath, "utf8"); + } + return { installPath, record }; +} + +function assertManagedInstallRemoved(params) { + const installPath = fs.readFileSync(params.installPathFile, "utf8").trim(); + const sourcePath = + params.sourcePathFile && fs.existsSync(params.sourcePathFile) + ? fs.readFileSync(params.sourcePathFile, "utf8").trim() + : ""; + assertPluginRemoved({ + pluginId: params.pluginId, + listFile: params.listFile, + }); + if (sourcePath && !fs.existsSync(sourcePath)) { + throw new Error(`${params.pluginId} source path was deleted during uninstall: ${sourcePath}`); + } + if (installPath !== sourcePath && fs.existsSync(installPath)) { + throw new Error( + `${params.pluginId} managed install path still exists after uninstall: ${installPath}`, + ); + } +} + function recordFixturePluginTrust() { const pluginId = process.argv[3]; const pluginRoot = process.argv[4]; @@ -246,6 +290,54 @@ function assertMarketplaceRecords() { } } +function assertPluginTgz() { + assertSimplePlugin( + "/tmp/plugins2.json", + "/tmp/plugins2-inspect.json", + "demo-plugin-tgz", + "demo.tgz", + ); + rememberPluginInstallPath({ + pluginId: "demo-plugin-tgz", + installPathFile: "/tmp/plugins2-install-path.txt", + source: "archive", + }); +} + +function assertPluginTgzRemoved() { + assertManagedInstallRemoved({ + pluginId: "demo-plugin-tgz", + listFile: "/tmp/plugins2-uninstalled.json", + installPathFile: "/tmp/plugins2-install-path.txt", + }); +} + +function assertPluginDir() { + const sourceDir = process.argv[3]; + assertSimplePlugin( + "/tmp/plugins3.json", + "/tmp/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", + source: "path", + sourcePath: sourceDir, + }); +} + +function assertPluginDirRemoved() { + assertManagedInstallRemoved({ + pluginId: "demo-plugin-dir", + listFile: "/tmp/plugins3-uninstalled.json", + installPathFile: "/tmp/plugins3-install-path.txt", + sourcePathFile: "/tmp/plugins3-source-path.txt", + }); +} + function assertGitPlugin() { const repoUrl = process.argv[3]; const gitRef = process.argv[4]; @@ -403,6 +495,22 @@ function assertPluginDirDeps() { throw new Error(`missing copied local plugin dependency: ${dependencyPackagePath}`); } 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", + source: "path", + sourcePath: sourceDir, + }); +} + +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", + }); } function assertLocalPathUpdateSkipped() { @@ -463,6 +571,32 @@ function assertNpmPluginUpdateUnchanged() { assertNpmPlugin(); } +function assertPluginFile() { + const sourceDir = process.argv[3]; + assertSimplePlugin( + "/tmp/plugins4.json", + "/tmp/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", + source: "path", + sourcePath: sourceDir, + }); +} + +function assertPluginFileRemoved() { + assertManagedInstallRemoved({ + pluginId: "demo-plugin-file", + listFile: "/tmp/plugins4-uninstalled.json", + installPathFile: "/tmp/plugins4-install-path.txt", + sourcePathFile: "/tmp/plugins4-source-path.txt", + }); +} + function assertNpmPluginRemoved() { const installPath = fs.readFileSync("/tmp/plugins-npm-install-path.txt", "utf8").trim(); const dependencyPackagePath = fs @@ -689,29 +823,15 @@ function assertClawHubUpdated() { const commands = { "record-fixture-plugin-trust": recordFixturePluginTrust, "demo-plugin": assertDemoPlugin, - "plugin-tgz": () => - assertSimplePlugin( - "/tmp/plugins2.json", - "/tmp/plugins2-inspect.json", - "demo-plugin-tgz", - "demo.tgz", - ), - "plugin-dir": () => - assertSimplePlugin( - "/tmp/plugins3.json", - "/tmp/plugins3-inspect.json", - "demo-plugin-dir", - "demo.dir", - ), + "plugin-tgz": assertPluginTgz, + "plugin-tgz-removed": assertPluginTgzRemoved, + "plugin-dir": assertPluginDir, + "plugin-dir-removed": assertPluginDirRemoved, "plugin-dir-update-skipped": assertLocalPathUpdateSkipped, "plugin-dir-deps": assertPluginDirDeps, - "plugin-file": () => - assertSimplePlugin( - "/tmp/plugins4.json", - "/tmp/plugins4-inspect.json", - "demo-plugin-file", - "demo.file", - ), + "plugin-dir-deps-removed": assertPluginDirDepsRemoved, + "plugin-file": assertPluginFile, + "plugin-file-removed": assertPluginFileRemoved, "plugin-npm": assertNpmPlugin, "plugin-npm-update": assertNpmPluginUpdateUnchanged, "plugin-npm-removed": assertNpmPluginRemoved, diff --git a/scripts/e2e/lib/plugins/sweep.sh b/scripts/e2e/lib/plugins/sweep.sh index b4d20636928..5b39228bbc5 100644 --- a/scripts/e2e/lib/plugins/sweep.sh +++ b/scripts/e2e/lib/plugins/sweep.sh @@ -36,6 +36,10 @@ node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-tgz --runtime --json >/tmp/pl 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 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")" write_fixture_plugin "$dir_plugin" demo-plugin-dir 0.0.1 demo.dir "Demo Plugin DIR" @@ -44,11 +48,15 @@ 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 scripts/e2e/lib/plugins/assertions.mjs plugin-dir +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 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 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")" write_fixture_plugin_with_vendored_dependency "$dir_deps_plugin" demo-plugin-dir-deps 0.0.1 demo.dir.deps "Demo Plugin DIR Deps" @@ -59,6 +67,10 @@ node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-dir-deps --runtime --json >/t 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 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")" write_fixture_plugin "$file_pack_dir/package" demo-plugin-file 0.0.1 demo.file "Demo Plugin FILE" @@ -67,7 +79,11 @@ run_logged install-file node "$OPENCLAW_ENTRY" plugins install "file:$file_pack_ node "$OPENCLAW_ENTRY" plugins list --json >/tmp/plugins4.json node "$OPENCLAW_ENTRY" plugins inspect demo-plugin-file --runtime --json >/tmp/plugins4-inspect.json -node scripts/e2e/lib/plugins/assertions.mjs plugin-file +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 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")"