UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

181 lines (147 loc) 6.38 kB
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import register from "./index.js"; describe("thread-ownership plugin", () => { const hooks: Record<string, Function> = {}; const api = { pluginConfig: {}, config: { agents: { list: [{ id: "test-agent", default: true, identity: { name: "TestBot" } }], }, }, id: "thread-ownership", name: "Thread Ownership", logger: { info: vi.fn(), warn: vi.fn(), debug: vi.fn() }, on: vi.fn((hookName: string, handler: Function) => { hooks[hookName] = handler; }), }; let originalFetch: typeof globalThis.fetch; beforeEach(() => { vi.clearAllMocks(); for (const key of Object.keys(hooks)) delete hooks[key]; process.env.SLACK_FORWARDER_URL = "http://localhost:8750"; process.env.SLACK_BOT_USER_ID = "U999"; originalFetch = globalThis.fetch; globalThis.fetch = vi.fn() as unknown as typeof globalThis.fetch; }); afterEach(() => { globalThis.fetch = originalFetch; delete process.env.SLACK_FORWARDER_URL; delete process.env.SLACK_BOT_USER_ID; vi.restoreAllMocks(); }); it("registers message_received and message_sending hooks", () => { register(api as any); expect(api.on).toHaveBeenCalledTimes(2); expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function)); expect(api.on).toHaveBeenCalledWith("message_sending", expect.any(Function)); }); describe("message_sending", () => { beforeEach(() => { register(api as any); }); it("allows non-slack channels", async () => { const result = await hooks.message_sending( { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" }, { channelId: "discord", conversationId: "C123" }, ); expect(result).toBeUndefined(); expect(globalThis.fetch).not.toHaveBeenCalled(); }); it("allows top-level messages (no threadTs)", async () => { const result = await hooks.message_sending( { content: "hello", metadata: {}, to: "C123" }, { channelId: "slack", conversationId: "C123" }, ); expect(result).toBeUndefined(); expect(globalThis.fetch).not.toHaveBeenCalled(); }); it("claims ownership successfully", async () => { vi.mocked(globalThis.fetch).mockResolvedValue( new Response(JSON.stringify({ owner: "test-agent" }), { status: 200 }), ); const result = await hooks.message_sending( { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" }, { channelId: "slack", conversationId: "C123" }, ); expect(result).toBeUndefined(); expect(globalThis.fetch).toHaveBeenCalledWith( "http://localhost:8750/api/v1/ownership/C123/1234.5678", expect.objectContaining({ method: "POST", body: JSON.stringify({ agent_id: "test-agent" }), }), ); }); it("cancels when thread owned by another agent", async () => { vi.mocked(globalThis.fetch).mockResolvedValue( new Response(JSON.stringify({ owner: "other-agent" }), { status: 409 }), ); const result = await hooks.message_sending( { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" }, { channelId: "slack", conversationId: "C123" }, ); expect(result).toEqual({ cancel: true }); expect(api.logger.info).toHaveBeenCalledWith(expect.stringContaining("cancelled send")); }); it("fails open on network error", async () => { vi.mocked(globalThis.fetch).mockRejectedValue(new Error("ECONNREFUSED")); const result = await hooks.message_sending( { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" }, { channelId: "slack", conversationId: "C123" }, ); expect(result).toBeUndefined(); expect(api.logger.warn).toHaveBeenCalledWith( expect.stringContaining("ownership check failed"), ); }); }); describe("message_received @-mention tracking", () => { beforeEach(() => { register(api as any); }); it("tracks @-mentions and skips ownership check for mentioned threads", async () => { // Simulate receiving a message that @-mentions the agent. await hooks.message_received( { content: "Hey @TestBot help me", metadata: { threadTs: "9999.0001", channelId: "C456" } }, { channelId: "slack", conversationId: "C456" }, ); // Now send in the same thread -- should skip the ownership HTTP call. const result = await hooks.message_sending( { content: "Sure!", metadata: { threadTs: "9999.0001", channelId: "C456" }, to: "C456" }, { channelId: "slack", conversationId: "C456" }, ); expect(result).toBeUndefined(); expect(globalThis.fetch).not.toHaveBeenCalled(); }); it("ignores @-mentions on non-slack channels", async () => { // Use a unique thread key so module-level state from other tests doesn't interfere. await hooks.message_received( { content: "Hey @TestBot", metadata: { threadTs: "7777.0001", channelId: "C999" } }, { channelId: "discord", conversationId: "C999" }, ); // The mention should not have been tracked, so sending should still call fetch. vi.mocked(globalThis.fetch).mockResolvedValue( new Response(JSON.stringify({ owner: "test-agent" }), { status: 200 }), ); await hooks.message_sending( { content: "Sure!", metadata: { threadTs: "7777.0001", channelId: "C999" }, to: "C999" }, { channelId: "slack", conversationId: "C999" }, ); expect(globalThis.fetch).toHaveBeenCalled(); }); it("tracks bot user ID mentions via <@U999> syntax", async () => { await hooks.message_received( { content: "Hey <@U999> help", metadata: { threadTs: "8888.0001", channelId: "C789" } }, { channelId: "slack", conversationId: "C789" }, ); const result = await hooks.message_sending( { content: "On it!", metadata: { threadTs: "8888.0001", channelId: "C789" }, to: "C789" }, { channelId: "slack", conversationId: "C789" }, ); expect(result).toBeUndefined(); expect(globalThis.fetch).not.toHaveBeenCalled(); }); }); });