Files
Moltbot/src/hooks/workspace.test.ts
2026-02-19 15:37:29 +01:00

106 lines
3.5 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { MANIFEST_KEY } from "../compat/legacy-names.js";
import { loadHookEntriesFromDir } from "./workspace.js";
describe("hooks workspace", () => {
it("ignores package.json hook paths that traverse outside package directory", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hooks-workspace-"));
const hooksRoot = path.join(root, "hooks");
fs.mkdirSync(hooksRoot, { recursive: true });
const pkgDir = path.join(hooksRoot, "pkg");
fs.mkdirSync(pkgDir, { recursive: true });
const outsideHookDir = path.join(root, "outside");
fs.mkdirSync(outsideHookDir, { recursive: true });
fs.writeFileSync(path.join(outsideHookDir, "HOOK.md"), "---\nname: outside\n---\n");
fs.writeFileSync(path.join(outsideHookDir, "handler.js"), "export default async () => {};\n");
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks: ["../outside"],
},
},
null,
2,
),
);
const entries = loadHookEntriesFromDir({ dir: hooksRoot, source: "openclaw-workspace" });
expect(entries.some((e) => e.hook.name === "outside")).toBe(false);
});
it("accepts package.json hook paths within package directory", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hooks-workspace-ok-"));
const hooksRoot = path.join(root, "hooks");
fs.mkdirSync(hooksRoot, { recursive: true });
const pkgDir = path.join(hooksRoot, "pkg");
const nested = path.join(pkgDir, "nested");
fs.mkdirSync(nested, { recursive: true });
fs.writeFileSync(path.join(nested, "HOOK.md"), "---\nname: nested\n---\n");
fs.writeFileSync(path.join(nested, "handler.js"), "export default async () => {};\n");
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks: ["./nested"],
},
},
null,
2,
),
);
const entries = loadHookEntriesFromDir({ dir: hooksRoot, source: "openclaw-workspace" });
expect(entries.some((e) => e.hook.name === "nested")).toBe(true);
});
it("ignores package.json hook paths that escape via symlink", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-hooks-workspace-link-"));
const hooksRoot = path.join(root, "hooks");
fs.mkdirSync(hooksRoot, { recursive: true });
const pkgDir = path.join(hooksRoot, "pkg");
const outsideDir = path.join(root, "outside");
const linkedDir = path.join(pkgDir, "linked");
fs.mkdirSync(pkgDir, { recursive: true });
fs.mkdirSync(outsideDir, { recursive: true });
fs.writeFileSync(path.join(outsideDir, "HOOK.md"), "---\nname: outside\n---\n");
fs.writeFileSync(path.join(outsideDir, "handler.js"), "export default async () => {};\n");
try {
fs.symlinkSync(outsideDir, linkedDir, process.platform === "win32" ? "junction" : "dir");
} catch {
return;
}
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify(
{
name: "pkg",
[MANIFEST_KEY]: {
hooks: ["./linked"],
},
},
null,
2,
),
);
const entries = loadHookEntriesFromDir({ dir: hooksRoot, source: "openclaw-workspace" });
expect(entries.some((e) => e.hook.name === "outside")).toBe(false);
});
});