995 lines
31 KiB
Dart
995 lines
31 KiB
Dart
import 'package:test/test.dart';
|
|
import 'package:webrtc_interface/webrtc_interface.dart';
|
|
|
|
import 'package:matrix/matrix.dart';
|
|
import 'package:matrix/src/voip/models/call_options.dart';
|
|
import 'package:matrix/src/voip/models/voip_id.dart';
|
|
import 'fake_client.dart';
|
|
import 'webrtc_stub.dart';
|
|
|
|
void main() {
|
|
late Client matrix;
|
|
late Room room;
|
|
late VoIP voip;
|
|
group('Call tests', () {
|
|
Logs().level = Level.info;
|
|
setUp(() async {
|
|
matrix = await getClient();
|
|
await matrix.abortSync();
|
|
|
|
voip = VoIP(matrix, MockWebRTCDelegate());
|
|
VoIP.customTxid = '1234';
|
|
final id = '!calls:example.com';
|
|
room = matrix.getRoomById(id)!;
|
|
});
|
|
|
|
test('Test call methods', () async {
|
|
final call = CallSession(
|
|
CallOptions(
|
|
callId: '1234',
|
|
type: CallType.kVoice,
|
|
dir: CallDirection.kOutgoing,
|
|
localPartyId: '4567',
|
|
voip: voip,
|
|
room: room,
|
|
iceServers: [],
|
|
),
|
|
);
|
|
await call.sendInviteToCall(
|
|
room,
|
|
'1234',
|
|
1234,
|
|
'4567',
|
|
'sdp',
|
|
txid: '1234',
|
|
);
|
|
await call.sendAnswerCall(room, '1234', 'sdp', '4567', txid: '1234');
|
|
await call.sendCallCandidates(room, '1234', '4567', [], txid: '1234');
|
|
await call.sendSelectCallAnswer(
|
|
room,
|
|
'1234',
|
|
'4567',
|
|
'6789',
|
|
txid: '1234',
|
|
);
|
|
await call.sendCallReject(room, '1234', '4567', txid: '1234');
|
|
await call.sendCallNegotiate(
|
|
room,
|
|
'1234',
|
|
1234,
|
|
'4567',
|
|
'sdp',
|
|
txid: '1234',
|
|
);
|
|
await call.sendHangupCall(
|
|
room,
|
|
'1234',
|
|
'4567',
|
|
'userHangup',
|
|
txid: '1234',
|
|
);
|
|
await call.sendAssertedIdentity(
|
|
room,
|
|
'1234',
|
|
'4567',
|
|
AssertedIdentity()
|
|
..displayName = 'name'
|
|
..id = 'some_id',
|
|
txid: '1234',
|
|
);
|
|
await call.sendCallReplaces(
|
|
room,
|
|
'1234',
|
|
'4567',
|
|
CallReplaces(),
|
|
txid: '1234',
|
|
);
|
|
await call.sendSDPStreamMetadataChanged(
|
|
room,
|
|
'1234',
|
|
'4567',
|
|
SDPStreamMetadata({}),
|
|
txid: '1234',
|
|
);
|
|
});
|
|
|
|
test('Call lifetime and age', () async {
|
|
expect(voip.currentCID, null);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': '1702472924955oq1uQbNAfU7wAaEA',
|
|
'party_id': 'DPCIPPBGPO',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'newevent',
|
|
originServerTs: DateTime.utc(1969),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await Future.delayed(Duration(seconds: 2));
|
|
// confirm that no call got created after 3 seconds, which is
|
|
// expected in this case because the originTs was old asf
|
|
expect(voip.currentCID, null);
|
|
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
unsigned: {'age': 60001},
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': 'unsignedTsInvalidCall',
|
|
'party_id': 'DPCIPPBGPO',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'newevent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await Future.delayed(Duration(seconds: 2));
|
|
// confirm that no call got created after 3 seconds, which is
|
|
// expected in this case because age was older than lifetime
|
|
expect(voip.currentCID, null);
|
|
});
|
|
test('Call connection and hanging up', () async {
|
|
expect(voip.currentCID, null);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': 'originTsValidCall',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerInviteEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.candidates',
|
|
content: {
|
|
'call_id': 'originTsValidCall',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'candidates': [
|
|
{
|
|
'candidate':
|
|
'candidate:01UDP2122252543uwu50184typhost',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
},
|
|
{
|
|
'candidate':
|
|
'candidate:31TCP2105524479uwu9typhosttcptypeactive',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
}
|
|
],
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerCallCandidatesEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
while (voip.currentCID !=
|
|
VoipId(roomId: room.id, callId: 'originTsValidCall')) {
|
|
// call invite looks valid, call should be created now :D
|
|
await Future.delayed(Duration(milliseconds: 50));
|
|
Logs().d('Waiting for currentCID to update');
|
|
}
|
|
expect(
|
|
voip.currentCID,
|
|
VoipId(roomId: room.id, callId: 'originTsValidCall'),
|
|
);
|
|
final call = voip.calls[voip.currentCID]!;
|
|
expect(call.state, CallState.kRinging);
|
|
await call.answer(txid: '1234');
|
|
|
|
call.pc!.onIceGatheringState!
|
|
.call(RTCIceGatheringState.RTCIceGatheringStateComplete);
|
|
// we send them manually anyway because our stub sends empty list of
|
|
// candidates
|
|
await call.sendCallCandidates(
|
|
room,
|
|
'originTsValidCall',
|
|
'GHTYAJCE',
|
|
[
|
|
{
|
|
'candidate': 'candidate:0 1 UDP 2122252543 uwu 50184 typ host',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
},
|
|
{
|
|
'candidate':
|
|
'candidate:3 1 TCP 2105524479 uwu 9 typ host tcptype active',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
}
|
|
],
|
|
txid: '1234',
|
|
);
|
|
|
|
expect(call.state, CallState.kConnecting);
|
|
|
|
// caller sends select answer
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.select_answer',
|
|
content: {
|
|
'call_id': 'originTsValidCall',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'lifetime': 10000,
|
|
'selected_party_id': 'GHTYAJCE',
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerSelectAnswerEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
call.pc!.onIceConnectionState!
|
|
.call(RTCIceConnectionState.RTCIceConnectionStateChecking);
|
|
call.pc!.onIceConnectionState!
|
|
.call(RTCIceConnectionState.RTCIceConnectionStateConnected);
|
|
// just to make sure there are no errors after running functions
|
|
// that are supposed to run once iceConnectionState is connected
|
|
await Future.delayed(Duration(seconds: 2));
|
|
|
|
expect(call.state, CallState.kConnected);
|
|
|
|
await call.hangup(reason: CallErrorCode.userHangup);
|
|
expect(call.state, CallState.kEnded);
|
|
expect(voip.currentCID, null);
|
|
});
|
|
|
|
test('Call answered elsewhere', () async {
|
|
expect(voip.currentCID, null);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': 'answer_elseWhere',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerInviteEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.candidates',
|
|
content: {
|
|
'call_id': 'answer_elseWhere',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'candidates': [
|
|
{
|
|
'candidate':
|
|
'candidate:01UDP2122252543uwu50184typhost',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
},
|
|
{
|
|
'candidate':
|
|
'candidate:31TCP2105524479uwu9typhosttcptypeactive',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
}
|
|
],
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerCallCandidatesEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
while (voip.currentCID !=
|
|
VoipId(roomId: room.id, callId: 'answer_elseWhere')) {
|
|
// call invite looks valid, call should be created now :D
|
|
await Future.delayed(Duration(milliseconds: 50));
|
|
Logs().d('Waiting for currentCID to update');
|
|
}
|
|
expect(
|
|
voip.currentCID,
|
|
VoipId(roomId: room.id, callId: 'answer_elseWhere'),
|
|
);
|
|
final call = voip.calls[voip.currentCID]!;
|
|
expect(call.state, CallState.kRinging);
|
|
|
|
// caller sends select answer
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.select_answer',
|
|
content: {
|
|
'call_id': 'answer_elseWhere',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'lifetime': 10000,
|
|
'selected_party_id':
|
|
'not_us', // selected some other device for answer
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerSelectAnswerEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
// wait for select answer to end the call
|
|
await Future.delayed(Duration(seconds: 2));
|
|
// call ended because answered elsewhere
|
|
expect(call.state, CallState.kEnded);
|
|
expect(voip.currentCID, null);
|
|
});
|
|
|
|
test('Reject incoming call', () async {
|
|
expect(voip.currentCID, null);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': 'reject_call',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerInviteEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.candidates',
|
|
content: {
|
|
'call_id': 'reject_call',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'candidates': [
|
|
{
|
|
'candidate':
|
|
'candidate:01UDP2122252543uwu50184typhost',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
},
|
|
{
|
|
'candidate':
|
|
'candidate:31TCP2105524479uwu9typhosttcptypeactive',
|
|
'sdpMid': '0',
|
|
'sdpMLineIndex': 0,
|
|
}
|
|
],
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerCallCandidatesEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
while (
|
|
voip.currentCID != VoipId(roomId: room.id, callId: 'reject_call')) {
|
|
// call invite looks valid, call should be created now :D
|
|
await Future.delayed(Duration(milliseconds: 50));
|
|
Logs().d('Waiting for currentCID to update');
|
|
}
|
|
expect(voip.currentCID, VoipId(roomId: room.id, callId: 'reject_call'));
|
|
final call = voip.calls[voip.currentCID]!;
|
|
expect(call.state, CallState.kRinging);
|
|
|
|
await call.reject();
|
|
|
|
// call ended because answered elsewhere
|
|
expect(call.state, CallState.kEnded);
|
|
expect(voip.currentCID, null);
|
|
});
|
|
|
|
test('Glare after invite was sent', () async {
|
|
expect(voip.currentCID, null);
|
|
final firstCall = await voip.inviteToCall(
|
|
room,
|
|
CallType.kVoice,
|
|
userId: '@alice:testing.com',
|
|
);
|
|
await firstCall.pc!.onRenegotiationNeeded!.call();
|
|
expect(firstCall.state, CallState.kInviteSent);
|
|
// KABOOM YOU JUST GLARED
|
|
await Future.delayed(Duration(seconds: 3));
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': 'zzzz_glare_2nd_call',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerInviteEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await Future.delayed(Duration(seconds: 3));
|
|
expect(
|
|
voip.currentCID,
|
|
VoipId(roomId: room.id, callId: firstCall.callId),
|
|
);
|
|
await firstCall.hangup(reason: CallErrorCode.userBusy);
|
|
});
|
|
test('Glare before invite was sent', () async {
|
|
expect(voip.currentCID, null);
|
|
final firstCall = await voip.inviteToCall(
|
|
room,
|
|
CallType.kVoice,
|
|
userId: '@alice:testing.com',
|
|
);
|
|
expect(firstCall.state, CallState.kCreateOffer);
|
|
// KABOOM YOU JUST GLARED, but this tiem you were still preparing your call
|
|
// so just cancel that instead
|
|
await Future.delayed(Duration(seconds: 3));
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': 'zzzz_glare_2nd_call',
|
|
'party_id': 'GHTYAJCE_caller',
|
|
'version': '1',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'callerInviteEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await Future.delayed(Duration(seconds: 3));
|
|
expect(
|
|
voip.currentCID,
|
|
VoipId(roomId: room.id, callId: 'zzzz_glare_2nd_call'),
|
|
);
|
|
});
|
|
|
|
test('getFamedlyCallEvents sort order', () {
|
|
room.setState(
|
|
Event(
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test1:example.com',
|
|
callId: '1111',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now()
|
|
.add(Duration(hours: 12))
|
|
.millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
type: EventTypes.GroupCallMember,
|
|
eventId: 'asdfasdf',
|
|
senderId: '@test1:example.com',
|
|
originServerTs: DateTime.now().add(Duration(hours: 12)),
|
|
room: room,
|
|
stateKey: '@test1:example.com',
|
|
),
|
|
);
|
|
room.setState(
|
|
Event(
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test2:example.com',
|
|
callId: '1111',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now().millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
type: EventTypes.GroupCallMember,
|
|
eventId: 'asdfasdf',
|
|
senderId: '@test2:example.com',
|
|
originServerTs: DateTime.now(),
|
|
room: room,
|
|
stateKey: '@test2:example.com',
|
|
),
|
|
);
|
|
room.setState(
|
|
Event(
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test2.0:example.com',
|
|
callId: '1111',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now().millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
type: EventTypes.GroupCallMember,
|
|
eventId: 'asdfasdf',
|
|
senderId: '@test2.0:example.com',
|
|
originServerTs: DateTime.now(),
|
|
room: room,
|
|
stateKey: '@test2.0:example.com',
|
|
),
|
|
);
|
|
room.setState(
|
|
Event(
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test3:example.com',
|
|
callId: '1111',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now()
|
|
.subtract(Duration(hours: 1))
|
|
.millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
type: EventTypes.GroupCallMember,
|
|
eventId: 'asdfasdf',
|
|
senderId: '@test3:example.com',
|
|
originServerTs: DateTime.now().subtract(Duration(hours: 1)),
|
|
room: room,
|
|
stateKey: '@test3:example.com',
|
|
),
|
|
);
|
|
expect(
|
|
room.getFamedlyCallEvents(voip).entries.elementAt(0).key,
|
|
'@test3:example.com',
|
|
);
|
|
expect(
|
|
room.getFamedlyCallEvents(voip).entries.elementAt(1).key,
|
|
'@test2:example.com',
|
|
);
|
|
expect(
|
|
room.getFamedlyCallEvents(voip).entries.elementAt(2).key,
|
|
'@test2.0:example.com',
|
|
);
|
|
expect(
|
|
room.getFamedlyCallEvents(voip).entries.elementAt(3).key,
|
|
'@test1:example.com',
|
|
);
|
|
expect(
|
|
room.getCallMembershipsFromRoom(voip).entries.elementAt(0).key,
|
|
'@test3:example.com',
|
|
);
|
|
expect(
|
|
room.getCallMembershipsFromRoom(voip).entries.elementAt(1).key,
|
|
'@test2:example.com',
|
|
);
|
|
expect(
|
|
room.getCallMembershipsFromRoom(voip).entries.elementAt(2).key,
|
|
'@test2.0:example.com',
|
|
);
|
|
expect(
|
|
room.getCallMembershipsFromRoom(voip).entries.elementAt(3).key,
|
|
'@test1:example.com',
|
|
);
|
|
});
|
|
|
|
test('Enabling group calls', () async {
|
|
// users default is 0 and so group calls not enabled
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test:example.com',
|
|
type: 'm.room.power_levels',
|
|
room: room,
|
|
eventId: '123a',
|
|
content: {
|
|
'events': {EventTypes.GroupCallMember: 100},
|
|
'state_default': 50,
|
|
'users_default': 0,
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '',
|
|
),
|
|
);
|
|
expect(room.canJoinGroupCall, false);
|
|
expect(room.groupCallsEnabledForEveryone, false);
|
|
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test:example.com',
|
|
type: 'm.room.power_levels',
|
|
room: room,
|
|
eventId: '123a',
|
|
content: {
|
|
'events': {EventTypes.GroupCallMember: 27},
|
|
'state_default': 50,
|
|
'users_default': 49,
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '',
|
|
),
|
|
);
|
|
expect(room.canJoinGroupCall, true);
|
|
expect(room.groupCallsEnabledForEveryone, true);
|
|
|
|
// state_default 50 and user_default 0, use enableGroupCall
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test:example.com',
|
|
type: 'm.room.power_levels',
|
|
room: room,
|
|
eventId: '123',
|
|
content: {
|
|
'state_default': 50,
|
|
'users': {'@test:fakeServer.notExisting': 100},
|
|
'users_default': 0,
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '',
|
|
),
|
|
);
|
|
expect(room.canJoinGroupCall, true); // because admin
|
|
expect(room.groupCallsEnabledForEveryone, false);
|
|
await room.enableGroupCalls();
|
|
expect(room.canJoinGroupCall, true);
|
|
expect(room.groupCallsEnabledForEveryone, true);
|
|
|
|
// state_default 50 and user_default unspecified, use enableGroupCall
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test:example.com',
|
|
type: 'm.room.power_levels',
|
|
room: room,
|
|
eventId: '123',
|
|
content: {
|
|
'state_default': 50,
|
|
'users': {'@test:fakeServer.notExisting': 100},
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '',
|
|
),
|
|
);
|
|
|
|
expect(room.canJoinGroupCall, true); // because admin
|
|
expect(room.groupCallsEnabledForEveryone, false);
|
|
await room.enableGroupCalls();
|
|
expect(room.canJoinGroupCall, true);
|
|
expect(room.groupCallsEnabledForEveryone, true);
|
|
|
|
// state_default is 0 so users should be able to send state events
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test:example.com',
|
|
type: 'm.room.power_levels',
|
|
room: room,
|
|
eventId: '123',
|
|
content: {
|
|
'state_default': 0,
|
|
'users': {'@test:fakeServer.notExisting': 100},
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '',
|
|
),
|
|
);
|
|
expect(room.canJoinGroupCall, true);
|
|
expect(room.groupCallsEnabledForEveryone, true);
|
|
});
|
|
|
|
test('group call participants count', () {
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test1:example.com',
|
|
type: EventTypes.GroupCallMember,
|
|
room: room,
|
|
eventId: '1234177',
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test1:example.com',
|
|
callId: 'participants_count',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now()
|
|
.subtract(Duration(hours: 1))
|
|
.millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '@test1:example.com',
|
|
),
|
|
);
|
|
|
|
expect(room.groupCallParticipantCount('participants_count', voip), 0);
|
|
expect(room.hasActiveGroupCall(voip), false);
|
|
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test2:example.com',
|
|
type: EventTypes.GroupCallMember,
|
|
room: room,
|
|
eventId: '1234177',
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test2:example.com',
|
|
callId: 'participants_count',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now()
|
|
.add(Duration(hours: 1))
|
|
.millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '@test2:example.com',
|
|
),
|
|
);
|
|
expect(room.groupCallParticipantCount('participants_count', voip), 1);
|
|
expect(room.hasActiveGroupCall(voip), true);
|
|
|
|
room.setState(
|
|
Event(
|
|
senderId: '@test3:example.com',
|
|
type: EventTypes.GroupCallMember,
|
|
room: room,
|
|
eventId: '1231234124123',
|
|
content: {
|
|
'memberships': [
|
|
CallMembership(
|
|
userId: '@test3:example.com',
|
|
callId: 'participants_count',
|
|
backend: MeshBackend(),
|
|
deviceId: '1111',
|
|
expiresTs: DateTime.now().millisecondsSinceEpoch,
|
|
roomId: room.id,
|
|
membershipId: voip.currentSessionId,
|
|
voip: voip,
|
|
).toJson(),
|
|
],
|
|
},
|
|
originServerTs: DateTime.now(),
|
|
stateKey: '@test3:example.com',
|
|
),
|
|
);
|
|
|
|
expect(room.groupCallParticipantCount('participants_count', voip), 2);
|
|
expect(room.hasActiveGroupCall(voip), true);
|
|
});
|
|
|
|
test('call persists after sending invite', () async {
|
|
CallSession? incomingCall;
|
|
|
|
// incoming call should not be created yet
|
|
incomingCall = voip.calls[voip.currentCID];
|
|
expect(incomingCall, isNull);
|
|
expect(incomingCall?.pc, isNull);
|
|
|
|
// send invite for the call
|
|
final outgoingCall = await voip.inviteToCall(
|
|
room,
|
|
CallType.kVoice,
|
|
userId: '@alice:testing.com',
|
|
);
|
|
|
|
// acknowledge the invite
|
|
await matrix.handleSync(
|
|
SyncUpdate(
|
|
nextBatch: 'something',
|
|
rooms: RoomsUpdate(
|
|
join: {
|
|
room.id: JoinedRoomUpdate(
|
|
timeline: TimelineUpdate(
|
|
events: [
|
|
MatrixEvent(
|
|
type: 'm.call.invite',
|
|
content: {
|
|
'lifetime': 60000,
|
|
'call_id': outgoingCall.callId,
|
|
'party_id': outgoingCall.localPartyId,
|
|
'version': '1',
|
|
'offer': {'type': 'offer', 'sdp': 'sdp'},
|
|
},
|
|
senderId: '@alice:testing.com',
|
|
eventId: 'outgoingCallInviteEvent',
|
|
originServerTs: DateTime.now(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
// incoming call pc should be created
|
|
// if this fails, the call was not properly created
|
|
incomingCall = voip.calls[voip.currentCID];
|
|
expect(incomingCall, isNotNull);
|
|
expect(incomingCall?.pc, isNotNull);
|
|
});
|
|
});
|
|
}
|