diff --git a/src/daemon/schtasks.startup-fallback.test.ts b/src/daemon/schtasks.startup-fallback.test.ts index 6e6a8521d6c..4fdadd2f110 100644 --- a/src/daemon/schtasks.startup-fallback.test.ts +++ b/src/daemon/schtasks.startup-fallback.test.ts @@ -57,6 +57,30 @@ async function writeGatewayScript(env: Record, port = 18789) { "utf8", ); } +async function writeStartupFallbackEntry(env: Record) { + const startupEntryPath = resolveStartupEntryPath(env); + await fs.mkdir(path.dirname(startupEntryPath), { recursive: true }); + await fs.writeFile(startupEntryPath, "@echo off\r\n", "utf8"); + return startupEntryPath; +} + +function expectStartupFallbackSpawn(env: Record) { + expect(spawn).toHaveBeenCalledWith( + "cmd.exe", + ["/d", "/s", "/c", quoteCmdScriptArg(resolveTaskScriptPath(env))], + expect.objectContaining({ detached: true, stdio: "ignore", windowsHide: true }), + ); +} + +function addStartupFallbackMissingResponses( + extraResponses: Array<{ code: number; stdout: string; stderr: string }> = [], +) { + schtasksResponses.push( + { code: 0, stdout: "", stderr: "" }, + { code: 1, stdout: "", stderr: "not found" }, + ...extraResponses, + ); +} beforeEach(() => { resetSchtasksBaseMocks(); spawn.mockClear(); @@ -119,22 +143,14 @@ describe("Windows startup fallback", () => { }); await expect(fs.access(resolveStartupEntryPath(env))).resolves.toBeUndefined(); - expect(spawn).toHaveBeenCalledWith( - "cmd.exe", - ["/d", "/s", "/c", quoteCmdScriptArg(resolveTaskScriptPath(env))], - expect.objectContaining({ detached: true, stdio: "ignore", windowsHide: true }), - ); + expectStartupFallbackSpawn(env); }); }); it("treats an installed Startup-folder launcher as loaded", async () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { - schtasksResponses.push( - { code: 0, stdout: "", stderr: "" }, - { code: 1, stdout: "", stderr: "not found" }, - ); - await fs.mkdir(path.dirname(resolveStartupEntryPath(env)), { recursive: true }); - await fs.writeFile(resolveStartupEntryPath(env), "@echo off\r\n", "utf8"); + addStartupFallbackMissingResponses(); + await writeStartupFallbackEntry(env); await expect(isScheduledTaskInstalled({ env })).resolves.toBe(true); }); @@ -142,12 +158,8 @@ describe("Windows startup fallback", () => { it("reports runtime from the gateway listener when using the Startup fallback", async () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { - schtasksResponses.push( - { code: 0, stdout: "", stderr: "" }, - { code: 1, stdout: "", stderr: "not found" }, - ); - await fs.mkdir(path.dirname(resolveStartupEntryPath(env)), { recursive: true }); - await fs.writeFile(resolveStartupEntryPath(env), "@echo off\r\n", "utf8"); + addStartupFallbackMissingResponses(); + await writeStartupFallbackEntry(env); inspectPortUsage.mockResolvedValue({ port: 18789, status: "busy", @@ -164,14 +176,11 @@ describe("Windows startup fallback", () => { it("restarts the Startup fallback by killing the current pid and relaunching the entry", async () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { - schtasksResponses.push( + addStartupFallbackMissingResponses([ { code: 0, stdout: "", stderr: "" }, { code: 1, stdout: "", stderr: "not found" }, - { code: 0, stdout: "", stderr: "" }, - { code: 1, stdout: "", stderr: "not found" }, - ); - await fs.mkdir(path.dirname(resolveStartupEntryPath(env)), { recursive: true }); - await fs.writeFile(resolveStartupEntryPath(env), "@echo off\r\n", "utf8"); + ]); + await writeStartupFallbackEntry(env); inspectPortUsage.mockResolvedValue({ port: 18789, status: "busy", @@ -184,11 +193,7 @@ describe("Windows startup fallback", () => { outcome: "completed", }); expect(killProcessTree).toHaveBeenCalledWith(5151, { graceMs: 300 }); - expect(spawn).toHaveBeenCalledWith( - "cmd.exe", - ["/d", "/s", "/c", quoteCmdScriptArg(resolveTaskScriptPath(env))], - expect.objectContaining({ detached: true, stdio: "ignore", windowsHide: true }), - ); + expectStartupFallbackSpawn(env); }); }); @@ -196,8 +201,7 @@ describe("Windows startup fallback", () => { await withWindowsEnv("openclaw-win-startup-", async ({ env }) => { schtasksResponses.push({ code: 0, stdout: "", stderr: "" }); await writeGatewayScript(env); - await fs.mkdir(path.dirname(resolveStartupEntryPath(env)), { recursive: true }); - await fs.writeFile(resolveStartupEntryPath(env), "@echo off\r\n", "utf8"); + await writeStartupFallbackEntry(env); inspectPortUsage .mockResolvedValueOnce({ port: 18789, diff --git a/src/slack/monitor/events/messages.test.ts b/src/slack/monitor/events/messages.test.ts index 922458a40b1..25fdb77c025 100644 --- a/src/slack/monitor/events/messages.test.ts +++ b/src/slack/monitor/events/messages.test.ts @@ -18,6 +18,7 @@ vi.mock("../../../pairing/pairing-store.js", () => ({ type MessageHandler = (args: { event: Record; body: unknown }) => Promise; type AppMentionHandler = MessageHandler; +type RegisteredEventName = "message" | "app_mention"; type MessageCase = { overrides?: SlackSystemEventTestOverrides; @@ -25,7 +26,7 @@ type MessageCase = { body?: unknown; }; -function createMessageHandlers(overrides?: SlackSystemEventTestOverrides) { +function createHandlers(eventName: RegisteredEventName, overrides?: SlackSystemEventTestOverrides) { const harness = createSlackSystemEventTestHarness(overrides); const handleSlackMessage = vi.fn(async () => {}); registerSlackMessageEvents({ @@ -33,22 +34,14 @@ function createMessageHandlers(overrides?: SlackSystemEventTestOverrides) { handleSlackMessage, }); return { - handler: harness.getHandler("message") as MessageHandler | null, + handler: harness.getHandler(eventName) as MessageHandler | null, handleSlackMessage, }; } -function createAppMentionHandlers(overrides?: SlackSystemEventTestOverrides) { - const harness = createSlackSystemEventTestHarness(overrides); - const handleSlackMessage = vi.fn(async () => {}); - registerSlackMessageEvents({ - ctx: harness.ctx, - handleSlackMessage, - }); - return { - handler: harness.getHandler("app_mention") as AppMentionHandler | null, - handleSlackMessage, - }; +function resetMessageMocks(): void { + messageQueueMock.mockClear(); + messageAllowMock.mockReset().mockResolvedValue([]); } function makeChangedEvent(overrides?: { channel?: string; user?: string }) { @@ -89,10 +82,40 @@ function makeThreadBroadcastEvent(overrides?: { channel?: string; user?: string }; } +function makeAppMentionEvent(overrides?: { + channel?: string; + channelType?: "channel" | "group" | "im" | "mpim"; + ts?: string; +}) { + return { + type: "app_mention", + channel: overrides?.channel ?? "C123", + channel_type: overrides?.channelType ?? "channel", + user: "U1", + text: "<@U_BOT> hello", + ts: overrides?.ts ?? "123.456", + }; +} + +async function invokeRegisteredHandler(input: { + eventName: RegisteredEventName; + overrides?: SlackSystemEventTestOverrides; + event: Record; + body?: unknown; +}) { + resetMessageMocks(); + const { handler, handleSlackMessage } = createHandlers(input.eventName, input.overrides); + expect(handler).toBeTruthy(); + await handler!({ + event: input.event, + body: input.body ?? {}, + }); + return { handleSlackMessage }; +} + async function runMessageCase(input: MessageCase = {}): Promise { - messageQueueMock.mockClear(); - messageAllowMock.mockReset().mockResolvedValue([]); - const { handler } = createMessageHandlers(input.overrides); + resetMessageMocks(); + const { handler } = createHandlers("message", input.overrides); expect(handler).toBeTruthy(); await handler!({ event: (input.event ?? makeChangedEvent()) as Record, @@ -151,12 +174,9 @@ describe("registerSlackMessageEvents", () => { }); it("passes regular message events to the message handler", async () => { - messageQueueMock.mockClear(); - messageAllowMock.mockReset().mockResolvedValue([]); - const { handler, handleSlackMessage } = createMessageHandlers({ dmPolicy: "open" }); - expect(handler).toBeTruthy(); - - await handler!({ + const { handleSlackMessage } = await invokeRegisteredHandler({ + eventName: "message", + overrides: { dmPolicy: "open" }, event: { type: "message", channel: "D1", @@ -164,7 +184,6 @@ describe("registerSlackMessageEvents", () => { text: "hello", ts: "123.456", }, - body: {}, }); expect(handleSlackMessage).toHaveBeenCalledTimes(1); @@ -172,9 +191,8 @@ describe("registerSlackMessageEvents", () => { }); it("handles channel and group messages via the unified message handler", async () => { - messageQueueMock.mockClear(); - messageAllowMock.mockReset().mockResolvedValue([]); - const { handler, handleSlackMessage } = createMessageHandlers({ + resetMessageMocks(); + const { handler, handleSlackMessage } = createHandlers("message", { dmPolicy: "open", channelType: "channel", }); @@ -206,23 +224,18 @@ describe("registerSlackMessageEvents", () => { }); it("applies subtype system-event handling for channel messages", async () => { - messageQueueMock.mockClear(); - messageAllowMock.mockReset().mockResolvedValue([]); - const { handler, handleSlackMessage } = createMessageHandlers({ - dmPolicy: "open", - channelType: "channel", - }); - - expect(handler).toBeTruthy(); - // message_changed events from channels arrive via the generic "message" // handler with channel_type:"channel" — not a separate event type. - await handler!({ + const { handleSlackMessage } = await invokeRegisteredHandler({ + eventName: "message", + overrides: { + dmPolicy: "open", + channelType: "channel", + }, event: { ...makeChangedEvent({ channel: "C1", user: "U1" }), channel_type: "channel", }, - body: {}, }); expect(handleSlackMessage).not.toHaveBeenCalled(); @@ -230,38 +243,20 @@ describe("registerSlackMessageEvents", () => { }); it("skips app_mention events for DM channel ids even with contradictory channel_type", async () => { - const { handler, handleSlackMessage } = createAppMentionHandlers({ dmPolicy: "open" }); - expect(handler).toBeTruthy(); - - await handler!({ - event: { - type: "app_mention", - channel: "D123", - channel_type: "channel", - user: "U1", - text: "<@U_BOT> hello", - ts: "123.456", - }, - body: {}, + const { handleSlackMessage } = await invokeRegisteredHandler({ + eventName: "app_mention", + overrides: { dmPolicy: "open" }, + event: makeAppMentionEvent({ channel: "D123", channelType: "channel" }), }); expect(handleSlackMessage).not.toHaveBeenCalled(); }); it("routes app_mention events from channels to the message handler", async () => { - const { handler, handleSlackMessage } = createAppMentionHandlers({ dmPolicy: "open" }); - expect(handler).toBeTruthy(); - - await handler!({ - event: { - type: "app_mention", - channel: "C123", - channel_type: "channel", - user: "U1", - text: "<@U_BOT> hello", - ts: "123.789", - }, - body: {}, + const { handleSlackMessage } = await invokeRegisteredHandler({ + eventName: "app_mention", + overrides: { dmPolicy: "open" }, + event: makeAppMentionEvent({ channel: "C123", channelType: "channel", ts: "123.789" }), }); expect(handleSlackMessage).toHaveBeenCalledTimes(1);