Skip to content

Commit c406a51

Browse files
sampaiodiegoggazzo
andauthored
test(federation): create DM tests (#37777)
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
1 parent acf14d6 commit c406a51

File tree

3 files changed

+349
-1
lines changed

3 files changed

+349
-1
lines changed

ee/packages/federation-matrix/jest.config.federation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default {
4949
mode: 'testops',
5050
testops: {
5151
api: { token: process.env.QASE_TESTOPS_JEST_API_TOKEN },
52-
project: 'RC',
52+
project: process.env.QASE_PROJECT || 'RC',
5353
run: {
5454
title: qaseRunTitle(),
5555
complete: true,
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
import type { ISubscription, IUser } from '@rocket.chat/core-typings';
2+
import type { MatrixEvent, Room, RoomEmittedEvents } from 'matrix-js-sdk';
3+
import { RoomStateEvent } from 'matrix-js-sdk';
4+
5+
import { api } from '../../../../../apps/meteor/tests/data/api-data';
6+
import { acceptRoomInvite, getSubscriptions } from '../../../../../apps/meteor/tests/data/rooms.helper';
7+
import { getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper';
8+
import type { TestUser, IRequestConfig } from '../../../../../apps/meteor/tests/data/users.helper';
9+
import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants';
10+
import { federationConfig } from '../helper/config';
11+
import { SynapseClient } from '../helper/synapse-client';
12+
13+
const waitForRoomEvent = async (room: Room, eventType: RoomEmittedEvents, validateEvent: (event: MatrixEvent) => void, timeoutMs = 5000) =>
14+
Promise.race([
15+
new Promise<void>((resolve, reject) => {
16+
room.once(eventType, async (event: MatrixEvent) => {
17+
try {
18+
await validateEvent(event);
19+
resolve();
20+
} catch (error) {
21+
reject(error);
22+
}
23+
});
24+
}),
25+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout waiting for event')), timeoutMs)),
26+
]);
27+
28+
(IS_EE ? describe : describe.skip)('Federation DMs', () => {
29+
let rc1AdminRequestConfig: IRequestConfig;
30+
// let rc1User1RequestConfig: IRequestConfig;
31+
let hs1AdminApp: SynapseClient;
32+
// let hs1User1App: SynapseClient;
33+
34+
beforeAll(async () => {
35+
// Create admin request config for RC1
36+
rc1AdminRequestConfig = await getRequestConfig(
37+
federationConfig.rc1.url,
38+
federationConfig.rc1.adminUser,
39+
federationConfig.rc1.adminPassword,
40+
);
41+
42+
// Create user1 in RC1 using federation config values
43+
await createUser(
44+
{
45+
username: federationConfig.rc1.additionalUser1.username,
46+
password: federationConfig.rc1.additionalUser1.password,
47+
email: `${federationConfig.rc1.additionalUser1.username}@rocket.chat`,
48+
name: federationConfig.rc1.additionalUser1.username,
49+
},
50+
rc1AdminRequestConfig,
51+
);
52+
53+
// Create admin Synapse client for HS1
54+
hs1AdminApp = new SynapseClient(federationConfig.hs1.url, federationConfig.hs1.adminUser, federationConfig.hs1.adminPassword);
55+
await hs1AdminApp.initialize();
56+
});
57+
58+
afterAll(async () => {
59+
if (hs1AdminApp) {
60+
await hs1AdminApp.close();
61+
}
62+
// if (hs1User1App) {
63+
// await hs1User1App.close();
64+
// }
65+
});
66+
67+
describe('1:1 Direct Messages', () => {
68+
let rcUser: TestUser<IUser>;
69+
let rcUserConfig: IRequestConfig;
70+
let hs1Room: Room | null;
71+
let invitedRoomId: string;
72+
73+
const userDm = `dm-federation-user-${Date.now()}`;
74+
const userDmId = `@${userDm}:${federationConfig.rc1.domain}`;
75+
76+
beforeAll(async () => {
77+
// create both RC and Synapse users
78+
rcUser = await createUser(
79+
{
80+
username: userDm,
81+
password: 'random',
82+
email: `${userDm}}@rocket.chat`,
83+
name: `DM Federation User ${Date.now()}`,
84+
},
85+
rc1AdminRequestConfig,
86+
);
87+
88+
rcUserConfig = await getRequestConfig(federationConfig.rc1.url, rcUser.username, 'random');
89+
});
90+
91+
afterAll(async () => {
92+
// delete both RC and Synapse users
93+
await deleteUser(rcUser, {}, rc1AdminRequestConfig);
94+
});
95+
96+
describe('Synapse as the resident server', () => {
97+
describe('Room list name validations', () => {
98+
it('should create a DM and invite user from rc', async () => {
99+
hs1Room = await hs1AdminApp.createDM([userDmId]);
100+
101+
expect(hs1Room).toHaveProperty('roomId');
102+
103+
const subs = await getSubscriptions(rcUserConfig);
104+
105+
const pendingInvitation = subs.update.find(
106+
(subscription) =>
107+
subscription.status === 'INVITED' &&
108+
subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`),
109+
);
110+
111+
expect(pendingInvitation).toHaveProperty('rid');
112+
113+
const membersBefore = await hs1Room!.getMembers();
114+
115+
expect(membersBefore.length).toBe(2);
116+
117+
const invitedMember = membersBefore.find((member) => member.userId === userDmId);
118+
119+
expect(invitedMember).toHaveProperty('membership', 'invite');
120+
121+
invitedRoomId = pendingInvitation!.rid;
122+
123+
const waitForRoomEventPromise = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => {
124+
expect(event).toHaveProperty('content.membership', 'join');
125+
expect(event).toHaveProperty('state_key', userDmId);
126+
});
127+
128+
const response = await acceptRoomInvite(invitedRoomId, rcUserConfig);
129+
expect(response.success).toBe(true);
130+
131+
await waitForRoomEventPromise;
132+
});
133+
it.todo('should display the fname properly');
134+
it.todo('should display the fname properly after the user from Synapse leaves the DM');
135+
});
136+
137+
describe('Permission validations', () => {
138+
it('should leave the DM from Rocket.Chat', async () => {
139+
const subs = await getSubscriptions(rcUserConfig);
140+
141+
const dmSubscription = subs.update.find(
142+
(subscription) =>
143+
subscription.t === 'd' && subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`),
144+
);
145+
146+
expect(dmSubscription).toHaveProperty('rid');
147+
148+
const response = await rcUserConfig.request
149+
.post(api('rooms.leave'))
150+
.set(rcUserConfig.credentials)
151+
.send({
152+
roomId: invitedRoomId,
153+
})
154+
.expect(200);
155+
156+
expect(response.body).toHaveProperty('success', true);
157+
158+
await waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => {
159+
expect(event).toHaveProperty('content.membership', 'leave');
160+
expect(event).toHaveProperty('state_key', userDmId);
161+
});
162+
});
163+
});
164+
165+
it.todo('should reflect the revoke invitation in the RC user subscriptions');
166+
});
167+
168+
describe('Rocket.Chat as the resident server', () => {
169+
it.todo('should create a DM and invite user from synapse');
170+
// const createResponse = await createDirectMessage({
171+
// usernames: [federationConfig.hs1.adminMatrixUserId],
172+
// config: rc1AdminRequestConfig,
173+
// });
174+
175+
// expect(createResponse.status).toBe(200);
176+
// expect(createResponse.body).toHaveProperty('success', true);
177+
// // createResponse.body.room._rid;
178+
179+
// const sub = await getSubscriptions(rc1AdminRequestConfig).then((subs) =>
180+
// subs.update.find((subscription) => subscription.rid === createResponse.body.room._rid),
181+
// );
182+
// expect(sub).toHaveProperty('rid', createResponse.body.room._rid);
183+
184+
// expect(sub).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId);
185+
186+
it.todo('should display the fname properly after reject the invitation');
187+
it.todo('should display the fname properly after accept the invitation');
188+
it.todo('should allow the user to leave the DM if it is not the only member');
189+
it.todo('should not allow to leave if the user is the only member');
190+
});
191+
});
192+
193+
describe('Multiple user DMs', () => {
194+
describe('Synapse as the resident server', () => {
195+
let rcUser1: TestUser<IUser>;
196+
// let rcUser2: TestUser<IUser>;
197+
198+
let rcUserConfig1: IRequestConfig;
199+
// let rcUserConfig2: IRequestConfig;
200+
201+
let hs1Room: Room | null;
202+
203+
let pendingInvitation1: ISubscription | undefined;
204+
// let pendingInvitation2: any;
205+
206+
let invitedRoomId1: string;
207+
// let invitedRoomId2: string;
208+
209+
const userDm1 = `dm-federation-user1-${Date.now()}`;
210+
const userDmId1 = `@${userDm1}:${federationConfig.rc1.domain}`;
211+
212+
const userDm2 = `dm-federation-user2-${Date.now()}`;
213+
const userDmId2 = `@${userDm2}:${federationConfig.rc1.domain}`;
214+
215+
beforeAll(async () => {
216+
// create both RC and Synapse users
217+
rcUser1 = await createUser(
218+
{
219+
username: userDm1,
220+
password: 'random',
221+
email: `${userDm1}}@rocket.chat`,
222+
name: `DM Federation User ${Date.now()}`,
223+
},
224+
rc1AdminRequestConfig,
225+
);
226+
227+
rcUserConfig1 = await getRequestConfig(federationConfig.rc1.url, rcUser1.username, 'random');
228+
229+
await createUser(
230+
{
231+
username: userDm2,
232+
password: 'random',
233+
email: `${userDm2}}@rocket.chat`,
234+
name: `DM Federation User ${Date.now()}`,
235+
},
236+
rc1AdminRequestConfig,
237+
);
238+
239+
// rcUserConfig2 = await getRequestConfig(federationConfig.rc1.url, rcUser2.username, 'random');
240+
});
241+
242+
afterAll(async () => {
243+
// delete both RC and Synapse users
244+
// await Promise.all([deleteUser(rcUser1, {}, rc1AdminRequestConfig), deleteUser(rcUser2, {}, rc1AdminRequestConfig)]);
245+
});
246+
247+
describe('Room list name validations', () => {
248+
it('should create a group DM with multiple RC users', async () => {
249+
hs1Room = await hs1AdminApp.createDM([userDmId1, userDmId2]);
250+
251+
expect(hs1Room).toHaveProperty('roomId');
252+
253+
const subs1 = await getSubscriptions(rcUserConfig1);
254+
255+
pendingInvitation1 = subs1.update.find(
256+
(subscription) =>
257+
subscription.status === 'INVITED' &&
258+
subscription.fname?.includes(`@${federationConfig.hs1.adminUser}:${federationConfig.hs1.domain}`),
259+
);
260+
261+
expect(pendingInvitation1).toHaveProperty('rid');
262+
263+
const membersBefore = await hs1Room!.getMembers();
264+
265+
expect(membersBefore.length).toBe(3);
266+
267+
const invitedMember = membersBefore.find((member) => member.userId === userDmId1);
268+
269+
expect(invitedMember).toHaveProperty('membership', 'invite');
270+
271+
invitedRoomId1 = pendingInvitation1!.rid;
272+
});
273+
274+
it('should display the name of the inviter on RC', async () => {
275+
expect(pendingInvitation1).toHaveProperty('fname', federationConfig.hs1.adminMatrixUserId);
276+
});
277+
278+
it.failing('should display the name of all users on RC after the invited user accepts the invitation', async () => {
279+
const waitForRoomEventPromise1 = waitForRoomEvent(hs1Room!, RoomStateEvent.Members, ({ event }) => {
280+
expect(event).toHaveProperty('content.membership', 'join');
281+
expect(event).toHaveProperty('state_key', userDmId1);
282+
});
283+
284+
const response = await acceptRoomInvite(invitedRoomId1, rcUserConfig1);
285+
expect(response.success).toBe(true);
286+
287+
await waitForRoomEventPromise1;
288+
289+
const subs1After = await getSubscriptions(rcUserConfig1);
290+
291+
const joinedSubscription1 = subs1After.update.find((subscription) => subscription.rid === invitedRoomId1);
292+
293+
expect(joinedSubscription1).toHaveProperty('fname', `${federationConfig.hs1.adminMatrixUserId}, ${userDm1}, ${userDm2}`);
294+
});
295+
it.todo('should update the display the name if the inviter from Synapse leaves the group DM');
296+
});
297+
describe('Permission validations', () => {
298+
it.todo('should allow a user to add another user to the group DM');
299+
it.todo('should allow a user to leave the group DM');
300+
});
301+
describe('Turning a 1:1 DM into a group DM', () => {
302+
it.todo('should show the invite to the third user');
303+
it.todo('should update the room name to reflect the three users after the third user accepts the invitation');
304+
});
305+
});
306+
describe('Rocket.Chat as the resident server', () => {
307+
describe('Room list name validations', () => {
308+
it.todo('should display the fname containing the two invited users for the inviter');
309+
it.todo("should display only the inviter's username for the invited user");
310+
it.todo('should update the fname when a user leaves the DM');
311+
it.todo('should update the fname when a user is added to the DM');
312+
});
313+
describe('Permission validations', () => {
314+
it.todo('should add a user to the DM');
315+
it.todo('should add another user by another user than the initial inviter');
316+
});
317+
describe('Duplicated rooms', () => {
318+
describe('When the third user leaves a DM', () => {
319+
describe('When there is an existing non-federated DM with the same users', () => {
320+
it.todo('should have two DMs with same users');
321+
it.todo('should return the non-federated room when trying to create a new DM with same users');
322+
});
323+
describe('When there is only federated DMs', () => {
324+
it.todo('should have two DMs with same users');
325+
it.todo('should return the oldest room when trying to create a new DM with same users');
326+
});
327+
});
328+
});
329+
describe('Turning a 1:1 DM into a group DM', () => {
330+
it.todo('should show the invite to the third user');
331+
it.todo('should update the room name to reflect the three users after the third user accepts the invitation');
332+
});
333+
});
334+
});
335+
});

ee/packages/federation-matrix/tests/helper/synapse-client.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,19 @@ export class SynapseClient {
152152
return room.room_id;
153153
}
154154

155+
async createDM(userIds: string[]) {
156+
if (!this.matrixClient) {
157+
throw new Error('Matrix client is not initialized');
158+
}
159+
160+
const dmRoom = await this.matrixClient.createRoom({
161+
is_direct: true,
162+
invite: userIds,
163+
});
164+
165+
return this.matrixClient.getRoom(dmRoom.room_id);
166+
}
167+
155168
async inviteUserToRoom(roomId: string, userId: string): Promise<void> {
156169
if (!this.matrixClient) {
157170
throw new Error('Matrix client is not initialized');

0 commit comments

Comments
 (0)