From c75cdf6b0b3fce5839d9bc660a58ebf4c38ac441 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 6 Apr 2026 05:53:53 +0100 Subject: [PATCH] test(plugins): share suite temp root helper in install path tests --- src/plugins/install.npm-spec.test.ts | 43 +++++----------------- src/plugins/install.path.test.ts | 47 ++++++------------------- src/plugins/test-helpers/fs-fixtures.ts | 40 +++++++++++++++++++++ 3 files changed, 60 insertions(+), 70 deletions(-) diff --git a/src/plugins/install.npm-spec.test.ts b/src/plugins/install.npm-spec.test.ts index d10e91b16e4..b678ef7635e 100644 --- a/src/plugins/install.npm-spec.test.ts +++ b/src/plugins/install.npm-spec.test.ts @@ -8,6 +8,7 @@ import { mockNpmPackMetadataResult, } from "../test-utils/npm-spec-install-test-helpers.js"; import { installPluginFromNpmSpec, PLUGIN_INSTALL_ERROR_CODE } from "./install.js"; +import { createSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; const runCommandWithTimeoutMock = vi.fn(); @@ -15,27 +16,9 @@ vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args), })); -let suiteTempRoot = ""; -let tempDirCounter = 0; const dynamicArchiveTemplatePathCache = new Map(); const pluginFixturesDir = path.resolve(process.cwd(), "test", "fixtures", "plugins-install"); - -function ensureSuiteTempRoot() { - if (suiteTempRoot) { - return suiteTempRoot; - } - const bundleTempRoot = path.join(process.cwd(), ".tmp"); - fs.mkdirSync(bundleTempRoot, { recursive: true }); - suiteTempRoot = fs.mkdtempSync(path.join(bundleTempRoot, "openclaw-plugin-install-npm-spec-")); - return suiteTempRoot; -} - -function makeTempDir() { - const dir = path.join(ensureSuiteTempRoot(), `case-${String(tempDirCounter)}`); - tempDirCounter += 1; - fs.mkdirSync(dir); - return dir; -} +const suiteTempRootTracker = createSuiteTempRootTracker("openclaw-plugin-install-npm-spec"); function readVoiceCallArchiveBuffer(version: string): Buffer { return fs.readFileSync(path.join(pluginFixturesDir, `voice-call-${version}.tgz`)); @@ -92,7 +75,7 @@ async function ensureDynamicArchiveTemplate(params: { if (cachedPath) { return cachedPath; } - const templateDir = makeTempDir(); + const templateDir = suiteTempRootTracker.makeTempDir(); const pkgDir = params.flatRoot ? templateDir : path.join(templateDir, "package"); fs.mkdirSync(pkgDir, { recursive: true }); if (params.withDistIndex) { @@ -106,7 +89,7 @@ async function ensureDynamicArchiveTemplate(params: { fs.writeFileSync(path.join(pkgDir, "package.json"), JSON.stringify(params.packageJson), "utf-8"); const archivePath = await packToArchive({ pkgDir, - outDir: ensureSuiteTempRoot(), + outDir: suiteTempRootTracker.ensureSuiteTempRoot(), outName: params.outName, flatRoot: params.flatRoot, }); @@ -115,16 +98,8 @@ async function ensureDynamicArchiveTemplate(params: { } afterAll(() => { - if (!suiteTempRoot) { - return; - } - try { - fs.rmSync(suiteTempRoot, { recursive: true, force: true }); - } finally { - suiteTempRoot = ""; - tempDirCounter = 0; - dynamicArchiveTemplatePathCache.clear(); - } + suiteTempRootTracker.cleanup(); + dynamicArchiveTemplatePathCache.clear(); }); beforeEach(() => { @@ -134,7 +109,7 @@ beforeEach(() => { describe("installPluginFromNpmSpec", () => { it("uses --ignore-scripts for npm pack and cleans up temp dir", async () => { - const stateDir = makeTempDir(); + const stateDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(stateDir, "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); @@ -190,7 +165,7 @@ describe("installPluginFromNpmSpec", () => { }); it("allows npm-spec installs with dangerous code patterns when forced unsafe install is set", async () => { - const stateDir = makeTempDir(); + const stateDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(stateDir, "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); @@ -364,7 +339,7 @@ describe("installPluginFromNpmSpec", () => { throw new Error(`unexpected command: ${argv.join(" ")}`); }); - const stateDir = makeTempDir(); + const stateDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(stateDir, "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); const result = await installPluginFromNpmSpec({ diff --git a/src/plugins/install.path.test.ts b/src/plugins/install.path.test.ts index 3e07f8cc76e..799917ee15d 100644 --- a/src/plugins/install.path.test.ts +++ b/src/plugins/install.path.test.ts @@ -11,30 +11,13 @@ import { installPluginFromPath, PLUGIN_INSTALL_ERROR_CODE, } from "./install.js"; +import { createSuiteTempRootTracker } from "./test-helpers/fs-fixtures.js"; vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: vi.fn(), })); -let suiteTempRoot = ""; -let tempDirCounter = 0; - -function ensureSuiteTempRoot() { - if (suiteTempRoot) { - return suiteTempRoot; - } - const bundleTempRoot = path.join(process.cwd(), ".tmp"); - fs.mkdirSync(bundleTempRoot, { recursive: true }); - suiteTempRoot = fs.mkdtempSync(path.join(bundleTempRoot, "openclaw-plugin-install-path-")); - return suiteTempRoot; -} - -function makeTempDir() { - const dir = path.join(ensureSuiteTempRoot(), `case-${String(tempDirCounter)}`); - tempDirCounter += 1; - fs.mkdirSync(dir); - return dir; -} +const suiteTempRootTracker = createSuiteTempRootTracker("openclaw-plugin-install-path"); async function packToArchive(params: { pkgDir: string; @@ -60,7 +43,7 @@ function setupBundleInstallFixture(params: { bundleFormat: "codex" | "claude" | "cursor"; name: string; }) { - const caseDir = makeTempDir(); + const caseDir = suiteTempRootTracker.makeTempDir(); const stateDir = path.join(caseDir, "state"); const pluginDir = path.join(caseDir, "plugin-src"); fs.mkdirSync(stateDir, { recursive: true }); @@ -100,7 +83,7 @@ function setupBundleInstallFixture(params: { } function setupDualFormatInstallFixture(params: { bundleFormat: "codex" | "claude" }) { - const caseDir = makeTempDir(); + const caseDir = suiteTempRootTracker.makeTempDir(); const stateDir = path.join(caseDir, "state"); const pluginDir = path.join(caseDir, "plugin-src"); fs.mkdirSync(path.join(pluginDir, "dist"), { recursive: true }); @@ -161,15 +144,7 @@ async function installFromFileWithWarnings(params: { } afterAll(() => { - if (!suiteTempRoot) { - return; - } - try { - fs.rmSync(suiteTempRoot, { recursive: true, force: true }); - } finally { - suiteTempRoot = ""; - tempDirCounter = 0; - } + suiteTempRootTracker.cleanup(); }); beforeEach(() => { @@ -193,7 +168,7 @@ describe("installPluginFromPath", () => { }); initializeGlobalHookRunner(createMockPluginRegistry([{ hookName: "before_install", handler }])); - const baseDir = makeTempDir(); + const baseDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(baseDir, "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); @@ -235,7 +210,7 @@ describe("installPluginFromPath", () => { }); it("blocks plain file installs when the scanner finds dangerous code patterns", async () => { - const baseDir = makeTempDir(); + const baseDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(baseDir, "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); @@ -256,7 +231,7 @@ describe("installPluginFromPath", () => { }); it("allows plain file installs with dangerous code patterns when forced unsafe install is set", async () => { - const baseDir = makeTempDir(); + const baseDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(baseDir, "extensions"); fs.mkdirSync(extensionsDir, { recursive: true }); @@ -280,7 +255,7 @@ describe("installPluginFromPath", () => { }); it("blocks hardlink alias overwrites when installing a plain file plugin", async () => { - const baseDir = makeTempDir(); + const baseDir = suiteTempRootTracker.makeTempDir(); const extensionsDir = path.join(baseDir, "extensions"); const outsideDir = path.join(baseDir, "outside"); fs.mkdirSync(extensionsDir, { recursive: true }); @@ -313,7 +288,7 @@ describe("installPluginFromPath", () => { bundleFormat: "claude", name: "Claude Sample", }); - const archivePath = path.join(makeTempDir(), "claude-bundle.tgz"); + const archivePath = path.join(suiteTempRootTracker.makeTempDir(), "claude-bundle.tgz"); await packToArchive({ pkgDir: pluginDir, @@ -337,7 +312,7 @@ describe("installPluginFromPath", () => { const { pluginDir, extensionsDir } = setupDualFormatInstallFixture({ bundleFormat: "claude", }); - const archivePath = path.join(makeTempDir(), "dual-format.tgz"); + const archivePath = path.join(suiteTempRootTracker.makeTempDir(), "dual-format.tgz"); await packToArchive({ pkgDir: pluginDir, diff --git a/src/plugins/test-helpers/fs-fixtures.ts b/src/plugins/test-helpers/fs-fixtures.ts index 58b0e1ffd43..b42c287457a 100644 --- a/src/plugins/test-helpers/fs-fixtures.ts +++ b/src/plugins/test-helpers/fs-fixtures.ts @@ -50,3 +50,43 @@ export async function cleanupTrackedTempDirsAsync(trackedDirs: string[]) { }), ); } + +export function createSuiteTempRootTracker(prefix: string) { + let suiteTempRoot = ""; + let tempDirCounter = 0; + + function ensureSuiteTempRoot() { + if (suiteTempRoot) { + return suiteTempRoot; + } + const bundleTempRoot = path.join(process.cwd(), ".tmp"); + fs.mkdirSync(bundleTempRoot, { recursive: true }); + suiteTempRoot = fs.mkdtempSync(path.join(bundleTempRoot, String(prefix) + "-")); + return suiteTempRoot; + } + + function makeTempDir() { + const dir = path.join(ensureSuiteTempRoot(), `case-${String(tempDirCounter)}`); + tempDirCounter += 1; + fs.mkdirSync(dir); + return dir; + } + + function cleanup() { + if (!suiteTempRoot) { + return; + } + try { + fs.rmSync(suiteTempRoot, { recursive: true, force: true }); + } finally { + suiteTempRoot = ""; + tempDirCounter = 0; + } + } + + return { + cleanup, + ensureSuiteTempRoot, + makeTempDir, + }; +}