test: cover legacy runtime deps update recovery (#75288)

This commit is contained in:
Peter Steinberger
2026-05-01 11:13:08 +01:00
parent af34a5db6e
commit 018f77cdc2
4 changed files with 172 additions and 19 deletions

View File

@@ -46,6 +46,26 @@ describe("stageBundledPluginRuntimeDeps", () => {
return path.join(repoRoot, ".artifacts", "bundled-runtime-deps-stamps", `${pluginId}.json`);
}
function legacyRuntimeDepsNodeModulesPath(
repoRoot: string,
stageKey = "fixture-plugin-1234567890abcdef",
) {
return path.join(repoRoot, ".local", "bundled-plugin-runtime-deps", stageKey, "node_modules");
}
function writeLegacyRuntimeDepsNodeModulesSymlink(params: {
pluginDir: string;
repoRoot: string;
stageKey?: string;
}) {
const legacyNodeModulesDir = legacyRuntimeDepsNodeModulesPath(params.repoRoot, params.stageKey);
const nodeModulesDir = path.join(params.pluginDir, "node_modules");
fs.mkdirSync(legacyNodeModulesDir, { recursive: true });
fs.writeFileSync(path.join(legacyNodeModulesDir, "legacy.js"), "module.exports = 0;\n", "utf8");
fs.symlinkSync(legacyNodeModulesDir, nodeModulesDir);
return { legacyNodeModulesDir, nodeModulesDir };
}
it("pins fallback install specs to exact installed versions", () => {
const { repoRoot } = createBundledPluginFixture({
packageJson: {
@@ -718,24 +738,17 @@ describe("stageBundledPluginRuntimeDeps", () => {
},
});
const directDir = path.join(repoRoot, "node_modules", "direct");
const legacyNodeModulesDir = path.join(
repoRoot,
".local",
"bundled-plugin-runtime-deps",
"fixture-plugin-1234567890abcdef",
"node_modules",
);
const nodeModulesDir = path.join(pluginDir, "node_modules");
fs.mkdirSync(directDir, { recursive: true });
fs.mkdirSync(legacyNodeModulesDir, { recursive: true });
fs.writeFileSync(
path.join(directDir, "package.json"),
'{ "name": "direct", "version": "1.0.0" }\n',
"utf8",
);
fs.writeFileSync(path.join(directDir, "index.js"), "module.exports = 'direct';\n", "utf8");
fs.writeFileSync(path.join(legacyNodeModulesDir, "legacy.js"), "module.exports = 0;\n", "utf8");
fs.symlinkSync(legacyNodeModulesDir, nodeModulesDir);
const { legacyNodeModulesDir, nodeModulesDir } = writeLegacyRuntimeDepsNodeModulesSymlink({
pluginDir,
repoRoot,
});
stageBundledPluginRuntimeDeps({ cwd: repoRoot });
@@ -746,6 +759,62 @@ describe("stageBundledPluginRuntimeDeps", () => {
expect(fs.existsSync(path.join(legacyNodeModulesDir, "legacy.js"))).toBe(true);
});
it("removes legacy OpenClaw-owned symlinked plugin node_modules when deps converge to empty", () => {
const { pluginDir, repoRoot } = createBundledPluginFixture({
packageJson: {
name: "@openclaw/fixture-plugin",
version: "1.0.0",
optionalDependencies: { optional: "1.0.0" },
openclaw: { bundle: { stageRuntimeDependencies: true } },
},
});
const rootNodeModulesDir = path.join(repoRoot, "node_modules");
fs.mkdirSync(rootNodeModulesDir, { recursive: true });
const { legacyNodeModulesDir, nodeModulesDir } = writeLegacyRuntimeDepsNodeModulesSymlink({
pluginDir,
repoRoot,
});
stageBundledPluginRuntimeDeps({ cwd: repoRoot });
expect(fs.existsSync(nodeModulesDir)).toBe(false);
expect(fs.existsSync(path.join(legacyNodeModulesDir, "legacy.js"))).toBe(true);
});
it("refuses nested symlink targets under the legacy runtime deps root", () => {
const { pluginDir, repoRoot } = createBundledPluginFixture({
packageJson: {
name: "@openclaw/fixture-plugin",
version: "1.0.0",
dependencies: { direct: "1.0.0" },
openclaw: { bundle: { stageRuntimeDependencies: true } },
},
});
const directDir = path.join(repoRoot, "node_modules", "direct");
const nestedLegacyNodeModulesDir = path.join(
repoRoot,
".local",
"bundled-plugin-runtime-deps",
"fixture-plugin-1234567890abcdef",
"nested",
"node_modules",
);
const nodeModulesDir = path.join(pluginDir, "node_modules");
fs.mkdirSync(directDir, { recursive: true });
fs.mkdirSync(nestedLegacyNodeModulesDir, { recursive: true });
fs.writeFileSync(
path.join(directDir, "package.json"),
'{ "name": "direct", "version": "1.0.0" }\n',
"utf8",
);
fs.writeFileSync(path.join(directDir, "index.js"), "module.exports = 'direct';\n", "utf8");
fs.symlinkSync(nestedLegacyNodeModulesDir, nodeModulesDir);
expect(() => stageBundledPluginRuntimeDeps({ cwd: repoRoot })).toThrow(
/refusing to replace runtime deps via symlinked path/u,
);
});
it("refuses to write a runtime deps stamp through a symlink", () => {
const { repoRoot } = createBundledPluginFixture({
packageJson: {