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();
});
});
Messung V0.5 in Prozent C=100 H=97 G=98
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland