mirror of https://github.com/openclaw/openclaw.git
200 lines
4.6 KiB
TypeScript
200 lines
4.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createHookRunner } from "./hooks.js";
|
|
import { addTestHook } from "./hooks.test-helpers.js";
|
|
import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js";
|
|
import type {
|
|
PluginHookBeforeInstallContext,
|
|
PluginHookBeforeInstallEvent,
|
|
PluginHookBeforeInstallResult,
|
|
PluginHookRegistration,
|
|
} from "./types.js";
|
|
|
|
function addBeforeInstallHook(
|
|
registry: PluginRegistry,
|
|
pluginId: string,
|
|
handler:
|
|
| (() => PluginHookBeforeInstallResult | Promise<PluginHookBeforeInstallResult>)
|
|
| PluginHookRegistration["handler"],
|
|
priority?: number,
|
|
) {
|
|
addTestHook({
|
|
registry,
|
|
pluginId,
|
|
hookName: "before_install",
|
|
handler: handler as PluginHookRegistration["handler"],
|
|
priority,
|
|
});
|
|
}
|
|
|
|
const stubCtx: PluginHookBeforeInstallContext = {
|
|
origin: "openclaw-workspace",
|
|
targetType: "skill",
|
|
requestKind: "skill-install",
|
|
};
|
|
|
|
const stubEvent: PluginHookBeforeInstallEvent = {
|
|
targetName: "demo-skill",
|
|
targetType: "skill",
|
|
sourcePath: "/tmp/demo-skill",
|
|
sourcePathKind: "directory",
|
|
origin: "openclaw-workspace",
|
|
request: {
|
|
kind: "skill-install",
|
|
mode: "install",
|
|
},
|
|
builtinScan: {
|
|
status: "ok",
|
|
scannedFiles: 1,
|
|
critical: 0,
|
|
warn: 0,
|
|
info: 0,
|
|
findings: [],
|
|
},
|
|
skill: {
|
|
installId: "deps",
|
|
},
|
|
};
|
|
|
|
describe("before_install hook merger", () => {
|
|
let registry: PluginRegistry;
|
|
|
|
beforeEach(() => {
|
|
registry = createEmptyPluginRegistry();
|
|
});
|
|
|
|
it("accumulates findings across handlers in priority order", async () => {
|
|
addBeforeInstallHook(
|
|
registry,
|
|
"plugin-a",
|
|
(): PluginHookBeforeInstallResult => ({
|
|
findings: [
|
|
{
|
|
ruleId: "first",
|
|
severity: "warn",
|
|
file: "a.ts",
|
|
line: 1,
|
|
message: "first finding",
|
|
},
|
|
],
|
|
}),
|
|
100,
|
|
);
|
|
addBeforeInstallHook(
|
|
registry,
|
|
"plugin-b",
|
|
(): PluginHookBeforeInstallResult => ({
|
|
findings: [
|
|
{
|
|
ruleId: "second",
|
|
severity: "critical",
|
|
file: "b.ts",
|
|
line: 2,
|
|
message: "second finding",
|
|
},
|
|
],
|
|
}),
|
|
50,
|
|
);
|
|
|
|
const runner = createHookRunner(registry);
|
|
const result = await runner.runBeforeInstall(stubEvent, stubCtx);
|
|
|
|
expect(result).toEqual({
|
|
findings: [
|
|
{
|
|
ruleId: "first",
|
|
severity: "warn",
|
|
file: "a.ts",
|
|
line: 1,
|
|
message: "first finding",
|
|
},
|
|
{
|
|
ruleId: "second",
|
|
severity: "critical",
|
|
file: "b.ts",
|
|
line: 2,
|
|
message: "second finding",
|
|
},
|
|
],
|
|
block: undefined,
|
|
blockReason: undefined,
|
|
});
|
|
});
|
|
|
|
it("short-circuits after block=true and preserves earlier findings", async () => {
|
|
const blocker = vi.fn(
|
|
(): PluginHookBeforeInstallResult => ({
|
|
findings: [
|
|
{
|
|
ruleId: "blocker",
|
|
severity: "critical",
|
|
file: "block.ts",
|
|
line: 3,
|
|
message: "blocked finding",
|
|
},
|
|
],
|
|
block: true,
|
|
blockReason: "policy blocked",
|
|
}),
|
|
);
|
|
const skipped = vi.fn(
|
|
(): PluginHookBeforeInstallResult => ({
|
|
findings: [
|
|
{
|
|
ruleId: "skipped",
|
|
severity: "warn",
|
|
file: "skip.ts",
|
|
line: 4,
|
|
message: "should not appear",
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
|
|
addBeforeInstallHook(
|
|
registry,
|
|
"plugin-a",
|
|
(): PluginHookBeforeInstallResult => ({
|
|
findings: [
|
|
{
|
|
ruleId: "first",
|
|
severity: "warn",
|
|
file: "a.ts",
|
|
line: 1,
|
|
message: "first finding",
|
|
},
|
|
],
|
|
}),
|
|
100,
|
|
);
|
|
addBeforeInstallHook(registry, "plugin-block", blocker, 50);
|
|
addBeforeInstallHook(registry, "plugin-skipped", skipped, 10);
|
|
|
|
const runner = createHookRunner(registry);
|
|
const result = await runner.runBeforeInstall(stubEvent, stubCtx);
|
|
|
|
expect(result).toEqual({
|
|
findings: [
|
|
{
|
|
ruleId: "first",
|
|
severity: "warn",
|
|
file: "a.ts",
|
|
line: 1,
|
|
message: "first finding",
|
|
},
|
|
{
|
|
ruleId: "blocker",
|
|
severity: "critical",
|
|
file: "block.ts",
|
|
line: 3,
|
|
message: "blocked finding",
|
|
},
|
|
],
|
|
block: true,
|
|
blockReason: "policy blocked",
|
|
});
|
|
expect(blocker).toHaveBeenCalledTimes(1);
|
|
expect(skipped).not.toHaveBeenCalled();
|
|
});
|
|
});
|