diff --git a/extensions/nextcloud-talk/src/message-actions.test.ts b/extensions/nextcloud-talk/src/message-actions.test.ts index 6bdbd97fa84e..9e4bd8dd3c2a 100644 --- a/extensions/nextcloud-talk/src/message-actions.test.ts +++ b/extensions/nextcloud-talk/src/message-actions.test.ts @@ -144,6 +144,41 @@ describe("nextcloudTalkMessageActions", () => { describe("handleAction", () => { const cfg = {} as CoreConfig; + beforeEach(() => { + // Dispatch now resolves the account and enforces the same enabled+configured + // gate as describeMessageTool, so react tests need a configured account. + hoisted.resolveNextcloudTalkAccount.mockReturnValue(configuredAccount); + }); + + it("rejects a disabled account before reaching the sender", async () => { + hoisted.resolveNextcloudTalkAccount.mockReturnValue(disabledAccount); + + await expect( + nextcloudTalkMessageActions.handleAction?.({ + channel: "nextcloud-talk", + action: "react", + params: { to: "room:abc123", messageId: "1", emoji: "👍" }, + cfg, + accountId: "work", + }), + ).rejects.toThrow(/is disabled or not configured/); + expect(hoisted.sendReactionNextcloudTalk).not.toHaveBeenCalled(); + }); + + it("rejects an unconfigured account before reaching the sender", async () => { + hoisted.resolveNextcloudTalkAccount.mockReturnValue(unconfiguredAccount); + + await expect( + nextcloudTalkMessageActions.handleAction?.({ + channel: "nextcloud-talk", + action: "react", + params: { to: "room:abc123", messageId: "1", emoji: "👍" }, + cfg, + }), + ).rejects.toThrow(/is disabled or not configured/); + expect(hoisted.sendReactionNextcloudTalk).not.toHaveBeenCalled(); + }); + it("invokes sendReactionNextcloudTalk with normalized params for the react action", async () => { const result = await nextcloudTalkMessageActions.handleAction?.({ channel: "nextcloud-talk", diff --git a/extensions/nextcloud-talk/src/message-actions.ts b/extensions/nextcloud-talk/src/message-actions.ts index 9cb23b8a7a05..0771bbc29659 100644 --- a/extensions/nextcloud-talk/src/message-actions.ts +++ b/extensions/nextcloud-talk/src/message-actions.ts @@ -49,6 +49,18 @@ export const nextcloudTalkMessageActions: ChannelMessageActionAdapter = { } if (action === "react") { + // describeMessageTool only offers `react` for enabled, configured accounts. + // An explicit accountId can still target a disabled or uncredentialed + // account, so enforce the same gate at dispatch before it reaches the + // sender — otherwise a disabled account with leftover credentials still + // emits reactions. + const account = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }); + if (!isAccountConfigured(account)) { + throw new Error( + `Nextcloud Talk account "${account.accountId}" is disabled or not configured.`, + ); + } + const target = readStringParam(params, "to", { required: true, label: "to (room token)",