refactor(hook-tests): share subagent hook helpers

This commit is contained in:
Peter Steinberger
2026-03-17 06:43:34 +00:00
parent 52ad686ab5
commit e56e4923bd
3 changed files with 65 additions and 64 deletions

View File

@@ -1,5 +1,9 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
getRequiredHookHandler,
registerHookHandlersForTest,
} from "../../test-utils/subagent-hooks.js";
import { registerDiscordSubagentHooks } from "./subagent-hooks.js";
type ThreadBindingRecord = {
@@ -55,26 +59,10 @@ function registerHandlersForTest(
},
},
) {
const handlers = new Map<string, (event: unknown, ctx: unknown) => unknown>();
const api = {
return registerHookHandlersForTest<OpenClawPluginApi>({
config,
on: (hookName: string, handler: (event: unknown, ctx: unknown) => unknown) => {
handlers.set(hookName, handler);
},
} as unknown as OpenClawPluginApi;
registerDiscordSubagentHooks(api);
return handlers;
}
function getRequiredHandler(
handlers: Map<string, (event: unknown, ctx: unknown) => unknown>,
hookName: string,
): (event: unknown, ctx: unknown) => unknown {
const handler = handlers.get(hookName);
if (!handler) {
throw new Error(`expected ${hookName} hook handler`);
}
return handler;
register: registerDiscordSubagentHooks,
});
}
function resolveSubagentDeliveryTargetForTest(requesterOrigin: {
@@ -84,7 +72,7 @@ function resolveSubagentDeliveryTargetForTest(requesterOrigin: {
threadId?: string;
}) {
const handlers = registerHandlersForTest();
const handler = getRequiredHandler(handlers, "subagent_delivery_target");
const handler = getRequiredHookHandler(handlers, "subagent_delivery_target");
return handler(
{
childSessionKey: "agent:main:subagent:child",
@@ -158,7 +146,7 @@ async function runSubagentSpawning(
event = createSpawnEventWithoutThread(),
) {
const handlers = registerHandlersForTest(config);
const handler = getRequiredHandler(handlers, "subagent_spawning");
const handler = getRequiredHookHandler(handlers, "subagent_spawning");
return await handler(event, {});
}
@@ -202,7 +190,7 @@ describe("discord subagent hook handlers", () => {
it("binds thread routing on subagent_spawning", async () => {
const handlers = registerHandlersForTest();
const handler = getRequiredHandler(handlers, "subagent_spawning");
const handler = getRequiredHookHandler(handlers, "subagent_spawning");
const result = await handler(createSpawnEvent(), {});
@@ -320,7 +308,7 @@ describe("discord subagent hook handlers", () => {
it("unbinds thread routing on subagent_ended", () => {
const handlers = registerHandlersForTest();
const handler = getRequiredHandler(handlers, "subagent_ended");
const handler = getRequiredHookHandler(handlers, "subagent_ended");
handler(
{

View File

@@ -1,5 +1,9 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
getRequiredHookHandler,
registerHookHandlersForTest,
} from "../../test-utils/subagent-hooks.js";
import { registerFeishuSubagentHooks } from "./subagent-hooks.js";
import {
__testing as threadBindingTesting,
@@ -12,26 +16,10 @@ const baseConfig = {
};
function registerHandlersForTest(config: Record<string, unknown> = baseConfig) {
const handlers = new Map<string, (event: unknown, ctx: unknown) => unknown>();
const api = {
return registerHookHandlersForTest<OpenClawPluginApi>({
config,
on: (hookName: string, handler: (event: unknown, ctx: unknown) => unknown) => {
handlers.set(hookName, handler);
},
} as unknown as OpenClawPluginApi;
registerFeishuSubagentHooks(api);
return handlers;
}
function getRequiredHandler(
handlers: Map<string, (event: unknown, ctx: unknown) => unknown>,
hookName: string,
): (event: unknown, ctx: unknown) => unknown {
const handler = handlers.get(hookName);
if (!handler) {
throw new Error(`expected ${hookName} hook handler`);
}
return handler;
register: registerFeishuSubagentHooks,
});
}
describe("feishu subagent hook handlers", () => {
@@ -49,7 +37,7 @@ describe("feishu subagent hook handlers", () => {
it("binds a Feishu DM conversation on subagent_spawning", async () => {
const handlers = registerHandlersForTest();
const handler = getRequiredHandler(handlers, "subagent_spawning");
const handler = getRequiredHookHandler(handlers, "subagent_spawning");
createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
const result = await handler(
@@ -70,7 +58,7 @@ describe("feishu subagent hook handlers", () => {
expect(result).toEqual({ status: "ok", threadBindingReady: true });
const deliveryTargetHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const deliveryTargetHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
expect(
deliveryTargetHandler(
{
@@ -96,7 +84,7 @@ describe("feishu subagent hook handlers", () => {
it("preserves the original Feishu DM delivery target", async () => {
const handlers = registerHandlersForTest();
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
const manager = createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
manager.bindConversation({
@@ -134,8 +122,8 @@ describe("feishu subagent hook handlers", () => {
it("binds a Feishu topic conversation and preserves parent context", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
const result = await spawnHandler(
@@ -183,8 +171,8 @@ describe("feishu subagent hook handlers", () => {
it("uses the requester session binding to preserve sender-scoped topic conversations", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
const manager = createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
manager.bindConversation({
@@ -252,8 +240,8 @@ describe("feishu subagent hook handlers", () => {
it("prefers requester-matching bindings when multiple child bindings exist", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
await spawnHandler(
@@ -312,8 +300,8 @@ describe("feishu subagent hook handlers", () => {
it("fails closed when requester-session bindings remain ambiguous for the same topic", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
const manager = createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
manager.bindConversation({
@@ -375,8 +363,8 @@ describe("feishu subagent hook handlers", () => {
it("fails closed when both topic-level and sender-scoped requester bindings exist", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
const manager = createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
manager.bindConversation({
@@ -438,9 +426,9 @@ describe("feishu subagent hook handlers", () => {
it("no-ops for non-Feishu channels and non-threaded spawns", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const endedHandler = getRequiredHandler(handlers, "subagent_ended");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
const endedHandler = getRequiredHookHandler(handlers, "subagent_ended");
await expect(
spawnHandler(
@@ -506,7 +494,7 @@ describe("feishu subagent hook handlers", () => {
});
it("returns an error for unsupported non-topic Feishu group conversations", async () => {
const handler = getRequiredHandler(registerHandlersForTest(), "subagent_spawning");
const handler = getRequiredHookHandler(registerHandlersForTest(), "subagent_spawning");
createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
await expect(
@@ -532,9 +520,9 @@ describe("feishu subagent hook handlers", () => {
it("unbinds Feishu bindings on subagent_ended", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const endedHandler = getRequiredHandler(handlers, "subagent_ended");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
const endedHandler = getRequiredHookHandler(handlers, "subagent_ended");
createFeishuThreadBindingManager({ cfg: baseConfig as any, accountId: "work" });
await spawnHandler(
@@ -581,8 +569,8 @@ describe("feishu subagent hook handlers", () => {
it("fails closed when the Feishu monitor-owned binding manager is unavailable", async () => {
const handlers = registerHandlersForTest();
const spawnHandler = getRequiredHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHandler(handlers, "subagent_delivery_target");
const spawnHandler = getRequiredHookHandler(handlers, "subagent_spawning");
const deliveryHandler = getRequiredHookHandler(handlers, "subagent_delivery_target");
await expect(
spawnHandler(

View File

@@ -0,0 +1,25 @@
export function registerHookHandlersForTest<TApi>(params: {
config: Record<string, unknown>;
register: (api: TApi) => void;
}) {
const handlers = new Map<string, (event: unknown, ctx: unknown) => unknown>();
const api = {
config: params.config,
on: (hookName: string, handler: (event: unknown, ctx: unknown) => unknown) => {
handlers.set(hookName, handler);
},
} as TApi;
params.register(api);
return handlers;
}
export function getRequiredHookHandler(
handlers: Map<string, (event: unknown, ctx: unknown) => unknown>,
hookName: string,
): (event: unknown, ctx: unknown) => unknown {
const handler = handlers.get(hookName);
if (!handler) {
throw new Error(`expected ${hookName} hook handler`);
}
return handler;
}