import type { IncomingMessage } from "node:http"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import type { ClawdbotConfig } from "../config/config.js"; import type { ChannelPlugin } from "../channels/plugins/types.js"; import { setActivePluginRegistry } from "../plugins/runtime.js"; import { createIMessageTestPlugin, createTestRegistry } from "../test-utils/channel-plugins.js"; import { extractHookToken, normalizeAgentPayload, normalizeWakePayload, resolveHooksConfig, } from "./hooks.js"; describe("gateway hooks helpers", () => { beforeEach(() => { setActivePluginRegistry(emptyRegistry); }); afterEach(() => { setActivePluginRegistry(emptyRegistry); }); test("resolveHooksConfig normalizes paths + requires token", () => { const base = { hooks: { enabled: true, token: "secret", path: "hooks///", }, } as ClawdbotConfig; const resolved = resolveHooksConfig(base); expect(resolved?.basePath).toBe("/hooks"); expect(resolved?.token).toBe("secret"); }); test("resolveHooksConfig rejects root path", () => { const cfg = { hooks: { enabled: true, token: "x", path: "/" }, } as ClawdbotConfig; expect(() => resolveHooksConfig(cfg)).toThrow("hooks.path may not be '/'"); }); test("extractHookToken prefers bearer > header > query", () => { const req = { headers: { authorization: "Bearer top", "x-clawdbot-token": "header", }, } as unknown as IncomingMessage; const url = new URL("http://localhost/hooks/wake?token=query"); expect(extractHookToken(req, url)).toBe("top"); const req2 = { headers: { "x-clawdbot-token": "header" }, } as unknown as IncomingMessage; expect(extractHookToken(req2, url)).toBe("header"); const req3 = { headers: {} } as unknown as IncomingMessage; expect(extractHookToken(req3, url)).toBe("query"); }); test("normalizeWakePayload trims + validates", () => { expect(normalizeWakePayload({ text: " hi " })).toEqual({ ok: true, value: { text: "hi", mode: "now" }, }); expect(normalizeWakePayload({ text: " ", mode: "now" }).ok).toBe(false); }); test("normalizeAgentPayload defaults + validates channel", () => { const ok = normalizeAgentPayload({ message: "hello" }, { idFactory: () => "fixed" }); expect(ok.ok).toBe(true); if (ok.ok) { expect(ok.value.sessionKey).toBe("hook:fixed"); expect(ok.value.channel).toBe("last"); expect(ok.value.name).toBe("Hook"); expect(ok.value.deliver).toBe(true); } const explicitNoDeliver = normalizeAgentPayload( { message: "hello", deliver: false }, { idFactory: () => "fixed" }, ); expect(explicitNoDeliver.ok).toBe(true); if (explicitNoDeliver.ok) { expect(explicitNoDeliver.value.deliver).toBe(false); } setActivePluginRegistry( createTestRegistry([ { pluginId: "imessage", source: "test", plugin: createIMessageTestPlugin(), }, ]), ); const imsg = normalizeAgentPayload( { message: "yo", channel: "imsg" }, { idFactory: () => "x" }, ); expect(imsg.ok).toBe(true); if (imsg.ok) { expect(imsg.value.channel).toBe("imessage"); } setActivePluginRegistry( createTestRegistry([ { pluginId: "msteams", source: "test", plugin: createMSTeamsPlugin({ aliases: ["teams"] }), }, ]), ); const teams = normalizeAgentPayload( { message: "yo", channel: "teams" }, { idFactory: () => "x" }, ); expect(teams.ok).toBe(true); if (teams.ok) { expect(teams.value.channel).toBe("msteams"); } const bad = normalizeAgentPayload({ message: "yo", channel: "sms" }); expect(bad.ok).toBe(false); }); }); const emptyRegistry = createTestRegistry([]); const createMSTeamsPlugin = (params: { aliases?: string[] }): ChannelPlugin => ({ id: "msteams", meta: { id: "msteams", label: "Microsoft Teams", selectionLabel: "Microsoft Teams (Bot Framework)", docsPath: "/channels/msteams", blurb: "Bot Framework; enterprise support.", aliases: params.aliases, }, capabilities: { chatTypes: ["direct"] }, config: { listAccountIds: () => [], resolveAccount: () => ({}), }, });