Files
openclaw/src/plugins/wired-hooks-inbound-claim.test.ts
Harold Hunt aa1454d1a8 Plugins: broaden plugin surface for Codex App Server (#45318)
* Plugins: add inbound claim and Telegram interaction seams

* Plugins: add Discord interaction surface

* Chore: fix formatting after plugin rebase

* fix(hooks): preserve observers after inbound claim

* test(hooks): cover claimed inbound observer delivery

* fix(plugins): harden typing lease refreshes

* fix(discord): pass real auth to plugin interactions

* fix(plugins): remove raw session binding runtime exposure

* fix(plugins): tighten interactive callback handling

* Plugins: gate conversation binding with approvals

* Plugins: migrate legacy plugin binding records

* Plugins/phone-control: update test command context

* Plugins: migrate legacy binding ids

* Plugins: migrate legacy codex session bindings

* Discord: fix plugin interaction handling

* Discord: support direct plugin conversation binds

* Plugins: preserve Discord command bind targets

* Tests: fix plugin binding and interactive fallout

* Discord: stabilize directory lookup tests

* Discord: route bound DMs to plugins

* Discord: restore plugin bindings after restart

* Telegram: persist detached plugin bindings

* Plugins: limit binding APIs to Telegram and Discord

* Plugins: harden bound conversation routing

* Plugins: fix extension target imports

* Plugins: fix Telegram runtime extension imports

* Plugins: format rebased binding handlers

* Discord: bind group DM interactions by channel

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-15 16:06:11 -07:00

176 lines
5.2 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createHookRunner } from "./hooks.js";
import { createMockPluginRegistry } from "./hooks.test-helpers.js";
describe("inbound_claim hook runner", () => {
it("stops at the first handler that claims the event", async () => {
const first = vi.fn().mockResolvedValue({ handled: true });
const second = vi.fn().mockResolvedValue({ handled: true });
const registry = createMockPluginRegistry([
{ hookName: "inbound_claim", handler: first },
{ hookName: "inbound_claim", handler: second },
]);
const runner = createHookRunner(registry);
const result = await runner.runInboundClaim(
{
content: "who are you",
channel: "telegram",
accountId: "default",
conversationId: "123:topic:77",
isGroup: true,
},
{
channelId: "telegram",
accountId: "default",
conversationId: "123:topic:77",
},
);
expect(result).toEqual({ handled: true });
expect(first).toHaveBeenCalledTimes(1);
expect(second).not.toHaveBeenCalled();
});
it("continues to the next handler when a higher-priority handler throws", async () => {
const logger = {
warn: vi.fn(),
error: vi.fn(),
};
const failing = vi.fn().mockRejectedValue(new Error("boom"));
const succeeding = vi.fn().mockResolvedValue({ handled: true });
const registry = createMockPluginRegistry([
{ hookName: "inbound_claim", handler: failing },
{ hookName: "inbound_claim", handler: succeeding },
]);
const runner = createHookRunner(registry, { logger });
const result = await runner.runInboundClaim(
{
content: "hi",
channel: "telegram",
accountId: "default",
conversationId: "123",
isGroup: false,
},
{
channelId: "telegram",
accountId: "default",
conversationId: "123",
},
);
expect(result).toEqual({ handled: true });
expect(logger.error).toHaveBeenCalledWith(
expect.stringContaining("inbound_claim handler from test-plugin failed: Error: boom"),
);
expect(succeeding).toHaveBeenCalledTimes(1);
});
it("can target a single plugin when core already owns the binding", async () => {
const first = vi.fn().mockResolvedValue({ handled: true });
const second = vi.fn().mockResolvedValue({ handled: true });
const registry = createMockPluginRegistry([
{ hookName: "inbound_claim", handler: first },
{ hookName: "inbound_claim", handler: second },
]);
registry.typedHooks[1].pluginId = "other-plugin";
const runner = createHookRunner(registry);
const result = await runner.runInboundClaimForPlugin(
"test-plugin",
{
content: "who are you",
channel: "discord",
accountId: "default",
conversationId: "channel:1",
isGroup: true,
},
{
channelId: "discord",
accountId: "default",
conversationId: "channel:1",
},
);
expect(result).toEqual({ handled: true });
expect(first).toHaveBeenCalledTimes(1);
expect(second).not.toHaveBeenCalled();
});
it("reports missing_plugin when the bound plugin is not loaded", async () => {
const registry = createMockPluginRegistry([]);
registry.plugins = [];
const runner = createHookRunner(registry);
const result = await runner.runInboundClaimForPluginOutcome(
"missing-plugin",
{
content: "who are you",
channel: "discord",
accountId: "default",
conversationId: "channel:1",
isGroup: true,
},
{
channelId: "discord",
accountId: "default",
conversationId: "channel:1",
},
);
expect(result).toEqual({ status: "missing_plugin" });
});
it("reports no_handler when the plugin is loaded but has no targeted hooks", async () => {
const registry = createMockPluginRegistry([]);
const runner = createHookRunner(registry);
const result = await runner.runInboundClaimForPluginOutcome(
"test-plugin",
{
content: "who are you",
channel: "discord",
accountId: "default",
conversationId: "channel:1",
isGroup: true,
},
{
channelId: "discord",
accountId: "default",
conversationId: "channel:1",
},
);
expect(result).toEqual({ status: "no_handler" });
});
it("reports error when a targeted handler throws and none claim the event", async () => {
const logger = {
warn: vi.fn(),
error: vi.fn(),
};
const failing = vi.fn().mockRejectedValue(new Error("boom"));
const registry = createMockPluginRegistry([{ hookName: "inbound_claim", handler: failing }]);
const runner = createHookRunner(registry, { logger });
const result = await runner.runInboundClaimForPluginOutcome(
"test-plugin",
{
content: "who are you",
channel: "discord",
accountId: "default",
conversationId: "channel:1",
isGroup: true,
},
{
channelId: "discord",
accountId: "default",
conversationId: "channel:1",
},
);
expect(result).toEqual({ status: "error", error: "boom" });
});
});