diff --git a/extensions/slack/src/actions.download-file.test.ts b/extensions/slack/src/actions.download-file.test.ts index 0042ab07895..8e23e2752c1 100644 --- a/extensions/slack/src/actions.download-file.test.ts +++ b/extensions/slack/src/actions.download-file.test.ts @@ -1,12 +1,19 @@ import type { WebClient } from "@slack/web-api"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const resolveSlackMedia = vi.fn(); +const createSlackWebClientMock = vi.hoisted(() => vi.fn()); vi.mock("./monitor/media.js", () => ({ resolveSlackMedia: (...args: Parameters) => resolveSlackMedia(...args), })); +vi.mock("./client.js", () => ({ + createSlackWebClient: createSlackWebClientMock, + createSlackWriteClient: createSlackWebClientMock, +})); + let downloadSlackFile: typeof import("./actions.js").downloadSlackFile; function createClient() { @@ -74,6 +81,7 @@ describe("downloadSlackFile", () => { beforeEach(() => { resolveSlackMedia.mockReset(); + createSlackWebClientMock.mockReset(); }); it("returns null when files.info has no private download URL", async () => { @@ -165,4 +173,47 @@ describe("downloadSlackFile", () => { expect(resolveSlackMedia).toHaveBeenCalledTimes(1); expectResolveSlackMediaCalledWithDefaults(); }); + + it("resolves the bot token from cfg when no explicit token or client is provided", async () => { + // Regression guard for the 95331e5cc5 migration: downloadSlackFile must + // thread opts.cfg into resolveToken so the cfg-only resolution branch works + // from any caller (not only action-runtime.ts which always injects token). + const client = createClient(); + mockSuccessfulMediaDownload(client); + createSlackWebClientMock.mockReturnValueOnce(client); + + const cfg = { + channels: { + slack: { + accounts: { + default: { + botToken: "xoxb-from-cfg", + }, + }, + }, + }, + } as unknown as OpenClawConfig; + + const result = await downloadSlackFile("F123", { + cfg, + accountId: "default", + maxBytes: 1024, + }); + + expect(createSlackWebClientMock).toHaveBeenCalledWith("xoxb-from-cfg"); + expect(resolveSlackMedia).toHaveBeenCalledWith({ + files: [ + { + id: "F123", + name: "image.png", + mimetype: "image/png", + url_private: undefined, + url_private_download: "https://files.slack.com/files-pri/T1-F123/image.png", + }, + ], + token: "xoxb-from-cfg", + maxBytes: 1024, + }); + expect(result).toEqual(makeResolvedSlackMedia()); + }); }); diff --git a/extensions/slack/src/actions.ts b/extensions/slack/src/actions.ts index 3d6215253ad..5034b01faec 100644 --- a/extensions/slack/src/actions.ts +++ b/extensions/slack/src/actions.ts @@ -442,7 +442,7 @@ export async function downloadSlackFile( fileId: string, opts: SlackActionClientOpts & { maxBytes: number; channelId?: string; threadId?: string }, ): Promise { - const token = resolveToken(opts.token, opts.accountId); + const token = resolveToken(opts.token, opts.accountId, opts.cfg); const client = await getClient(opts); // Fetch fresh file metadata (includes a current url_private_download).