import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; /** * Integration test: verifies that routeMessage serializes same-channel messages * through ChannelQueue so they are processed in FIFO order. * * runAgent is mocked to record call order without spawning a real process. * discord.js and the database are also mocked so no real I/O occurs. */ // ---- Mock runAgent BEFORE importing router ---- const runOrder: string[] = []; vi.mock("../../src/agent/runner", () => ({ runAgent: vi.fn(async ({ userMessage }: { userMessage: string }) => { // Simulate brief async work so the promise chain is exercised await new Promise((resolve) => setTimeout(resolve, 5)); runOrder.push(userMessage); return `echo: ${userMessage}`; }), })); // ---- Import after mock registration ---- import { routeMessage } from "../../src/router"; // ---- Helpers ---- /** Builds a minimal fake discord.js Message object. */ function makeMessage(channelId: string, content: string) { const sent: string[] = []; const channel = { name: "test-channel", sendTyping: vi.fn().mockResolvedValue(undefined), send: vi.fn(async (text: string) => { sent.push(text); return { id: `msg-${Math.random()}` }; }), }; return { channelId, content, id: `mid-${content}`, author: { username: "tester" }, channel, _sent: sent, } as unknown as import("discord.js").Message & { _sent: string[] }; } /** Minimal fake DisclawDatabase. */ function makeDb(channelId: string) { return { getWorkspaceByChannelId: vi.fn((_id: string) => ({ id: 1, workspace_path: "/fake/workspace", })), getConversationHistory: vi.fn(() => []), addConversation: vi.fn(), } as unknown as import("../../src/db/database").DisclawDatabase; } /** Minimal fake DisclawConfig. */ const fakeConfig = {} as import("../../src/config/loader").DisclawConfig; // ---- Tests ---- describe("channel-queue / router integration", () => { beforeEach(() => { runOrder.length = 0; }); afterEach(() => { vi.restoreAllMocks(); }); it("processes 3 messages in the same channel in FIFO order", async () => { const channelId = "ch-999"; const db = makeDb(channelId); // Fire all three without awaiting — they should serialise internally const p1 = routeMessage(makeMessage(channelId, "first"), db, fakeConfig, null); const p2 = routeMessage(makeMessage(channelId, "second"), db, fakeConfig, null); const p3 = routeMessage(makeMessage(channelId, "third"), db, fakeConfig, null); await Promise.all([p1, p2, p3]); expect(runOrder).toEqual(["first", "second", "third"]); }); it("returns true for every handled message", async () => { const channelId = "ch-handled"; const db = makeDb(channelId); const results = await Promise.all([ routeMessage(makeMessage(channelId, "a"), db, fakeConfig, null), routeMessage(makeMessage(channelId, "b"), db, fakeConfig, null), ]); expect(results).toEqual([true, true]); }); it("returns false when channel is the management channel", async () => { const channelId = "ch-mgmt"; const db = makeDb(channelId); const result = await routeMessage( makeMessage(channelId, "hi"), db, fakeConfig, channelId // same id => management channel ); expect(result).toBe(false); }); });