From ba2d57d024697a28e8427db5a02931c6c0a41aa4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 20:27:04 +0000 Subject: [PATCH] refactor: share mattermost test harnesses --- .../src/mattermost/monitor.authz.test.ts | 83 +++++++------------ .../src/mattermost/reactions.test.ts | 43 ++++++---- .../src/mattermost/slash-commands.test.ts | 51 +++++------- .../src/mattermost/slash-http.test.ts | 37 +++++---- 4 files changed, 96 insertions(+), 118 deletions(-) diff --git a/extensions/mattermost/src/mattermost/monitor.authz.test.ts b/extensions/mattermost/src/mattermost/monitor.authz.test.ts index 92fd0a3c3f4..68919da7908 100644 --- a/extensions/mattermost/src/mattermost/monitor.authz.test.ts +++ b/extensions/mattermost/src/mattermost/monitor.authz.test.ts @@ -16,6 +16,35 @@ const accountFixture: ResolvedMattermostAccount = { config: {}, }; +function authorizeGroupCommand(senderId: string) { + return authorizeMattermostCommandInvocation({ + account: { + ...accountFixture, + config: { + groupPolicy: "allowlist", + allowFrom: ["trusted-user"], + }, + }, + cfg: { + commands: { + useAccessGroups: true, + }, + }, + senderId, + senderName: senderId, + channelId: "chan-1", + channelInfo: { + id: "chan-1", + type: "O", + name: "general", + display_name: "General", + }, + storeAllowFrom: [], + allowTextCommands: true, + hasControlCommand: true, + }); +} + describe("mattermost monitor authz", () => { it("keeps DM allowlist merged with pairing-store entries", () => { const resolved = resolveMattermostEffectiveAllowFromLists({ @@ -72,32 +101,7 @@ describe("mattermost monitor authz", () => { }); it("denies group control commands when the sender is outside the allowlist", () => { - const decision = authorizeMattermostCommandInvocation({ - account: { - ...accountFixture, - config: { - groupPolicy: "allowlist", - allowFrom: ["trusted-user"], - }, - }, - cfg: { - commands: { - useAccessGroups: true, - }, - }, - senderId: "attacker", - senderName: "attacker", - channelId: "chan-1", - channelInfo: { - id: "chan-1", - type: "O", - name: "general", - display_name: "General", - }, - storeAllowFrom: [], - allowTextCommands: true, - hasControlCommand: true, - }); + const decision = authorizeGroupCommand("attacker"); expect(decision).toMatchObject({ ok: false, @@ -107,32 +111,7 @@ describe("mattermost monitor authz", () => { }); it("authorizes group control commands for allowlisted senders", () => { - const decision = authorizeMattermostCommandInvocation({ - account: { - ...accountFixture, - config: { - groupPolicy: "allowlist", - allowFrom: ["trusted-user"], - }, - }, - cfg: { - commands: { - useAccessGroups: true, - }, - }, - senderId: "trusted-user", - senderName: "trusted-user", - channelId: "chan-1", - channelInfo: { - id: "chan-1", - type: "O", - name: "general", - display_name: "General", - }, - storeAllowFrom: [], - allowTextCommands: true, - hasControlCommand: true, - }); + const decision = authorizeGroupCommand("trusted-user"); expect(decision).toMatchObject({ ok: true, diff --git a/extensions/mattermost/src/mattermost/reactions.test.ts b/extensions/mattermost/src/mattermost/reactions.test.ts index 0b07c1b497b..2659f2e1a99 100644 --- a/extensions/mattermost/src/mattermost/reactions.test.ts +++ b/extensions/mattermost/src/mattermost/reactions.test.ts @@ -14,6 +14,28 @@ describe("mattermost reactions", () => { resetMattermostReactionBotUserCacheForTests(); }); + async function addReactionWithFetch( + fetchMock: ReturnType, + ) { + return addMattermostReaction({ + cfg: createMattermostTestConfig(), + postId: "POST1", + emojiName: "thumbsup", + fetchImpl: fetchMock as unknown as typeof fetch, + }); + } + + async function removeReactionWithFetch( + fetchMock: ReturnType, + ) { + return removeMattermostReaction({ + cfg: createMattermostTestConfig(), + postId: "POST1", + emojiName: "thumbsup", + fetchImpl: fetchMock as unknown as typeof fetch, + }); + } + it("adds reactions by calling /users/me then POST /reactions", async () => { const fetchMock = createMattermostReactionFetchMock({ mode: "add", @@ -21,12 +43,7 @@ describe("mattermost reactions", () => { emojiName: "thumbsup", }); - const result = await addMattermostReaction({ - cfg: createMattermostTestConfig(), - postId: "POST1", - emojiName: "thumbsup", - fetchImpl: fetchMock as unknown as typeof fetch, - }); + const result = await addReactionWithFetch(fetchMock); expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalled(); @@ -41,12 +58,7 @@ describe("mattermost reactions", () => { body: { id: "err", message: "boom" }, }); - const result = await addMattermostReaction({ - cfg: createMattermostTestConfig(), - postId: "POST1", - emojiName: "thumbsup", - fetchImpl: fetchMock as unknown as typeof fetch, - }); + const result = await addReactionWithFetch(fetchMock); expect(result.ok).toBe(false); if (!result.ok) { @@ -61,12 +73,7 @@ describe("mattermost reactions", () => { emojiName: "thumbsup", }); - const result = await removeMattermostReaction({ - cfg: createMattermostTestConfig(), - postId: "POST1", - emojiName: "thumbsup", - fetchImpl: fetchMock as unknown as typeof fetch, - }); + const result = await removeReactionWithFetch(fetchMock); expect(result).toEqual({ ok: true }); expect(fetchMock).toHaveBeenCalled(); diff --git a/extensions/mattermost/src/mattermost/slash-commands.test.ts b/extensions/mattermost/src/mattermost/slash-commands.test.ts index 4beaea98ca5..d53c8f99203 100644 --- a/extensions/mattermost/src/mattermost/slash-commands.test.ts +++ b/extensions/mattermost/src/mattermost/slash-commands.test.ts @@ -10,6 +10,25 @@ import { } from "./slash-commands.js"; describe("slash-commands", () => { + async function registerSingleStatusCommand( + request: (path: string, init?: { method?: string }) => Promise, + ) { + const client = { request } as unknown as MattermostClient; + return registerSlashCommands({ + client, + teamId: "team-1", + creatorUserId: "bot-user", + callbackUrl: "http://gateway/callback", + commands: [ + { + trigger: "oc_status", + description: "status", + autoComplete: true, + }, + ], + }); + } + it("parses application/x-www-form-urlencoded payloads", () => { const payload = parseSlashCommandPayload( "token=t1&team_id=team&channel_id=ch1&user_id=u1&command=%2Foc_status&text=now", @@ -101,21 +120,7 @@ describe("slash-commands", () => { } throw new Error(`unexpected request path: ${path}`); }); - const client = { request } as unknown as MattermostClient; - - const result = await registerSlashCommands({ - client, - teamId: "team-1", - creatorUserId: "bot-user", - callbackUrl: "http://gateway/callback", - commands: [ - { - trigger: "oc_status", - description: "status", - autoComplete: true, - }, - ], - }); + const result = await registerSingleStatusCommand(request); expect(result).toHaveLength(1); expect(result[0]?.managed).toBe(false); @@ -144,21 +149,7 @@ describe("slash-commands", () => { } throw new Error(`unexpected request path: ${path}`); }); - const client = { request } as unknown as MattermostClient; - - const result = await registerSlashCommands({ - client, - teamId: "team-1", - creatorUserId: "bot-user", - callbackUrl: "http://gateway/callback", - commands: [ - { - trigger: "oc_status", - description: "status", - autoComplete: true, - }, - ], - }); + const result = await registerSingleStatusCommand(request); expect(result).toHaveLength(0); expect(request).toHaveBeenCalledTimes(1); diff --git a/extensions/mattermost/src/mattermost/slash-http.test.ts b/extensions/mattermost/src/mattermost/slash-http.test.ts index 92a6babe35c..a89bfc4e33a 100644 --- a/extensions/mattermost/src/mattermost/slash-http.test.ts +++ b/extensions/mattermost/src/mattermost/slash-http.test.ts @@ -58,6 +58,23 @@ const accountFixture: ResolvedMattermostAccount = { config: {}, }; +async function runSlashRequest(params: { + commandTokens: Set; + body: string; + method?: string; +}) { + const handler = createSlashCommandHttpHandler({ + account: accountFixture, + cfg: {} as OpenClawConfig, + runtime: {} as RuntimeEnv, + commandTokens: params.commandTokens, + }); + const req = createRequest({ method: params.method, body: params.body }); + const response = createResponse(); + await handler(req, response.res); + return response; +} + describe("slash-http", () => { it("rejects non-POST methods", async () => { const handler = createSlashCommandHttpHandler({ @@ -93,36 +110,20 @@ describe("slash-http", () => { }); it("fails closed when no command tokens are registered", async () => { - const handler = createSlashCommandHttpHandler({ - account: accountFixture, - cfg: {} as OpenClawConfig, - runtime: {} as RuntimeEnv, + const response = await runSlashRequest({ commandTokens: new Set(), - }); - const req = createRequest({ body: "token=tok1&team_id=t1&channel_id=c1&user_id=u1&command=%2Foc_status&text=", }); - const response = createResponse(); - - await handler(req, response.res); expect(response.res.statusCode).toBe(401); expect(response.getBody()).toContain("Unauthorized: invalid command token."); }); it("rejects unknown command tokens", async () => { - const handler = createSlashCommandHttpHandler({ - account: accountFixture, - cfg: {} as OpenClawConfig, - runtime: {} as RuntimeEnv, + const response = await runSlashRequest({ commandTokens: new Set(["known-token"]), - }); - const req = createRequest({ body: "token=unknown&team_id=t1&channel_id=c1&user_id=u1&command=%2Foc_status&text=", }); - const response = createResponse(); - - await handler(req, response.res); expect(response.res.statusCode).toBe(401); expect(response.getBody()).toContain("Unauthorized: invalid command token.");