test: isolate facade bundled fixture roots

This commit is contained in:
Peter Steinberger
2026-04-28 20:04:06 +01:00
parent 6d4599a796
commit 702e5fc4a9
2 changed files with 135 additions and 41 deletions

View File

@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
clearBundledRuntimeDependencyNodePaths,
resolveBundledRuntimeDependencyInstallRoot,
@@ -15,19 +15,17 @@ import {
setFacadeLoaderJitiFactoryForTest,
} from "./facade-loader.js";
import { listImportedBundledPluginFacadeIds as listImportedFacadeRuntimeIds } from "./facade-runtime.js";
import {
createBundledPluginPublicSurfaceFixture,
createPluginSdkTestHarness,
createThrowingBundledPluginPublicSurfaceFixture,
} from "./test-helpers.js";
import { createPluginSdkTestHarness } from "./test-helpers.js";
const { createTempDirSync } = createPluginSdkTestHarness();
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
const originalPluginStageDir = process.env.OPENCLAW_PLUGIN_STAGE_DIR;
const trustedBundledFixturesRoot = path.resolve("dist-runtime", "extensions");
const FACADE_LOADER_GLOBAL = "__openclawTestLoadBundledPluginPublicSurfaceModuleSync";
const STAGED_RUNTIME_DEP_NAME = "openclaw-facade-loader-runtime-dep";
type FacadeLoaderJitiFactory = NonNullable<Parameters<typeof setFacadeLoaderJitiFactoryForTest>[0]>;
const trustedBundledFixtureDirs: string[] = [];
function forceNodeRuntimeVersionsForTest(): () => void {
const originalVersions = process.versions;
@@ -47,17 +45,52 @@ function forceNodeRuntimeVersionsForTest(): () => void {
};
}
function createTrustedBundledFixtureRoot(prefix: string): string {
fs.mkdirSync(trustedBundledFixturesRoot, { recursive: true });
const rootDir = fs.mkdtempSync(path.join(trustedBundledFixturesRoot, `.${prefix}`));
trustedBundledFixtureDirs.push(rootDir);
return rootDir;
}
function writePluginPackageJson(pluginDir: string, name = "demo"): void {
writeJsonFile(path.join(pluginDir, "package.json"), {
name: `@openclaw/plugin-${name}`,
version: "0.0.0",
type: "module",
});
}
function createBundledPluginDir(prefix: string, marker: string): string {
return createBundledPluginPublicSurfaceFixture({ createTempDirSync, marker, prefix });
const rootDir = createTrustedBundledFixtureRoot(prefix);
const pluginDir = path.join(rootDir, "demo");
fs.mkdirSync(pluginDir, { recursive: true });
writePluginPackageJson(pluginDir);
fs.writeFileSync(
path.join(pluginDir, "api.js"),
`export const marker = ${JSON.stringify(marker)};\n`,
"utf8",
);
return rootDir;
}
function createThrowingPluginDir(prefix: string): string {
return createThrowingBundledPluginPublicSurfaceFixture({ createTempDirSync, prefix });
const rootDir = createTrustedBundledFixtureRoot(prefix);
const pluginDir = path.join(rootDir, "bad");
fs.mkdirSync(pluginDir, { recursive: true });
writePluginPackageJson(pluginDir, "bad");
fs.writeFileSync(
path.join(pluginDir, "api.js"),
`throw new Error("plugin load failure");\n`,
"utf8",
);
return rootDir;
}
function createCircularPluginDir(prefix: string): string {
const rootDir = createTempDirSync(prefix);
fs.mkdirSync(path.join(rootDir, "demo"), { recursive: true });
const rootDir = createTrustedBundledFixtureRoot(prefix);
const pluginDir = path.join(rootDir, "demo");
fs.mkdirSync(pluginDir, { recursive: true });
writePluginPackageJson(pluginDir);
fs.writeFileSync(
path.join(rootDir, "facade.mjs"),
[
@@ -71,14 +104,14 @@ function createCircularPluginDir(prefix: string): string {
"utf8",
);
fs.writeFileSync(
path.join(rootDir, "demo", "helper.js"),
path.join(pluginDir, "helper.js"),
['import { marker } from "../facade.mjs";', "export const circularMarker = marker;", ""].join(
"\n",
),
"utf8",
);
fs.writeFileSync(
path.join(rootDir, "demo", "api.js"),
path.join(pluginDir, "api.js"),
['import "./helper.js";', 'export const marker = "circular-ok";', ""].join("\n"),
"utf8",
);
@@ -102,21 +135,17 @@ function createPackagedBundledPluginDirWithStagedRuntimeDep(params: {
pluginRoot: string;
stageRoot: string;
} {
const packageRoot = createTempDirSync(params.prefix);
const pluginRoot = path.join(packageRoot, "dist", "extensions", "demo");
const stageRoot = path.join(packageRoot, "stage");
const packageRoot = path.resolve(".");
const pluginRoot = path.join(trustedBundledFixturesRoot, "demo");
const stageRoot = createTempDirSync(`${params.prefix}stage-`);
const env = {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(packageRoot, "dist", "extensions"),
OPENCLAW_BUNDLED_PLUGINS_DIR: trustedBundledFixturesRoot,
OPENCLAW_PLUGIN_STAGE_DIR: stageRoot,
};
trustedBundledFixtureDirs.push(pluginRoot);
fs.mkdirSync(pluginRoot, { recursive: true });
writeJsonFile(path.join(packageRoot, "package.json"), {
name: "openclaw",
version: "0.0.0",
type: "module",
});
writeJsonFile(path.join(pluginRoot, "package.json"), {
name: "@openclaw/plugin-demo",
version: "0.0.0",
@@ -154,7 +183,7 @@ function createPackagedBundledPluginDirWithStagedRuntimeDep(params: {
);
return {
bundledPluginsDir: path.join(packageRoot, "dist", "extensions"),
bundledPluginsDir: trustedBundledFixturesRoot,
env,
installRoot,
modulePath,
@@ -163,8 +192,18 @@ function createPackagedBundledPluginDirWithStagedRuntimeDep(params: {
stageRoot,
};
}
beforeEach(() => {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
delete process.env.OPENCLAW_PLUGIN_STAGE_DIR;
});
afterEach(() => {
vi.restoreAllMocks();
for (const dir of trustedBundledFixtureDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
resetFacadeLoaderStateForTest();
setFacadeLoaderJitiFactoryForTest(undefined);
clearBundledRuntimeDependencyNodePaths();
@@ -187,7 +226,7 @@ afterEach(() => {
});
describe("plugin-sdk facade loader", () => {
it("honors bundled plugin dir overrides outside the package root", () => {
it("honors trusted bundled plugin dir overrides", () => {
const overrideA = createBundledPluginDir("openclaw-facade-loader-a-", "override-a");
const overrideB = createBundledPluginDir("openclaw-facade-loader-b-", "override-b");
@@ -251,11 +290,14 @@ describe("plugin-sdk facade loader", () => {
});
it("uses the runtime-supported Jiti boundary for Windows dist facade loads", () => {
const dir = createTempDirSync("openclaw-facade-loader-windows-dist-");
const bundledPluginsDir = path.join(dir, "dist");
fs.mkdirSync(path.join(bundledPluginsDir, "demo"), { recursive: true });
const bundledPluginsDir = createTrustedBundledFixtureRoot(
"openclaw-facade-loader-windows-dist-",
);
const pluginDir = path.join(bundledPluginsDir, "demo");
fs.mkdirSync(pluginDir, { recursive: true });
writePluginPackageJson(pluginDir);
fs.writeFileSync(
path.join(bundledPluginsDir, "demo", "api.js"),
path.join(pluginDir, "api.js"),
'export const marker = "windows-dist-ok";\n',
"utf8",
);
@@ -314,13 +356,15 @@ describe("plugin-sdk facade loader", () => {
expect(loaded.marker).toBe("facade:staged");
expect(fs.existsSync(path.join(fixture.pluginRoot, "node_modules"))).toBe(false);
expect(fs.realpathSync(fileURLToPath(loaded.moduleUrl))).toBe(
fs.realpathSync(path.join(fixture.installRoot, "dist", "extensions", "demo", "api.js")),
fs.realpathSync(
path.join(fixture.installRoot, "dist-runtime", "extensions", "demo", "api.js"),
),
);
});
it("loads built bundled async public surfaces through staged runtime deps", async () => {
const fixture = createPackagedBundledPluginDirWithStagedRuntimeDep({
marker: "async-staged",
marker: "staged",
prefix: "openclaw-facade-loader-built-async-",
});
@@ -333,9 +377,11 @@ describe("plugin-sdk facade loader", () => {
env: fixture.env,
});
expect(loaded.marker).toBe("facade:async-staged");
expect(loaded.marker).toBe("facade:staged");
expect(fs.realpathSync(fileURLToPath(loaded.moduleUrl))).toBe(
fs.realpathSync(path.join(fixture.installRoot, "dist", "extensions", "demo", "api.js")),
fs.realpathSync(
path.join(fixture.installRoot, "dist-runtime", "extensions", "demo", "api.js"),
),
);
});

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js";
import { createPluginActivationSource, normalizePluginsConfig } from "../plugins/config-state.js";
import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
@@ -17,27 +17,72 @@ import {
loadBundledPluginPublicSurfaceModuleSync,
resetFacadeRuntimeStateForTest,
} from "./facade-runtime.js";
import {
createBundledPluginPublicSurfaceFixture,
createPluginSdkTestHarness,
createThrowingBundledPluginPublicSurfaceFixture,
} from "./test-helpers.js";
import { createPluginSdkTestHarness } from "./test-helpers.js";
const { createTempDirSync } = createPluginSdkTestHarness();
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
const trustedBundledFixturesRoot = path.resolve("dist-runtime", "extensions");
const trustedBundledFixtureDirs: string[] = [];
function writeJsonFile(filePath: string, value: unknown): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function createTrustedBundledFixtureRoot(prefix: string): string {
fs.mkdirSync(trustedBundledFixturesRoot, { recursive: true });
const rootDir = fs.mkdtempSync(path.join(trustedBundledFixturesRoot, `.${prefix}`));
trustedBundledFixtureDirs.push(rootDir);
return rootDir;
}
function writePluginPackageJson(pluginDir: string, name = "demo"): void {
writeJsonFile(path.join(pluginDir, "package.json"), {
name: `@openclaw/plugin-${name}`,
version: "0.0.0",
type: "module",
});
}
function createBundledPluginDir(prefix: string, marker: string): string {
return createBundledPluginPublicSurfaceFixture({ createTempDirSync, marker, prefix });
const rootDir = createTrustedBundledFixtureRoot(prefix);
const pluginDir = path.join(rootDir, "demo");
fs.mkdirSync(pluginDir, { recursive: true });
writePluginPackageJson(pluginDir);
fs.writeFileSync(
path.join(pluginDir, "api.js"),
`export const marker = ${JSON.stringify(marker)};\n`,
"utf8",
);
return rootDir;
}
function createThrowingPluginDir(prefix: string): string {
return createThrowingBundledPluginPublicSurfaceFixture({ createTempDirSync, prefix });
const rootDir = createTrustedBundledFixtureRoot(prefix);
const pluginDir = path.join(rootDir, "bad");
fs.mkdirSync(pluginDir, { recursive: true });
writePluginPackageJson(pluginDir, "bad");
fs.writeFileSync(
path.join(pluginDir, "api.js"),
`throw new Error("plugin load failure");\n`,
"utf8",
);
return rootDir;
}
beforeEach(() => {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
delete process.env.OPENCLAW_STATE_DIR;
});
afterEach(() => {
vi.restoreAllMocks();
for (const dir of trustedBundledFixtureDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
clearRuntimeConfigSnapshot();
resetFacadeRuntimeStateForTest();
resetFacadeActivationCheckRuntimeStateForTest();
@@ -62,7 +107,7 @@ afterEach(() => {
});
describe("plugin-sdk facade runtime", () => {
it("honors bundled plugin dir overrides outside the package root", () => {
it("honors trusted bundled plugin dir overrides", () => {
const overrideA = createBundledPluginDir("openclaw-facade-runtime-a-", "override-a");
const overrideB = createBundledPluginDir("openclaw-facade-runtime-b-", "override-b");
@@ -88,7 +133,10 @@ describe("plugin-sdk facade runtime", () => {
});
it("falls back to package source surfaces when an override dir is partial", () => {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createTempDirSync("openclaw-facade-runtime-empty-");
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createBundledPluginDir(
"openclaw-facade-runtime-partial-",
"partial",
);
expect(
__testing.resolveFacadeModuleLocation({