diff --git a/scripts/e2e/lib/plugins/assertions.mjs b/scripts/e2e/lib/plugins/assertions.mjs index d9f7dd7f6c77..38a72ef9250e 100644 --- a/scripts/e2e/lib/plugins/assertions.mjs +++ b/scripts/e2e/lib/plugins/assertions.mjs @@ -115,8 +115,8 @@ function pathsEqual(left, right) { } function getInstallRecords() { - const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json"); - const config = fs.existsSync(configPath) ? readJson(configPath) : {}; + const configPath = openClawConfigPath(); + const config = readOpenClawConfig(); const allowLegacyCompat = process.env.OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT === "1"; const index = readPluginInstallIndex({ configPath, @@ -128,9 +128,23 @@ function getInstallRecords() { return index.installRecords ?? {}; } +function openClawConfigPath() { + return path.join(process.env.HOME, ".openclaw", "openclaw.json"); +} + function readOpenClawConfig() { - const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json"); - return fs.existsSync(configPath) ? readJson(configPath) : {}; + const configPath = openClawConfigPath(); + return fs.existsSync(configPath) ? readRequiredOpenClawConfig() : {}; +} + +function readRequiredOpenClawConfig() { + const configPath = openClawConfigPath(); + try { + return readJson(configPath); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`failed to read OpenClaw config ${configPath}: ${message}`, { cause: error }); + } } function assertPluginRemoved(params) { diff --git a/test/scripts/plugins-assertions.test.ts b/test/scripts/plugins-assertions.test.ts index ef9ef4a30527..4ecd5769d28d 100644 --- a/test/scripts/plugins-assertions.test.ts +++ b/test/scripts/plugins-assertions.test.ts @@ -413,6 +413,40 @@ test -d "$OPENCLAW_PLUGINS_TMP_DIR" } }); + it("rejects unreadable config during plugin uninstall proof", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-plugins-assertions-")); + const home = path.join(root, "home"); + const scratchRoot = path.join(root, "scratch"); + const removedInstallPath = path.join(home, ".openclaw", "extensions", "demo-plugin-tgz"); + + try { + writeJson(path.join(scratchRoot, "plugins2-uninstalled.json"), { plugins: [] }); + writeFileSync( + path.join(scratchRoot, "plugins2-install-path.txt"), + removedInstallPath, + "utf8", + ); + writeJson(path.join(home, ".openclaw", "plugins", "installs.json"), { + installRecords: {}, + }); + writeFileSync(path.join(home, ".openclaw", "openclaw.json"), "{ malformed\n", "utf8"); + + const result = spawnSync(process.execPath, [ASSERTIONS_SCRIPT, "plugin-tgz-removed"], { + encoding: "utf8", + env: { + ...process.env, + HOME: home, + OPENCLAW_PLUGINS_TMP_DIR: scratchRoot, + }, + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("failed to read OpenClaw config"); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + it("times out stalled ClawHub package metadata requests", async () => { const server = createServer((_request, _response) => {}); await new Promise((resolve) => {