Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ee/packages/federation-matrix/src/FederationMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ export class FederationMatrix extends ServiceClass implements IFederationMatrixS

this.logger.debug({ msg: 'Matrix room created', response: matrixRoomResult });

if (room.topic) {
await federationSDK.setRoomTopic(matrixRoomResult.room_id, matrixUserId, room.topic);
}

await Rooms.setAsFederated(room._id, { mrid: matrixRoomResult.room_id, origin: this.serverName });

// Members are NOT invited here - invites are sent via beforeAddUserToRoom callback.
Expand Down
98 changes: 98 additions & 0 deletions ee/packages/federation-matrix/tests/end-to-end/room.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { IMessage, IUser } from '@rocket.chat/core-typings';
import type { Room } from 'matrix-js-sdk';
import { EventTimeline } from 'matrix-js-sdk';

import {
createRoom,
Expand Down Expand Up @@ -713,6 +715,102 @@ import { SynapseClient } from '../helper/synapse-client';
});
});

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);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard the topic event before calling getContent() (or assert it’s defined) so the test fails cleanly and doesn’t throw when the state hasn’t synced yet.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ee/packages/federation-matrix/tests/end-to-end/room.spec.ts, line 763:

<comment>Guard the `topic` event before calling `getContent()` (or assert it’s defined) so the test fails cleanly and doesn’t throw when the state hasn’t synced yet.</comment>

<file context>
@@ -713,6 +715,102 @@ import { SynapseClient } from '../helper/synapse-client';
+
+					const [topic] = hs1Room1.getLiveTimeline().getState(EventTimeline.FORWARDS)?.getStateEvents('m.room.topic') || [];
+
+					expect(topic.getContent().topic).toBe(channelTopic);
+				});
+			});
</file context>
Fix with Cubic

});
});

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', () => {
Expand Down
Loading