From 3cd137487e8feec7b079fd98cf141b07aa14c852 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 10 Dec 2025 23:16:27 -0300 Subject: [PATCH 1/7] test(federation): create dm tests --- .../tests/end-to-end/dms.spec.ts | 339 ++++++++++++++++++ .../tests/helper/synapse-client.ts | 13 + 2 files changed, 352 insertions(+) create mode 100644 ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts diff --git a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts new file mode 100644 index 0000000000000..8ba0c95ee24a6 --- /dev/null +++ b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts @@ -0,0 +1,339 @@ +import type { IUser } from '@rocket.chat/core-typings'; +import type { MatrixEvent, Room, RoomEmittedEvents } from 'matrix-js-sdk'; +import { RoomStateEvent } from 'matrix-js-sdk'; + +import { api } from '../../../../../apps/meteor/tests/data/api-data'; +import { acceptRoomInvite, getSubscriptions } from '../../../../../apps/meteor/tests/data/rooms.helper'; +import { getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper'; +import type { TestUser, IRequestConfig } from '../../../../../apps/meteor/tests/data/users.helper'; +import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants'; +import { federationConfig } from '../helper/config'; +import { SynapseClient } from '../helper/synapse-client'; + +const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, validateEvent: (event: MatrixEvent) => void, timeoutMs = 5000) => + Promise.race([ + new Promise((resolve, reject) => { + room.once(eventType, async (event: MatrixEvent) => { + try { + await validateEvent(event); + resolve(); + } catch (error) { + reject(error); + } + }); + }), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for event')), timeoutMs)), + ]); + +(IS_EE ? describe : describe.skip)('Federation DMs', () => { + let rc1AdminRequestConfig: IRequestConfig; + // let rc1User1RequestConfig: IRequestConfig; + let hs1AdminApp: SynapseClient; + // let hs1User1App: SynapseClient; + + beforeAll(async () => { + // Create admin request config for RC1 + rc1AdminRequestConfig = await getRequestConfig( + federationConfig.rc1.url, + federationConfig.rc1.adminUser, + federationConfig.rc1.adminPassword, + ); + + // Create user1 in RC1 using federation config values + await createUser( + { + username: federationConfig.rc1.additionalUser1.username, + password: federationConfig.rc1.additionalUser1.password, + email: `${federationConfig.rc1.additionalUser1.username}@rocket.chat`, + name: federationConfig.rc1.additionalUser1.username, + }, + rc1AdminRequestConfig, + ); + + // Create user1 request config for RC1 + // rc1User1RequestConfig = await getRequestConfig( + // federationConfig.rc1.url, + // federationConfig.rc1.additionalUser1.username, + // federationConfig.rc1.additionalUser1.password, + // ); + + // Create admin Synapse client for HS1 + hs1AdminApp = new SynapseClient(federationConfig.hs1.url, federationConfig.hs1.adminUser, federationConfig.hs1.adminPassword); + await hs1AdminApp.initialize(); + + // Create user1 Synapse client for HS1 + // hs1User1App = new SynapseClient( + // federationConfig.hs1.url, + // federationConfig.hs1.additionalUser1.matrixUserId, + // federationConfig.hs1.additionalUser1.password, + // ); + // await hs1User1App.initialize(); + }); + + afterAll(async () => { + if (hs1AdminApp) { + await hs1AdminApp.close(); + } + // if (hs1User1App) { + // await hs1User1App.close(); + // } + }); + + describe('1:1 Direct Messages', () => { + let rcUser: TestUser; + let rcUserConfig: IRequestConfig; + let hs1Room: Room | null; + let invitedRoomId: string; + + const userDm = `dm-federation-user-${Date.now()}`; + const userDmId = `@${userDm}:${federationConfig.rc1.domain}`; + + beforeAll(async () => { + // create both RC and Synapse users + rcUser = await createUser( + { + username: userDm, + password: 'random', + email: `${userDm}}@rocket.chat`, + name: `DM Federation User ${Date.now()}`, + }, + rc1AdminRequestConfig, + ); + + rcUserConfig = await getRequestConfig(federationConfig.rc1.url, rcUser.username, 'random'); + }); + + afterAll(async () => { + // delete both RC and Synapse users + await deleteUser(rcUser, {}, rc1AdminRequestConfig); + }); + + it('should create a DM from Synapse', async () => { + hs1Room = await hs1AdminApp.createDM([userDmId]); + + expect(hs1Room).toHaveProperty('roomId'); + + const subs = await getSubscriptions(rcUserConfig); + + const pendingInvitation = subs.update.find( + (subscription) => + subscription.status === 'INVITED' && + subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); + + expect(pendingInvitation).toHaveProperty('rid'); + + const membersBefore = await hs1Room!.getMembers(); + + expect(membersBefore.length).toBe(2); + + const invitedMember = membersBefore.find((member) => member.userId === userDmId); + + expect(invitedMember).toHaveProperty('membership', 'invite'); + + invitedRoomId = pendingInvitation!.rid; + + const response = await acceptRoomInvite(invitedRoomId, rcUserConfig); + expect(response.success).toBe(true); + + await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'join'); + expect(event).toHaveProperty('state_key', userDmId); + }); + }); + + it('should leave the DM from Rocket.Chat', async () => { + const subs = await getSubscriptions(rcUserConfig); + + const dmSubscription = subs.update.find( + (subscription) => + subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); + + expect(dmSubscription).toHaveProperty('rid'); + + const response = await rcUserConfig.request + .post(api('rooms.leave')) + .set(rcUserConfig.credentials) + .send({ + roomId: invitedRoomId, + }) + .expect(200); + + expect(response.body).toHaveProperty('success', true); + + await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'leave'); + expect(event).toHaveProperty('state_key', userDmId); + }); + }); + }); + + describe.only('Group Direct Messages', () => { + let rcUser1: TestUser; + let rcUser2: TestUser; + + let rcUserConfig1: IRequestConfig; + let rcUserConfig2: IRequestConfig; + + let hs1Room: Room | null; + let invitedRoomId1: string; + let invitedRoomId2: string; + + const userDm1 = `dm-federation-user1-${Date.now()}`; + const userDmId1 = `@${userDm1}:${federationConfig.rc1.domain}`; + + const userDm2 = `dm-federation-user2-${Date.now()}`; + const userDmId2 = `@${userDm2}:${federationConfig.rc1.domain}`; + + beforeAll(async () => { + // create both RC and Synapse users + rcUser1 = await createUser( + { + username: userDm1, + password: 'random', + email: `${userDm1}}@rocket.chat`, + name: `DM Federation User ${Date.now()}`, + }, + rc1AdminRequestConfig, + ); + + rcUserConfig1 = await getRequestConfig(federationConfig.rc1.url, rcUser1.username, 'random'); + + rcUser2 = await createUser( + { + username: userDm2, + password: 'random', + email: `${userDm2}}@rocket.chat`, + name: `DM Federation User ${Date.now()}`, + }, + rc1AdminRequestConfig, + ); + + rcUserConfig2 = await getRequestConfig(federationConfig.rc1.url, rcUser2.username, 'random'); + }); + + afterAll(async () => { + // delete both RC and Synapse users + // await Promise.all([deleteUser(rcUser1, {}, rc1AdminRequestConfig), deleteUser(rcUser2, {}, rc1AdminRequestConfig)]); + }); + + it('should create a DM from Synapse', async () => { + hs1Room = await hs1AdminApp.createDM([userDmId1, userDmId2, '@diego:rc.host']); + + console.log('Created HS1 room:', hs1Room); + + expect(hs1Room).toHaveProperty('roomId'); + + const subs1 = await getSubscriptions(rcUserConfig1); + + console.log('Subscriptions for user 1:', subs1); + + const pendingInvitation1 = subs1.update.find( + (subscription) => + subscription.status === 'INVITED' && + subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); + + console.log('pendingInvitation1 ->', pendingInvitation1); + + expect(pendingInvitation1).toHaveProperty('rid'); + expect(pendingInvitation1).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); + + const membersBefore = await hs1Room!.getMembers(); + + expect(membersBefore.length).toBe(3); + + const invitedMember = membersBefore.find((member) => member.userId === userDmId1); + + expect(invitedMember).toHaveProperty('membership', 'invite'); + + invitedRoomId1 = pendingInvitation1!.rid; + + const waitForRoomEventPromise1 = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + console.log('RECEIVED EVENT', event); + + expect(event).toHaveProperty('content.membership', 'join'); + expect(event).toHaveProperty('state_key', userDmId1); + }); + + const response = await acceptRoomInvite(invitedRoomId1, rcUserConfig1); + expect(response.success).toBe(true); + + await waitForRoomEventPromise1; + + // approve user 2 + const subs2 = await getSubscriptions(rcUserConfig2); + + console.log('Subscriptions for user 2:', subs2); + + const pendingInvitation2 = subs2.update.find( + (subscription) => + subscription.status === 'INVITED' && + subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); + + console.log('pendingInvitation2 ->', pendingInvitation2); + + expect(pendingInvitation2).toHaveProperty('rid'); + expect(pendingInvitation2).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); + + const membersBefore2 = await hs1Room!.getMembers(); + + expect(membersBefore2.length).toBe(3); + + const invitedMember2 = membersBefore2.find((member) => member.userId === userDmId2); + + expect(invitedMember2).toHaveProperty('membership', 'invite'); + + invitedRoomId2 = pendingInvitation2!.rid; + + const waitForRoomEventPromise2 = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + console.log('RECEIVED EVENT2222', event); + + expect(event).toHaveProperty('content.membership', 'join'); + expect(event).toHaveProperty('state_key', userDmId2); + }); + + const response2 = await acceptRoomInvite(invitedRoomId2, rcUserConfig2); + expect(response2.success).toBe(true); + + console.log('response2 ->', response2); + + await waitForRoomEventPromise2; + }); + + it.skip('should leave the DM from Rocket.Chat', async () => { + const subs = await getSubscriptions(rcUserConfig); + + const dmSubscription = subs.update.find( + (subscription) => + subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); + + expect(dmSubscription).toHaveProperty('rid'); + + const response = await rcUserConfig.request + .post(api('rooms.leave')) + .set(rcUserConfig.credentials) + .send({ + roomId: invitedRoomId, + }) + .expect(200); + + expect(response.body).toHaveProperty('success', true); + + await Promise.race([ + new Promise((resolve) => { + hs1Room?.once(RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'leave'); + expect(event).toHaveProperty('state_key', userDmId); + + resolve(); + }); + }), + new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for membership change')), 5000)), + ]); + }); + }); +}); diff --git a/ee/packages/federation-matrix/tests/helper/synapse-client.ts b/ee/packages/federation-matrix/tests/helper/synapse-client.ts index 732eb7b503b5f..e30ccda64e04d 100644 --- a/ee/packages/federation-matrix/tests/helper/synapse-client.ts +++ b/ee/packages/federation-matrix/tests/helper/synapse-client.ts @@ -152,6 +152,19 @@ export class SynapseClient { return room.room_id; } + async createDM(userIds: string[]) { + if (!this.matrixClient) { + throw new Error('Matrix client is not initialized'); + } + + const dmRoom = await this.matrixClient.createRoom({ + is_direct: true, + invite: userIds, + }); + + return this.matrixClient.getRoom(dmRoom.room_id); + } + async inviteUserToRoom(roomId: string, userId: string): Promise { if (!this.matrixClient) { throw new Error('Matrix client is not initialized'); From c7203a68f9793496c20129535f55f15bf6271e87 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 12 Dec 2025 10:56:25 -0300 Subject: [PATCH 2/7] test(federation): enhance DM tests with user invitation and leave scenarios --- .../tests/end-to-end/dms.spec.ts | 122 ++++++++++++------ 1 file changed, 82 insertions(+), 40 deletions(-) diff --git a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts index 8ba0c95ee24a6..781258f45bdaf 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts @@ -3,7 +3,7 @@ import type { MatrixEvent, Room, RoomEmittedEvents } from 'matrix-js-sdk'; import { RoomStateEvent } from 'matrix-js-sdk'; import { api } from '../../../../../apps/meteor/tests/data/api-data'; -import { acceptRoomInvite, getSubscriptions } from '../../../../../apps/meteor/tests/data/rooms.helper'; +import { acceptRoomInvite, createDirectMessage, createRoom, getSubscriptions } from '../../../../../apps/meteor/tests/data/rooms.helper'; import { getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper'; import type { TestUser, IRequestConfig } from '../../../../../apps/meteor/tests/data/users.helper'; import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants'; @@ -108,68 +108,110 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida await deleteUser(rcUser, {}, rc1AdminRequestConfig); }); - it('should create a DM from Synapse', async () => { - hs1Room = await hs1AdminApp.createDM([userDmId]); + describe('Residend Synapse', () => { + it('should create a DM and invite user from rc', async () => { + hs1Room = await hs1AdminApp.createDM([userDmId]); - expect(hs1Room).toHaveProperty('roomId'); + expect(hs1Room).toHaveProperty('roomId'); - const subs = await getSubscriptions(rcUserConfig); + const subs = await getSubscriptions(rcUserConfig); - const pendingInvitation = subs.update.find( - (subscription) => - subscription.status === 'INVITED' && - subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); + const pendingInvitation = subs.update.find( + (subscription) => + subscription.status === 'INVITED' && + subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); - expect(pendingInvitation).toHaveProperty('rid'); + expect(pendingInvitation).toHaveProperty('rid'); - const membersBefore = await hs1Room!.getMembers(); + const membersBefore = await hs1Room!.getMembers(); - expect(membersBefore.length).toBe(2); + expect(membersBefore.length).toBe(2); - const invitedMember = membersBefore.find((member) => member.userId === userDmId); + const invitedMember = membersBefore.find((member) => member.userId === userDmId); - expect(invitedMember).toHaveProperty('membership', 'invite'); + expect(invitedMember).toHaveProperty('membership', 'invite'); - invitedRoomId = pendingInvitation!.rid; + invitedRoomId = pendingInvitation!.rid; - const response = await acceptRoomInvite(invitedRoomId, rcUserConfig); - expect(response.success).toBe(true); + const response = await acceptRoomInvite(invitedRoomId, rcUserConfig); + expect(response.success).toBe(true); - await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { - expect(event).toHaveProperty('content.membership', 'join'); - expect(event).toHaveProperty('state_key', userDmId); + await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'join'); + expect(event).toHaveProperty('state_key', userDmId); + }); + }); + + it('should leave the DM from Rocket.Chat', async () => { + const subs = await getSubscriptions(rcUserConfig); + + const dmSubscription = subs.update.find( + (subscription) => + subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); + + expect(dmSubscription).toHaveProperty('rid'); + + const response = await rcUserConfig.request + .post(api('rooms.leave')) + .set(rcUserConfig.credentials) + .send({ + roomId: invitedRoomId, + }) + .expect(200); + + expect(response.body).toHaveProperty('success', true); + + await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'leave'); + expect(event).toHaveProperty('state_key', userDmId); + }); }); }); - it('should leave the DM from Rocket.Chat', async () => { - const subs = await getSubscriptions(rcUserConfig); + describe('Resident RC', () => { + it('should create a DM and invite user from synapse', async () => { + const createResponse = await createDirectMessage({ + usernames: [federationConfig.hs1.adminMatrixUserId], + config: rc1AdminRequestConfig, + }); - const dmSubscription = subs.update.find( - (subscription) => - subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); + expect(createResponse.status).toBe(200); + expect(createResponse.body).toHaveProperty('success', true); + // createResponse.body.room._rid; - expect(dmSubscription).toHaveProperty('rid'); + const sub = await getSubscriptions(rc1AdminRequestConfig).then((subs) => + subs.update.find((subscription) => subscription.rid === createResponse.body.room._rid), + ); + expect(sub).toHaveProperty('rid', createResponse.body.room._rid); - const response = await rcUserConfig.request - .post(api('rooms.leave')) - .set(rcUserConfig.credentials) - .send({ - roomId: invitedRoomId, - }) - .expect(200); + expect(sub).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); + }); - expect(response.body).toHaveProperty('success', true); + it.todo('should display the fname properly after reject the invitation'); + it.todo('should display the fname properly after accept the invitation'); + it.todo('should allow the user to leave the DM if it is not the only member'); + it.todo('should not allow to leave if the user is the only member'); + }); + }); - await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { - expect(event).toHaveProperty('content.membership', 'leave'); - expect(event).toHaveProperty('state_key', userDmId); + describe('Multiple user DMs', () => { + describe('Resident RC', () => { + describe('fname display', () => { + it.todo('should display the fname containing the two invited users for the inviter'); + it.todo("should display only the inviter's username for the invited user"); + it.todo('should update the fname when a user leaves the DM'); + it.todo('should update the fname when a user is added to the DM'); + }); + describe('permissions', () => { + it.todo('should possible to add a user to the DM'); + it.todo('should possible to add for another user besides the initial inviter'); }); }); }); - describe.only('Group Direct Messages', () => { + describe('Group Direct Messages', () => { let rcUser1: TestUser; let rcUser2: TestUser; From e4d22bf3f08240388d9d8315fcb2708a5c038e22 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 12 Dec 2025 14:42:58 -0300 Subject: [PATCH 3/7] test(direct message): add a placeholder for a test on revoking invitations in user subscriptions --- ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts index 781258f45bdaf..1c044aed5895c 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts @@ -168,6 +168,8 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida expect(event).toHaveProperty('state_key', userDmId); }); }); + + it.todo('should reflect the revoke invitation in the RC user subscriptions'); }); describe('Resident RC', () => { From f9efe7c97a91654dc4bc89885a1566d03537f943 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 12 Dec 2025 18:34:59 -0300 Subject: [PATCH 4/7] test: implement a few DM tests --- .../tests/end-to-end/dms.spec.ts | 372 +++++++----------- 1 file changed, 152 insertions(+), 220 deletions(-) diff --git a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts index 1c044aed5895c..9a2dfbc543d74 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts @@ -1,9 +1,9 @@ -import type { IUser } from '@rocket.chat/core-typings'; +import type { ISubscription, IUser } from '@rocket.chat/core-typings'; import type { MatrixEvent, Room, RoomEmittedEvents } from 'matrix-js-sdk'; import { RoomStateEvent } from 'matrix-js-sdk'; import { api } from '../../../../../apps/meteor/tests/data/api-data'; -import { acceptRoomInvite, createDirectMessage, createRoom, getSubscriptions } from '../../../../../apps/meteor/tests/data/rooms.helper'; +import { acceptRoomInvite, getSubscriptions } from '../../../../../apps/meteor/tests/data/rooms.helper'; import { getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper'; import type { TestUser, IRequestConfig } from '../../../../../apps/meteor/tests/data/users.helper'; import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants'; @@ -50,24 +50,9 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida rc1AdminRequestConfig, ); - // Create user1 request config for RC1 - // rc1User1RequestConfig = await getRequestConfig( - // federationConfig.rc1.url, - // federationConfig.rc1.additionalUser1.username, - // federationConfig.rc1.additionalUser1.password, - // ); - // Create admin Synapse client for HS1 hs1AdminApp = new SynapseClient(federationConfig.hs1.url, federationConfig.hs1.adminUser, federationConfig.hs1.adminPassword); await hs1AdminApp.initialize(); - - // Create user1 Synapse client for HS1 - // hs1User1App = new SynapseClient( - // federationConfig.hs1.url, - // federationConfig.hs1.additionalUser1.matrixUserId, - // federationConfig.hs1.additionalUser1.password, - // ); - // await hs1User1App.initialize(); }); afterAll(async () => { @@ -108,88 +93,95 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida await deleteUser(rcUser, {}, rc1AdminRequestConfig); }); - describe('Residend Synapse', () => { - it('should create a DM and invite user from rc', async () => { - hs1Room = await hs1AdminApp.createDM([userDmId]); + describe('Synapse as the resident server', () => { + describe('Room list name validations', () => { + it('should create a DM and invite user from rc', async () => { + hs1Room = await hs1AdminApp.createDM([userDmId]); - expect(hs1Room).toHaveProperty('roomId'); + expect(hs1Room).toHaveProperty('roomId'); - const subs = await getSubscriptions(rcUserConfig); + const subs = await getSubscriptions(rcUserConfig); - const pendingInvitation = subs.update.find( - (subscription) => - subscription.status === 'INVITED' && - subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); + const pendingInvitation = subs.update.find( + (subscription) => + subscription.status === 'INVITED' && + subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); - expect(pendingInvitation).toHaveProperty('rid'); + expect(pendingInvitation).toHaveProperty('rid'); - const membersBefore = await hs1Room!.getMembers(); + const membersBefore = await hs1Room!.getMembers(); - expect(membersBefore.length).toBe(2); + expect(membersBefore.length).toBe(2); - const invitedMember = membersBefore.find((member) => member.userId === userDmId); + const invitedMember = membersBefore.find((member) => member.userId === userDmId); - expect(invitedMember).toHaveProperty('membership', 'invite'); + expect(invitedMember).toHaveProperty('membership', 'invite'); - invitedRoomId = pendingInvitation!.rid; + invitedRoomId = pendingInvitation!.rid; + + const waitForRoomEventPromise = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'join'); + expect(event).toHaveProperty('state_key', userDmId); + }); - const response = await acceptRoomInvite(invitedRoomId, rcUserConfig); - expect(response.success).toBe(true); + const response = await acceptRoomInvite(invitedRoomId, rcUserConfig); + expect(response.success).toBe(true); - await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { - expect(event).toHaveProperty('content.membership', 'join'); - expect(event).toHaveProperty('state_key', userDmId); + await waitForRoomEventPromise; }); + it.todo('should display the fname properly'); + it.todo('should display the fname properly after the user from Synapse leaves the DM'); }); - it('should leave the DM from Rocket.Chat', async () => { - const subs = await getSubscriptions(rcUserConfig); + describe('Permission validations', () => { + it('should leave the DM from Rocket.Chat', async () => { + const subs = await getSubscriptions(rcUserConfig); - const dmSubscription = subs.update.find( - (subscription) => - subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); + const dmSubscription = subs.update.find( + (subscription) => + subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); - expect(dmSubscription).toHaveProperty('rid'); + expect(dmSubscription).toHaveProperty('rid'); - const response = await rcUserConfig.request - .post(api('rooms.leave')) - .set(rcUserConfig.credentials) - .send({ - roomId: invitedRoomId, - }) - .expect(200); + const response = await rcUserConfig.request + .post(api('rooms.leave')) + .set(rcUserConfig.credentials) + .send({ + roomId: invitedRoomId, + }) + .expect(200); - expect(response.body).toHaveProperty('success', true); + expect(response.body).toHaveProperty('success', true); - await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { - expect(event).toHaveProperty('content.membership', 'leave'); - expect(event).toHaveProperty('state_key', userDmId); + await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'leave'); + expect(event).toHaveProperty('state_key', userDmId); + }); }); }); it.todo('should reflect the revoke invitation in the RC user subscriptions'); }); - describe('Resident RC', () => { - it('should create a DM and invite user from synapse', async () => { - const createResponse = await createDirectMessage({ - usernames: [federationConfig.hs1.adminMatrixUserId], - config: rc1AdminRequestConfig, - }); + describe('Rocket.Chat as the resident server', () => { + it.todo('should create a DM and invite user from synapse'); + // const createResponse = await createDirectMessage({ + // usernames: [federationConfig.hs1.adminMatrixUserId], + // config: rc1AdminRequestConfig, + // }); - expect(createResponse.status).toBe(200); - expect(createResponse.body).toHaveProperty('success', true); - // createResponse.body.room._rid; + // expect(createResponse.status).toBe(200); + // expect(createResponse.body).toHaveProperty('success', true); + // // createResponse.body.room._rid; - const sub = await getSubscriptions(rc1AdminRequestConfig).then((subs) => - subs.update.find((subscription) => subscription.rid === createResponse.body.room._rid), - ); - expect(sub).toHaveProperty('rid', createResponse.body.room._rid); + // const sub = await getSubscriptions(rc1AdminRequestConfig).then((subs) => + // subs.update.find((subscription) => subscription.rid === createResponse.body.room._rid), + // ); + // expect(sub).toHaveProperty('rid', createResponse.body.room._rid); - expect(sub).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); - }); + // expect(sub).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); it.todo('should display the fname properly after reject the invitation'); it.todo('should display the fname properly after accept the invitation'); @@ -199,185 +191,125 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida }); describe('Multiple user DMs', () => { - describe('Resident RC', () => { - describe('fname display', () => { - it.todo('should display the fname containing the two invited users for the inviter'); - it.todo("should display only the inviter's username for the invited user"); - it.todo('should update the fname when a user leaves the DM'); - it.todo('should update the fname when a user is added to the DM'); - }); - describe('permissions', () => { - it.todo('should possible to add a user to the DM'); - it.todo('should possible to add for another user besides the initial inviter'); - }); - }); - }); + describe('Synapse as the resident server', () => { + let rcUser1: TestUser; + // let rcUser2: TestUser; - describe('Group Direct Messages', () => { - let rcUser1: TestUser; - let rcUser2: TestUser; + let rcUserConfig1: IRequestConfig; + // let rcUserConfig2: IRequestConfig; - let rcUserConfig1: IRequestConfig; - let rcUserConfig2: IRequestConfig; + let hs1Room: Room | null; - let hs1Room: Room | null; - let invitedRoomId1: string; - let invitedRoomId2: string; + let pendingInvitation1: ISubscription | undefined; + // let pendingInvitation2: any; - const userDm1 = `dm-federation-user1-${Date.now()}`; - const userDmId1 = `@${userDm1}:${federationConfig.rc1.domain}`; + let invitedRoomId1: string; + // let invitedRoomId2: string; - const userDm2 = `dm-federation-user2-${Date.now()}`; - const userDmId2 = `@${userDm2}:${federationConfig.rc1.domain}`; + const userDm1 = `dm-federation-user1-${Date.now()}`; + const userDmId1 = `@${userDm1}:${federationConfig.rc1.domain}`; - beforeAll(async () => { - // create both RC and Synapse users - rcUser1 = await createUser( - { - username: userDm1, - password: 'random', - email: `${userDm1}}@rocket.chat`, - name: `DM Federation User ${Date.now()}`, - }, - rc1AdminRequestConfig, - ); + const userDm2 = `dm-federation-user2-${Date.now()}`; + const userDmId2 = `@${userDm2}:${federationConfig.rc1.domain}`; - rcUserConfig1 = await getRequestConfig(federationConfig.rc1.url, rcUser1.username, 'random'); - - rcUser2 = await createUser( - { - username: userDm2, - password: 'random', - email: `${userDm2}}@rocket.chat`, - name: `DM Federation User ${Date.now()}`, - }, - rc1AdminRequestConfig, - ); - - rcUserConfig2 = await getRequestConfig(federationConfig.rc1.url, rcUser2.username, 'random'); - }); - - afterAll(async () => { - // delete both RC and Synapse users - // await Promise.all([deleteUser(rcUser1, {}, rc1AdminRequestConfig), deleteUser(rcUser2, {}, rc1AdminRequestConfig)]); - }); - - it('should create a DM from Synapse', async () => { - hs1Room = await hs1AdminApp.createDM([userDmId1, userDmId2, '@diego:rc.host']); - - console.log('Created HS1 room:', hs1Room); - - expect(hs1Room).toHaveProperty('roomId'); - - const subs1 = await getSubscriptions(rcUserConfig1); - - console.log('Subscriptions for user 1:', subs1); - - const pendingInvitation1 = subs1.update.find( - (subscription) => - subscription.status === 'INVITED' && - subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); - - console.log('pendingInvitation1 ->', pendingInvitation1); - - expect(pendingInvitation1).toHaveProperty('rid'); - expect(pendingInvitation1).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); - - const membersBefore = await hs1Room!.getMembers(); - - expect(membersBefore.length).toBe(3); - - const invitedMember = membersBefore.find((member) => member.userId === userDmId1); + beforeAll(async () => { + // create both RC and Synapse users + rcUser1 = await createUser( + { + username: userDm1, + password: 'random', + email: `${userDm1}}@rocket.chat`, + name: `DM Federation User ${Date.now()}`, + }, + rc1AdminRequestConfig, + ); - expect(invitedMember).toHaveProperty('membership', 'invite'); + rcUserConfig1 = await getRequestConfig(federationConfig.rc1.url, rcUser1.username, 'random'); - invitedRoomId1 = pendingInvitation1!.rid; + await createUser( + { + username: userDm2, + password: 'random', + email: `${userDm2}}@rocket.chat`, + name: `DM Federation User ${Date.now()}`, + }, + rc1AdminRequestConfig, + ); - const waitForRoomEventPromise1 = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { - console.log('RECEIVED EVENT', event); + // rcUserConfig2 = await getRequestConfig(federationConfig.rc1.url, rcUser2.username, 'random'); + }); - expect(event).toHaveProperty('content.membership', 'join'); - expect(event).toHaveProperty('state_key', userDmId1); + afterAll(async () => { + // delete both RC and Synapse users + // await Promise.all([deleteUser(rcUser1, {}, rc1AdminRequestConfig), deleteUser(rcUser2, {}, rc1AdminRequestConfig)]); }); - const response = await acceptRoomInvite(invitedRoomId1, rcUserConfig1); - expect(response.success).toBe(true); + describe('Room list name validations', () => { + it('should create a group DM with multiple RC users', async () => { + hs1Room = await hs1AdminApp.createDM([userDmId1, userDmId2]); - await waitForRoomEventPromise1; + expect(hs1Room).toHaveProperty('roomId'); - // approve user 2 - const subs2 = await getSubscriptions(rcUserConfig2); + const subs1 = await getSubscriptions(rcUserConfig1); - console.log('Subscriptions for user 2:', subs2); + pendingInvitation1 = subs1.update.find( + (subscription) => + subscription.status === 'INVITED' && + subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), + ); - const pendingInvitation2 = subs2.update.find( - (subscription) => - subscription.status === 'INVITED' && - subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); + expect(pendingInvitation1).toHaveProperty('rid'); - console.log('pendingInvitation2 ->', pendingInvitation2); + const membersBefore = await hs1Room!.getMembers(); - expect(pendingInvitation2).toHaveProperty('rid'); - expect(pendingInvitation2).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); + expect(membersBefore.length).toBe(3); - const membersBefore2 = await hs1Room!.getMembers(); + const invitedMember = membersBefore.find((member) => member.userId === userDmId1); - expect(membersBefore2.length).toBe(3); + expect(invitedMember).toHaveProperty('membership', 'invite'); - const invitedMember2 = membersBefore2.find((member) => member.userId === userDmId2); + invitedRoomId1 = pendingInvitation1!.rid; + }); - expect(invitedMember2).toHaveProperty('membership', 'invite'); + it('should display the name of the inviter on RC', async () => { + expect(pendingInvitation1).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId); + }); - invitedRoomId2 = pendingInvitation2!.rid; + it.failing('should display the name of all users on RC after the invited user accepts the invitation', async () => { + const waitForRoomEventPromise1 = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { + expect(event).toHaveProperty('content.membership', 'join'); + expect(event).toHaveProperty('state_key', userDmId1); + }); - const waitForRoomEventPromise2 = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => { - console.log('RECEIVED EVENT2222', event); + const response = await acceptRoomInvite(invitedRoomId1, rcUserConfig1); + expect(response.success).toBe(true); - expect(event).toHaveProperty('content.membership', 'join'); - expect(event).toHaveProperty('state_key', userDmId2); - }); + await waitForRoomEventPromise1; - const response2 = await acceptRoomInvite(invitedRoomId2, rcUserConfig2); - expect(response2.success).toBe(true); + const subs1After = await getSubscriptions(rcUserConfig1); - console.log('response2 ->', response2); + const joinedSubscription1 = subs1After.update.find((subscription) => subscription.rid === invitedRoomId1); - await waitForRoomEventPromise2; + expect(joinedSubscription1).toHaveProperty('fname', `${federationConfig.hs1.adminMatrixUserId}, ${userDm1}, ${userDm2}`); + }); + it.todo('should update the display the name if the inviter from Synapse leaves the group DM'); + }); + describe('Permission validations', () => { + it.todo('should allow a user to add another user to the group DM'); + it.todo('should allow a user to leave the group DM'); + }); }); - - it.skip('should leave the DM from Rocket.Chat', async () => { - const subs = await getSubscriptions(rcUserConfig); - - const dmSubscription = subs.update.find( - (subscription) => - subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`), - ); - - expect(dmSubscription).toHaveProperty('rid'); - - const response = await rcUserConfig.request - .post(api('rooms.leave')) - .set(rcUserConfig.credentials) - .send({ - roomId: invitedRoomId, - }) - .expect(200); - - expect(response.body).toHaveProperty('success', true); - - await Promise.race([ - new Promise((resolve) => { - hs1Room?.once(RoomStateEvent.Members, ({ event }) => { - expect(event).toHaveProperty('content.membership', 'leave'); - expect(event).toHaveProperty('state_key', userDmId); - - resolve(); - }); - }), - new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for membership change')), 5000)), - ]); + describe('Rocket.Chat as the resident server', () => { + describe('Room list name validations', () => { + it.todo('should display the fname containing the two invited users for the inviter'); + it.todo("should display only the inviter's username for the invited user"); + it.todo('should update the fname when a user leaves the DM'); + it.todo('should update the fname when a user is added to the DM'); + }); + describe('Permission validations', () => { + it.todo('should add a user to the DM'); + it.todo('should add another user by another user than the initial inviter'); + }); }); }); }); From ac1ffd0b8055450937d1d5849c6e39be28b5f49d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 12 Dec 2025 18:36:47 -0300 Subject: [PATCH 5/7] test: add support to a different qase project --- ee/packages/federation-matrix/jest.config.federation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/packages/federation-matrix/jest.config.federation.ts b/ee/packages/federation-matrix/jest.config.federation.ts index 7e90526c90470..85e34daae709c 100644 --- a/ee/packages/federation-matrix/jest.config.federation.ts +++ b/ee/packages/federation-matrix/jest.config.federation.ts @@ -49,7 +49,7 @@ export default { mode: 'testops', testops: { api: { token: process.env.QASE_TESTOPS_JEST_API_TOKEN }, - project: 'RC', + project: process.env.QASE_PROJECT || 'RC', run: { title: qaseRunTitle(), complete: true, From f81cfcb2f86aa9d17e689656cc7a91aac6623a7d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 15 Dec 2025 10:31:54 -0300 Subject: [PATCH 6/7] test: more use cases --- .../federation-matrix/tests/end-to-end/dms.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts index 9a2dfbc543d74..4773041278e2e 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts @@ -310,6 +310,18 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida it.todo('should add a user to the DM'); it.todo('should add another user by another user than the initial inviter'); }); + describe('Duplicated rooms', () => { + describe('When the third user leaves a DM', () => { + describe('When there is an existing non-federated DM with the same users', () => { + it.todo('should have two DMs with same users'); + it.todo('should return the non-federated room when trying to create a new DM with same users'); + }); + describe('When there is only federated DMs', () => { + it.todo('should have two DMs with same users'); + it.todo('should return the oldest room when trying to create a new DM with same users'); + }); + }); + }); }); }); }); From d2514f45962ecd1ee71e208cf38131089301ecd5 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 15 Dec 2025 14:54:31 -0300 Subject: [PATCH 7/7] new test case for 1:1 DMs becoming multiple DM --- .../federation-matrix/tests/end-to-end/dms.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts index 4773041278e2e..732a630123d3f 100644 --- a/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts +++ b/ee/packages/federation-matrix/tests/end-to-end/dms.spec.ts @@ -298,6 +298,10 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida it.todo('should allow a user to add another user to the group DM'); it.todo('should allow a user to leave the group DM'); }); + describe('Turning a 1:1 DM into a group DM', () => { + it.todo('should show the invite to the third user'); + it.todo('should update the room name to reflect the three users after the third user accepts the invitation'); + }); }); describe('Rocket.Chat as the resident server', () => { describe('Room list name validations', () => { @@ -322,6 +326,10 @@ const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, valida }); }); }); + describe('Turning a 1:1 DM into a group DM', () => { + it.todo('should show the invite to the third user'); + it.todo('should update the room name to reflect the three users after the third user accepts the invitation'); + }); }); }); });