mirror of https://github.com/openclaw/openclaw.git
144 lines
4.8 KiB
TypeScript
144 lines
4.8 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
|
|
|
const tempDirs: string[] = [];
|
|
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
|
|
|
function createBundledPluginDir(prefix: string, marker: string): string {
|
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
tempDirs.push(rootDir);
|
|
fs.mkdirSync(path.join(rootDir, "demo"), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(rootDir, "demo", "api.js"),
|
|
`export const marker = ${JSON.stringify(marker)};\n`,
|
|
"utf8",
|
|
);
|
|
return rootDir;
|
|
}
|
|
|
|
function createThrowingPluginDir(prefix: string): string {
|
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
tempDirs.push(rootDir);
|
|
fs.mkdirSync(path.join(rootDir, "bad"), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(rootDir, "bad", "api.js"),
|
|
`throw new Error("plugin load failure");\n`,
|
|
"utf8",
|
|
);
|
|
return rootDir;
|
|
}
|
|
|
|
function createCircularPluginDir(prefix: string): string {
|
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
tempDirs.push(rootDir);
|
|
fs.mkdirSync(path.join(rootDir, "demo"), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(rootDir, "facade.mjs"),
|
|
[
|
|
`import { loadBundledPluginPublicSurfaceModuleSync } from ${JSON.stringify(
|
|
new URL("./facade-runtime.js", import.meta.url).href,
|
|
)};`,
|
|
`export const marker = loadBundledPluginPublicSurfaceModuleSync({ dirName: "demo", artifactBasename: "api.js" }).marker;`,
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(rootDir, "demo", "helper.js"),
|
|
['import { marker } from "../facade.mjs";', "export const circularMarker = marker;", ""].join(
|
|
"\n",
|
|
),
|
|
"utf8",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(rootDir, "demo", "api.js"),
|
|
['import "./helper.js";', 'export const marker = "circular-ok";', ""].join("\n"),
|
|
"utf8",
|
|
);
|
|
return rootDir;
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
if (originalBundledPluginsDir === undefined) {
|
|
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
|
} else {
|
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
|
|
}
|
|
for (const dir of tempDirs.splice(0, tempDirs.length)) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe("plugin-sdk facade runtime", () => {
|
|
it("honors bundled plugin dir overrides outside the package root", () => {
|
|
const overrideA = createBundledPluginDir("openclaw-facade-runtime-a-", "override-a");
|
|
const overrideB = createBundledPluginDir("openclaw-facade-runtime-b-", "override-b");
|
|
|
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideA;
|
|
const fromA = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "demo",
|
|
artifactBasename: "api.js",
|
|
});
|
|
expect(fromA.marker).toBe("override-a");
|
|
|
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideB;
|
|
const fromB = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "demo",
|
|
artifactBasename: "api.js",
|
|
});
|
|
expect(fromB.marker).toBe("override-b");
|
|
});
|
|
|
|
it("returns the same object identity on repeated calls (sentinel consistency)", () => {
|
|
const dir = createBundledPluginDir("openclaw-facade-identity-", "identity-check");
|
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
|
|
|
const first = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "demo",
|
|
artifactBasename: "api.js",
|
|
});
|
|
const second = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "demo",
|
|
artifactBasename: "api.js",
|
|
});
|
|
expect(first).toBe(second);
|
|
expect(first.marker).toBe("identity-check");
|
|
});
|
|
|
|
it("breaks circular facade re-entry during module evaluation", () => {
|
|
const dir = createCircularPluginDir("openclaw-facade-circular-");
|
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
|
|
|
const loaded = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "demo",
|
|
artifactBasename: "api.js",
|
|
});
|
|
|
|
expect(loaded.marker).toBe("circular-ok");
|
|
});
|
|
|
|
it("clears the cache on load failure so retries re-execute", () => {
|
|
const dir = createThrowingPluginDir("openclaw-facade-throw-");
|
|
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
|
|
|
expect(() =>
|
|
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "bad",
|
|
artifactBasename: "api.js",
|
|
}),
|
|
).toThrow("plugin load failure");
|
|
|
|
// A second call must also throw (not return a stale empty sentinel).
|
|
expect(() =>
|
|
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
|
dirName: "bad",
|
|
artifactBasename: "api.js",
|
|
}),
|
|
).toThrow("plugin load failure");
|
|
});
|
|
});
|