import 'dart:async'; import 'dart:convert'; import 'package:built_value/json_object.dart'; import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/spreed.dart' as spreed; import 'package:test/test.dart'; import 'helper.dart'; void main() { group( 'spreed', () { late DockerImage image; setUpAll(() async => image = await getDockerImage()); late DockerContainer container; late TestNextcloudClient client1; setUp(() async { container = await getDockerContainer( image, useApache: true, ); client1 = await getTestClient(container); }); tearDown(() => container.destroy()); Future createTestRoom() async => (await client1.spreed.room.createRoom( roomType: spreed.RoomType.public.value, roomName: 'Test', )) .body .ocs .data; group('Helpers', () { test('Is supported', () async { final response = await client1.core.ocs.getCapabilities(); expect(response.statusCode, 200); expect(() => response.headers, isA()); final result = client1.spreed.isSupported(response.body.ocs.data); expect(result.isSupported, isTrue); }); test('Participant permissions', () async { expect(spreed.ParticipantPermission.$default.value, 0); expect(spreed.ParticipantPermission.fromValue(0), {spreed.ParticipantPermission.$default}); expect({spreed.ParticipantPermission.$default}.value, 0); expect(spreed.ParticipantPermission.custom.value, 1); expect(spreed.ParticipantPermission.canSendMessageAndShareAndReact.value, 128); expect(spreed.ParticipantPermission.fromValue(129), { spreed.ParticipantPermission.custom, spreed.ParticipantPermission.canSendMessageAndShareAndReact, }); expect( { spreed.ParticipantPermission.custom, spreed.ParticipantPermission.canSendMessageAndShareAndReact, }.value, 129, ); }); }); group('Room', () { test('Get rooms', () async { final response = await client1.spreed.room.getRooms(); expect(response.body.ocs.data, hasLength(1)); expect(response.body.ocs.data[0].id, 1); expect(response.body.ocs.data[0].token, isNotEmpty); expect(response.body.ocs.data[0].type, spreed.RoomType.changelog.value); expect(response.body.ocs.data[0].name, 'user1'); expect(response.body.ocs.data[0].displayName, 'Talk updates ✅'); expect(response.body.ocs.data[0].participantType, spreed.ParticipantType.user.value); expect(spreed.ParticipantPermission.fromValue(response.body.ocs.data[0].permissions), { spreed.ParticipantPermission.startCall, spreed.ParticipantPermission.joinCall, spreed.ParticipantPermission.canPublishAudio, spreed.ParticipantPermission.canPublishVideo, spreed.ParticipantPermission.canScreenShare, spreed.ParticipantPermission.canSendMessageAndShareAndReact, }); }); test('Session', () async { var room = await createTestRoom(); expect(room.sessionId, '0'); final response = await client1.spreed.room.joinRoom(token: room.token); expect(response.body.ocs.data.id, room.id); expect(response.body.ocs.data.sessionId, isNot(room.sessionId)); room = (await client1.spreed.room.getSingleRoom(token: room.token)).body.ocs.data; expect(room.sessionId, response.body.ocs.data.sessionId); await client1.spreed.room.leaveRoom(token: room.token); room = (await client1.spreed.room.getSingleRoom(token: room.token)).body.ocs.data; expect(room.sessionId, '0'); }); group('Create room', () { test('One-to-One', () async { final response = await client1.spreed.room.createRoom( roomType: spreed.RoomType.oneToOne.value, invite: 'user2', ); expect(response.body.ocs.data.id, 1); expect(response.body.ocs.data.token, isNotEmpty); expect(response.body.ocs.data.type, spreed.RoomType.oneToOne.value); expect(response.body.ocs.data.name, 'user2'); expect(response.body.ocs.data.displayName, 'User Two'); expect(response.body.ocs.data.participantType, spreed.ParticipantType.owner.value); expect(spreed.ParticipantPermission.fromValue(response.body.ocs.data.permissions), { spreed.ParticipantPermission.startCall, spreed.ParticipantPermission.joinCall, spreed.ParticipantPermission.canIgnoreLobby, spreed.ParticipantPermission.canPublishAudio, spreed.ParticipantPermission.canPublishVideo, spreed.ParticipantPermission.canScreenShare, spreed.ParticipantPermission.canSendMessageAndShareAndReact, }); }); test('Group', () async { final response = await client1.spreed.room.createRoom( roomType: spreed.RoomType.group.value, invite: 'admin', ); expect(response.body.ocs.data.id, 1); expect(response.body.ocs.data.token, isNotEmpty); expect(response.body.ocs.data.type, spreed.RoomType.group.value); expect(response.body.ocs.data.name, 'admin'); expect(response.body.ocs.data.displayName, 'admin'); expect(response.body.ocs.data.participantType, spreed.ParticipantType.owner.value); expect(spreed.ParticipantPermission.fromValue(response.body.ocs.data.permissions), { spreed.ParticipantPermission.startCall, spreed.ParticipantPermission.joinCall, spreed.ParticipantPermission.canIgnoreLobby, spreed.ParticipantPermission.canPublishAudio, spreed.ParticipantPermission.canPublishVideo, spreed.ParticipantPermission.canScreenShare, spreed.ParticipantPermission.canSendMessageAndShareAndReact, }); }); test('Public', () async { final response = await client1.spreed.room.createRoom( roomType: spreed.RoomType.public.value, roomName: 'abc', ); expect(response.body.ocs.data.id, 1); expect(response.body.ocs.data.token, isNotEmpty); expect(response.body.ocs.data.type, spreed.RoomType.public.value); expect(response.body.ocs.data.name, 'abc'); expect(response.body.ocs.data.displayName, 'abc'); expect(response.body.ocs.data.participantType, spreed.ParticipantType.owner.value); expect(spreed.ParticipantPermission.fromValue(response.body.ocs.data.permissions), { spreed.ParticipantPermission.startCall, spreed.ParticipantPermission.joinCall, spreed.ParticipantPermission.canIgnoreLobby, spreed.ParticipantPermission.canPublishAudio, spreed.ParticipantPermission.canPublishVideo, spreed.ParticipantPermission.canScreenShare, spreed.ParticipantPermission.canSendMessageAndShareAndReact, }); }); }); }); group('Chat', () { test('Send message', () async { final startTime = DateTime.now(); final room = (await client1.spreed.room.createRoom( roomType: spreed.RoomType.oneToOne.value, invite: 'user2', )) .body .ocs .data; final response = await client1.spreed.chat.sendMessage( token: room.token, message: 'bla', ); expect(response.body.ocs.data!.id, 2); expect(response.body.ocs.data!.actorType, spreed.ActorType.users.name); expect(response.body.ocs.data!.actorId, 'user1'); expect(response.body.ocs.data!.actorDisplayName, 'User One'); expectDateInReasonableTimeRange( DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data!.timestamp * 1000), startTime, ); expect(response.body.ocs.data!.message, 'bla'); expect(response.body.ocs.data!.messageType, spreed.MessageType.comment.name); }); group('Get messages', () { test('Directly', () async { final startTime = DateTime.now(); final room = (await client1.spreed.room.createRoom( roomType: spreed.RoomType.oneToOne.value, invite: 'user2', )) .body .ocs .data; await client1.spreed.chat.sendMessage( token: room.token, message: '123', replyTo: (await client1.spreed.chat.sendMessage( token: room.token, message: 'bla', )) .body .ocs .data! .id, ); final response = await client1.spreed.chat.receiveMessages( token: room.token, lookIntoFuture: 0, ); expect(response.headers.xChatLastGiven, '1'); expect(response.headers.xChatLastCommonRead, '1'); expect(response.body.ocs.data, hasLength(3)); expect(response.body.ocs.data[0].id, 3); expect(response.body.ocs.data[0].actorType, spreed.ActorType.users.name); expect(response.body.ocs.data[0].actorId, 'user1'); expect(response.body.ocs.data[0].actorDisplayName, 'User One'); expectDateInReasonableTimeRange( DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[0].timestamp * 1000), startTime, ); expect(response.body.ocs.data[0].message, '123'); expect(response.body.ocs.data[0].messageType, spreed.MessageType.comment.name); expect(response.body.ocs.data[0].parent!.id, 2); expect(response.body.ocs.data[0].parent!.actorType, spreed.ActorType.users.name); expect(response.body.ocs.data[0].parent!.actorId, 'user1'); expect(response.body.ocs.data[0].parent!.actorDisplayName, 'User One'); expectDateInReasonableTimeRange( DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[0].parent!.timestamp * 1000), startTime, ); expect(response.body.ocs.data[0].parent!.message, 'bla'); expect(response.body.ocs.data[0].parent!.messageType, spreed.MessageType.comment.name); expect(response.body.ocs.data[1].id, 2); expect(response.body.ocs.data[1].actorType, spreed.ActorType.users.name); expect(response.body.ocs.data[1].actorId, 'user1'); expect(response.body.ocs.data[1].actorDisplayName, 'User One'); expectDateInReasonableTimeRange( DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[1].timestamp * 1000), startTime, ); expect(response.body.ocs.data[1].message, 'bla'); expect(response.body.ocs.data[1].messageType, spreed.MessageType.comment.name); expect(response.body.ocs.data[2].id, 1); expect(response.body.ocs.data[2].actorType, spreed.ActorType.users.name); expect(response.body.ocs.data[2].actorId, 'user1'); expect(response.body.ocs.data[2].actorDisplayName, 'User One'); expectDateInReasonableTimeRange( DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[2].timestamp * 1000), startTime, ); expect(response.body.ocs.data[2].message, 'You created the conversation'); expect(response.body.ocs.data[2].systemMessage, 'conversation_created'); expect(response.body.ocs.data[2].messageType, spreed.MessageType.system.name); }); test('Polling', () async { final startTime = DateTime.now(); final room = await createTestRoom(); final message = (await client1.spreed.chat.sendMessage( token: room.token, message: 'bla', )) .body .ocs .data!; unawaited( Future.delayed(const Duration(seconds: 1)).then((final _) async { await client1.spreed.chat.sendMessage( token: room.token, message: '123', ); }), ); final response = await client1.spreed.chat.receiveMessages( token: room.token, lookIntoFuture: 1, timeout: 3, lastKnownMessageId: message.id, ); expect(response.body.ocs.data, hasLength(1)); expect(response.body.ocs.data[0].id, 3); expect(response.body.ocs.data[0].actorType, spreed.ActorType.users.name); expect(response.body.ocs.data[0].actorId, 'user1'); expect(response.body.ocs.data[0].actorDisplayName, 'User One'); expectDateInReasonableTimeRange( DateTime.fromMillisecondsSinceEpoch(response.body.ocs.data[0].timestamp * 1000), startTime, ); expect(response.body.ocs.data[0].message, '123'); expect(response.body.ocs.data[0].messageType, spreed.MessageType.comment.name); }); }); }); group('Call', () { test('Start and end call', () async { var room = await createTestRoom(); expect(room.hasCall, isFalse); room = (await client1.spreed.room.joinRoom(token: room.token)).body.ocs.data; await client1.spreed.call.joinCall(token: room.token); room = (await client1.spreed.room.getSingleRoom(token: room.token)).body.ocs.data; expect(room.hasCall, isTrue); await client1.spreed.call.leaveCall(token: room.token); room = (await client1.spreed.room.getSingleRoom(token: room.token)).body.ocs.data; expect(room.hasCall, isFalse); }); }); group('Signaling', () { test('Get settings', () async { final room = await createTestRoom(); final response = await client1.spreed.signaling.getSettings(token: room.token); expect(response.body.ocs.data.signalingMode, 'internal'); expect(response.body.ocs.data.userId, 'user1'); expect(response.body.ocs.data.hideWarning, false); expect(response.body.ocs.data.server, ''); expect(response.body.ocs.data.ticket, contains(':user1:')); expect(response.body.ocs.data.helloAuthParams.$10.userid, 'user1'); expect(response.body.ocs.data.helloAuthParams.$10.ticket, contains(':user1:')); expect(response.body.ocs.data.helloAuthParams.$20.token.split('').where((final x) => x == '.'), hasLength(2)); expect(response.body.ocs.data.stunservers, hasLength(1)); expect(response.body.ocs.data.stunservers[0].urls, hasLength(1)); expect(response.body.ocs.data.stunservers[0].urls[0], 'stun:stun.nextcloud.com:443'); expect(response.body.ocs.data.turnservers, hasLength(1)); expect(response.body.ocs.data.turnservers[0].urls, hasLength(4)); expect( response.body.ocs.data.turnservers[0].urls[0], 'turn:staticauth.openrelay.metered.ca:443?transport=udp', ); expect( response.body.ocs.data.turnservers[0].urls[1], 'turn:staticauth.openrelay.metered.ca:443?transport=tcp', ); expect( response.body.ocs.data.turnservers[0].urls[2], 'turns:staticauth.openrelay.metered.ca:443?transport=udp', ); expect( response.body.ocs.data.turnservers[0].urls[3], 'turns:staticauth.openrelay.metered.ca:443?transport=tcp', ); expect(response.body.ocs.data.turnservers[0].username, isNotEmpty); expect((response.body.ocs.data.turnservers[0].credential as StringJsonObject).asString, isNotEmpty); expect(response.body.ocs.data.sipDialinInfo, ''); }); test('Send and receive messages', () async { final room = (await client1.spreed.room.createRoom( roomType: spreed.RoomType.oneToOne.value, invite: 'user2', )) .body .ocs .data; final room1 = (await client1.spreed.room.joinRoom(token: room.token)).body.ocs.data; await client1.spreed.call.joinCall(token: room.token); final client2 = await getTestClient( container, username: 'user2', ); final room2 = (await client2.spreed.room.joinRoom(token: room.token)).body.ocs.data; await client2.spreed.call.joinCall(token: room.token); await client1.spreed.signaling.sendMessages( token: room.token, messages: json.encode([ { 'ev': 'message', 'sessionId': room1.sessionId, 'fn': json.encode({ 'to': room2.sessionId, }), }, ]), ); await Future.delayed(const Duration(seconds: 1)); final messages = (await client2.spreed.signaling.pullMessages(token: room.token)).body.ocs.data; expect(messages, hasLength(2)); expect(messages[0].type, 'message'); expect(json.decode(messages[0].data.string!), {'to': room2.sessionId, 'from': room1.sessionId}); expect(messages[1].type, 'usersInRoom'); expect(messages[1].data.builtListSignalingSession, hasLength(2)); expect(messages[1].data.builtListSignalingSession![0].userId, 'user1'); expect(messages[1].data.builtListSignalingSession![1].userId, 'user2'); }); }); }, retry: retryCount, timeout: timeout, ); }