fix: isolate bundled plugin test roots (#73235) (thanks @zqchris)

This commit is contained in:
Peter Steinberger
2026-04-28 20:21:07 +01:00
parent 2f7c4070f4
commit b8f071a139
6 changed files with 38 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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