1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +03:00

GroupCallEventhandler Tests (#2654)

* GroupCallEventhandler Tests

Fixes https://github.com/vector-im/element-call/issues/545

* Fix long line

* Fix strict mode error

Co-authored-by: Robin <robin@robin.town>

* Fix typo

Co-authored-by: Robin <robin@robin.town>

Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
David Baker
2022-09-08 21:46:28 +01:00
committed by GitHub
parent 60e175a0e0
commit aebe26db96
4 changed files with 261 additions and 52 deletions

View File

@@ -14,6 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {
ClientEvent,
ClientEventHandlerMap,
EventType,
GroupCall,
GroupCallIntent,
GroupCallType,
ISendEventResponse,
MatrixClient,
MatrixEvent,
Room,
RoomStateEvent,
RoomStateEventHandlerMap,
} from "../../src";
import { TypedEventEmitter } from "../../src/models/typed-event-emitter";
import { ReEmitter } from "../../src/ReEmitter";
import { SyncState } from "../../src/sync";
import { CallEvent, CallEventHandlerMap, MatrixCall } from "../../src/webrtc/call";
import { CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "../../src/webrtc/callEventHandler";
import { GroupCallEventHandlerMap } from "../../src/webrtc/groupCall";
import { GroupCallEventHandlerEvent } from "../../src/webrtc/groupCallEventHandler";
import { IScreensharingOpts, MediaHandler } from "../../src/webrtc/mediaHandler"; import { IScreensharingOpts, MediaHandler } from "../../src/webrtc/mediaHandler";
export const DUMMY_SDP = ( export const DUMMY_SDP = (
@@ -295,6 +316,57 @@ export class MockMediaDevices {
typed(): MediaDevices { return this as unknown as MediaDevices; } typed(): MediaDevices { return this as unknown as MediaDevices; }
} }
type EmittedEvents = CallEventHandlerEvent | CallEvent | ClientEvent | RoomStateEvent | GroupCallEventHandlerEvent;
type EmittedEventMap = CallEventHandlerEventHandlerMap &
CallEventHandlerMap &
ClientEventHandlerMap &
RoomStateEventHandlerMap &
GroupCallEventHandlerMap;
export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, EmittedEventMap> {
public mediaHandler = new MockMediaHandler();
constructor(public userId: string, public deviceId: string, public sessionId: string) {
super();
}
groupCallEventHandler = {
groupCalls: new Map<string, GroupCall>(),
};
callEventHandler = {
calls: new Map<string, MatrixCall>(),
};
sendStateEvent = jest.fn<Promise<ISendEventResponse>, [
roomId: string, eventType: EventType, content: any, statekey: string,
]>();
sendToDevice = jest.fn<Promise<{}>, [
eventType: string,
contentMap: { [userId: string]: { [deviceId: string]: Record<string, any> } },
txnId?: string,
]>();
getMediaHandler(): MediaHandler { return this.mediaHandler.typed(); }
getUserId(): string { return this.userId; }
getDeviceId(): string { return this.deviceId; }
getSessionId(): string { return this.sessionId; }
getTurnServers = () => [];
isFallbackICEServerAllowed = () => false;
reEmitter = new ReEmitter(new TypedEventEmitter());
getUseE2eForGroupCall = () => false;
checkTurnServers = () => null;
getSyncState = jest.fn<SyncState, []>().mockReturnValue(SyncState.Syncing);
getRooms = jest.fn<Room[], []>().mockReturnValue([]);
typed(): MatrixClient { return this as unknown as MatrixClient; }
}
export function installWebRTCMocks() { export function installWebRTCMocks() {
global.navigator = { global.navigator = {
mediaDevices: new MockMediaDevices().typed(), mediaDevices: new MockMediaDevices().typed(),
@@ -318,3 +390,26 @@ export function installWebRTCMocks() {
// @ts-ignore Mock // @ts-ignore Mock
global.RTCRtpReceiver = {}; global.RTCRtpReceiver = {};
} }
export function makeMockGroupCallStateEvent(roomId: string, groupCallId: string): MatrixEvent {
return {
getType: jest.fn().mockReturnValue(EventType.GroupCallPrefix),
getRoomId: jest.fn().mockReturnValue(roomId),
getTs: jest.fn().mockReturnValue(0),
getContent: jest.fn().mockReturnValue({
"m.type": GroupCallType.Video,
"m.intent": GroupCallIntent.Prompt,
}),
getStateKey: jest.fn().mockReturnValue(groupCallId),
} as unknown as MatrixEvent;
}
export function makeMockGroupCallMemberStateEvent(roomId: string, groupCallId: string): MatrixEvent {
return {
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
getRoomId: jest.fn().mockReturnValue(roomId),
getTs: jest.fn().mockReturnValue(0),
getContent: jest.fn().mockReturnValue({}),
getStateKey: jest.fn().mockReturnValue(groupCallId),
} as unknown as MatrixEvent;
}

View File

@@ -18,7 +18,6 @@ import {
EventType, EventType,
GroupCallIntent, GroupCallIntent,
GroupCallType, GroupCallType,
ISendEventResponse,
MatrixCall, MatrixCall,
MatrixEvent, MatrixEvent,
Room, Room,
@@ -28,19 +27,16 @@ import { GroupCall } from "../../../src/webrtc/groupCall";
import { MatrixClient } from "../../../src/client"; import { MatrixClient } from "../../../src/client";
import { import {
installWebRTCMocks, installWebRTCMocks,
MockMediaHandler, MockCallMatrixClient,
MockMediaStream, MockMediaStream,
MockMediaStreamTrack, MockMediaStreamTrack,
MockRTCPeerConnection, MockRTCPeerConnection,
} from '../../test-utils/webrtc'; } from '../../test-utils/webrtc';
import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes"; import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from "../../../src/webrtc/callEventTypes";
import { sleep } from "../../../src/utils"; import { sleep } from "../../../src/utils";
import { ReEmitter } from "../../../src/ReEmitter"; import { CallEventHandlerEvent } from '../../../src/webrtc/callEventHandler';
import { TypedEventEmitter } from '../../../src/models/typed-event-emitter';
import { MediaHandler } from '../../../src/webrtc/mediaHandler';
import { CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from '../../../src/webrtc/callEventHandler';
import { CallFeed } from '../../../src/webrtc/callFeed'; import { CallFeed } from '../../../src/webrtc/callFeed';
import { CallEvent, CallEventHandlerMap, CallState } from '../../../src/webrtc/call'; import { CallEvent, CallState } from '../../../src/webrtc/call';
import { flushPromises } from '../../test-utils/flushPromises'; import { flushPromises } from '../../test-utils/flushPromises';
const FAKE_ROOM_ID = "!fake:test.dummy"; const FAKE_ROOM_ID = "!fake:test.dummy";
@@ -107,49 +103,6 @@ const createAndEnterGroupCall = async (cli: MatrixClient, room: Room): Promise<G
return groupCall; return groupCall;
}; };
type EmittedEvents = CallEventHandlerEvent | CallEvent;
type EmittedEventMap = CallEventHandlerEventHandlerMap & CallEventHandlerMap;
class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, EmittedEventMap> {
public mediaHandler = new MockMediaHandler();
constructor(public userId: string, public deviceId: string, public sessionId: string) {
super();
}
groupCallEventHandler = {
groupCalls: new Map<string, GroupCall>(),
};
callEventHandler = {
calls: new Map<string, MatrixCall>(),
};
sendStateEvent = jest.fn<Promise<ISendEventResponse>, [
roomId: string, eventType: EventType, content: any, statekey: string,
]>();
sendToDevice = jest.fn<Promise<{}>, [
eventType: string,
contentMap: { [userId: string]: { [deviceId: string]: Record<string, any> } },
txnId?: string,
]>();
getMediaHandler(): MediaHandler { return this.mediaHandler.typed(); }
getUserId(): string { return this.userId; }
getDeviceId(): string { return this.deviceId; }
getSessionId(): string { return this.sessionId; }
getTurnServers = () => [];
isFallbackICEServerAllowed = () => false;
reEmitter = new ReEmitter(new TypedEventEmitter());
getUseE2eForGroupCall = () => false;
checkTurnServers = () => null;
typed(): MatrixClient { return this as unknown as MatrixClient; }
}
class MockCall { class MockCall {
constructor(public roomId: string, public groupCallId: string) { constructor(public roomId: string, public groupCallId: string) {
} }

View File

@@ -0,0 +1,162 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ClientEvent, GroupCall, Room, RoomState, RoomStateEvent } from "../../../src";
import { SyncState } from "../../../src/sync";
import { GroupCallEventHandler, GroupCallEventHandlerEvent } from "../../../src/webrtc/groupCallEventHandler";
import { flushPromises } from "../../test-utils/flushPromises";
import {
makeMockGroupCallMemberStateEvent,
makeMockGroupCallStateEvent,
MockCallMatrixClient,
} from "../../test-utils/webrtc";
const FAKE_USER_ID = "@alice:test.dummy";
const FAKE_DEVICE_ID = "AAAAAAA";
const FAKE_SESSION_ID = "session1";
const FAKE_ROOM_ID = "!roomid:test.dummy";
const FAKE_GROUP_CALL_ID = "fakegroupcallid";
describe('Group Call Event Handler', function() {
let groupCallEventHandler: GroupCallEventHandler;
let mockClient: MockCallMatrixClient;
let mockRoom: Room;
beforeEach(() => {
mockClient = new MockCallMatrixClient(
FAKE_USER_ID, FAKE_DEVICE_ID, FAKE_SESSION_ID,
);
groupCallEventHandler = new GroupCallEventHandler(mockClient.typed());
mockRoom = {
roomId: FAKE_ROOM_ID,
currentState: {
getStateEvents: jest.fn().mockReturnValue([makeMockGroupCallStateEvent(
FAKE_ROOM_ID, FAKE_GROUP_CALL_ID,
)]),
},
} as unknown as Room;
(mockClient as any).getRoom = jest.fn().mockReturnValue(mockRoom);
});
it("waits until client starts syncing", async () => {
mockClient.getSyncState.mockReturnValue(null);
let isStarted = false;
(async () => {
await groupCallEventHandler.start();
isStarted = true;
})();
const setSyncState = async (newState: SyncState) => {
const oldState = mockClient.getSyncState();
mockClient.getSyncState.mockReturnValue(newState);
mockClient.emit(ClientEvent.Sync, newState, oldState, undefined);
await flushPromises();
};
await flushPromises();
expect(isStarted).toEqual(false);
await setSyncState(SyncState.Prepared);
expect(isStarted).toEqual(false);
await setSyncState(SyncState.Syncing);
expect(isStarted).toEqual(true);
});
it("finds existing group calls when started", async () => {
const mockClientEmit = mockClient.emit = jest.fn();
mockClient.getRooms.mockReturnValue([mockRoom]);
await groupCallEventHandler.start();
expect(mockClientEmit).toHaveBeenCalledWith(
GroupCallEventHandlerEvent.Incoming,
expect.objectContaining({
groupCallId: FAKE_GROUP_CALL_ID,
}),
);
groupCallEventHandler.stop();
});
it("can wait until a room is ready for group calls", async () => {
await groupCallEventHandler.start();
const prom = groupCallEventHandler.waitUntilRoomReadyForGroupCalls(FAKE_ROOM_ID);
let resolved = false;
(async () => {
await prom;
resolved = true;
})();
expect(resolved).toEqual(false);
mockClient.emit(ClientEvent.Room, mockRoom);
await prom;
expect(resolved).toEqual(true);
groupCallEventHandler.stop();
});
it("fires events for incoming calls", async () => {
const onIncomingGroupCall = jest.fn();
mockClient.on(GroupCallEventHandlerEvent.Incoming, onIncomingGroupCall);
await groupCallEventHandler.start();
mockClient.emit(
RoomStateEvent.Events,
makeMockGroupCallStateEvent(
FAKE_ROOM_ID, FAKE_GROUP_CALL_ID,
),
{
roomId: FAKE_ROOM_ID,
} as unknown as RoomState,
null,
);
expect(onIncomingGroupCall).toHaveBeenCalledWith(expect.objectContaining({
groupCallId: FAKE_GROUP_CALL_ID,
}));
mockClient.off(GroupCallEventHandlerEvent.Incoming, onIncomingGroupCall);
});
it("sends member events to group calls", async () => {
await groupCallEventHandler.start();
const mockGroupCall = {
onMemberStateChanged: jest.fn(),
};
groupCallEventHandler.groupCalls.set(FAKE_ROOM_ID, mockGroupCall as unknown as GroupCall);
const mockStateEvent = makeMockGroupCallMemberStateEvent(FAKE_ROOM_ID, FAKE_GROUP_CALL_ID);
mockClient.emit(
RoomStateEvent.Events,
mockStateEvent,
{
roomId: FAKE_ROOM_ID,
} as unknown as RoomState,
null,
);
expect(mockGroupCall.onMemberStateChanged).toHaveBeenCalledWith(mockStateEvent);
});
});

View File

@@ -15,7 +15,6 @@ limitations under the License.
*/ */
import { MatrixEvent } from '../models/event'; import { MatrixEvent } from '../models/event';
import { RoomStateEvent } from '../models/room-state';
import { MatrixClient, ClientEvent } from '../client'; import { MatrixClient, ClientEvent } from '../client';
import { import {
GroupCall, GroupCall,
@@ -24,7 +23,7 @@ import {
IGroupCallDataChannelOptions, IGroupCallDataChannelOptions,
} from "./groupCall"; } from "./groupCall";
import { Room } from "../models/room"; import { Room } from "../models/room";
import { RoomState } from "../models/room-state"; import { RoomState, RoomStateEvent } from "../models/room-state";
import { RoomMember } from "../models/room-member"; import { RoomMember } from "../models/room-member";
import { logger } from '../logger'; import { logger } from '../logger';
import { EventType } from "../@types/event"; import { EventType } from "../@types/event";