Skip to content

Commit ebb973b

Browse files
robintowntoger5
authored andcommitted
Make it easier to mock call memberships for specific user IDs
1 parent f8f1bf3 commit ebb973b

File tree

4 files changed

+89
-59
lines changed

4 files changed

+89
-59
lines changed

spec/unit/matrixrtc/MatrixRTCSession.spec.ts

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ limitations under the License.
1616

1717
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src";
1818
import { KnownMembership } from "../../../src/@types/membership";
19-
import { type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
2019
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
2120
import { type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
2221
import { secureRandomString } from "../../../src/randomstring";
23-
import { makeMockEvent, makeMockRoom, makeMockRoomState, membershipTemplate, makeKey } from "./mocks";
22+
import { makeMockEvent, makeMockRoom, membershipTemplate, makeKey, type MembershipData, mockRoomState } from "./mocks";
2423
import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts";
2524

2625
const mockFocus = { type: "mock" };
@@ -48,7 +47,7 @@ describe("MatrixRTCSession", () => {
4847

4948
describe("roomSessionForRoom", () => {
5049
it("creates a room-scoped session from room state", () => {
51-
const mockRoom = makeMockRoom(membershipTemplate);
50+
const mockRoom = makeMockRoom([membershipTemplate]);
5251

5352
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
5453
expect(sess?.memberships.length).toEqual(1);
@@ -75,7 +74,7 @@ describe("MatrixRTCSession", () => {
7574
});
7675

7776
it("ignores memberships events of members not in the room", () => {
78-
const mockRoom = makeMockRoom(membershipTemplate);
77+
const mockRoom = makeMockRoom([membershipTemplate]);
7978
mockRoom.hasMembershipState = (state) => state === KnownMembership.Join;
8079
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
8180
expect(sess?.memberships.length).toEqual(0);
@@ -202,6 +201,59 @@ describe("MatrixRTCSession", () => {
202201
});
203202
});
204203

204+
describe("updateCallMembershipEvent", () => {
205+
const mockFocus = { type: "livekit", livekit_service_url: "https://test.org" };
206+
const joinSessionConfig = {};
207+
208+
const sessionMembershipData: MembershipData = {
209+
call_id: "",
210+
scope: "m.room",
211+
application: "m.call",
212+
user_id: "@mock:user.example",
213+
device_id: "AAAAAAA_session",
214+
focus_active: mockFocus,
215+
foci_preferred: [mockFocus],
216+
};
217+
218+
let sendStateEventMock: jest.Mock;
219+
let sendDelayedStateMock: jest.Mock;
220+
221+
let sentStateEvent: Promise<void>;
222+
let sentDelayedState: Promise<void>;
223+
224+
beforeEach(() => {
225+
sentStateEvent = new Promise((resolve) => {
226+
sendStateEventMock = jest.fn(resolve);
227+
});
228+
sentDelayedState = new Promise((resolve) => {
229+
sendDelayedStateMock = jest.fn(() => {
230+
resolve();
231+
return {
232+
delay_id: "id",
233+
};
234+
});
235+
});
236+
client.sendStateEvent = sendStateEventMock;
237+
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
238+
});
239+
240+
async function testSession(membershipData: MembershipData): Promise<void> {
241+
sess = MatrixRTCSession.roomSessionForRoom(client, makeMockRoom([membershipData]));
242+
243+
sess.joinRoomSession([mockFocus], mockFocus, joinSessionConfig);
244+
await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 500))]);
245+
246+
expect(sendStateEventMock).toHaveBeenCalledTimes(1);
247+
248+
await Promise.race([sentDelayedState, new Promise((resolve) => setTimeout(resolve, 500))]);
249+
expect(sendDelayedStateMock).toHaveBeenCalledTimes(1);
250+
}
251+
252+
it("sends events", async () => {
253+
await testSession(sessionMembershipData);
254+
});
255+
});
256+
205257
describe("getOldestMembership", () => {
206258
it("returns the oldest membership event", () => {
207259
jest.useFakeTimers();
@@ -302,7 +354,7 @@ describe("MatrixRTCSession", () => {
302354

303355
describe("onMembershipsChanged", () => {
304356
it("does not emit if no membership changes", () => {
305-
const mockRoom = makeMockRoom(membershipTemplate);
357+
const mockRoom = makeMockRoom([membershipTemplate]);
306358
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
307359

308360
const onMembershipsChanged = jest.fn();
@@ -313,13 +365,13 @@ describe("MatrixRTCSession", () => {
313365
});
314366

315367
it("emits on membership changes", () => {
316-
const mockRoom = makeMockRoom(membershipTemplate);
368+
const mockRoom = makeMockRoom([membershipTemplate]);
317369
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
318370

319371
const onMembershipsChanged = jest.fn();
320372
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
321373

322-
mockRoom.getLiveTimeline().getState = jest.fn().mockReturnValue(makeMockRoomState([], mockRoom.roomId));
374+
mockRoomState(mockRoom, []);
323375
sess.onRTCSessionMemberUpdate();
324376

325377
expect(onMembershipsChanged).toHaveBeenCalled();
@@ -503,18 +555,14 @@ describe("MatrixRTCSession", () => {
503555
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
504556

505557
// member2 leaves triggering key rotation
506-
mockRoom.getLiveTimeline().getState = jest
507-
.fn()
508-
.mockReturnValue(makeMockRoomState([membershipTemplate], mockRoom.roomId));
558+
mockRoomState(mockRoom, [membershipTemplate]);
509559
sess.onRTCSessionMemberUpdate();
510560

511561
// member2 re-joins which should trigger an immediate re-send
512562
const keysSentPromise2 = new Promise<EncryptionKeysEventContent>((resolve) => {
513563
sendEventMock.mockImplementation((_roomId, _evType, payload) => resolve(payload));
514564
});
515-
mockRoom.getLiveTimeline().getState = jest
516-
.fn()
517-
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId));
565+
mockRoomState(mockRoom, [membershipTemplate, member2]);
518566
sess.onRTCSessionMemberUpdate();
519567
// but, that immediate resend is throttled so we need to wait a bit
520568
jest.advanceTimersByTime(1000);
@@ -565,9 +613,7 @@ describe("MatrixRTCSession", () => {
565613
device_id: "BBBBBBB",
566614
});
567615

568-
mockRoom.getLiveTimeline().getState = jest
569-
.fn()
570-
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId));
616+
mockRoomState(mockRoom, [membershipTemplate, member2]);
571617
sess.onRTCSessionMemberUpdate();
572618

573619
await keysSentPromise2;
@@ -592,9 +638,7 @@ describe("MatrixRTCSession", () => {
592638
});
593639

594640
const mockRoom = makeMockRoom([member1, member2]);
595-
mockRoom.getLiveTimeline().getState = jest
596-
.fn()
597-
.mockReturnValue(makeMockRoomState([member1, member2], mockRoom.roomId));
641+
mockRoomState(mockRoom, [member1, member2]);
598642

599643
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
600644
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
@@ -641,10 +685,6 @@ describe("MatrixRTCSession", () => {
641685
};
642686

643687
const mockRoom = makeMockRoom([member1, member2]);
644-
mockRoom.getLiveTimeline().getState = jest
645-
.fn()
646-
.mockReturnValue(makeMockRoomState([member1, member2], mockRoom.roomId));
647-
648688
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
649689
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
650690

@@ -674,6 +714,7 @@ describe("MatrixRTCSession", () => {
674714

675715
// update created_ts
676716
member2.created_ts = 5000;
717+
mockRoomState(mockRoom, [member1, member2]);
677718

678719
const keysSentPromise2 = new Promise((resolve) => {
679720
sendEventMock.mockImplementation(resolve);
@@ -737,9 +778,7 @@ describe("MatrixRTCSession", () => {
737778
sendEventMock.mockImplementation((_roomId, _evType, payload) => resolve(payload));
738779
});
739780

740-
mockRoom.getLiveTimeline().getState = jest
741-
.fn()
742-
.mockReturnValue(makeMockRoomState([membershipTemplate], mockRoom.roomId));
781+
mockRoomState(mockRoom, [membershipTemplate]);
743782
sess.onRTCSessionMemberUpdate();
744783

745784
jest.advanceTimersByTime(KEY_DELAY);
@@ -784,7 +823,7 @@ describe("MatrixRTCSession", () => {
784823
it("wraps key index around to 0 when it reaches the maximum", async () => {
785824
// this should give us keys with index [0...255, 0, 1]
786825
const membersToTest = 258;
787-
const members: SessionMembershipData[] = [];
826+
const members: MembershipData[] = [];
788827
for (let i = 0; i < membersToTest; i++) {
789828
members.push(Object.assign({}, membershipTemplate, { device_id: `DEVICE${i}` }));
790829
}
@@ -804,11 +843,7 @@ describe("MatrixRTCSession", () => {
804843
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
805844
} else {
806845
// otherwise update the state reducing the membership each time in order to trigger key rotation
807-
mockRoom.getLiveTimeline().getState = jest
808-
.fn()
809-
.mockReturnValue(
810-
makeMockRoomState(members.slice(0, membersToTest - i), mockRoom.roomId),
811-
);
846+
mockRoomState(mockRoom, members.slice(0, membersToTest - i));
812847
}
813848

814849
sess!.onRTCSessionMemberUpdate();
@@ -849,9 +884,7 @@ describe("MatrixRTCSession", () => {
849884
device_id: "BBBBBBB",
850885
});
851886

852-
mockRoom.getLiveTimeline().getState = jest
853-
.fn()
854-
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId));
887+
mockRoomState(mockRoom, [membershipTemplate, member2]);
855888
sess.onRTCSessionMemberUpdate();
856889

857890
await new Promise((resolve) => {

spec/unit/matrixrtc/MatrixRTCSessionManager.spec.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { type Mock } from "jest-mock";
18-
1917
import { ClientEvent, EventTimeline, MatrixClient } from "../../../src";
2018
import { RoomStateEvent } from "../../../src/models/room-state";
2119
import { MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
22-
import { makeMockRoom, makeMockRoomState, membershipTemplate } from "./mocks";
20+
import { makeMockRoom, membershipTemplate, mockRoomState } from "./mocks";
2321

2422
describe("MatrixRTCSessionManager", () => {
2523
let client: MatrixClient;
@@ -52,19 +50,16 @@ describe("MatrixRTCSessionManager", () => {
5250
it("Fires event when session ends", () => {
5351
const onEnded = jest.fn();
5452
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
55-
const room1 = makeMockRoom(membershipTemplate);
53+
const room1 = makeMockRoom([membershipTemplate]);
5654
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
5755
jest.spyOn(client, "getRoom").mockReturnValue(room1);
5856

5957
client.emit(ClientEvent.Room, room1);
6058

61-
(room1.getLiveTimeline as Mock).mockReturnValue({
62-
getState: jest.fn().mockReturnValue(makeMockRoomState([{}], room1.roomId)),
63-
});
59+
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
6460

6561
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
6662
const membEvent = roomState.getStateEvents("")[0];
67-
6863
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
6964

7065
expect(onEnded).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));

spec/unit/matrixrtc/MembershipManager.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe("MembershipManager", () => {
8181
// Default to fake timers.
8282
jest.useFakeTimers();
8383
client = makeMockClient("@alice:example.org", "AAAAAAA");
84-
room = makeMockRoom(membershipTemplate);
84+
room = makeMockRoom([membershipTemplate]);
8585
// Provide a default mock that is like the default "non error" server behaviour.
8686
(client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" });
8787
(client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined);
@@ -436,11 +436,11 @@ describe("MembershipManager", () => {
436436
type: "livekit",
437437
},
438438
],
439-
device_id: client.getDeviceId(),
439+
user_id: client.getUserId()!,
440+
device_id: client.getDeviceId()!,
440441
created_ts: 1000,
441442
},
442443
room.roomId,
443-
client.getUserId()!,
444444
),
445445
);
446446
expect(manager.getActiveFocus()).toStrictEqual(focus);
@@ -482,7 +482,7 @@ describe("MembershipManager", () => {
482482

483483
await manager.onRTCSessionMemberUpdate([
484484
mockCallMembership(membershipTemplate, room.roomId),
485-
mockCallMembership(myMembership as SessionMembershipData, room.roomId, client.getUserId() ?? undefined),
485+
mockCallMembership({ ...myMembership as SessionMembershipData, user_id: client.getUserId()! }, room.roomId),
486486
]);
487487

488488
await jest.advanceTimersByTimeAsync(1);
@@ -797,7 +797,7 @@ describe("MembershipManager", () => {
797797

798798
it("Should prefix log with MembershipManager used", () => {
799799
const client = makeMockClient("@alice:example.org", "AAAAAAA");
800-
const room = makeMockRoom(membershipTemplate);
800+
const room = makeMockRoom([membershipTemplate]);
801801

802802
const membershipManager = new MembershipManager(undefined, room, client, () => undefined, logger);
803803

spec/unit/matrixrtc/mocks.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import { EventType, type Room, RoomEvent, type MatrixClient, type MatrixEvent }
2020
import { CallMembership, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
2121
import { secureRandomString } from "../../../src/randomstring";
2222

23-
type MembershipData = SessionMembershipData[] | SessionMembershipData | {};
23+
export type MembershipData = (SessionMembershipData | {}) & { user_id: string };
2424

25-
export const membershipTemplate: SessionMembershipData = {
25+
export const membershipTemplate: SessionMembershipData & { user_id: string } = {
2626
application: "m.call",
2727
call_id: "",
28+
user_id: "@mock:user.example",
2829
device_id: "AAAAAAA",
2930
scope: "m.room",
3031
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
@@ -68,7 +69,7 @@ export function makeMockClient(userId: string, deviceId: string): MockClient {
6869
}
6970

7071
export function makeMockRoom(
71-
membershipData: MembershipData,
72+
membershipData: MembershipData[],
7273
): Room & { emitTimelineEvent: (event: MatrixEvent) => void } {
7374
const roomId = secureRandomString(8);
7475
// Caching roomState here so it does not get recreated when calling `getLiveTimeline.getState()`
@@ -87,10 +88,8 @@ export function makeMockRoom(
8788
});
8889
}
8990

90-
export function makeMockRoomState(membershipData: MembershipData, roomId: string) {
91-
const events = Array.isArray(membershipData)
92-
? membershipData.map((m) => mockRTCEvent(m, roomId))
93-
: [mockRTCEvent(membershipData, roomId)];
91+
function makeMockRoomState(membershipData: MembershipData[], roomId: string) {
92+
const events = membershipData.map((m) => mockRTCEvent(m, roomId));
9493
const keysAndEvents = events.map((e) => {
9594
const data = e.getContent() as SessionMembershipData;
9695
return [`_${e.sender?.userId}_${data.device_id}`];
@@ -120,6 +119,10 @@ export function makeMockRoomState(membershipData: MembershipData, roomId: string
120119
};
121120
}
122121

122+
export function mockRoomState(room: Room, membershipData: MembershipData[]): void {
123+
room.getLiveTimeline().getState = jest.fn().mockReturnValue(makeMockRoomState(membershipData, room.roomId));
124+
}
125+
123126
export function makeMockEvent(
124127
type: string,
125128
sender: string,
@@ -138,13 +141,12 @@ export function makeMockEvent(
138141
} as unknown as MatrixEvent;
139142
}
140143

141-
export function mockRTCEvent(membershipData: MembershipData, roomId: string, customSender?: string): MatrixEvent {
142-
const sender = customSender ?? "@mock:user.example";
144+
export function mockRTCEvent({ user_id: sender, ...membershipData }: MembershipData, roomId: string): MatrixEvent {
143145
return makeMockEvent(EventType.GroupCallMemberPrefix, sender, roomId, membershipData);
144146
}
145147

146-
export function mockCallMembership(membershipData: MembershipData, roomId: string, sender?: string): CallMembership {
147-
return new CallMembership(mockRTCEvent(membershipData, roomId, sender), membershipData);
148+
export function mockCallMembership(membershipData: MembershipData, roomId: string): CallMembership {
149+
return new CallMembership(mockRTCEvent(membershipData, roomId), membershipData);
148150
}
149151

150152
export function makeKey(id: number, key: string): { key: string; index: number } {

0 commit comments

Comments
 (0)