Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
2207 lines
90 KiB
TypeScript
2207 lines
90 KiB
TypeScript
import type { IMessage, IUser } from '@rocket.chat/core-typings';
|
|
import type { Room } from 'matrix-js-sdk';
|
|
import { EventTimeline } from 'matrix-js-sdk';
|
|
|
|
import { api } from '../../../../../apps/meteor/tests/data/api-data';
|
|
import {
|
|
createRoom,
|
|
getRoomInfo,
|
|
getGroupHistory,
|
|
findRoomMember,
|
|
addUserToRoom,
|
|
addUserToRoomViaMethod,
|
|
addUserToRoomSlashCommand,
|
|
acceptRoomInvite,
|
|
rejectRoomInvite,
|
|
getRoomMembers,
|
|
getSubscriptions,
|
|
} from '../../../../../apps/meteor/tests/data/rooms.helper';
|
|
import { type IRequestConfig, getRequestConfig, createUser, deleteUser } from '../../../../../apps/meteor/tests/data/users.helper';
|
|
import { IS_EE } from '../../../../../apps/meteor/tests/e2e/config/constants';
|
|
import { retry } from '../../../../../apps/meteor/tests/end-to-end/api/helpers/retry';
|
|
import { federationConfig } from '../helper/config';
|
|
import { createDDPListener } from '../helper/ddp-listener';
|
|
import { SynapseClient } from '../helper/synapse-client';
|
|
|
|
// import { KnownMembership } from 'matrix-js-sdk';
|
|
// import { t } from 'i18next';
|
|
|
|
(IS_EE ? describe : describe.skip)('Federation', () => {
|
|
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('Rooms', () => {
|
|
describe('Create direct message rooms', () => {
|
|
// Creating a fresh user for this test suite to avoid collisions,
|
|
// since DMs are unique for each user pair
|
|
let userRequestConfig: IRequestConfig;
|
|
let createdUser: IUser;
|
|
beforeAll(async () => {
|
|
const user = { username: `user-${Date.now()}`, password: '123' };
|
|
createdUser = await createUser(user, rc1AdminRequestConfig);
|
|
userRequestConfig = await getRequestConfig(federationConfig.rc1.url, user.username, user.password);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await deleteUser(createdUser, {}, rc1AdminRequestConfig);
|
|
});
|
|
|
|
it('should create a federated room when federated members are added', async () => {
|
|
const response = await createRoom({
|
|
type: 'd',
|
|
username: federationConfig.hs1.adminMatrixUserId,
|
|
config: userRequestConfig,
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toHaveProperty('success', true);
|
|
expect(response.body).toHaveProperty('room');
|
|
expect(response.body.room).toHaveProperty('_id');
|
|
expect(response.body.room).toHaveProperty('t', 'd');
|
|
|
|
const roomInfo = await getRoomInfo(response.body.room._id, userRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
});
|
|
|
|
it('should create a non-federated room when only local members are added', async () => {
|
|
const response = await createRoom({
|
|
type: 'd',
|
|
username: federationConfig.rc1.additionalUser1.username,
|
|
config: userRequestConfig,
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toHaveProperty('success', true);
|
|
expect(response.body).toHaveProperty('room');
|
|
expect(response.body.room).toHaveProperty('_id');
|
|
expect(response.body.room).toHaveProperty('t', 'd');
|
|
|
|
const roomInfo = await getRoomInfo(response.body.room._id, userRequestConfig);
|
|
expect(roomInfo.room).not.toHaveProperty('federated');
|
|
});
|
|
});
|
|
|
|
describe('Create a room on RC as private, explicitly not federated, with federated users in creation modal', () => {
|
|
describe('Add 1 federated user in the creation modal', () => {
|
|
it('should not allow the creation of the room', async () => {
|
|
const channelName = `non-federated-channel-single-fed-${Date.now()}`;
|
|
|
|
// RC view: Attempt to create a non-federated private room with 1 federated user
|
|
const response = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId],
|
|
extraData: {
|
|
federated: false,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// RC view: Verify the room creation failed
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toHaveProperty('success', false);
|
|
expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
|
|
});
|
|
});
|
|
|
|
describe('Add 2 federated users in the creation modal', () => {
|
|
it('should not allow the creation of the room', async () => {
|
|
const channelName = `non-federated-channel-multi-fed-${Date.now()}`;
|
|
|
|
// RC view: Attempt to create a non-federated private room with 2 federated users
|
|
const response = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
|
|
extraData: {
|
|
federated: false,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// RC view: Verify the room creation failed
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toHaveProperty('success', false);
|
|
expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
|
|
});
|
|
});
|
|
|
|
describe('Add 1 federated user and 1 local user in the creation modal', () => {
|
|
it('should not allow the creation of the room', async () => {
|
|
const channelName = `non-federated-channel-mixed-${Date.now()}`;
|
|
|
|
// RC view: Attempt to create a non-federated private room with 1 federated user and 1 local user
|
|
const response = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId, federationConfig.rc1.additionalUser1.username],
|
|
extraData: {
|
|
federated: false,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// RC view: Verify the room creation failed
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toHaveProperty('success', false);
|
|
expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Create a room on RC as private, do not mark as federated and', () => {
|
|
let nonFederatedChannel: { _id: string; name: string; t: string; federated?: boolean };
|
|
|
|
beforeEach(async () => {
|
|
const channelName = `non-federated-channel-${Date.now()}`;
|
|
|
|
// Create a non-federated private room (without federated members)
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: false,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
nonFederatedChannel = createResponse.body.group;
|
|
|
|
expect(nonFederatedChannel).toHaveProperty('_id');
|
|
expect(nonFederatedChannel).toHaveProperty('name', channelName);
|
|
expect(nonFederatedChannel).toHaveProperty('t', 'p');
|
|
expect(nonFederatedChannel).not.toHaveProperty('federated', true);
|
|
}, 10000);
|
|
|
|
// No cleanup needed - rooms are left for debugging purposes
|
|
|
|
describe('Go to the members list and try to add a federated user', () => {
|
|
it('should not allow and show an error message', async () => {
|
|
// RC view: Attempt to add a federated user to the non-federated room via the REST invite endpoint
|
|
const [response] = await addUserToRoom({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId],
|
|
rid: nonFederatedChannel._id,
|
|
type: 'p',
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// The REST endpoint rejects the invite with the federation-specific error: the federated
|
|
// user already exists locally (a federated DM was created earlier), so it resolves and the
|
|
// federation guard rejects adding it to a non-federated room.
|
|
expect(response.status).toBe(400);
|
|
expect(response.body).toHaveProperty('success', false);
|
|
expect(response.body).toHaveProperty('errorType', 'error-federated-users-in-non-federated-rooms');
|
|
|
|
// RC view: Verify the federated user was NOT added to the room's member list
|
|
const federatedUserInRoom = await findRoomMember(
|
|
nonFederatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
expect(federatedUserInRoom).toBeNull();
|
|
|
|
// RC view: Verify room remains non-federated
|
|
const roomInfo = await getRoomInfo(nonFederatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).not.toHaveProperty('federated', true);
|
|
});
|
|
});
|
|
|
|
describe('Go to the composer and use the /invite slash command to add a federated user', () => {
|
|
it('should not allow and show an error message', async () => {
|
|
// Set up DDP listener to catch ephemeral messages
|
|
const ddpListener = createDDPListener(federationConfig.rc1.url, rc1AdminRequestConfig);
|
|
|
|
// Connect to DDP and subscribe to ephemeral messages
|
|
await ddpListener.connect();
|
|
|
|
// RC view: Execute the /invite slash command to add a federated user
|
|
const response = await addUserToRoomSlashCommand({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId],
|
|
rid: nonFederatedChannel._id,
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// The slash command returns success but broadcasts ephemeral messages
|
|
// instead of throwing errors
|
|
expect(response.body).toHaveProperty('success', true);
|
|
expect(response.body).toHaveProperty('message');
|
|
const messageData = JSON.parse(response.body.message);
|
|
expect(messageData).toHaveProperty('msg', 'result');
|
|
|
|
// Wait for the ephemeral message to be broadcast
|
|
const ephemeralMessage = await ddpListener.waitForEphemeralMessage(
|
|
'You cannot add external users to a non-federated room',
|
|
5000, // 5 second timeout
|
|
nonFederatedChannel._id,
|
|
);
|
|
|
|
// Verify the ephemeral message content
|
|
expect(ephemeralMessage.msg).toContain('You cannot add external users to a non-federated room');
|
|
expect(ephemeralMessage.u.username).toBe('rocket.cat');
|
|
expect(ephemeralMessage.private).toBe(true);
|
|
expect(ephemeralMessage.rid).toBe(nonFederatedChannel._id); // Verify it's for the correct room
|
|
|
|
// RC view: Verify the federated user was NOT added to the room's member list
|
|
const federatedUserInRoom = await findRoomMember(
|
|
nonFederatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
expect(federatedUserInRoom).toBeNull();
|
|
|
|
// RC view: Verify room remains non-federated
|
|
const roomInfo = await getRoomInfo(nonFederatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).not.toHaveProperty('federated', true);
|
|
|
|
ddpListener.disconnect();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Create a room on RC as private and federated and', () => {
|
|
describe('Add 1 federated user in the creation modal', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-${Date.now()}`;
|
|
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// For private groups, the response has 'group' property, not 'channel'
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
|
|
// TODO: Figure out why syncing events are not working and uncomment this when we get the state change from
|
|
// invite to join
|
|
// const joinedRoomId = await this.hs1App.getRoomIdByRoomNameAndMembership(channelName, KnownMembership.Join);
|
|
// expect(acceptedRoomId, 'Expected to have joined the room, but joinedRoomId is different from acceptedRoomId').to.equal(joinedRoomId);
|
|
}, 10000);
|
|
|
|
it('should show the room on the remote Element or RC', async () => {
|
|
// RC view: Check in RC
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Element
|
|
const elementRoom = hs1AdminApp.getRoom(channelName);
|
|
expect(elementRoom).toHaveProperty('name', channelName);
|
|
});
|
|
|
|
it('should show the new user in the members list', async () => {
|
|
// RC view: Check in RC that the federated user is in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Element (Matrix) that the federated user is in the members list
|
|
const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
expect(rc1AdminUserInSynapse).not.toBeNull();
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system message that the user was invited', async () => {
|
|
// RC view: Check in RC. We don't check in Synapse because this is not part of the protocol
|
|
// Get the room history to find the system message
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for a system message about the user being invited
|
|
// Members added during room creation are invited (status: 'INVITED'), not auto-joined
|
|
const inviteMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
|
|
);
|
|
|
|
expect(inviteMessage).toBeDefined();
|
|
expect(inviteMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
expect(inviteMessage?.u?.username).toBe(federationConfig.rc1.adminUser);
|
|
});
|
|
});
|
|
|
|
describe('Add 2 or more federated users in the creation modal', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-multi-${Date.now()}`;
|
|
|
|
// Create room with both federated users
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// For private groups, the response has 'group' property, not 'channel'
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Accept invitations for both users
|
|
const acceptedRoomId1 = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId1).not.toBe('');
|
|
|
|
const acceptedRoomId2 = await hs1User1App.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId2).not.toBe('');
|
|
|
|
// TODO: Figure out why syncing events are not working and uncomment this when we get the state change from
|
|
// invite to join
|
|
// const joinedRoomId = await this.hs1App.getRoomIdByRoomNameAndMembership(channelName, KnownMembership.Join);
|
|
// expect(acceptedRoomId, 'Expected to have joined the room, but joinedRoomId is different from acceptedRoomId').to.equal(joinedRoomId);
|
|
}, 15000);
|
|
|
|
it('should show the room on all the involved remote Element or RC', async () => {
|
|
// RC view: Check in RC
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Element for admin user
|
|
const elementRoom1 = hs1AdminApp.getRoom(channelName);
|
|
expect(elementRoom1).toHaveProperty('name', channelName);
|
|
|
|
// Synapse view: Check in Element for user1
|
|
const elementRoom2 = hs1User1App.getRoom(channelName);
|
|
expect(elementRoom2).toHaveProperty('name', channelName);
|
|
});
|
|
|
|
it('should show the new users in the members list of all RCs involved', async () => {
|
|
// RC view: Check in RC that both federated users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1User1InRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.additionalUser1.matrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1User1InRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
expect(hs1User1InRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for admin user that all users are in the members list
|
|
const rc1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
const hs1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
|
|
const hs1User1InSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
|
|
expect(rc1AdminUserInSynapseAdmin).not.toBeNull();
|
|
expect(hs1AdminUserInSynapseAdmin).not.toBeNull();
|
|
expect(hs1User1InSynapseAdmin).not.toBeNull();
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for additional user that all users are in the members list
|
|
const rc1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
const hs1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
|
|
const hs1User1InSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId);
|
|
|
|
expect(rc1AdminUserInSynapseUser1).not.toBeNull();
|
|
expect(hs1AdminUserInSynapseUser1).not.toBeNull();
|
|
expect(hs1User1InSynapseUser1).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system messages that the users were invited', async () => {
|
|
// RC view: Check in RC. We don't check in Synapse because this is not part of the protocol
|
|
// Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about both users being invited
|
|
// Members added during room creation are invited (status: 'INVITED'), not auto-joined
|
|
const adminInviteMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
|
|
);
|
|
|
|
const hs1User1InviteMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.hs1.additionalUser1.matrixUserId,
|
|
);
|
|
|
|
expect(adminInviteMessage).toBeDefined();
|
|
expect(adminInviteMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
expect(adminInviteMessage?.u?.username).toBe(federationConfig.rc1.adminUser);
|
|
|
|
expect(hs1User1InviteMessage).toBeDefined();
|
|
expect(hs1User1InviteMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
|
|
expect(hs1User1InviteMessage?.u?.username).toBe(federationConfig.rc1.adminUser);
|
|
});
|
|
|
|
it('should show the system messages that the users joined when they accept the invites', async () => {
|
|
// RC view: Check in RC. We don't check in Synapse because this is not part of the protocol
|
|
// Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about both users joining after accepting invites
|
|
// 'uj' (user joined) message types
|
|
const adminJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
const hs1User1JoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.additionalUser1.matrixUserId),
|
|
);
|
|
|
|
expect(adminJoinedMessage).toBeDefined();
|
|
expect(adminJoinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
expect(adminJoinedMessage?.u?.username).toBe(federationConfig.hs1.adminMatrixUserId);
|
|
|
|
expect(hs1User1JoinedMessage).toBeDefined();
|
|
expect(hs1User1JoinedMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
|
|
expect(hs1User1JoinedMessage?.u?.username).toBe(federationConfig.hs1.additionalUser1.matrixUserId);
|
|
});
|
|
});
|
|
|
|
describe('Add 1 federated user and 1 local user in the creation modal', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-mixed-${Date.now()}`;
|
|
|
|
// Create room with 1 federated user (from Synapse) and 1 local user (from RC)
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [
|
|
federationConfig.hs1.adminMatrixUserId, // federated user
|
|
federationConfig.rc1.additionalUser1.username, // local user
|
|
],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
// For private groups, the response has 'group' property, not 'channel'
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Accept invitation for the federated user
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
|
|
// Accept invitation for the local user (rc1User1)
|
|
await acceptRoomInvite(federatedChannel._id, rc1User1RequestConfig);
|
|
}, 15000);
|
|
|
|
it('should show the room on the remote Element or RC and local for the second user', async () => {
|
|
// RC view: Check in RC (admin view)
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// RC view: Check in RC (user1 view - local user)
|
|
const roomInfoUser1 = await getRoomInfo(federatedChannel._id, rc1User1RequestConfig);
|
|
expect(roomInfoUser1.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfoUser1.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for federated user
|
|
const room = hs1AdminApp.getRoom(channelName);
|
|
expect(room).toHaveProperty('name', channelName);
|
|
expect(room.getMyMembership()).toBe('join');
|
|
});
|
|
|
|
it('should show the 2 new users in the members list', async () => {
|
|
// RC view: Check in RC (admin view) that both users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
|
|
const rc1User1InRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.additionalUser1.username,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(rc1User1InRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// RC view: Check in RC (user1 view) that both users are in the members list
|
|
const rc1AdminUserInRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
const rc1User1InRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.additionalUser1.username,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
const hs1AdminUserInRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRCUser1).not.toBeNull();
|
|
expect(rc1User1InRCUser1).not.toBeNull();
|
|
expect(hs1AdminUserInRCUser1).not.toBeNull();
|
|
expect(hs1AdminUserInRCUser1?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) that both users are in the members list
|
|
const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
const rc1User1InSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.additionalUser1.matrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
|
|
expect(rc1AdminUserInSynapse).not.toBeNull();
|
|
expect(rc1User1InSynapse).not.toBeNull();
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the 2 system messages that the users were invited', async () => {
|
|
// RC view: Check in RC (admin view) for system messages about both users being invited
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about both users being invited
|
|
// Members added during room creation are invited (status: 'INVITED'), not auto-joined
|
|
const localUserInviteMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.rc1.additionalUser1.username,
|
|
);
|
|
|
|
const federatedUserInviteMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
|
|
);
|
|
|
|
expect(localUserInviteMessage).toBeDefined();
|
|
expect(localUserInviteMessage?.msg).toContain(federationConfig.rc1.additionalUser1.username);
|
|
expect(localUserInviteMessage?.u?.username).toBe(federationConfig.rc1.adminUser);
|
|
|
|
expect(federatedUserInviteMessage).toBeDefined();
|
|
expect(federatedUserInviteMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
expect(federatedUserInviteMessage?.u?.username).toBe(federationConfig.rc1.adminUser);
|
|
|
|
// RC view: Check in RC (user1 view) for system messages about both users being invited
|
|
const historyResponseUser1 = await getGroupHistory(federatedChannel._id, rc1User1RequestConfig);
|
|
expect(Array.isArray(historyResponseUser1.messages)).toBe(true);
|
|
|
|
const localUserInviteMessageUser1 = historyResponseUser1.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.rc1.additionalUser1.username,
|
|
);
|
|
|
|
const federatedUserInviteMessageUser1 = historyResponseUser1.messages.find(
|
|
(message: IMessage) => message.t === 'ui' && message.msg && message.msg === federationConfig.hs1.adminMatrixUserId,
|
|
);
|
|
|
|
expect(localUserInviteMessageUser1).toBeDefined();
|
|
expect(federatedUserInviteMessageUser1).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Create a room with a topic', () => {
|
|
describe('Create a federated room with a topic', () => {
|
|
let channelName: string;
|
|
let channelTopic: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-with-topic-${Date.now()}`;
|
|
channelTopic = 'This is a test topic for federation';
|
|
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId],
|
|
extraData: {
|
|
federated: true,
|
|
topic: channelTopic,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
}, 10000);
|
|
|
|
it('should set the topic on the Rocket.Chat side', async () => {
|
|
// RC view: Verify the topic is set in Rocket.Chat
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('topic', channelTopic);
|
|
});
|
|
|
|
it('should set the topic on the Matrix side', async () => {
|
|
const hs1Room1 = (await hs1AdminApp.matrixClient.getRoom(federatedChannel.federation.mrid)) as Room;
|
|
|
|
expect(hs1Room1).toBeDefined();
|
|
|
|
const [topic] = hs1Room1.getLiveTimeline().getState(EventTimeline.FORWARDS)?.getStateEvents('m.room.topic') || [];
|
|
|
|
expect(topic.getContent().topic).toBe(channelTopic);
|
|
});
|
|
});
|
|
|
|
describe('Create a federated room without a topic', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-no-topic-${Date.now()}`;
|
|
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation.mrid');
|
|
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
}, 10000);
|
|
|
|
it('should not set a topic on the Rocket.Chat side', async () => {
|
|
// RC view: Verify no topic is set in Rocket.Chat
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room?.topic).toBeUndefined();
|
|
});
|
|
|
|
it('should not set a topic on the Matrix side', async () => {
|
|
const hs1Room1 = (await hs1AdminApp.matrixClient.getRoom(federatedChannel.federation.mrid)) as Room;
|
|
|
|
expect(hs1Room1).toBeDefined();
|
|
|
|
const [topic] = hs1Room1.getLiveTimeline().getState(EventTimeline.FORWARDS)?.getStateEvents('m.room.topic') || [];
|
|
|
|
expect(topic).toBeUndefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Create a room on RC as private and federated, then invite users', () => {
|
|
describe('Go to the members list and', () => {
|
|
describe('Add a federated user', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-invite-single-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add federated user to the room via the REST invite endpoint
|
|
const [addUserResponse] = await addUserToRoom({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId],
|
|
rid: federatedChannel._id,
|
|
type: 'p',
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
expect(addUserResponse.body).toHaveProperty('success', true);
|
|
|
|
// Accept invitation for the federated user
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
}, 15000);
|
|
|
|
it('should show the room on the remote Element or RC', async () => {
|
|
// RC view: Check in RC
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Element
|
|
const elementRoom = hs1AdminApp.getRoom(channelName);
|
|
expect(elementRoom).toHaveProperty('name', channelName);
|
|
});
|
|
|
|
it('should show the new user in the members list', async () => {
|
|
// RC view: Check in RC that both users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Element (Matrix) that both users are in the members list
|
|
const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
expect(rc1AdminUserInSynapse).not.toBeNull();
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system message that the user joined', async () => {
|
|
// RC view: Check in RC
|
|
// Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about the user joining after accepting invite
|
|
// look for 'uj' (user joined) message types
|
|
const joinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(joinedMessage).toBeDefined();
|
|
expect(joinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
});
|
|
});
|
|
|
|
describe('Add 2 or more federated users at the same time', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-invite-multi-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add both federated users to the room via the REST invite endpoint
|
|
const addUserResponse = await addUserToRoom({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
|
|
rid: federatedChannel._id,
|
|
type: 'p',
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
addUserResponse.forEach((response) => expect(response.body).toHaveProperty('success', true));
|
|
|
|
// Accept invitations for both users
|
|
const acceptedRoomId1 = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId1).not.toBe('');
|
|
|
|
const acceptedRoomId2 = await hs1User1App.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId2).not.toBe('');
|
|
}, 15000);
|
|
|
|
it('should show the room on all the involved remote Element or RC', async () => {
|
|
// RC view: Check in RC
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Element for admin user
|
|
const elementRoom1 = hs1AdminApp.getRoom(channelName);
|
|
expect(elementRoom1).toHaveProperty('name', channelName);
|
|
|
|
// Synapse view: Check in Element for user1
|
|
const elementRoom2 = hs1User1App.getRoom(channelName);
|
|
expect(elementRoom2).toHaveProperty('name', channelName);
|
|
});
|
|
|
|
it('should show the new users in the members list of all RCs involved', async () => {
|
|
// RC view: Check in RC that all users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1User1InRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.additionalUser1.matrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1User1InRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
expect(hs1User1InRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for admin user that all users are in the members list
|
|
const rc1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
const hs1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
|
|
const hs1User1InSynapseAdmin = await hs1AdminApp.findRoomMember(
|
|
channelName,
|
|
federationConfig.hs1.additionalUser1.matrixUserId,
|
|
{
|
|
initialDelay: 2000,
|
|
},
|
|
);
|
|
|
|
expect(rc1AdminUserInSynapseAdmin).not.toBeNull();
|
|
expect(hs1AdminUserInSynapseAdmin).not.toBeNull();
|
|
expect(hs1User1InSynapseAdmin).not.toBeNull();
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for additional user that all users are in the members list
|
|
const rc1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
const hs1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
|
|
const hs1User1InSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId);
|
|
|
|
expect(rc1AdminUserInSynapseUser1).not.toBeNull();
|
|
expect(hs1AdminUserInSynapseUser1).not.toBeNull();
|
|
expect(hs1User1InSynapseUser1).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system messages that the users joined', async () => {
|
|
// RC view: Check in RC
|
|
// Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about both users joining after accepting invites
|
|
// 'uj' (user joined) message types
|
|
const adminJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(adminJoinedMessage).toBeDefined();
|
|
expect(adminJoinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
|
|
// Look for 'uj' (user joined) message types
|
|
const hs1User1JoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.additionalUser1.matrixUserId),
|
|
);
|
|
|
|
expect(hs1User1JoinedMessage).toBeDefined();
|
|
expect(hs1User1JoinedMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
|
|
});
|
|
});
|
|
|
|
describe('Add 1 federated user and 1 local user at the same time', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-invite-mixed-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add 1 federated user and 1 local user to the room via the REST invite endpoint
|
|
const addUserResponse = await addUserToRoom({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.rc1.additionalUser1.username],
|
|
rid: federatedChannel._id,
|
|
type: 'p',
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
addUserResponse.forEach((response) => expect(response.body).toHaveProperty('success', true));
|
|
|
|
// Accept invitation for the federated user
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
|
|
// Accept invitation for the local user (rc1User1)
|
|
await acceptRoomInvite(federatedChannel._id, rc1User1RequestConfig);
|
|
}, 15000);
|
|
|
|
it('should show the room on the remote Element or RC and local for the second user', async () => {
|
|
// RC view: Check in RC (admin view)
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// RC view: Check in RC (user1 view - local user)
|
|
const roomInfoUser1 = await getRoomInfo(federatedChannel._id, rc1User1RequestConfig);
|
|
expect(roomInfoUser1.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfoUser1.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for federated user
|
|
const room = hs1AdminApp.getRoom(channelName);
|
|
expect(room).toHaveProperty('name', channelName);
|
|
expect(room.getMyMembership()).toBe('join');
|
|
});
|
|
|
|
it('should show the 2 new users in the members list', async () => {
|
|
// RC view: Check in RC (admin view) that all users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
|
|
const rc1User1InRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.additionalUser1.username,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(rc1User1InRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// RC view: Check in RC (user1 view) that all users are in the members list
|
|
const rc1AdminUserInRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
const rc1User1InRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.additionalUser1.username,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
const hs1AdminUserInRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRCUser1).not.toBeNull();
|
|
expect(rc1User1InRCUser1).not.toBeNull();
|
|
expect(hs1AdminUserInRCUser1).not.toBeNull();
|
|
expect(hs1AdminUserInRCUser1?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) that all users are in the members list
|
|
const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
const rc1User1InSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.additionalUser1.matrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
|
|
expect(rc1AdminUserInSynapse).not.toBeNull();
|
|
expect(rc1User1InSynapse).not.toBeNull();
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the 2 system messages that the user joined', async () => {
|
|
// RC view: Check in RC (admin view) for system messages about both users joining
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// 'uj' (user joined) message types
|
|
const localUserJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
|
|
);
|
|
|
|
expect(localUserJoinedMessage).toBeDefined();
|
|
expect(localUserJoinedMessage?.msg).toContain(federationConfig.rc1.additionalUser1.username);
|
|
|
|
const federatedUserJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(federatedUserJoinedMessage).toBeDefined();
|
|
expect(federatedUserJoinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
|
|
// RC view: Check in RC (user1 view) for system messages about both users joining
|
|
const historyResponseUser1 = await getGroupHistory(federatedChannel._id, rc1User1RequestConfig);
|
|
expect(Array.isArray(historyResponseUser1.messages)).toBe(true);
|
|
|
|
// Look for 'uj' (user joined) message types
|
|
const localUserJoinedMessageUser1 = historyResponseUser1.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
|
|
);
|
|
|
|
expect(localUserJoinedMessageUser1).toBeDefined();
|
|
expect(localUserJoinedMessageUser1?.msg).toContain(federationConfig.rc1.additionalUser1.username);
|
|
|
|
const federatedUserJoinedMessageUser1 = historyResponseUser1.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(federatedUserJoinedMessageUser1).toBeDefined();
|
|
expect(federatedUserJoinedMessageUser1?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Go to the composer and use the /invite slash command to', () => {
|
|
describe('Add a federated user', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-slash-single-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add federated user to the room via the /invite slash command
|
|
const addUserResponse = await addUserToRoomSlashCommand({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId],
|
|
rid: federatedChannel._id,
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
expect(addUserResponse.body).toHaveProperty('success', true);
|
|
|
|
// Accept invitation for the federated user
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
}, 15000);
|
|
|
|
it('should show the room on the remote Element or RC', async () => {
|
|
// RC view: Check in RC
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Element
|
|
const elementRoom = hs1AdminApp.getRoom(channelName);
|
|
expect(elementRoom).toHaveProperty('name', channelName);
|
|
});
|
|
|
|
it('should show the new user in the members list', async () => {
|
|
// RC view: Check in RC that both users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Element (Matrix) that both users are in the members list
|
|
const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
expect(rc1AdminUserInSynapse).not.toBeNull();
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system message that the user joined', async () => {
|
|
// RC view: Check in RC
|
|
// Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about the user joining after accepting invite
|
|
// 'uj' (user joined) message types
|
|
const joinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(joinedMessage).toBeDefined();
|
|
expect(joinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
});
|
|
});
|
|
|
|
describe('Add 2 or more federated users at the same time', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-slash-multi-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add both federated users to the room via the /invite slash command
|
|
const addUserResponse = await addUserToRoomSlashCommand({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.hs1.additionalUser1.matrixUserId],
|
|
rid: federatedChannel._id,
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
expect(addUserResponse.body).toHaveProperty('success', true);
|
|
|
|
// Accept invitations for both users
|
|
const acceptedRoomId1 = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId1).not.toBe('');
|
|
|
|
const acceptedRoomId2 = await hs1User1App.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId2).not.toBe('');
|
|
}, 15000);
|
|
|
|
it('should show the room on all the involved remote Element or RC', async () => {
|
|
// RC view: Check in RC
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Element for admin user
|
|
const elementRoom1 = hs1AdminApp.getRoom(channelName);
|
|
expect(elementRoom1).toHaveProperty('name', channelName);
|
|
|
|
// Synapse view: Check in Element for user1
|
|
const elementRoom2 = hs1User1App.getRoom(channelName);
|
|
expect(elementRoom2).toHaveProperty('name', channelName);
|
|
});
|
|
|
|
it('should show the new users in the members list of all RCs involved', async () => {
|
|
// RC view: Check in RC that all users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1User1InRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.additionalUser1.matrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1User1InRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
expect(hs1User1InRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for admin user that all users are in the members list
|
|
const rc1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
const hs1AdminUserInSynapseAdmin = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
|
|
const hs1User1InSynapseAdmin = await hs1AdminApp.findRoomMember(
|
|
channelName,
|
|
federationConfig.hs1.additionalUser1.matrixUserId,
|
|
{
|
|
initialDelay: 2000,
|
|
},
|
|
);
|
|
|
|
expect(rc1AdminUserInSynapseAdmin).not.toBeNull();
|
|
expect(hs1AdminUserInSynapseAdmin).not.toBeNull();
|
|
expect(hs1User1InSynapseAdmin).not.toBeNull();
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for additional user that all users are in the members list
|
|
const rc1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
const hs1AdminUserInSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId);
|
|
const hs1User1InSynapseUser1 = await hs1User1App.findRoomMember(channelName, federationConfig.hs1.additionalUser1.matrixUserId);
|
|
|
|
expect(rc1AdminUserInSynapseUser1).not.toBeNull();
|
|
expect(hs1AdminUserInSynapseUser1).not.toBeNull();
|
|
expect(hs1User1InSynapseUser1).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system messages that the user joined on all RCs involved', async () => {
|
|
// RC view: Check in RC
|
|
// Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about both users joining after accepting invites
|
|
// 'uj' (user joined) message types
|
|
const adminJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(adminJoinedMessage).toBeDefined();
|
|
expect(adminJoinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
|
|
const hs1User1JoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.additionalUser1.matrixUserId),
|
|
);
|
|
|
|
expect(hs1User1JoinedMessage).toBeDefined();
|
|
expect(hs1User1JoinedMessage?.msg).toContain(federationConfig.hs1.additionalUser1.matrixUserId);
|
|
});
|
|
});
|
|
|
|
describe('Add 1 federated user and 1 local user at the same time', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-slash-mixed-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add 1 federated user and 1 local user to the room via the /invite slash command
|
|
const addUserResponse = await addUserToRoomSlashCommand({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId, federationConfig.rc1.additionalUser1.username],
|
|
rid: federatedChannel._id,
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
expect(addUserResponse.body).toHaveProperty('success', true);
|
|
|
|
// Accept invitation for the federated user
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
|
|
// Accept invitation for the local user (rc1User1)
|
|
await acceptRoomInvite(federatedChannel._id, rc1User1RequestConfig);
|
|
}, 15000);
|
|
|
|
it('should show the room on the remote Element or RC and local for the second user', async () => {
|
|
// RC view: Check in RC (admin view)
|
|
const roomInfo = await getRoomInfo(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(roomInfo.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfo.room).toHaveProperty('federated', true);
|
|
|
|
// RC view: Check in RC (user1 view - local user)
|
|
const roomInfoUser1 = await getRoomInfo(federatedChannel._id, rc1User1RequestConfig);
|
|
expect(roomInfoUser1.room).toHaveProperty('_id', federatedChannel._id);
|
|
expect(roomInfoUser1.room).toHaveProperty('federated', true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) for federated user
|
|
const room = hs1AdminApp.getRoom(channelName);
|
|
expect(room).toHaveProperty('name', channelName);
|
|
expect(room.getMyMembership()).toBe('join');
|
|
});
|
|
|
|
it('should show the 2 new users in the members list', async () => {
|
|
// RC view: Check in RC (admin view) that all users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(federatedChannel._id, federationConfig.rc1.adminUser, {}, rc1AdminRequestConfig);
|
|
const rc1User1InRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.additionalUser1.username,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(rc1User1InRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// RC view: Check in RC (user1 view) that all users are in the members list
|
|
const rc1AdminUserInRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
const rc1User1InRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.additionalUser1.username,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
const hs1AdminUserInRCUser1 = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{},
|
|
rc1User1RequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRCUser1).not.toBeNull();
|
|
expect(rc1User1InRCUser1).not.toBeNull();
|
|
expect(hs1AdminUserInRCUser1).not.toBeNull();
|
|
expect(hs1AdminUserInRCUser1?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Synapse (Matrix) that all users are in the members list
|
|
const rc1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
const rc1User1InSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.additionalUser1.matrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
initialDelay: 2000,
|
|
});
|
|
|
|
expect(rc1AdminUserInSynapse).not.toBeNull();
|
|
expect(rc1User1InSynapse).not.toBeNull();
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the 2 system messages that the user joined', async () => {
|
|
// RC view: Check in RC (admin view) for system messages about both users joining
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for system messages about both users joining after accepting invites
|
|
// 'uj' (user joined) message types
|
|
const localUserJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
|
|
);
|
|
|
|
expect(localUserJoinedMessage).toBeDefined();
|
|
expect(localUserJoinedMessage?.msg).toContain(federationConfig.rc1.additionalUser1.username);
|
|
|
|
const federatedUserJoinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(federatedUserJoinedMessage).toBeDefined();
|
|
expect(federatedUserJoinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
|
|
// RC view: Check in RC (user1 view) for system messages about both users joining
|
|
const historyResponseUser1 = await getGroupHistory(federatedChannel._id, rc1User1RequestConfig);
|
|
expect(Array.isArray(historyResponseUser1.messages)).toBe(true);
|
|
|
|
// Look for 'uj' (user joined) message types
|
|
const localUserJoinedMessageUser1 = historyResponseUser1.messages.find(
|
|
(message: IMessage) =>
|
|
message.t === 'uj' && message.msg && message.msg.includes(federationConfig.rc1.additionalUser1.username),
|
|
);
|
|
|
|
const federatedUserJoinedMessageUser1 = historyResponseUser1.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(localUserJoinedMessageUser1).toBeDefined();
|
|
expect(localUserJoinedMessageUser1?.msg).toContain(federationConfig.rc1.additionalUser1.username);
|
|
|
|
expect(federatedUserJoinedMessageUser1).toBeDefined();
|
|
expect(federatedUserJoinedMessageUser1?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Use the deprecated addUsersToRoom DDP method to', () => {
|
|
describe('Add a federated user', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-ddp-single-${Date.now()}`;
|
|
|
|
// Create empty federated room without members
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
expect((federatedChannel as any).federation).toHaveProperty('version', 1);
|
|
|
|
// Wait for federation setup to complete (Matrix room creation and mrid assignment)
|
|
// This ensures the room.federation.mrid field is properly set before adding users
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Add federated user to the room via the deprecated addUsersToRoom DDP method
|
|
const addUserResponse = await addUserToRoomViaMethod({
|
|
usernames: [federationConfig.hs1.adminMatrixUserId],
|
|
rid: federatedChannel._id,
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
expect(addUserResponse.body).toHaveProperty('success', true);
|
|
|
|
// Accept invitation for the federated user
|
|
const acceptedRoomId = await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
expect(acceptedRoomId).not.toBe('');
|
|
}, 15000);
|
|
|
|
it('should show the new user in the members list', async () => {
|
|
// RC view: Check in RC that both users are in the members list
|
|
const rc1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.rc1.adminUser,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
const hs1AdminUserInRC = await findRoomMember(
|
|
federatedChannel._id,
|
|
federationConfig.hs1.adminMatrixUserId,
|
|
{ initialDelay: 0 },
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
expect(rc1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC).not.toBeNull();
|
|
expect(hs1AdminUserInRC?.federated).toBe(true);
|
|
|
|
// Synapse view: Check in Element (Matrix) that both users are in the members list
|
|
const hs1AdminUserInSynapse = await hs1AdminApp.findRoomMember(channelName, federationConfig.hs1.adminMatrixUserId, {
|
|
delay: 2000,
|
|
});
|
|
expect(hs1AdminUserInSynapse).not.toBeNull();
|
|
});
|
|
|
|
it('should show the system message that the user joined', async () => {
|
|
// RC view: Get the room history to find the system messages
|
|
const historyResponse = await getGroupHistory(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(Array.isArray(historyResponse.messages)).toBe(true);
|
|
|
|
// Look for 'uj' (user joined) system message after accepting the invite
|
|
const joinedMessage = historyResponse.messages.find(
|
|
(message: IMessage) => message.t === 'uj' && message.msg && message.msg.includes(federationConfig.hs1.adminMatrixUserId),
|
|
);
|
|
|
|
expect(joinedMessage).toBeDefined();
|
|
expect(joinedMessage?.msg).toContain(federationConfig.hs1.adminMatrixUserId);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Accept/Reject invitation permissions', () => {
|
|
describe('User tries to accept another user invitation', () => {
|
|
let channelName: string;
|
|
let federatedChannel: any;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-accept-permission-${Date.now()}`;
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.rc1.additionalUser1.username],
|
|
extraData: {
|
|
federated: true,
|
|
},
|
|
config: rc1AdminRequestConfig,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('name', channelName);
|
|
expect(federatedChannel).toHaveProperty('t', 'p');
|
|
expect(federatedChannel).toHaveProperty('federated', true);
|
|
}, 10000);
|
|
|
|
it('should not allow admin to accept invitation on behalf of another user', async () => {
|
|
// RC view: Admin tries to accept rc1User1's invitation
|
|
const response = await acceptRoomInvite(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBe(
|
|
'Failed to handle invite: No subscription found or user does not have permission to accept or reject this invite',
|
|
);
|
|
});
|
|
|
|
it('should not allow admin to reject invitation on behalf of another user', async () => {
|
|
// RC view: Admin tries to reject rc1User1's invitation
|
|
const response = await rejectRoomInvite(federatedChannel._id, rc1AdminRequestConfig);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBe(
|
|
'Failed to handle invite: No subscription found or user does not have permission to accept or reject this invite',
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Inviting a RC user from Synapse', () => {
|
|
describe('Room that already contains previous events', () => {
|
|
let matrixRoomId: string;
|
|
let channelName: string;
|
|
let rid: string;
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-from-synapse-${Date.now()}`;
|
|
matrixRoomId = await hs1AdminApp.createRoom(channelName);
|
|
|
|
await hs1AdminApp.matrixClient.sendTextMessage(matrixRoomId, 'Message from admin');
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.hs1.additionalUser1.matrixUserId);
|
|
await hs1User1App.matrixClient.joinRoom(matrixRoomId);
|
|
await hs1User1App.matrixClient.sendTextMessage(matrixRoomId, 'Message from user1');
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
rid = pendingInvitation!.rid!;
|
|
|
|
await acceptRoomInvite(rid, rc1AdminRequestConfig);
|
|
}, 15000);
|
|
|
|
describe('It should reflect all the members and messagens on the rocket.chat side', () => {
|
|
it('should show all the three users in the members list', async () => {
|
|
await retry(
|
|
'Getting room members until all are present',
|
|
async () => {
|
|
const members = await getRoomMembers(rid, rc1AdminRequestConfig);
|
|
|
|
expect(members.members.length).toBe(3);
|
|
expect(members.members.find((member: IUser) => member.username === federationConfig.rc1.adminUser)).not.toBeNull();
|
|
expect(
|
|
members.members.find((member: IUser) => member.username === federationConfig.rc1.additionalUser1.username),
|
|
).not.toBeNull();
|
|
expect(members.members.find((member: IUser) => member.username === federationConfig.hs1.adminMatrixUserId)).not.toBeNull();
|
|
},
|
|
{ delayMs: 200 },
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Rejecting an invitation from Synapse', () => {
|
|
let matrixRoomId: string;
|
|
let channelName: string;
|
|
let rid: string;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-reject-from-synapse-${Date.now()}`;
|
|
matrixRoomId = await hs1AdminApp.createRoom(channelName);
|
|
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
rid = pendingInvitation!.rid!;
|
|
}, 15000);
|
|
|
|
it('should allow RC user to reject the invite and remove the subscription', async () => {
|
|
const rejectResponse = await rejectRoomInvite(rid, rc1AdminRequestConfig);
|
|
expect(rejectResponse.success).toBe(true);
|
|
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
const invitedSub = subscriptions.update.find((sub) => sub.fname?.includes(channelName));
|
|
expect(invitedSub).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
describe('Revoked invitation flow from Synapse', () => {
|
|
describe('Synapse revokes an invitation before the RC user responds', () => {
|
|
let matrixRoomId: string;
|
|
let channelName: string;
|
|
let rid: string;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-revoked-${Date.now()}`;
|
|
matrixRoomId = await hs1AdminApp.createRoom(channelName);
|
|
|
|
// hs1 invites RC user
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
rid = pendingInvitation!.rid!;
|
|
|
|
// hs1 revokes the invitation by kicking the invited user
|
|
await hs1AdminApp.matrixClient.kick(matrixRoomId, federationConfig.rc1.adminMatrixUserId, 'Invitation revoked');
|
|
}, 15000);
|
|
|
|
it('should fail when RC user tries to accept the revoked invitation', async () => {
|
|
const acceptResponse = await acceptRoomInvite(rid, rc1AdminRequestConfig);
|
|
expect(acceptResponse.success).toBe(false);
|
|
});
|
|
|
|
it('should not have the subscription on the RC side after revoked invitation', async () => {
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
const invitedSub = subscriptions.update.find((sub) => sub.fname?.includes(channelName));
|
|
expect(invitedSub).toBeFalsy();
|
|
});
|
|
|
|
it('should have the RC user with leave membership on Synapse side after revoked invitation', async () => {
|
|
const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
expect(member?.membership).toBe('leave');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Re-inviting a RC user from Synapse after leaving or being kicked', () => {
|
|
describe('Kick a RC user from Synapse, send a message, then re-invite the same user', () => {
|
|
let matrixRoomId: string;
|
|
let channelName: string;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-reinvite-kicked-${Date.now()}`;
|
|
|
|
// Step 1: Create a room on Synapse
|
|
matrixRoomId = await hs1AdminApp.createRoom(channelName);
|
|
|
|
// Step 2: Invite RC user from Synapse
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
|
|
// Step 3: RC user accepts the invite
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
|
|
const rid = pendingInvitation!.rid!;
|
|
await acceptRoomInvite(rid, rc1AdminRequestConfig);
|
|
|
|
// Wait for join to propagate to Synapse
|
|
await retry(
|
|
'waiting for RC user join to propagate to Synapse',
|
|
async () => {
|
|
const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
expect(member?.membership).toBe('join');
|
|
},
|
|
{ delayMs: 500 },
|
|
);
|
|
|
|
// Step 4: Kick RC user from Synapse
|
|
await hs1AdminApp.matrixClient.kick(matrixRoomId, federationConfig.rc1.adminMatrixUserId, 'Kicked for re-invite test');
|
|
|
|
// Wait for kick to propagate to Synapse
|
|
await retry(
|
|
'waiting for RC user kick to propagate to Synapse',
|
|
async () => {
|
|
const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
expect(member?.membership).toBe('leave');
|
|
},
|
|
{ delayMs: 500 },
|
|
);
|
|
|
|
// Step 5: Send a message from Synapse (while RC user is kicked)
|
|
await hs1AdminApp.matrixClient.sendTextMessage(matrixRoomId, 'Message sent after kicking RC user');
|
|
}, 30000);
|
|
|
|
it('should allow re-inviting the same RC user after being kicked', async () => {
|
|
// Step 6: Re-invite the same RC user from Synapse
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
|
|
// Step 7: Validate the invite was created successfully on the RC side
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
expect(pendingInvitation).toHaveProperty('rid');
|
|
expect(pendingInvitation).toHaveProperty('fname');
|
|
expect(pendingInvitation!.fname).toContain(channelName);
|
|
});
|
|
});
|
|
|
|
describe('RC user leaves the room, Synapse sends a message, then re-invites the same user', () => {
|
|
let matrixRoomId: string;
|
|
let channelName: string;
|
|
|
|
beforeAll(async () => {
|
|
channelName = `federated-channel-reinvite-left-${Date.now()}`;
|
|
|
|
// Step 1: Create a room on Synapse
|
|
matrixRoomId = await hs1AdminApp.createRoom(channelName);
|
|
|
|
// Step 2: Invite RC user from Synapse
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
|
|
// Step 3: RC user accepts the invite
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
|
|
const rid = pendingInvitation!.rid!;
|
|
await acceptRoomInvite(rid, rc1AdminRequestConfig);
|
|
|
|
// Wait for join to propagate to Synapse
|
|
await retry(
|
|
'waiting for RC user join to propagate to Synapse',
|
|
async () => {
|
|
const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
expect(member?.membership).toBe('join');
|
|
},
|
|
{ delayMs: 500 },
|
|
);
|
|
|
|
// Step 4: RC user leaves the room
|
|
await rc1AdminRequestConfig.request
|
|
.post(api('rooms.leave'))
|
|
.set(rc1AdminRequestConfig.credentials)
|
|
.send({ roomId: rid })
|
|
.expect(200);
|
|
|
|
// Wait for leave to propagate to Synapse
|
|
await retry(
|
|
'waiting for RC user leave to propagate to Synapse',
|
|
async () => {
|
|
const member = await hs1AdminApp.findRoomMember(channelName, federationConfig.rc1.adminMatrixUserId);
|
|
expect(member?.membership).toBe('leave');
|
|
},
|
|
{ delayMs: 500 },
|
|
);
|
|
|
|
// Step 5: Send a message from Synapse (while RC user has left)
|
|
await hs1AdminApp.matrixClient.sendTextMessage(matrixRoomId, 'Message sent after RC user left');
|
|
}, 30000);
|
|
|
|
it('should allow re-inviting the same RC user after they left', async () => {
|
|
// Step 6: Re-invite the same RC user from Synapse
|
|
await hs1AdminApp.matrixClient.invite(matrixRoomId, federationConfig.rc1.adminMatrixUserId);
|
|
|
|
// Step 7: Validate the invite was created successfully on the RC side
|
|
const subscriptions = await getSubscriptions(rc1AdminRequestConfig);
|
|
const pendingInvitation = subscriptions.update.find(
|
|
(subscription) => subscription.status === 'INVITED' && subscription.fname?.includes(channelName),
|
|
);
|
|
|
|
expect(pendingInvitation).not.toBeUndefined();
|
|
expect(pendingInvitation).toHaveProperty('rid');
|
|
expect(pendingInvitation).toHaveProperty('fname');
|
|
expect(pendingInvitation!.fname).toContain(channelName);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe.skip('Synchronizing user names across federated servers', () => {
|
|
const ts = Date.now();
|
|
|
|
const rcUser1 = {
|
|
username: `sync-name-user1-${ts}`,
|
|
fullName: `Sync Name User1 ${ts}`,
|
|
newFullName: `Updated Sync Name User1 ${ts}`,
|
|
adminUpdatedName: `Admin Updated User1 ${ts}`,
|
|
get matrixId() {
|
|
return `@${this.username}:${federationConfig.rc1.domain}`;
|
|
},
|
|
config: {} as IRequestConfig,
|
|
user: {} as IUser,
|
|
};
|
|
|
|
const newSynapseDisplayName = `Updated Synapse Name ${ts}`;
|
|
|
|
let channelName: string;
|
|
let federatedChannel: { _id: string; name: string; t: string; federated?: boolean; federation?: { mrid: string } };
|
|
|
|
beforeAll(async () => {
|
|
rcUser1.user = await createUser(
|
|
{
|
|
username: rcUser1.username,
|
|
password: 'random',
|
|
email: `${rcUser1.username}@rocket.chat`,
|
|
name: rcUser1.fullName,
|
|
},
|
|
rc1AdminRequestConfig,
|
|
);
|
|
|
|
rcUser1.config = await getRequestConfig(federationConfig.rc1.url, rcUser1.username, 'random');
|
|
|
|
channelName = `sync-name-channel-${ts}`;
|
|
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: channelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId],
|
|
extraData: { federated: true },
|
|
config: rcUser1.config,
|
|
});
|
|
|
|
federatedChannel = createResponse.body.group;
|
|
|
|
expect(federatedChannel).toHaveProperty('_id');
|
|
expect(federatedChannel).toHaveProperty('federation');
|
|
|
|
await hs1AdminApp.acceptInvitationForRoomName(channelName);
|
|
}, 20000);
|
|
|
|
afterAll(async () => {
|
|
await deleteUser(rcUser1.user, {}, rc1AdminRequestConfig);
|
|
});
|
|
|
|
describe('When a RC local user changes their display name', () => {
|
|
it('should propagate the updated displayname to the Synapse side', async () => {
|
|
// Action: update the RC user's display name via their own profile
|
|
await rcUser1.config.request
|
|
.post(api('users.updateOwnBasicInfo'))
|
|
.set(rcUser1.config.credentials)
|
|
.send({ data: { name: rcUser1.newFullName } })
|
|
.expect(200);
|
|
|
|
// Synapse view: verify the member's displayname was updated in the federated room
|
|
await retry(
|
|
'waiting for RC user displayname to propagate to Synapse',
|
|
async () => {
|
|
const members = await hs1AdminApp.getRoomMembers(channelName);
|
|
const member = members.find((m) => m.userId === rcUser1.matrixId);
|
|
expect(member).toBeDefined();
|
|
expect(member?.name).toBe(rcUser1.newFullName);
|
|
},
|
|
{ retries: 10, delayMs: 1000 },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('When a RC admin changes the user display name via admin APIs', () => {
|
|
it('should propagate the updated displayname to the Synapse side', async () => {
|
|
// Action: update the RC user's display name via the admin users.update API
|
|
await rc1AdminRequestConfig.request
|
|
.post(api('users.update'))
|
|
.set(rc1AdminRequestConfig.credentials)
|
|
.send({ userId: rcUser1.user._id, data: { name: rcUser1.adminUpdatedName } })
|
|
.expect(200);
|
|
|
|
// Synapse view: verify the member's displayname was updated in the federated room
|
|
await retry(
|
|
'waiting for admin-updated displayname to propagate to Synapse',
|
|
async () => {
|
|
const members = await hs1AdminApp.getRoomMembers(channelName);
|
|
const member = members.find((m) => m.userId === rcUser1.matrixId);
|
|
expect(member).toBeDefined();
|
|
expect(member?.name).toBe(rcUser1.adminUpdatedName);
|
|
},
|
|
{ retries: 10, delayMs: 1000 },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('When a new federated room is created on RC', () => {
|
|
it.failing('should show the user current display name on the Synapse side', async () => {
|
|
// At this point rcUser1's name is adminUpdatedName from the previous test
|
|
const newChannelName = `sync-name-channel-2-${ts}`;
|
|
|
|
const createResponse = await createRoom({
|
|
type: 'p',
|
|
name: newChannelName,
|
|
members: [federationConfig.hs1.adminMatrixUserId],
|
|
extraData: { federated: true },
|
|
config: rcUser1.config,
|
|
});
|
|
|
|
const newChannel = createResponse.body.group;
|
|
expect(newChannel).toHaveProperty('_id');
|
|
expect(newChannel).toHaveProperty('federation');
|
|
|
|
await hs1AdminApp.acceptInvitationForRoomName(newChannelName);
|
|
|
|
// Synapse view: the RC user should appear with their current name in the new room
|
|
await retry(
|
|
'waiting for RC user to appear with correct displayname in new Synapse room',
|
|
async () => {
|
|
const members = await hs1AdminApp.getRoomMembers(newChannelName);
|
|
const member = members.find((m) => m.userId === rcUser1.matrixId);
|
|
expect(member).toBeDefined();
|
|
expect(member?.name).toBe(rcUser1.adminUpdatedName);
|
|
},
|
|
{ retries: 10, delayMs: 1000 },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('When a Synapse user changes their displayname', () => {
|
|
afterAll(async () => {
|
|
await hs1AdminApp.matrixClient.setDisplayName(federationConfig.hs1.adminMatrixUserId); // reset Synapse admin name
|
|
});
|
|
|
|
it('should propagate the updated name to the RC side', async () => {
|
|
// Action: update the Synapse user's displayname — Synapse broadcasts new m.room.member
|
|
// events to all joined rooms, which RC federation receives and debounces the name update
|
|
await hs1AdminApp.matrixClient.setDisplayName(newSynapseDisplayName);
|
|
|
|
// RC view: verify the federated user's name was updated — wait for debounce (> 2s) + propagation
|
|
await retry(
|
|
'waiting for Synapse user displayname to propagate to RC',
|
|
async () => {
|
|
const response = await rc1AdminRequestConfig.request
|
|
.get(api('users.info'))
|
|
.set(rc1AdminRequestConfig.credentials)
|
|
.query({ username: federationConfig.hs1.adminMatrixUserId })
|
|
.expect(200);
|
|
|
|
expect(response.body.user).toHaveProperty('name', newSynapseDisplayName);
|
|
},
|
|
{ retries: 15, delayMs: 1000 },
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|