import { afterEach, describe, expect, it, vi } from "vitest"; import { TriggerQueue, type QueuedTrigger } from "../../src/feishu/triggerQueue.js"; import type { MessageReceiveEvent } from "../../src/feishu/client.js"; describe("TriggerQueue", () => { afterEach(() => { vi.useRealTimers(); }); it("dequeues triggers in FIFO order", () => { const queue = new TriggerQueue(); queue.enqueue("project-1", makeTrigger("first")); queue.enqueue("project-1", makeTrigger("second")); expect(queue.dequeue("project-1")?.prompt).toBe("first"); expect(queue.dequeue("project-1")?.prompt).toBe("second"); }); it("returns the 1-based queue position from enqueue", () => { const queue = new TriggerQueue(); expect(queue.enqueue("project-1", makeTrigger("first"))).toBe(1); expect(queue.enqueue("project-1", makeTrigger("second"))).toBe(2); expect(queue.enqueue("project-1", makeTrigger("third"))).toBe(3); }); it("returns 0 when the project queue is full", () => { const queue = new TriggerQueue({ maxQueueSize: 2 }); expect(queue.enqueue("project-1", makeTrigger("first"))).toBe(1); expect(queue.enqueue("project-1", makeTrigger("second"))).toBe(2); expect(queue.enqueue("project-1", makeTrigger("third"))).toBe(0); expect(queue.length("project-1")).toBe(2); }); it("returns null when dequeueing an empty queue", () => { const queue = new TriggerQueue(); expect(queue.dequeue("project-1")).toBeNull(); }); it("peeks without removing the trigger", () => { const queue = new TriggerQueue(); queue.enqueue("project-1", makeTrigger("first")); expect(queue.peek("project-1")?.prompt).toBe("first"); expect(queue.length("project-1")).toBe(1); expect(queue.dequeue("project-1")?.prompt).toBe("first"); }); it("reports length and pending state per project", () => { const queue = new TriggerQueue(); expect(queue.length("project-1")).toBe(0); expect(queue.hasPending("project-1")).toBe(false); queue.enqueue("project-1", makeTrigger("first")); expect(queue.length("project-1")).toBe(1); expect(queue.hasPending("project-1")).toBe(true); }); it("purges expired triggers", () => { vi.useFakeTimers(); vi.setSystemTime(1_000); const queue = new TriggerQueue({ maxWaitMs: 100 }); queue.enqueue("project-1", makeTrigger("old")); vi.setSystemTime(1_050); queue.enqueue("project-1", makeTrigger("fresh")); vi.setSystemTime(1_101); expect(queue.purgeExpired()).toBe(1); expect(queue.length("project-1")).toBe(1); expect(queue.dequeue("project-1")?.prompt).toBe("fresh"); }); it("clears all triggers for a project", () => { const queue = new TriggerQueue(); queue.enqueue("project-1", makeTrigger("first")); queue.enqueue("project-1", makeTrigger("second")); queue.enqueue("project-2", makeTrigger("other")); expect(queue.clear("project-1")).toBe(2); expect(queue.length("project-1")).toBe(0); expect(queue.length("project-2")).toBe(1); }); it("clears all project queues", () => { const queue = new TriggerQueue(); queue.enqueue("project-1", makeTrigger("first")); queue.enqueue("project-2", makeTrigger("other")); queue.clearAll(); expect(queue.hasPending("project-1")).toBe(false); expect(queue.hasPending("project-2")).toBe(false); }); it("keeps multiple project queues independent", () => { const queue = new TriggerQueue(); expect(queue.enqueue("project-1", makeTrigger("p1-first"))).toBe(1); expect(queue.enqueue("project-2", makeTrigger("p2-first"))).toBe(1); expect(queue.enqueue("project-1", makeTrigger("p1-second"))).toBe(2); expect(queue.dequeue("project-2")?.prompt).toBe("p2-first"); expect(queue.dequeue("project-1")?.prompt).toBe("p1-first"); expect(queue.dequeue("project-1")?.prompt).toBe("p1-second"); }); }); function makeTrigger(prompt: string): Omit { return { chatId: "chat-1", prompt, roleId: "draft", executionKind: "conversation", msg: makeMessage(prompt), senderOpenId: "ou-user", actor: { feishuOpenId: "ou-user", chatId: "chat-1" }, }; } function makeMessage(prompt: string): MessageReceiveEvent["message"] { return { message_id: `message-${prompt}`, chat_id: "chat-1", chat_type: "group", message_type: "text", content: JSON.stringify({ text: `@_user_1 ${prompt}` }), mentions: [{ key: "@_user_1", id: { open_id: "ou-bot" }, name: "Bot" }], }; }