diff --git a/extensions/slack/src/monitor.threading.missing-thread-ts.test.ts b/extensions/slack/src/monitor.threading.missing-thread-ts.test.ts index 50cb7e84e7d..8c237041557 100644 --- a/extensions/slack/src/monitor.threading.missing-thread-ts.test.ts +++ b/extensions/slack/src/monitor.threading.missing-thread-ts.test.ts @@ -1,117 +1,61 @@ -import { resetInboundDedupe } from "openclaw/plugin-sdk/reply-runtime"; -import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { - flush, - getSlackClient, - getSlackHandlerOrThrow, - getSlackTestState, - resetSlackTestState, - startSlackMonitor, - stopSlackMonitor, -} from "./monitor.test-helpers.js"; +import { describe, expect, it, vi } from "vitest"; +import { createSlackThreadTsResolver } from "./monitor/thread-resolution.js"; +import type { SlackMessageEvent } from "./types.js"; -let monitorSlackProvider: typeof import("./monitor.js").monitorSlackProvider; - -const slackTestState = getSlackTestState(); - -type SlackConversationsClient = { - history: ReturnType; - info: ReturnType; -}; - -function makeThreadReplyEvent() { +function makeThreadReplyMessage(): SlackMessageEvent { return { - event: { - type: "message", - user: "U1", - text: "hello", - ts: "456", - parent_user_id: "U2", - channel: "C1", - channel_type: "channel", - }, + type: "message", + user: "U1", + text: "hello", + ts: "456", + parent_user_id: "U2", + channel: "C1", + channel_type: "channel", }; } -function getConversationsClient(): SlackConversationsClient { - const client = getSlackClient(); - if (!client) { - throw new Error("Slack client not registered"); - } - return client.conversations as SlackConversationsClient; -} - async function runMissingThreadScenario(params: { historyResponse?: { messages: Array<{ ts?: string; thread_ts?: string }> }; historyError?: Error; }) { - slackTestState.replyMock.mockResolvedValue({ text: "thread reply" }); - - const conversations = getConversationsClient(); + const history = vi.fn(); if (params.historyError) { - conversations.history.mockRejectedValueOnce(params.historyError); + history.mockRejectedValueOnce(params.historyError); } else { - conversations.history.mockResolvedValueOnce( - params.historyResponse ?? { messages: [{ ts: "456" }] }, - ); + history.mockResolvedValueOnce(params.historyResponse ?? { messages: [{ ts: "456" }] }); } - const { controller, run } = startSlackMonitor(monitorSlackProvider); - const handler = await getSlackHandlerOrThrow("message"); - await handler(makeThreadReplyEvent()); + const resolver = createSlackThreadTsResolver({ + client: { conversations: { history } } as never, + cacheTtlMs: 60_000, + maxSize: 5, + }); - await flush(); - await stopSlackMonitor({ controller, run }); - - expect(slackTestState.sendMock).toHaveBeenCalledTimes(1); - return slackTestState.sendMock.mock.calls[0]?.[2]; + return await resolver.resolve({ + message: makeThreadReplyMessage(), + source: "message", + }); } -beforeEach(() => { - resetInboundDedupe(); -}); - -beforeAll(async () => { - ({ monitorSlackProvider } = await import("./monitor.js")); -}); - -beforeEach(() => { - resetInboundDedupe(); - resetSlackTestState({ - messages: { responsePrefix: "PFX" }, - channels: { - slack: { - dm: { enabled: true, policy: "open", allowFrom: ["*"] }, - groupPolicy: "open", - channels: { C1: { allow: true, requireMention: false } }, - }, - }, - }); - const conversations = getConversationsClient(); - conversations.info.mockResolvedValue({ - channel: { name: "general", is_channel: true }, - }); -}); - -describe("monitorSlackProvider threading", () => { +describe("Slack missing thread_ts recovery", () => { it("recovers missing thread_ts when parent_user_id is present", async () => { - const options = await runMissingThreadScenario({ + const message = await runMissingThreadScenario({ historyResponse: { messages: [{ ts: "456", thread_ts: "111.222" }] }, }); - expect(options).toMatchObject({ threadTs: "111.222" }); + expect(message).toMatchObject({ thread_ts: "111.222" }); }); it("continues without thread_ts when history lookup returns no thread result", async () => { - const options = await runMissingThreadScenario({ + const message = await runMissingThreadScenario({ historyResponse: { messages: [{ ts: "456" }] }, }); - expect(options).not.toMatchObject({ threadTs: "111.222" }); + expect(message.thread_ts).toBeUndefined(); }); it("continues without thread_ts when history lookup throws", async () => { - const options = await runMissingThreadScenario({ + const message = await runMissingThreadScenario({ historyError: new Error("history failed"), }); - expect(options).not.toMatchObject({ threadTs: "111.222" }); + expect(message.thread_ts).toBeUndefined(); }); });