mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 12:18:37 +00:00
refactor(tests): dedupe manifest registry link fixture setup
This commit is contained in:
@@ -47,6 +47,74 @@ function countDuplicateWarnings(registry: ReturnType<typeof loadPluginManifestRe
|
|||||||
).length;
|
).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareLinkedManifestFixture(params: { id: string; mode: "symlink" | "hardlink" }): {
|
||||||
|
rootDir: string;
|
||||||
|
linked: boolean;
|
||||||
|
} {
|
||||||
|
const rootDir = makeTempDir();
|
||||||
|
const outsideDir = makeTempDir();
|
||||||
|
const outsideManifest = path.join(outsideDir, "openclaw.plugin.json");
|
||||||
|
const linkedManifest = path.join(rootDir, "openclaw.plugin.json");
|
||||||
|
fs.writeFileSync(path.join(rootDir, "index.ts"), "export default function () {}", "utf-8");
|
||||||
|
fs.writeFileSync(
|
||||||
|
outsideManifest,
|
||||||
|
JSON.stringify({ id: params.id, configSchema: { type: "object" } }),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (params.mode === "symlink") {
|
||||||
|
fs.symlinkSync(outsideManifest, linkedManifest);
|
||||||
|
} else {
|
||||||
|
fs.linkSync(outsideManifest, linkedManifest);
|
||||||
|
}
|
||||||
|
return { rootDir, linked: true };
|
||||||
|
} catch (err) {
|
||||||
|
if (params.mode === "symlink") {
|
||||||
|
return { rootDir, linked: false };
|
||||||
|
}
|
||||||
|
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
|
||||||
|
return { rootDir, linked: false };
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSingleCandidateRegistry(params: {
|
||||||
|
idHint: string;
|
||||||
|
rootDir: string;
|
||||||
|
origin: "bundled" | "global" | "workspace" | "config";
|
||||||
|
}) {
|
||||||
|
return loadRegistry([
|
||||||
|
createPluginCandidate({
|
||||||
|
idHint: params.idHint,
|
||||||
|
rootDir: params.rootDir,
|
||||||
|
origin: params.origin,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnsafeManifestDiagnostic(registry: ReturnType<typeof loadPluginManifestRegistry>) {
|
||||||
|
return registry.diagnostics.some((diag) => diag.message.includes("unsafe plugin manifest path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectUnsafeWorkspaceManifestRejected(params: {
|
||||||
|
id: string;
|
||||||
|
mode: "symlink" | "hardlink";
|
||||||
|
}) {
|
||||||
|
const fixture = prepareLinkedManifestFixture({ id: params.id, mode: params.mode });
|
||||||
|
if (!fixture.linked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const registry = loadSingleCandidateRegistry({
|
||||||
|
idHint: params.id,
|
||||||
|
rootDir: fixture.rootDir,
|
||||||
|
origin: "workspace",
|
||||||
|
});
|
||||||
|
expect(registry.plugins).toHaveLength(0);
|
||||||
|
expect(hasUnsafeManifestDiagnostic(registry)).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
while (tempDirs.length > 0) {
|
while (tempDirs.length > 0) {
|
||||||
const dir = tempDirs.pop();
|
const dir = tempDirs.pop();
|
||||||
@@ -169,104 +237,31 @@ describe("loadPluginManifestRegistry", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("rejects manifest paths that escape plugin root via symlink", () => {
|
it("rejects manifest paths that escape plugin root via symlink", () => {
|
||||||
const rootDir = makeTempDir();
|
expectUnsafeWorkspaceManifestRejected({ id: "unsafe-symlink", mode: "symlink" });
|
||||||
const outsideDir = makeTempDir();
|
|
||||||
const outsideManifest = path.join(outsideDir, "openclaw.plugin.json");
|
|
||||||
const linkedManifest = path.join(rootDir, "openclaw.plugin.json");
|
|
||||||
fs.writeFileSync(path.join(rootDir, "index.ts"), "export default function () {}", "utf-8");
|
|
||||||
fs.writeFileSync(
|
|
||||||
outsideManifest,
|
|
||||||
JSON.stringify({ id: "unsafe-symlink", configSchema: { type: "object" } }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
fs.symlinkSync(outsideManifest, linkedManifest);
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const registry = loadRegistry([
|
|
||||||
createPluginCandidate({
|
|
||||||
idHint: "unsafe-symlink",
|
|
||||||
rootDir,
|
|
||||||
origin: "workspace",
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
expect(registry.plugins).toHaveLength(0);
|
|
||||||
expect(
|
|
||||||
registry.diagnostics.some((diag) => diag.message.includes("unsafe plugin manifest path")),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects manifest paths that escape plugin root via hardlink", () => {
|
it("rejects manifest paths that escape plugin root via hardlink", () => {
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rootDir = makeTempDir();
|
expectUnsafeWorkspaceManifestRejected({ id: "unsafe-hardlink", mode: "hardlink" });
|
||||||
const outsideDir = makeTempDir();
|
|
||||||
const outsideManifest = path.join(outsideDir, "openclaw.plugin.json");
|
|
||||||
const linkedManifest = path.join(rootDir, "openclaw.plugin.json");
|
|
||||||
fs.writeFileSync(path.join(rootDir, "index.ts"), "export default function () {}", "utf-8");
|
|
||||||
fs.writeFileSync(
|
|
||||||
outsideManifest,
|
|
||||||
JSON.stringify({ id: "unsafe-hardlink", configSchema: { type: "object" } }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
fs.linkSync(outsideManifest, linkedManifest);
|
|
||||||
} catch (err) {
|
|
||||||
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
const registry = loadRegistry([
|
|
||||||
createPluginCandidate({
|
|
||||||
idHint: "unsafe-hardlink",
|
|
||||||
rootDir,
|
|
||||||
origin: "workspace",
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
expect(registry.plugins).toHaveLength(0);
|
|
||||||
expect(
|
|
||||||
registry.diagnostics.some((diag) => diag.message.includes("unsafe plugin manifest path")),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows bundled manifest paths that are hardlinked aliases", () => {
|
it("allows bundled manifest paths that are hardlinked aliases", () => {
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rootDir = makeTempDir();
|
const fixture = prepareLinkedManifestFixture({ id: "bundled-hardlink", mode: "hardlink" });
|
||||||
const outsideDir = makeTempDir();
|
if (!fixture.linked) {
|
||||||
const outsideManifest = path.join(outsideDir, "openclaw.plugin.json");
|
return;
|
||||||
const linkedManifest = path.join(rootDir, "openclaw.plugin.json");
|
|
||||||
fs.writeFileSync(path.join(rootDir, "index.ts"), "export default function () {}", "utf-8");
|
|
||||||
fs.writeFileSync(
|
|
||||||
outsideManifest,
|
|
||||||
JSON.stringify({ id: "bundled-hardlink", configSchema: { type: "object" } }),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
fs.linkSync(outsideManifest, linkedManifest);
|
|
||||||
} catch (err) {
|
|
||||||
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const registry = loadRegistry([
|
const registry = loadSingleCandidateRegistry({
|
||||||
createPluginCandidate({
|
idHint: "bundled-hardlink",
|
||||||
idHint: "bundled-hardlink",
|
rootDir: fixture.rootDir,
|
||||||
rootDir,
|
origin: "bundled",
|
||||||
origin: "bundled",
|
});
|
||||||
}),
|
|
||||||
]);
|
|
||||||
expect(registry.plugins.some((entry) => entry.id === "bundled-hardlink")).toBe(true);
|
expect(registry.plugins.some((entry) => entry.id === "bundled-hardlink")).toBe(true);
|
||||||
expect(
|
expect(hasUnsafeManifestDiagnostic(registry)).toBe(false);
|
||||||
registry.diagnostics.some((diag) => diag.message.includes("unsafe plugin manifest path")),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user