From be3c62b205b23064b83b42a16190a8e32b19539e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:17:46 +0000 Subject: [PATCH 1/8] fix: implement Updater.getMessageUpdater in deno-runtime Co-authored-by: d-gubert <1810309+d-gubert@users.noreply.github.com> --- .../lib/accessors/modify/ModifyUpdater.ts | 22 +++++++++++++++++++ .../lib/accessors/tests/ModifyUpdater.test.ts | 9 ++++++++ .../src/server/accessors/MessageUpdater.ts | 18 +++++++++++++++ .../src/server/accessors/ModifyUpdater.ts | 2 ++ 4 files changed, 51 insertions(+) create mode 100644 packages/apps-engine/src/server/accessors/MessageUpdater.ts diff --git a/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts b/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts index dc9178be1f52d..ad0783fb40811 100644 --- a/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts +++ b/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts @@ -1,6 +1,7 @@ import type { IModifyUpdater } from '@rocket.chat/apps-engine/definition/accessors/IModifyUpdater.ts'; import type { ILivechatUpdater } from '@rocket.chat/apps-engine/definition/accessors/ILivechatUpdater.ts'; import type { IUserUpdater } from '@rocket.chat/apps-engine/definition/accessors/IUserUpdater.ts'; +import type { IMessageUpdater } from '@rocket.chat/apps-engine/definition/accessors/IMessageUpdater.ts'; import type { IMessageBuilder } from '@rocket.chat/apps-engine/definition/accessors/IMessageBuilder.ts'; import type { IRoomBuilder } from '@rocket.chat/apps-engine/definition/accessors/IRoomBuilder.ts'; import type { IUser } from '@rocket.chat/apps-engine/definition/users/IUser.ts'; @@ -71,6 +72,27 @@ export class ModifyUpdater implements IModifyUpdater { ) as IUserUpdater; } + public getMessageUpdater(): IMessageUpdater { + return new Proxy( + { __kind: 'getMessageUpdater' }, + { + get: + (_target: unknown, prop: string) => + (...params: unknown[]) => + prop === 'toJSON' + ? {} + : this.senderFn({ + method: `accessor:getModifier:getUpdater:getMessageUpdater:${prop}`, + params, + }) + .then((response) => response.result) + .catch((err) => { + throw formatErrorResponse(err); + }), + }, + ) as IMessageUpdater; + } + public async message(messageId: string, editor: IUser): Promise { const response = await this.senderFn({ method: 'bridges:getMessageBridge:doGetById', diff --git a/packages/apps-engine/deno-runtime/lib/accessors/tests/ModifyUpdater.test.ts b/packages/apps-engine/deno-runtime/lib/accessors/tests/ModifyUpdater.test.ts index 487c10725028a..d351d6ebba721 100644 --- a/packages/apps-engine/deno-runtime/lib/accessors/tests/ModifyUpdater.test.ts +++ b/packages/apps-engine/deno-runtime/lib/accessors/tests/ModifyUpdater.test.ts @@ -128,6 +128,15 @@ describe('ModifyUpdater', () => { }); }); + it('correctly formats requests to MessageUpdater methods', async () => { + const result = (await modifyUpdater.getMessageUpdater().addReaction('message-id', 'user-id', ':smile:')) as any; + + assertEquals(result, { + method: 'accessor:getModifier:getUpdater:getMessageUpdater:addReaction', + params: ['message-id', 'user-id', ':smile:'], + }); + }); + describe('Error Handling', () => { describe('message', () => { it('throws an instance of Error when senderFn throws an error', async () => { diff --git a/packages/apps-engine/src/server/accessors/MessageUpdater.ts b/packages/apps-engine/src/server/accessors/MessageUpdater.ts new file mode 100644 index 0000000000000..218ebdf9b5840 --- /dev/null +++ b/packages/apps-engine/src/server/accessors/MessageUpdater.ts @@ -0,0 +1,18 @@ +import type { Reaction } from '../../definition/messages'; +import type { IMessageUpdater } from '../../definition/accessors/IMessageUpdater'; +import type { AppBridges } from '../bridges'; + +export class MessageUpdater implements IMessageUpdater { + constructor( + private readonly bridges: AppBridges, + private readonly appId: string, + ) {} + + public async addReaction(messageId: string, userId: string, reaction: Reaction): Promise { + return this.bridges.getMessageBridge().doAddReaction(messageId, userId, reaction, this.appId); + } + + public async removeReaction(messageId: string, userId: string, reaction: Reaction): Promise { + return this.bridges.getMessageBridge().doRemoveReaction(messageId, userId, reaction, this.appId); + } +} diff --git a/packages/apps-engine/src/server/accessors/ModifyUpdater.ts b/packages/apps-engine/src/server/accessors/ModifyUpdater.ts index 496e497860690..e06aad123d659 100644 --- a/packages/apps-engine/src/server/accessors/ModifyUpdater.ts +++ b/packages/apps-engine/src/server/accessors/ModifyUpdater.ts @@ -1,5 +1,6 @@ import { LivechatUpdater } from './LivechatUpdater'; import { MessageBuilder } from './MessageBuilder'; +import { MessageUpdater } from './MessageUpdater'; import { RoomBuilder } from './RoomBuilder'; import { UserUpdater } from './UserUpdater'; import type { ILivechatUpdater, IMessageBuilder, IMessageUpdater, IModifyUpdater, IRoomBuilder } from '../../definition/accessors'; @@ -23,6 +24,7 @@ export class ModifyUpdater implements IModifyUpdater { ) { this.livechatUpdater = new LivechatUpdater(this.bridges, this.appId); this.userUpdater = new UserUpdater(this.bridges, this.appId); + this.messageUpdater = new MessageUpdater(this.bridges, this.appId); } public getLivechatUpdater(): ILivechatUpdater { From ba1dd7b1b529a237396ab3ba257279a6852cd2b4 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Thu, 26 Mar 2026 13:52:37 -0300 Subject: [PATCH 2/8] refactor: modify updater --- .../lib/accessors/modify/ModifyUpdater.ts | 60 ++++++------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts b/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts index ad0783fb40811..301437627de18 100644 --- a/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts +++ b/packages/apps-engine/deno-runtime/lib/accessors/modify/ModifyUpdater.ts @@ -28,11 +28,19 @@ const { RocketChatAssociationModel } = require('@rocket.chat/apps-engine/definit }; export class ModifyUpdater implements IModifyUpdater { - constructor(private readonly senderFn: typeof Messenger.sendRequest) {} + private readonly livechatUpdater: ILivechatUpdater; + private readonly userUpdater: IUserUpdater; + private readonly messageUpdater: IMessageUpdater; + + constructor(private readonly senderFn: typeof Messenger.sendRequest) { + this.livechatUpdater = this.proxify('getLivechatUpdater'); + this.userUpdater = this.proxify('getUserUpdater'); + this.messageUpdater = this.proxify('getMessageUpdater'); + } - public getLivechatUpdater(): ILivechatUpdater { + private proxify(target: 'getLivechatUpdater' | 'getUserUpdater' | 'getMessageUpdater'): T { return new Proxy( - { __kind: 'getLivechatUpdater' }, + { __kind: target }, { get: (_target: unknown, prop: string) => @@ -40,7 +48,7 @@ export class ModifyUpdater implements IModifyUpdater { prop === 'toJSON' ? {} : this.senderFn({ - method: `accessor:getModifier:getUpdater:getLivechatUpdater:${prop}`, + method: `accessor:getModifier:getUpdater:${target}:${prop}`, params, }) .then((response) => response.result) @@ -48,49 +56,19 @@ export class ModifyUpdater implements IModifyUpdater { throw formatErrorResponse(err); }), }, - ) as ILivechatUpdater; + ) as T; + } + + public getLivechatUpdater(): ILivechatUpdater { + return this.livechatUpdater; } public getUserUpdater(): IUserUpdater { - return new Proxy( - { __kind: 'getUserUpdater' }, - { - get: - (_target: unknown, prop: string) => - (...params: unknown[]) => - prop === 'toJSON' - ? {} - : this.senderFn({ - method: `accessor:getModifier:getUpdater:getUserUpdater:${prop}`, - params, - }) - .then((response) => response.result) - .catch((err) => { - throw formatErrorResponse(err); - }), - }, - ) as IUserUpdater; + return this.userUpdater; } public getMessageUpdater(): IMessageUpdater { - return new Proxy( - { __kind: 'getMessageUpdater' }, - { - get: - (_target: unknown, prop: string) => - (...params: unknown[]) => - prop === 'toJSON' - ? {} - : this.senderFn({ - method: `accessor:getModifier:getUpdater:getMessageUpdater:${prop}`, - params, - }) - .then((response) => response.result) - .catch((err) => { - throw formatErrorResponse(err); - }), - }, - ) as IMessageUpdater; + return this.messageUpdater; } public async message(messageId: string, editor: IUser): Promise { From 2b26cbf651ff86f7223372bc78a0b838cae25041 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Thu, 26 Mar 2026 13:53:05 -0300 Subject: [PATCH 3/8] fix: setReaction toggle when false --- apps/meteor/app/reactions/server/setReaction.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/meteor/app/reactions/server/setReaction.ts b/apps/meteor/app/reactions/server/setReaction.ts index 483b1fff8ed7a..1a6d8dfa537f6 100644 --- a/apps/meteor/app/reactions/server/setReaction.ts +++ b/apps/meteor/app/reactions/server/setReaction.ts @@ -119,8 +119,7 @@ export async function executeSetReaction( throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' }); } - const userAlreadyReacted = - message.reactions && Boolean(message.reactions[reaction]) && message.reactions[reaction].usernames.includes(user.username as string); + const userAlreadyReacted = Boolean(message.reactions?.[reaction]?.usernames?.includes(user.username as string)); // When shouldReact was not informed, toggle the reaction. if (shouldReact === undefined) { From 0bc0bc0f674c97478d9647fae3be491e543ef1bb Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Thu, 26 Mar 2026 13:59:21 -0300 Subject: [PATCH 4/8] test: end-to-end test for message reaction --- .../tests/data/apps/app-packages/README.md | 44 ++++++++++++++++ .../tests/data/apps/app-packages/index.ts | 2 + .../message-updater-test_0.0.1.zip | Bin 0 -> 4759 bytes .../end-to-end/apps/app-message-reactions.ts | 49 ++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 apps/meteor/tests/data/apps/app-packages/message-updater-test_0.0.1.zip create mode 100644 apps/meteor/tests/end-to-end/apps/app-message-reactions.ts diff --git a/apps/meteor/tests/data/apps/app-packages/README.md b/apps/meteor/tests/data/apps/app-packages/README.md index bdb3f214bafbe..b4b56c0e0179a 100644 --- a/apps/meteor/tests/data/apps/app-packages/README.md +++ b/apps/meteor/tests/data/apps/app-packages/README.md @@ -293,3 +293,47 @@ export class UpdateStatusTextEndpoint extends ApiEndpoint { ``` + +#### Message Reaction Test + +File name: `message-updater-test_0.0.1.zip` + +An app used to test the message reaction updates. Provides a `/msg-update` slashcommand that takes an action of `'add' | 'remove'` and a message id, then adds or removes a reaction in the target message accordingly using the App's bot user. + +
+App source code + +```typescript +export class MessageUpdaterTestApp extends App { + protected async extendConfiguration(configuration: IConfigurationExtend, _environmentRead: IEnvironmentRead): Promise { + await configuration.slashCommands.provideSlashCommand(new class UpdateCommand implements ISlashCommand { + command = 'msg-update'; + i18nDescription = 'msg-update'; + i18nParamsExample = 'msg-update'; + providesPreview = false; + + constructor(private readonly app: App) { } + + public async executor(context: SlashCommandContext, read: IRead, modify: IModify, _http: IHttp, _persis: IPersistence) { + const [action, msgId] = context.getArguments() as ['add' | 'remove', string]; + + const user = await read.getUserReader().getAppUser(); + + if (!user) { + this.app.getLogger().error(`Could't find app user`); + return; + } + + if (action === 'add') { + await modify.getUpdater().getMessageUpdater().addReaction(msgId, user.id, ':+1:'); + this.app.getLogger().debug(`Added reaction 👍 to message ${msgId}`); + } else { + await modify.getUpdater().getMessageUpdater().removeReaction(msgId, user.id, ':+1:'); + this.app.getLogger().debug(`Removed reaction 👍 from message ${msgId}`); + } + } + }(this)); + } +} +``` +
diff --git a/apps/meteor/tests/data/apps/app-packages/index.ts b/apps/meteor/tests/data/apps/app-packages/index.ts index 4fa2e5d719f76..dc453b53a6bec 100644 --- a/apps/meteor/tests/data/apps/app-packages/index.ts +++ b/apps/meteor/tests/data/apps/app-packages/index.ts @@ -7,3 +7,5 @@ export const appAPIParameterTest = path.resolve(__dirname, './api-parameter-test export const appCausingNestedRequests = path.resolve(__dirname, './nested-requests_0.0.1.zip'); export const appUpdateStatusTest = path.resolve(__dirname, './update-status-test_0.0.1.zip'); + +export const messageReactionTest = path.resolve(__dirname, './message-updater-test_0.0.1.zip'); diff --git a/apps/meteor/tests/data/apps/app-packages/message-updater-test_0.0.1.zip b/apps/meteor/tests/data/apps/app-packages/message-updater-test_0.0.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..cf9bd96cb65c20a0bb6ad494910bcbc828700bd7 GIT binary patch literal 4759 zcmZ`-cQhRA(qFxYi0CDHiPdYY7CoYOR*lu`>b+Yv$O@t?LG%*6_aK7Z=!A_HB9yHzw9;8xj-|2jz#w2Ci++z7Nq#cu#bw zoL*)@O!}?ntaR2=3A`BgX8qS%2UdHajOtlr`|iIZ)g8h`5jUHA6_1k%^8o6{K!15D zM$!?A`-j5t4?7;VK%(kwfI485W_npKE`AF8>r|Tx_d;Z|9!R+0^Zw95dQSR45=py( z2o8kVo7tC|O9D>Y{$ej98dkF@Bq5 zz)_%>WlGu1@;5tL6rUGc8=a0KP=U9SIt2rN3JO|a866syVou>|(o;%EX3~-+pNOARs6n2a|oeKA{du zlll-+V=-!!qA6&;}aT@P*7#T1%Ia6$natT4l;Q&f7Kjc?c=vA7Bq7Qu@@qE z8)6!_ zwZ~x@985ZikEMQlohrtTC|A)a;nx zX!a(oS(M0em7FM*FA4QJSE8t9)&O*&bYH<)AWr2snT|w)B7U?pN1RD8O$oObUc@#S z3*&fc$r;o_nuUWmOZVgO>>Yk?UaH~eOwP(~+|7a^Ej5Pm#KjXLXq9gNg2F#)1rCDyhpe-Y6{ld(@Ua_OlsS1o3?WQ7RDK&f45V%;DBRX1* zy@)U603mZAe53~wrAe=GkU2g!3;z^r-nawiZ@QoS(hYvp!5`+LCNS=hU%NIki*^>pQ1jLpX@_-?X*);3D}nq_FT;z&xX|B~YZC|K7Q z@zcR6Bf|-UQm7|}WXJl-qiOLhI%00@P|cZcElzK~oUU-WjwAiZZ;UT{P}lhX*TxC| ze}vq2?tc;Ctz`SprtRqgNtg8;dO#8X>yr2#IDKC#X$z>?s(oczW!#xNq$nb>Riv4D zV-=;vP|47J;FIPXfEFI1l}!4TQe8IfKI7Z~Q%yWIjoGOh(({d(Ck&YjWfqGtSaYXK88RHN^fUf%QGQf}XjCrbE0 zndkMB6FnwPSJ6-Gd^i%f&v+O@h~a9MBG`}(30Xyl19UYB2-%CsJ2!5#PR9b=>VHP- zpStL$1%dxnlw09XU7VkJdI)%VIM!zUmY*TP8SUGBU+euyz%rqjCxeZ5TATF)SW+}m zb~=4aE?(NWZe8lRle0kbi49$u_#GccwYK#iM^l#7R1e3w{<;A@W5KW zuUiycu&GR5@*V&HG`nJf2Z$_ti0eQAh&1F=Vao^vF_YrM z)k}|#yqTB^T3cJYb)#`+Ug|(#duy zUVe2FvD>(i+qDRJRqRQg*n_wiwi}K#e`>pm#lw0TU`Kd&B9v|MoAQdT!^~2mjYK)y znqnK>8P|D`0JkarS@wA|XtU5n%S1ZidT>b*(?1W5we9@GE<=*2Uy^BBS>)EFtoJ;O zqqJqL(l-QXKl|-HUPeA`ixMBWl+@|zXLQY)e|i31e~A)$jLu4Hm)bvFp<_uKk<)4l zwNNX$PrPi4Jam=bYABvQbT4mWQa@7pV525U;?GiDXuAKo8%-a>)1Az8JiqcVDaTr= z1XOp^qA`@xAlVduKyWJhp?C1$2LZB_o4u3V9dW*dpxMCI!ol6SM^0+ykp_+MTT z8+?u-u9Kr~ws8*P>FOzG9}W51cloLOGm70jr$>&zc)v+XWD+VuSOo!H%r_o=yQF|i zLlNdbP4z_QN(jHR=UxP6=6<~bl|d@!Sf(huGSMDRDksHVVG-ifF=n)r;77QHvt$OF z$d1JHaq$7-40 z`BP+mVcA)WN4hLnqvh*6B9fs8mR1iwlm@`-d0&UZm8~q%srSlbs1Z@Jq3L0-$n@#Q zI3e%9eP&ACQ5PjIk*S^Aiip$^V2pLo7RTIeuzl5H+l1VPXhdfqI#3`BM>1YWsDl5J z^p{glduc~!Tw1eD`2t2a&|JHn$%^n3=J<^@*7(gJOFet43LjKTKE&*3 zNOGj3VI~Ufh%c=n^tk#)InQEcqk~c9UMm>);0!E@_tTdr+N^4C;9oPs zIOVtk8*nue+s#Ze8twx3Tf^kE$)($F@DMIow>#ZlW(&bFqXBiuQQ1^R1_ZAVPs{fbXCzl zty~%UbTu=n%rmd>7v;NW*O_WG_iOl6Wj_`24{7{nA0e}~bVlfGxui?f8ddrXjx zm#WFFY!{#nNspfqlP^!nU6dyDf#aR$yRsiDH$C-Eo>f|joHOplx~yPwD{@+xp<_@kN0a@jkBxHeuDs<{CD!d4x9I)&1uD)_dYA zMY0;#fl0fekxDP}mYz3NaKxc|n6e}aFa4T-3`45J19S=D*{EQ~64mrO-1*bd!D8Wq z69|zBmjj}lFQp!v{CpbtRmK(!GlQ$c^_#@k=Qr`*H|d~@YH@>*TCxmC%esZcm-w@8 z$PxTmh>!tNu}cqo_l=cyV3VQ8;4XbrWgU(hI!gSscI2L~nJiZ5z%&oAp^T$7`h_{4 z^OB(4i%J~NCsx63eGGxJG4tW=p&Wuo9GH`m0Ev(#b~V=rU)FFN8taK%#<{M{zvN^1 zGI?!ATRuL;L@V?rdc%mqZaNZoBpU~IYb@-Lm7j;P5n(x}IC5-zUxySc^{mm>Tiu1b z;;U(hn6+o77OCvt9>qs!H2=WJOOeqCSOtB26|+l}tnpj8;z^{1KKJ9r2UKkFi2X_P z@N6p5bG+v7a&uY6vj*SP96WN?n`eK79OFzB>DcOh=Ua={=t}ibZ7(L*sk7AgRzpT~ z8Rc)JX_~+9Z@)w3Z6h<6+;`k_bcWNsfBd9S>cD?XOSk;R1vJyb1kLG!zw>najV7u# zi*q&cWZ=%*C^n zjW(MlL6R3i=lWuCe*%U+ow_`vYBl1lGWB6O41YcyTRy$%`N2WYLW)b@RtT^Gv-tIm z-HT2Nakw0cS#X|1Y>xU%7W33^ee=^7w3%f;%#Qu93JyLwFPI*NXE{G8ZK1bl?13ls z&C9eW0X8X9z1z-MEc#qhYKB<+7eEkUywaARt81Wl8Tz{@X>lh%QBfzZ7|;d?(S)0nD>`^cC~#qhU5Ld_m8S&ze>Ar zrX9gn4Db)%TQV3SRYy<-E{MZ4XPqV!Pg(jz)JM~_R;wJt^3-WMj4jorp_66v zze6pJ+3K1jW<}k)XLbseI?-9=pWp4?X`Fr-eaOG1duX-JGDBWOZlW!U5lFNF$41k7iYpP zM(QtE$*wUsCrc-eyEl)uu7I`f@WeuXbYKR+{+iw6ll1aD_(o^?=AY2f!TTf*j~D=O zx@BEWENpq4|Gk&_3rsdg{gM8*rTK5e|6Z#6+c5N2{9h}Uzr6f~>Hm&u|6td@s5wdr o|KAAz?^b^h@81*pPb&+8f8$?WO}twl008do!Q9?icmHVr0J(kH1^@s6 literal 0 HcmV?d00001 diff --git a/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts b/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts new file mode 100644 index 0000000000000..2b51100fb466c --- /dev/null +++ b/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts @@ -0,0 +1,49 @@ +import { expect } from 'chai'; +import { after, before, describe, it } from 'mocha'; + +import { getCredentials } from '../../data/api-data'; +import { messageReactionTest } from '../../data/apps/app-packages'; +import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper'; +import { sendSimpleMessage, getMessageById } from '../../data/chat.helper'; +import { createRoom, deleteRoom } from '../../data/rooms.helper'; +import { executeAppSlashCommand } from '../../data/slashcommands.helpers'; +import { IS_EE } from '../../e2e/config/constants'; + +(IS_EE ? describe : describe.skip)('Apps - Message Reactions', () => { + let roomId: string; + + before((done) => getCredentials(done)); + + before(async () => { + await cleanupApps(); + await installLocalTestPackage(messageReactionTest); + + const res = await createRoom({ type: 'c', name: `apps-reactions-test-${Date.now()}` }); + roomId = res.body.channel._id; + }); + + after(async () => { + await deleteRoom({ type: 'c', roomId }); + await cleanupApps(); + }); + + it('should add and remove a thumbs-up reaction to a message', async () => { + const sendRes = await sendSimpleMessage({ roomId, text: 'reaction test message' }); + const messageId = sendRes.body.message._id; + + const slashRes = await executeAppSlashCommand('msg-update', roomId, `add ${messageId}`); + expect(slashRes.status, 'Slash command execution failed').to.equal(200); + + let message = await getMessageById({ msgId: messageId }); + expect(message.reactions, 'Message reactions should exist after adding').to.exist; + expect(message.reactions, 'Message should have a thumbs-up reaction').to.have.property(':+1:'); + + // Now remove the reaction + const removeRes = await executeAppSlashCommand('msg-update', roomId, `remove ${messageId}`); + expect(removeRes.status, 'Remove reaction slash command failed').to.equal(200); + + message = await getMessageById({ msgId: messageId }); + const hasThumbsUp = Boolean(message.reactions && ':+1:' in message.reactions); + expect(hasThumbsUp, 'Thumbs-up reaction should have been removed').to.be.false; + }); +}); From cda7228da46adea6bd0f33b1ec252153e9499384 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Thu, 26 Mar 2026 15:04:03 -0300 Subject: [PATCH 5/8] refactor: clearer error messages on tests --- apps/meteor/tests/end-to-end/apps/app-message-reactions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts b/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts index 2b51100fb466c..68c44c2b067e5 100644 --- a/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts +++ b/apps/meteor/tests/end-to-end/apps/app-message-reactions.ts @@ -32,7 +32,7 @@ import { IS_EE } from '../../e2e/config/constants'; const messageId = sendRes.body.message._id; const slashRes = await executeAppSlashCommand('msg-update', roomId, `add ${messageId}`); - expect(slashRes.status, 'Slash command execution failed').to.equal(200); + expect(slashRes.status, 'Slash command to add reaction failed').to.equal(200); let message = await getMessageById({ msgId: messageId }); expect(message.reactions, 'Message reactions should exist after adding').to.exist; @@ -40,7 +40,7 @@ import { IS_EE } from '../../e2e/config/constants'; // Now remove the reaction const removeRes = await executeAppSlashCommand('msg-update', roomId, `remove ${messageId}`); - expect(removeRes.status, 'Remove reaction slash command failed').to.equal(200); + expect(removeRes.status, 'Slash command to remove reaction failed').to.equal(200); message = await getMessageById({ msgId: messageId }); const hasThumbsUp = Boolean(message.reactions && ':+1:' in message.reactions); From 66c5c9ced9e4bf0d18bc14afcb0089c1b59aced2 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 6 Apr 2026 18:56:17 -0300 Subject: [PATCH 6/8] refactor: leaked permission change in previous test made new test break --- apps/meteor/tests/end-to-end/api/rooms.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index 96d9385569030..efc96c1ef552a 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -22,7 +22,13 @@ import { sleep } from '../../../lib/utils/sleep'; import { getCredentials, api, request, credentials } from '../../data/api-data'; import { sendSimpleMessage, deleteMessage } from '../../data/chat.helper'; import { imgURL } from '../../data/interactions'; -import { getSettingValueById, updateEEPermission, updatePermission, updateSetting } from '../../data/permissions.helper'; +import { + getSettingValueById, + restorePermissionToRoles, + updateEEPermission, + updatePermission, + updateSetting, +} from '../../data/permissions.helper'; import { assignRoleToUser, createCustomRole, deleteCustomRole } from '../../data/roles.helper'; import { createRoom, deleteRoom } from '../../data/rooms.helper'; import { createTeam, deleteTeam } from '../../data/teams.helper'; @@ -3939,7 +3945,11 @@ describe('[Rooms]', () => { deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }), ]); - await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); + await Promise.all([ + deleteTeam(credentials, publicTeam.name), + deleteTeam(credentials, privateTeam.name), + restorePermissionToRoles('view-c-room'), + ]); await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); }); From f562b3e3c4d3d345e0d6187cadac67576921fe1d Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 7 Apr 2026 15:14:08 -0300 Subject: [PATCH 7/8] fix typo --- apps/meteor/tests/data/apps/app-packages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/data/apps/app-packages/README.md b/apps/meteor/tests/data/apps/app-packages/README.md index b4b56c0e0179a..5aa5845a0463f 100644 --- a/apps/meteor/tests/data/apps/app-packages/README.md +++ b/apps/meteor/tests/data/apps/app-packages/README.md @@ -320,7 +320,7 @@ export class MessageUpdaterTestApp extends App { const user = await read.getUserReader().getAppUser(); if (!user) { - this.app.getLogger().error(`Could't find app user`); + this.app.getLogger().error(`Couldn't find app user`); return; } From c6ed2c761ae61eb23c817b54b2ec741a2d6634de Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 7 Apr 2026 18:37:07 -0300 Subject: [PATCH 8/8] add changeset --- .changeset/quick-impalas-pump.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/quick-impalas-pump.md diff --git a/.changeset/quick-impalas-pump.md b/.changeset/quick-impalas-pump.md new file mode 100644 index 0000000000000..71a0718491dca --- /dev/null +++ b/.changeset/quick-impalas-pump.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/apps-engine': patch +'@rocket.chat/meteor': patch +--- + +Fixes the issue of the lacking MessageUpdater not being available to apps during runtime