From b8f071a13903622cd86abd59ea32731e26c7767c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 20:21:07 +0100 Subject: [PATCH] fix: isolate bundled plugin test roots (#73235) (thanks @zqchris) --- src/gateway/server.cron.test.ts | 1 + .../server.models-voicewake-misc.test.ts | 8 +++++--- src/plugins/bundled-dir.test.ts | 1 + src/plugins/bundled-dir.ts | 18 ++++++++++++++++++ src/plugins/bundled-runtime-deps.ts | 12 ++++++++++-- test/setup.shared.ts | 3 +++ 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/gateway/server.cron.test.ts b/src/gateway/server.cron.test.ts index 6a9528f86fd..584babcec16 100644 --- a/src/gateway/server.cron.test.ts +++ b/src/gateway/server.cron.test.ts @@ -795,6 +795,7 @@ describe("gateway server cron", () => { }); test("ignores ambient disabled channel env when validating announce delivery", async () => { + vi.stubEnv("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1"); vi.stubEnv("SLACK_BOT_TOKEN", "xoxb-ambient"); vi.stubEnv("TELEGRAM_BOT_TOKEN", "ambient-telegram"); const { prevSkipCron } = await setupCronTestRun({ diff --git a/src/gateway/server.models-voicewake-misc.test.ts b/src/gateway/server.models-voicewake-misc.test.ts index c53c87d2062..0e8e63634a1 100644 --- a/src/gateway/server.models-voicewake-misc.test.ts +++ b/src/gateway/server.models-voicewake-misc.test.ts @@ -147,9 +147,11 @@ const expectedSortedCatalog = (): ModelCatalogRpcEntry[] => [ describe("gateway server models + voicewake", () => { const listModels = async (params?: { view?: "default" | "configured" | "all" }) => - params - ? rpcReq<{ models: ModelCatalogRpcEntry[] }>(ws, "models.list", params) - : rpcReq<{ models: ModelCatalogRpcEntry[] }>(ws, "models.list"); + withEnvAsync({ OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1" }, async () => + params + ? await rpcReq<{ models: ModelCatalogRpcEntry[] }>(ws, "models.list", params) + : await rpcReq<{ models: ModelCatalogRpcEntry[] }>(ws, "models.list"), + ); const seedPiCatalog = () => { piSdkMock.enabled = true; diff --git a/src/plugins/bundled-dir.test.ts b/src/plugins/bundled-dir.test.ts index 65bf9837ba2..61657b5ecd0 100644 --- a/src/plugins/bundled-dir.test.ts +++ b/src/plugins/bundled-dir.test.ts @@ -330,6 +330,7 @@ describe("resolveBundledPluginsDir", () => { process.execArgv.length = 0; process.env.VITEST = "true"; process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(overrideRoot, "extensions"); + delete process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR; delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; const bundledDir = resolveBundledPluginsDir(); diff --git a/src/plugins/bundled-dir.ts b/src/plugins/bundled-dir.ts index 76cf6070d5d..4e6ceda56b2 100644 --- a/src/plugins/bundled-dir.ts +++ b/src/plugins/bundled-dir.ts @@ -7,6 +7,7 @@ import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; import { resolveUserPath } from "../utils.js"; const DISABLED_BUNDLED_PLUGINS_DIR = path.join(os.tmpdir(), "openclaw-empty-bundled-plugins"); +const TEST_TRUST_BUNDLED_PLUGINS_DIR_ENV = "OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR"; let bundledPluginsDirOverrideForTest: string | undefined; export function areBundledPluginsDisabled(env: NodeJS.ProcessEnv = process.env): boolean { @@ -27,6 +28,20 @@ function isSourceCheckoutRoot(packageRoot: string): boolean { ); } +function isTruthyEnvValue(value: string | undefined): boolean { + const normalized = value?.trim().toLowerCase(); + return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on"; +} + +function shouldTrustTestBundledPluginsDirOverride(env: NodeJS.ProcessEnv): boolean { + const isVitestProcess = Boolean(env.VITEST) || Boolean(process.env.VITEST); + return ( + isVitestProcess && + (isTruthyEnvValue(env[TEST_TRUST_BUNDLED_PLUGINS_DIR_ENV]) || + isTruthyEnvValue(process.env[TEST_TRUST_BUNDLED_PLUGINS_DIR_ENV])) + ); +} + function hasUsableBundledPluginTree(pluginsDir: string): boolean { if (!fs.existsSync(pluginsDir)) { return false; @@ -183,6 +198,9 @@ export function resolveBundledPluginsDir(env: NodeJS.ProcessEnv = process.env): if (override) { const resolvedOverride = resolveUserPath(override, env); if (fs.existsSync(resolvedOverride)) { + if (shouldTrustTestBundledPluginsDirOverride(env)) { + return path.resolve(resolvedOverride); + } const trustedOverride = resolveTrustedExistingOverride(resolvedOverride); if (trustedOverride) { return trustedOverride; diff --git a/src/plugins/bundled-runtime-deps.ts b/src/plugins/bundled-runtime-deps.ts index 771e7ffd35b..3ec9ee2652b 100644 --- a/src/plugins/bundled-runtime-deps.ts +++ b/src/plugins/bundled-runtime-deps.ts @@ -1015,10 +1015,10 @@ function resolveExistingExternalBundledRuntimeDepsRoots(params: { packageRoot: string; env: NodeJS.ProcessEnv; }): string[] | null { - const packageRoot = path.resolve(params.packageRoot); + const packageRoot = realpathOrResolve(params.packageRoot); const externalBaseDirs = resolveBundledRuntimeDepsExternalBaseDirs(params.env); for (const externalBaseDir of externalBaseDirs) { - const relative = path.relative(path.resolve(externalBaseDir), packageRoot); + const relative = path.relative(realpathOrResolve(externalBaseDir), packageRoot); if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) { continue; } @@ -1031,6 +1031,14 @@ function resolveExistingExternalBundledRuntimeDepsRoots(params: { return null; } +function realpathOrResolve(targetPath: string): string { + try { + return fs.realpathSync.native(targetPath); + } catch { + return path.resolve(targetPath); + } +} + function resolveSourceCheckoutRuntimeDepsCacheDir(params: { pluginId: string; pluginRoot: string; diff --git a/test/setup.shared.ts b/test/setup.shared.ts index 3ad9f77286e..6e21ea75686 100644 --- a/test/setup.shared.ts +++ b/test/setup.shared.ts @@ -37,6 +37,9 @@ vi.mock("@mariozechner/clipboard", () => ({ // Ensure Vitest environment is properly set. process.env.VITEST = "true"; +// Tests frequently point bundled plugin discovery at temp fixture roots. Production still rejects +// arbitrary OPENCLAW_BUNDLED_PLUGINS_DIR overrides unless this Vitest-only opt-in is present. +process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR ??= "1"; // Config validation walks plugin manifests; keep an aggressive cache in tests to avoid // repeated filesystem discovery across suites/workers. process.env.OPENCLAW_PLUGIN_MANIFEST_CACHE_MS ??= "60000";