Files
openclaw/src/plugins/wired-hooks-message.test.ts
Peter Steinberger 98e3f729bc refactor: remove dead plugin loader exports (#105937)
* refactor(plugins): trim activation and contract exports

* test(plugins): restore fixture cleanup

* refactor(plugins): trim install and loader exports

* test(plugins): fully reset loader caches

* refactor(plugins): trim metadata and catalog exports

* test(plugins): preserve catalog trust coverage

* refactor(plugins): trim provider and plugin exports

* refactor(plugins): trim runtime and tool exports

* test(plugins): update dead-export consumers

* test(plugins): remove empty dead-export suites

* refactor(plugins): align exports with split registry

* refactor(plugins): trim drifted loader exports

* style(plugins): format test fixtures

* refactor(scripts): use supported plugin APIs

* refactor(plugins): finish dead export cleanup

* chore(deadcode): refresh export baseline

* test(cli): mock production memory state

* chore(deadcode): sync latest export baseline

* fix(tests): keep plugin fixtures inside core

* chore(deadcode): refresh rebased export baseline

* chore(deadcode): sync current ratchets

* fix(plugins): retain reserved slot invariant

* fix(plugins): preserve dead-export invariants

* test(plugins): use neutral catalog query fixture

* test(plugins): satisfy catalog lint

* test(plugins): preserve integrity drift coverage

* fix(ci): register skill experience live proof
2026-07-13 01:29:33 -07:00

163 lines
5.3 KiB
TypeScript

/**
* Test: message_sending & message_sent hook wiring
*
* Tests the hook runner methods directly since outbound delivery is deeply integrated.
*/
import { describe, expect, it, vi } from "vitest";
import { createHookRunnerWithRegistry } from "./hooks.test-fixtures.js";
import type {
PluginHookMessageSendingEvent,
PluginHookMessageSendingResult,
PluginHookMessageSentEvent,
} from "./types.js";
function createDeferred<T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
const promise = new Promise<T>((res) => {
resolve = res;
});
return { promise, resolve };
}
async function expectMessageHookCall(params: {
hookName: "message_sending" | "message_sent";
event: PluginHookMessageSendingEvent | PluginHookMessageSentEvent;
hookResult?: PluginHookMessageSendingResult;
expectedResult?: PluginHookMessageSendingResult;
channelCtx: { channelId: string };
}) {
const handler =
params.hookResult === undefined ? vi.fn() : vi.fn().mockReturnValue(params.hookResult);
const { runner } = createHookRunnerWithRegistry([{ hookName: params.hookName, handler }]);
if (params.hookName === "message_sending") {
const result = await runner.runMessageSending(
params.event as PluginHookMessageSendingEvent,
params.channelCtx,
);
if (params.expectedResult === undefined) {
expect(result).toBeUndefined();
} else {
expect(result).toEqual(params.expectedResult);
}
} else {
await runner.runMessageSent(params.event as PluginHookMessageSentEvent, params.channelCtx);
}
expect(handler).toHaveBeenCalledWith(params.event, params.channelCtx);
}
describe("message_sending hook runner", () => {
const demoChannelCtx = { channelId: "demo-channel" };
it.each([
{
name: "runMessageSending invokes registered hooks and returns modified content",
event: { to: "user-123", content: "original content" },
hookResult: { content: "modified content" },
expected: { content: "modified content" },
},
{
name: "runMessageSending can cancel message delivery",
event: { to: "user-123", content: "blocked" },
hookResult: { cancel: true, cancelReason: "policy", metadata: { owner: "agent-2" } },
expected: { cancel: true, cancelReason: "policy", metadata: { owner: "agent-2" } },
},
] as const)("$name", async ({ event, hookResult, expected }) => {
await expectMessageHookCall({
hookName: "message_sending",
event,
hookResult,
expectedResult: expected,
channelCtx: demoChannelCtx,
});
});
it("fails open after the default per-handler timeout", async () => {
vi.useFakeTimers();
try {
const logger = { warn: vi.fn(), error: vi.fn() };
const firstStarted = createDeferred<void>();
const first = vi.fn(() => {
firstStarted.resolve();
return new Promise<PluginHookMessageSendingResult>(() => {});
});
const second = vi.fn().mockResolvedValue({ content: "after timeout" });
const { runner } = createHookRunnerWithRegistry(
[
{ hookName: "message_sending", handler: first },
{ hookName: "message_sending", handler: second },
],
{ logger },
);
const resultPromise = runner.runMessageSending(
{ to: "user-123", content: "original content" },
demoChannelCtx,
);
await firstStarted.promise;
await vi.advanceTimersByTimeAsync(15_000);
await expect(resultPromise).resolves.toEqual({ content: "after timeout" });
expect(second).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenCalledWith(
"[hooks] message_sending handler from test-plugin failed: timed out after 15000ms",
);
expect(vi.getTimerCount()).toBe(0);
} finally {
vi.useRealTimers();
}
});
it("preserves a handler-specific timeout longer than the default", async () => {
vi.useFakeTimers();
try {
const handler = vi.fn(
() =>
new Promise<PluginHookMessageSendingResult>((resolve) => {
setTimeout(() => resolve({ content: "slow result" }), 16_000);
}),
);
const { runner } = createHookRunnerWithRegistry([
{ hookName: "message_sending", handler, timeoutMs: 20_000 },
]);
const resultPromise = runner.runMessageSending(
{ to: "user-123", content: "original content" },
demoChannelCtx,
);
let settled = false;
void resultPromise.then(() => {
settled = true;
});
await vi.advanceTimersByTimeAsync(15_000);
expect(settled).toBe(false);
await vi.advanceTimersByTimeAsync(1_000);
await expect(resultPromise).resolves.toEqual({ content: "slow result" });
expect(vi.getTimerCount()).toBe(0);
} finally {
vi.useRealTimers();
}
});
});
describe("message_sent hook runner", () => {
const demoChannelCtx = { channelId: "demo-channel" };
it.each([
{
name: "runMessageSent invokes registered hooks with success=true",
event: { to: "user-123", content: "hello", success: true },
},
{
name: "runMessageSent invokes registered hooks with error on failure",
event: { to: "user-123", content: "hello", success: false, error: "timeout" },
},
] as const)("$name", async ({ event }) => {
await expectMessageHookCall({
hookName: "message_sent",
event,
channelCtx: demoChannelCtx,
});
});
});