Files
openclaw/src/plugins/hook-runner-global.test.ts
Jesse Merhi 4cb94cc2cf fix: keep trusted policies with hook registry (#94545)
* fix: keep trusted policies with hook registry

* fix: compose trusted policies with live hooks

* fix: preserve trusted policy order

* test: update hook runner mock

* fix: keep hook policy registry internal
2026-06-22 10:15:24 +10:00

135 lines
4.4 KiB
TypeScript

/** Verifies global hook runner sequencing, mutation, and error behavior. */
import { afterEach, describe, expect, it, vi } from "vitest";
import { createMockPluginRegistry } from "./hooks.test-helpers.js";
import { createEmptyPluginRegistry } from "./registry-empty.js";
import {
pinActivePluginChannelRegistry,
releasePinnedPluginChannelRegistry,
setActivePluginRegistry,
} from "./runtime.js";
import { createPluginRecord } from "./status.test-helpers.js";
async function importHookRunnerGlobalModule() {
return import("./hook-runner-global.js");
}
async function importHookRunnerGlobalStateModule() {
return import("./hook-runner-global-state.js");
}
type HookRunnerGlobalModule = Awaited<ReturnType<typeof importHookRunnerGlobalModule>>;
type HookRunner = NonNullable<ReturnType<HookRunnerGlobalModule["getGlobalHookRunner"]>>;
function expectGlobalHookRunner(
runner: ReturnType<HookRunnerGlobalModule["getGlobalHookRunner"]>,
): HookRunner {
if (runner === null) {
throw new Error("Expected global hook runner");
}
expect(typeof runner.hasHooks).toBe("function");
return runner;
}
async function expectGlobalRunnerState(expected: { hasRunner: boolean; registry?: unknown }) {
const mod = await importHookRunnerGlobalModule();
expect(mod.getGlobalHookRunner() === null).toBe(!expected.hasRunner);
if ("registry" in expected) {
expect(mod.getGlobalPluginRegistry()).toBe(expected.registry ?? null);
}
return mod;
}
afterEach(async () => {
const mod = await importHookRunnerGlobalModule();
mod.resetGlobalHookRunner();
setActivePluginRegistry(createEmptyPluginRegistry());
});
describe("hook-runner-global", () => {
async function createInitializedModule() {
const modA = await importHookRunnerGlobalModule();
const registry = createMockPluginRegistry([{ hookName: "message_received", handler: vi.fn() }]);
modA.initializeGlobalHookRunner(registry);
return { modA, registry };
}
it("preserves the initialized runner across module reloads", async () => {
const { modA, registry } = await createInitializedModule();
expect(expectGlobalHookRunner(modA.getGlobalHookRunner()).hasHooks("message_received")).toBe(
true,
);
vi.resetModules();
const modB = await expectGlobalRunnerState({ hasRunner: true, registry });
expect(expectGlobalHookRunner(modB.getGlobalHookRunner()).hasHooks("message_received")).toBe(
true,
);
});
it("clears the shared state across module reloads", async () => {
await createInitializedModule();
vi.resetModules();
const modB = await expectGlobalRunnerState({ hasRunner: true });
modB.resetGlobalHookRunner();
expect(modB.getGlobalHookRunner()).toBeNull();
expect(modB.getGlobalPluginRegistry()).toBeNull();
vi.resetModules();
await expectGlobalRunnerState({ hasRunner: false });
});
it("exposes trusted policies from the same live registry set as hooks", async () => {
const mod = await importHookRunnerGlobalModule();
const gatewayRegistry = createMockPluginRegistry([
{
hookName: "before_tool_call",
pluginId: "rovoclaw",
handler: vi.fn(),
},
]);
gatewayRegistry.plugins = [createPluginRecord({ id: "rovoclaw" })];
gatewayRegistry.trustedToolPolicies = [
{
pluginId: "rovoclaw",
pluginName: "RovoClaw",
source: "test",
policy: {
id: "atl-sec-core",
description: "trusted policy",
evaluate: () => undefined,
},
},
];
setActivePluginRegistry(gatewayRegistry);
mod.initializeGlobalHookRunner(gatewayRegistry);
pinActivePluginChannelRegistry(gatewayRegistry);
try {
const laterRegistry = createEmptyPluginRegistry();
laterRegistry.plugins = [createPluginRecord({ id: "openai" })];
setActivePluginRegistry(laterRegistry);
mod.initializeGlobalHookRunner(laterRegistry);
expect(expectGlobalHookRunner(mod.getGlobalHookRunner()).hasHooks("before_tool_call")).toBe(
true,
);
expect(mod.getGlobalPluginRegistry()).toBe(laterRegistry);
const stateMod = await importHookRunnerGlobalStateModule();
expect(
stateMod
.getGlobalHookRunnerRegistry()
?.trustedToolPolicies?.map((registration) => [
registration.pluginId,
registration.policy.id,
]),
).toEqual([["rovoclaw", "atl-sec-core"]]);
} finally {
releasePinnedPluginChannelRegistry(gatewayRegistry);
}
});
});