diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index a8a985dba2f..637242c998d 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -54,13 +54,16 @@ use OCA\Talk\Events\AttendeeRemovedEvent; use OCA\Talk\Events\AttendeesAddedEvent; use OCA\Talk\Events\AttendeesRemovedEvent; +use OCA\Talk\Events\BeforeAttendeeRemovedEvent; use OCA\Talk\Events\BeforeAttendeesAddedEvent; use OCA\Talk\Events\BeforeCallEndedForEveryoneEvent; use OCA\Talk\Events\BeforeChatMessageSentEvent; use OCA\Talk\Events\BeforeDuplicateShareSentEvent; use OCA\Talk\Events\BeforeGuestJoinedRoomEvent; use OCA\Talk\Events\BeforeParticipantModifiedEvent; +use OCA\Talk\Events\BeforeRoomDeletedEvent; use OCA\Talk\Events\BeforeRoomsFetchEvent; +use OCA\Talk\Events\BeforeSessionLeftRoomEvent; use OCA\Talk\Events\BeforeUserJoinedRoomEvent; use OCA\Talk\Events\BotInstallEvent; use OCA\Talk\Events\BotUninstallEvent; @@ -68,6 +71,7 @@ use OCA\Talk\Events\CallNotificationSendEvent; use OCA\Talk\Events\ChatMessageSentEvent; use OCA\Talk\Events\EmailInvitationSentEvent; +use OCA\Talk\Events\GuestJoinedRoomEvent; use OCA\Talk\Events\GuestsCleanedUpEvent; use OCA\Talk\Events\LobbyModifiedEvent; use OCA\Talk\Events\MessageParseEvent; @@ -77,6 +81,7 @@ use OCA\Talk\Events\RoomModifiedEvent; use OCA\Talk\Events\SessionLeftRoomEvent; use OCA\Talk\Events\SystemMessageSentEvent; +use OCA\Talk\Events\SystemMessagesMultipleSentEvent; use OCA\Talk\Events\UserJoinedRoomEvent; use OCA\Talk\Federation\CloudFederationProviderTalk; use OCA\Talk\Federation\Listener as FederationListener; @@ -271,9 +276,31 @@ public function register(IRegistrationContext $context): void { // Federation listeners $context->registerEventListener(RoomModifiedEvent::class, FederationListener::class); - // Signaling listeners + // Signaling listeners (External) + $context->registerEventListener(AttendeesAddedEvent::class, SignalingListener::class); + $context->registerEventListener(AttendeeRemovedEvent::class, SignalingListener::class); + $context->registerEventListener(AttendeesRemovedEvent::class, SignalingListener::class); + $context->registerEventListener(SessionLeftRoomEvent::class, SignalingListener::class); + + $context->registerEventListener(CallEndedForEveryoneEvent::class, SignalingListener::class); + $context->registerEventListener(GuestsCleanedUpEvent::class, SignalingListener::class); + $context->registerEventListener(LobbyModifiedEvent::class, SignalingListener::class); + + $context->registerEventListener(ChatMessageSentEvent::class, SignalingListener::class); + $context->registerEventListener(SystemMessageSentEvent::class, SignalingListener::class); + $context->registerEventListener(SystemMessagesMultipleSentEvent::class, SignalingListener::class); + + // Signaling listeners (Both) + $context->registerEventListener(BeforeRoomDeletedEvent::class, SignalingListener::class); + $context->registerEventListener(ParticipantModifiedEvent::class, SignalingListener::class); $context->registerEventListener(RoomModifiedEvent::class, SignalingListener::class); + // Signaling listeners (Internal) + $context->registerEventListener(BeforeSessionLeftRoomEvent::class, SignalingListener::class); + $context->registerEventListener(BeforeAttendeeRemovedEvent::class, SignalingListener::class); + $context->registerEventListener(GuestJoinedRoomEvent::class, SignalingListener::class); + $context->registerEventListener(UserJoinedRoomEvent::class, SignalingListener::class); + // Video verification $context->registerEventListener(BeforeUserJoinedRoomEvent::class, PublicShareAuthListener::class); $context->registerEventListener(BeforeGuestJoinedRoomEvent::class, PublicShareAuthListener::class); @@ -299,17 +326,10 @@ public function register(IRegistrationContext $context): void { } public function boot(IBootContext $context): void { - $server = $context->getServerContainer(); - $context->injectFn([$this, 'registerCollaborationResourceProvider']); $context->injectFn([$this, 'registerClientLinks']); $context->injectFn([$this, 'registerNavigationLink']); $context->injectFn([$this, 'registerCloudFederationProviderManager']); - - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $server->get(IEventDispatcher::class); - - SignalingListener::register($dispatcher); } public function registerCollaborationResourceProvider(IProviderManager $resourceManager, IEventDispatcher $dispatcher): void { diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index ead074f4941..b0315fe6e04 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -860,6 +860,7 @@ public function setBreakoutRoomStatus(Room $room, int $status): bool { $this->dispatcher->dispatch(Room::EVENT_AFTER_SET_BREAKOUT_ROOM_STATUS, $event); $oldStatus = $room->getBreakoutRoomStatus(); $event = new RoomModifiedEvent($room, ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_STATUS, $status, $oldStatus); + $this->dispatcher->dispatchTyped($event); return true; } diff --git a/lib/Signaling/BackendNotifier.php b/lib/Signaling/BackendNotifier.php index bab6c8b3981..fc19ecc177b 100644 --- a/lib/Signaling/BackendNotifier.php +++ b/lib/Signaling/BackendNotifier.php @@ -155,14 +155,14 @@ private function backendRequest(Room $room, array $data): ?IResponse { * The given users are now invited to a room. * * @param Room $room - * @param array[] $users + * @param Attendee[] $attendees * @throws \Exception */ - public function roomInvited(Room $room, array $users): void { + public function roomInvited(Room $room, array $attendees): void { $userIds = []; - foreach ($users as $user) { - if ($user['actorType'] === Attendee::ACTOR_USERS) { - $userIds[] = $user['actorId']; + foreach ($attendees as $attendee) { + if ($attendee->getActorType() === Attendee::ACTOR_USERS) { + $userIds[] = $attendee->getActorId(); } } $start = microtime(true); @@ -179,7 +179,7 @@ public function roomInvited(Room $room, array $users): void { $duration = microtime(true) - $start; $this->logger->debug('Now invited to {token}: {users} ({duration})', [ 'token' => $room->getToken(), - 'users' => print_r($users, true), + 'users' => print_r($userIds, true), 'duration' => sprintf('%.2f', $duration), 'app' => 'spreed-hpb', ]); @@ -189,10 +189,16 @@ public function roomInvited(Room $room, array $users): void { * The given users are no longer invited to a room. * * @param Room $room - * @param string[] $userIds + * @param Attendee[] $attendees * @throws \Exception */ - public function roomsDisinvited(Room $room, array $userIds): void { + public function roomsDisinvited(Room $room, array $attendees): void { + $userIds = []; + foreach ($attendees as $attendee) { + if ($attendee->getActorType() === Attendee::ACTOR_USERS) { + $userIds[] = $attendee->getActorId(); + } + } $start = microtime(true); $this->backendRequest($room, [ 'type' => 'disinvite', diff --git a/lib/Signaling/Listener.php b/lib/Signaling/Listener.php index d2b431c08fe..2cd405b6c31 100644 --- a/lib/Signaling/Listener.php +++ b/lib/Signaling/Listener.php @@ -23,21 +23,30 @@ namespace OCA\Talk\Signaling; -use OCA\Talk\Chat\ChatManager; use OCA\Talk\Config; -use OCA\Talk\Events\AddParticipantsEvent; -use OCA\Talk\Events\ChatEvent; -use OCA\Talk\Events\DuplicatedParticipantEvent; -use OCA\Talk\Events\EndCallForEveryoneEvent; -use OCA\Talk\Events\ModifyEveryoneEvent; -use OCA\Talk\Events\ModifyParticipantEvent; -use OCA\Talk\Events\ModifyRoomEvent; +use OCA\Talk\Events\AMessageSentEvent; +use OCA\Talk\Events\AParticipantModifiedEvent; +use OCA\Talk\Events\ARoomModifiedEvent; +use OCA\Talk\Events\ASystemMessageSentEvent; +use OCA\Talk\Events\AttendeeRemovedEvent; +use OCA\Talk\Events\AttendeesAddedEvent; +use OCA\Talk\Events\AttendeesRemovedEvent; +use OCA\Talk\Events\BeforeAttendeeRemovedEvent; +use OCA\Talk\Events\BeforeRoomDeletedEvent; +use OCA\Talk\Events\BeforeSessionLeftRoomEvent; +use OCA\Talk\Events\CallEndedForEveryoneEvent; +use OCA\Talk\Events\ChatMessageSentEvent; +use OCA\Talk\Events\GuestJoinedRoomEvent; +use OCA\Talk\Events\GuestsCleanedUpEvent; +use OCA\Talk\Events\LobbyModifiedEvent; use OCA\Talk\Events\ParticipantEvent; -use OCA\Talk\Events\RemoveParticipantEvent; -use OCA\Talk\Events\RemoveUserEvent; +use OCA\Talk\Events\ParticipantModifiedEvent; use OCA\Talk\Events\RoomEvent; use OCA\Talk\Events\RoomModifiedEvent; -use OCA\Talk\GuestManager; +use OCA\Talk\Events\SessionLeftRoomEvent; +use OCA\Talk\Events\SystemMessageSentEvent; +use OCA\Talk\Events\SystemMessagesMultipleSentEvent; +use OCA\Talk\Events\UserJoinedRoomEvent; use OCA\Talk\Manager; use OCA\Talk\Model\BreakoutRoom; use OCA\Talk\Participant; @@ -45,7 +54,6 @@ use OCA\Talk\Service\ParticipantService; use OCA\Talk\Service\SessionService; use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; use OCP\Server; @@ -53,142 +61,216 @@ * @template-implements IEventListener */ class Listener implements IEventListener { + public function __construct( + protected Config $talkConfig, + protected Messages $internalSignaling, + protected BackendNotifier $externalSignaling, + protected Manager $manager, + protected ParticipantService $participantService, + protected SessionService $sessionService, + ) { + } public function handle(Event $event): void { - if ($event instanceof RoomModifiedEvent) { - self::notifyAfterRoomSettingsChanged($event); + if ($this->talkConfig->getSignalingMode() === Config::SIGNALING_INTERNAL) { + $this->handleInternalSignaling($event); + } else { + $this->handleExternalSignaling($event); } } - public static function register(IEventDispatcher $dispatcher): void { - self::registerInternalSignaling($dispatcher); - self::registerExternalSignaling($dispatcher); + protected function handleInternalSignaling(Event $event): void { + match (get_class($event)) { + BeforeSessionLeftRoomEvent::class, + BeforeAttendeeRemovedEvent::class, + GuestJoinedRoomEvent::class, + BeforeRoomDeletedEvent::class, + UserJoinedRoomEvent::class => $this->refreshParticipantList($event->getRoom()), + ParticipantModifiedEvent::class => $this->refreshParticipantListParticipantModified($event), // in_call, name, permissions + RoomModifiedEvent::class => $this->refreshParticipantListRoomModified($event), // *_permissions + default => null, // Ignoring events subscribed by the external signaling + }; } - protected static function isUsingInternalSignaling(): bool { - $config = Server::get(Config::class); - return $config->getSignalingMode() === Config::SIGNALING_INTERNAL; + protected function refreshParticipantList(Room $room): void { + $this->internalSignaling->addMessageForAllParticipants($room, 'refresh-participant-list'); } - protected static function registerInternalSignaling(IEventDispatcher $dispatcher): void { - $dispatcher->addListener(Room::EVENT_AFTER_ROOM_CONNECT, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(Room::EVENT_AFTER_GUEST_CONNECT, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(Room::EVENT_AFTER_SESSION_JOIN_CALL, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(Room::EVENT_AFTER_SESSION_UPDATE_CALL_FLAGS, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(Room::EVENT_AFTER_SESSION_LEAVE_CALL, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(Room::EVENT_AFTER_PERMISSIONS_SET, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(GuestManager::EVENT_AFTER_NAME_UPDATE, [self::class, 'refreshParticipantListUsingRoomEvent']); - $dispatcher->addListener(Room::EVENT_BEFORE_ROOM_DELETE, [self::class, 'refreshParticipantListUsingRoomEvent']); - - $dispatcher->addListener(Room::EVENT_BEFORE_USER_REMOVE, [self::class, 'refreshParticipantListUsingParticipantEvent']); - $dispatcher->addListener(Room::EVENT_BEFORE_PARTICIPANT_REMOVE, [self::class, 'refreshParticipantListUsingParticipantEvent']); - $dispatcher->addListener(Room::EVENT_BEFORE_ROOM_DISCONNECT, [self::class, 'refreshParticipantListUsingParticipantEvent']); - $dispatcher->addListener(Room::EVENT_AFTER_PARTICIPANT_PERMISSIONS_SET, [self::class, 'refreshParticipantListUsingParticipantEvent']); - } + protected function refreshParticipantListParticipantModified(ParticipantModifiedEvent $event): void { + if (!in_array($event->getProperty(), [ + AParticipantModifiedEvent::PROPERTY_IN_CALL, + AParticipantModifiedEvent::PROPERTY_NAME, + AParticipantModifiedEvent::PROPERTY_PERMISSIONS, + ], true)) { + return; + } - protected static function registerExternalSignaling(IEventDispatcher $dispatcher): void { - $dispatcher->addListener(Room::EVENT_AFTER_USERS_ADD, [self::class, 'notifyAfterUsersAdd']); - $dispatcher->addListener(Room::EVENT_AFTER_NAME_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_DESCRIPTION_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_PASSWORD_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_TYPE_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_READONLY_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_LISTABLE_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_LOBBY_STATE_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_SIP_ENABLED_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_SET_CALL_RECORDING, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_SET_BREAKOUT_ROOM_MODE, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_SET_BREAKOUT_ROOM_STATUS, [self::class, 'notifyAfterRoomSettingsChanged']); - // TODO remove handler with "roomModified" in favour of handler with - // "participantsModified" once the clients no longer expect a - // "roomModified" message for participant type changes. - $dispatcher->addListener(Room::EVENT_AFTER_PARTICIPANT_TYPE_SET, [self::class, 'notifyAfterRoomSettingsChanged']); - $dispatcher->addListener(Room::EVENT_AFTER_PARTICIPANT_TYPE_SET, [self::class, 'notifyAfterParticipantTypeAndPermissionsSet']); - $dispatcher->addListener(Room::EVENT_AFTER_PARTICIPANT_PERMISSIONS_SET, [self::class, 'notifyAfterParticipantTypeAndPermissionsSet']); - $dispatcher->addListener(Room::EVENT_AFTER_PERMISSIONS_SET, [self::class, 'notifyAfterPermissionSet']); - $dispatcher->addListener(Room::EVENT_BEFORE_ROOM_DELETE, [self::class, 'notifyBeforeRoomDeleted']); - $dispatcher->addListener(Room::EVENT_AFTER_USER_REMOVE, [self::class, 'notifyAfterUserRemoved']); - $dispatcher->addListener(Room::EVENT_AFTER_PARTICIPANT_REMOVE, [self::class, 'notifyAfterParticipantRemoved']); - $dispatcher->addListener(Room::EVENT_AFTER_ROOM_DISCONNECT, [self::class, 'notifyAfterRoomDisconected']); - $dispatcher->addListener(Room::EVENT_AFTER_SESSION_JOIN_CALL, [self::class, 'notifyAfterJoinUpdateAndLeave']); - $dispatcher->addListener(Room::EVENT_AFTER_SESSION_UPDATE_CALL_FLAGS, [self::class, 'notifyAfterJoinUpdateAndLeave']); - $dispatcher->addListener(Room::EVENT_AFTER_SESSION_LEAVE_CALL, [self::class, 'notifyAfterJoinUpdateAndLeave']); - $dispatcher->addListener(Room::EVENT_AFTER_END_CALL_FOR_EVERYONE, [self::class, 'sendEndCallForEveryone']); - $dispatcher->addListener(Room::EVENT_AFTER_GUESTS_CLEAN, [self::class, 'notifyParticipantsAfterGuestClean']); - $dispatcher->addListener(Room::EVENT_AFTER_SET_CALL_RECORDING, [self::class, 'sendSignalingMessageWhenToggleRecording']); - $dispatcher->addListener(Room::EVENT_AFTER_SET_BREAKOUT_ROOM_STATUS, [self::class, 'notifyParticipantsAfterSetBreakoutRoomStatus']); - $dispatcher->addListener(GuestManager::EVENT_AFTER_NAME_UPDATE, [self::class, 'notifyParticipantsAfterNameUpdated']); - $dispatcher->addListener(ChatManager::EVENT_AFTER_MESSAGE_SEND, [self::class, 'notifyUsersViaExternalSignalingToRefreshTheChat']); - $dispatcher->addListener(ChatManager::EVENT_AFTER_SYSTEM_MESSAGE_SEND, [self::class, 'notifyUsersViaExternalSignalingToRefreshTheChat']); - $dispatcher->addListener(ChatManager::EVENT_AFTER_MULTIPLE_SYSTEM_MESSAGE_SEND, [self::class, 'notifyUsersViaExternalSignalingToRefreshTheChat']); + $this->refreshParticipantList($event->getRoom()); } - public static function refreshParticipantListUsingRoomEvent(RoomEvent $event): void { - if (!self::isUsingInternalSignaling()) { + protected function refreshParticipantListRoomModified(RoomModifiedEvent $event): void { + if (!in_array($event->getProperty(), [ + ARoomModifiedEvent::PROPERTY_CALL_PERMISSIONS, + ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS, + ], true)) { return; } + $this->refreshParticipantList($event->getRoom()); + } + + protected function handleExternalSignaling(Event $event): void { + match (get_class($event)) { + RoomModifiedEvent::class, + LobbyModifiedEvent::class => $this->notifyRoomModified($event), + BeforeRoomDeletedEvent::class => $this->notifyBeforeRoomDeleted($event), + CallEndedForEveryoneEvent::class => $this->notifyCallEndedForEveryone($event), + GuestsCleanedUpEvent::class => $this->notifyGuestsCleanedUp($event), + AttendeesAddedEvent::class => $this->notifyAttendeesAdded($event), + AttendeeRemovedEvent::class => $this->notifyAttendeeRemoved($event), + AttendeesRemovedEvent::class => $this->notifyAttendeesRemoved($event), + ParticipantModifiedEvent::class => $this->notifyParticipantModified($event), + SessionLeftRoomEvent::class => $this->notifySessionLeftRoom($event), + ChatMessageSentEvent::class, + SystemMessageSentEvent::class, + SystemMessagesMultipleSentEvent::class => $this->notifyMessageSent($event), + default => null, // Ignoring events subscribed by the internal signaling + }; + } + + public static function refreshParticipantListUsingRoomEvent(RoomEvent $event): void { $messages = Server::get(Messages::class); $messages->addMessageForAllParticipants($event->getRoom(), 'refresh-participant-list'); } public static function refreshParticipantListUsingParticipantEvent(ParticipantEvent $event): void { - if (!self::isUsingInternalSignaling()) { - return; - } - $messages = Server::get(Messages::class); $messages->addMessageForAllParticipants($event->getRoom(), 'refresh-participant-list'); } - public static function notifyAfterUsersAdd(AddParticipantsEvent $event): void { - if (self::isUsingInternalSignaling()) { + protected function notifyRoomModified(ARoomModifiedEvent $event): void { + if (!in_array($event->getProperty(), [ + ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_MODE, + ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_STATUS, + ARoomModifiedEvent::PROPERTY_CALL_RECORDING, + ARoomModifiedEvent::PROPERTY_CALL_PERMISSIONS, + ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS, + ARoomModifiedEvent::PROPERTY_DESCRIPTION, + ARoomModifiedEvent::PROPERTY_LISTABLE, + ARoomModifiedEvent::PROPERTY_LOBBY, + ARoomModifiedEvent::PROPERTY_NAME, + ARoomModifiedEvent::PROPERTY_PASSWORD, + ARoomModifiedEvent::PROPERTY_READ_ONLY, + ARoomModifiedEvent::PROPERTY_SIP_ENABLED, + ARoomModifiedEvent::PROPERTY_TYPE, + ], true)) { return; } - $notifier = Server::get(BackendNotifier::class); + if ($event->getProperty() === ARoomModifiedEvent::PROPERTY_CALL_PERMISSIONS + || $event->getProperty() === ARoomModifiedEvent::PROPERTY_DEFAULT_PERMISSIONS) { + $this->notifyRoomPermissionsModified($event); + // The room permission itself does not need a signaling message anymore + return; + } - $notifier->roomInvited($event->getRoom(), $event->getParticipants()); + if ($event->getProperty() === ARoomModifiedEvent::PROPERTY_CALL_RECORDING) { + $this->notifyRoomRecordingModified($event); + } + if ($event->getProperty() === ARoomModifiedEvent::PROPERTY_BREAKOUT_ROOM_STATUS) { + $this->notifyBreakoutRoomStatusModified($event); + } + $this->externalSignaling->roomModified($event->getRoom()); } - public static function notifyAfterRoomSettingsChanged(RoomEvent $event): void { - if (self::isUsingInternalSignaling()) { + protected function notifyRoomRecordingModified(ARoomModifiedEvent $event): void { + $room = $event->getRoom(); + $message = [ + 'type' => 'recording', + 'recording' => [ + 'status' => $event->getNewValue(), + ], + ]; + + $this->externalSignaling->sendRoomMessage($room, $message); + } + + protected function notifyCallEndedForEveryone(CallEndedForEveryoneEvent $event): void { + $sessionIds = $event->getSessionIds(); + + if (empty($sessionIds)) { return; } - $notifier = Server::get(BackendNotifier::class); + $this->externalSignaling->roomInCallChanged( + $event->getRoom(), + $event->getNewValue(), + [], + true + ); + } - $notifier->roomModified($event->getRoom()); + protected function notifyBeforeRoomDeleted(BeforeRoomDeletedEvent $event): void { + $room = $event->getRoom(); + $this->externalSignaling->roomDeleted($room, $this->participantService->getParticipantUserIds($room)); } - public static function notifyAfterParticipantTypeAndPermissionsSet(ModifyParticipantEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; + protected function notifyGuestsCleanedUp(GuestsCleanedUpEvent $event): void { + // TODO: The list of removed session ids should be passed through the event + // so the signaling server can optimize forwarding the message. + $sessionIds = []; + $this->externalSignaling->participantsModified($event->getRoom(), $sessionIds); + } + + protected function notifyParticipantModified(AParticipantModifiedEvent $event): void { + if ($event->getProperty() === AParticipantModifiedEvent::PROPERTY_TYPE) { + // TODO remove handler with "roomModified" in favour of handler with + // "participantsModified" once the clients no longer expect a + // "roomModified" message for participant type changes. + $this->externalSignaling->roomModified($event->getRoom()); } - $notifier = Server::get(BackendNotifier::class); + if ($event->getProperty() === AParticipantModifiedEvent::PROPERTY_NAME) { + $this->notifyParticipantNameModified($event); + } - $sessionIds = []; - // If the participant is not active in the room the "participants" - // request will be sent anyway, although with an empty "changed" - // property. + if ($event->getProperty() === AParticipantModifiedEvent::PROPERTY_IN_CALL) { + $this->notifyParticipantInCallModified($event); + } - $sessionService = Server::get(SessionService::class); - $sessions = $sessionService->getAllSessionsForAttendee($event->getParticipant()->getAttendee()); + if ($event->getProperty() === AParticipantModifiedEvent::PROPERTY_TYPE + || $event->getProperty() === AParticipantModifiedEvent::PROPERTY_PERMISSIONS) { + $this->notifyParticipantTypeOrPermissionsModified($event); + } + } + + protected function notifyParticipantNameModified(AParticipantModifiedEvent $event): void { + $sessionIds = []; + $sessions = $this->sessionService->getAllSessionsForAttendee($event->getParticipant()->getAttendee()); foreach ($sessions as $session) { $sessionIds[] = $session->getSessionId(); } - $notifier->participantsModified($event->getRoom(), $sessionIds); + if (!empty($sessionIds)) { + $this->externalSignaling->participantsModified($event->getRoom(), $sessionIds); + } } - public static function notifyAfterPermissionSet(RoomEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; + protected function notifyParticipantTypeOrPermissionsModified(AParticipantModifiedEvent $event): void { + $sessionIds = []; + + // If the participant is not active in the room the "participants" + // request will be sent anyway, although with an empty "changed" + // property. + $sessions = $this->sessionService->getAllSessionsForAttendee($event->getParticipant()->getAttendee()); + foreach ($sessions as $session) { + $sessionIds[] = $session->getSessionId(); } - $notifier = Server::get(BackendNotifier::class); + $this->externalSignaling->participantsModified($event->getRoom(), $sessionIds); + } + protected function notifyRoomPermissionsModified(ARoomModifiedEvent $event): void { $sessionIds = []; // Setting the room permissions resets the permissions of all @@ -201,9 +283,7 @@ public static function notifyAfterPermissionSet(RoomEvent $event): void { // to be set on all participants can not be sent either, as the // general permissions could be overriden by custom attendee // permissions in specific participants. - - $participantService = Server::get(ParticipantService::class); - $participants = $participantService->getSessionsAndParticipantsForRoom($event->getRoom()); + $participants = $this->participantService->getSessionsAndParticipantsForRoom($event->getRoom()); foreach ($participants as $participant) { $session = $participant->getSession(); if ($session) { @@ -211,38 +291,18 @@ public static function notifyAfterPermissionSet(RoomEvent $event): void { } } - $notifier->participantsModified($event->getRoom(), $sessionIds); + $this->externalSignaling->participantsModified($event->getRoom(), $sessionIds); } - public static function notifyBeforeRoomDeleted(RoomEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - $participantService = Server::get(ParticipantService::class); - - $room = $event->getRoom(); - $notifier->roomDeleted($room, $participantService->getParticipantUserIds($room)); + protected function notifyAttendeesAdded(AttendeesAddedEvent $event): void { + $this->externalSignaling->roomInvited($event->getRoom(), $event->getAttendees()); } - public static function notifyAfterUserRemoved(RemoveUserEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - - $notifier->roomsDisinvited($event->getRoom(), [$event->getUser()->getUID()]); + protected function notifyAttendeesRemoved(AttendeesRemovedEvent $event): void { + $this->externalSignaling->roomsDisinvited($event->getRoom(), $event->getAttendees()); } - public static function notifyAfterParticipantRemoved(RemoveParticipantEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - + protected function notifyAttendeeRemoved(AttendeeRemovedEvent $event): void { $sessionIds = []; $sessions = $event->getSessions(); @@ -251,54 +311,42 @@ public static function notifyAfterParticipantRemoved(RemoveParticipantEvent $eve } if (!empty($sessionIds)) { - $notifier->roomSessionsRemoved($event->getRoom(), $sessionIds); + $this->externalSignaling->roomSessionsRemoved($event->getRoom(), $sessionIds); } } - public static function notifyAfterRoomDisconected(ParticipantEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - + protected function notifySessionLeftRoom(SessionLeftRoomEvent $event): void { $sessionIds = []; if ($event->getParticipant()->getSession()) { // If a previous duplicated session is being removed it must be - // notified to the external signaling server. Otherwise only for + // notified to the external signaling server. Otherwise, only for // guests disconnecting is "leaving" and therefor should trigger a // disinvite. $attendeeParticipantType = $event->getParticipant()->getAttendee()->getParticipantType(); - if ($event instanceof DuplicatedParticipantEvent + if ($event->isRejoining() || $attendeeParticipantType === Participant::GUEST || $attendeeParticipantType === Participant::GUEST_MODERATOR) { $sessionIds[] = $event->getParticipant()->getSession()->getSessionId(); - $notifier->roomSessionsRemoved($event->getRoom(), $sessionIds); + $this->externalSignaling->roomSessionsRemoved($event->getRoom(), $sessionIds); } } } - public static function notifyAfterJoinUpdateAndLeave(ModifyParticipantEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - if ($event instanceof ModifyEveryoneEvent) { + protected function notifyParticipantInCallModified(AParticipantModifiedEvent $event): void { + if ($event->getDetail(AParticipantModifiedEvent::DETAIL_IN_CALL_END_FOR_EVERYONE)) { // If everyone is disconnected, we will not do O(n) requests. - // Instead, the listener of Room::EVENT_AFTER_END_CALL_FOR_EVERYONE + // Instead, the listener of CallEndedForEveryoneEvent // will send all sessions to the HPB with 1 request. return; } - $notifier = Server::get(BackendNotifier::class); - $sessionIds = []; if ($event->getParticipant()->getSession()) { $sessionIds[] = $event->getParticipant()->getSession()->getSessionId(); } if (!empty($sessionIds)) { - $notifier->roomInCallChanged( + $this->externalSignaling->roomInCallChanged( $event->getRoom(), $event->getNewValue(), $sessionIds @@ -306,87 +354,48 @@ public static function notifyAfterJoinUpdateAndLeave(ModifyParticipantEvent $eve } } - public static function sendEndCallForEveryone(EndCallForEveryoneEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $sessionIds = $event->getSessionIds(); - - if (empty($sessionIds)) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - - $notifier->roomInCallChanged( - $event->getRoom(), - $event->getNewValue(), - [], - true - ); - } - - public static function notifyParticipantsAfterGuestClean(RoomEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - - // TODO: The list of removed session ids should be passed through the event - // so the signaling server can optimize forwarding the message. - $sessionIds = []; - $notifier->participantsModified($event->getRoom(), $sessionIds); - } - - public static function notifyParticipantsAfterSetBreakoutRoomStatus(RoomEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - + protected function notifyBreakoutRoomStatusModified(ARoomModifiedEvent $event): void { $room = $event->getRoom(); if ($room->getBreakoutRoomStatus() === BreakoutRoom::STATUS_STARTED) { - self::notifyParticipantsAfterBreakoutRoomStarted($room); + $this->notifyBreakoutRoomStarted($room); } else { - self::notifyParticipantsAfterBreakoutRoomStopped($room); + $this->notifyBreakoutRoomStopped($room); } } - private static function notifyParticipantsAfterBreakoutRoomStarted(Room $room): void { - $manager = Server::get(Manager::class); - $breakoutRooms = $manager->getMultipleRoomsByObject(BreakoutRoom::PARENT_OBJECT_TYPE, $room->getToken()); - - $switchToData = []; - - $participantService = Server::get(ParticipantService::class); - $parentRoomParticipants = $participantService->getSessionsAndParticipantsForRoom($room); + protected function notifyBreakoutRoomStarted(Room $room): void { + $breakoutRooms = $this->manager->getMultipleRoomsByObject(BreakoutRoom::PARENT_OBJECT_TYPE, $room->getToken()); - $notifier = Server::get(BackendNotifier::class); + $parentRoomParticipants = $this->participantService->getSessionsAndParticipantsForRoom($room); foreach ($breakoutRooms as $breakoutRoom) { $sessionIds = []; - $breakoutRoomParticipants = $participantService->getParticipantsForRoom($breakoutRoom); + $breakoutRoomParticipants = $this->participantService->getParticipantsForRoom($breakoutRoom); foreach ($breakoutRoomParticipants as $breakoutRoomParticipant) { - foreach (self::getSessionIdsForNonModeratorsMatchingParticipant($breakoutRoomParticipant, $parentRoomParticipants) as $sessionId) { + foreach ($this->getSessionIdsForNonModeratorsMatchingParticipant($breakoutRoomParticipant, $parentRoomParticipants) as $sessionId) { $sessionIds[] = $sessionId; } } if (!empty($sessionIds)) { - $notifier->switchToRoom($room, $breakoutRoom->getToken(), $sessionIds); + $this->externalSignaling->switchToRoom($room, $breakoutRoom->getToken(), $sessionIds); } } } - private static function getSessionIdsForNonModeratorsMatchingParticipant(Participant $targetParticipant, array $participants) { + /** + * @param Participant $targetParticipant + * @param Participant[] $participants + * @return string[] + */ + protected function getSessionIdsForNonModeratorsMatchingParticipant(Participant $targetParticipant, array $participants): array { $sessionIds = []; foreach ($participants as $participant) { - if ($participant->getAttendee()->getActorType() === $targetParticipant->getAttendee()->getActorType() && - $participant->getAttendee()->getActorId() === $targetParticipant->getAttendee()->getActorId() && - !$participant->hasModeratorPermissions()) { + if ($participant->getAttendee()->getActorType() === $targetParticipant->getAttendee()->getActorType() + && $participant->getAttendee()->getActorId() === $targetParticipant->getAttendee()->getActorId() + && !$participant->hasModeratorPermissions()) { $session = $participant->getSession(); if ($session) { $sessionIds[] = $session->getSessionId(); @@ -397,18 +406,13 @@ private static function getSessionIdsForNonModeratorsMatchingParticipant(Partici return $sessionIds; } - private static function notifyParticipantsAfterBreakoutRoomStopped(Room $room): void { - $manager = Server::get(Manager::class); - $breakoutRooms = $manager->getMultipleRoomsByObject(BreakoutRoom::PARENT_OBJECT_TYPE, $room->getToken()); - - $participantService = Server::get(ParticipantService::class); - - $notifier = Server::get(BackendNotifier::class); + protected function notifyBreakoutRoomStopped(Room $room): void { + $breakoutRooms = $this->manager->getMultipleRoomsByObject(BreakoutRoom::PARENT_OBJECT_TYPE, $room->getToken()); foreach ($breakoutRooms as $breakoutRoom) { $sessionIds = []; - $participants = $participantService->getSessionsAndParticipantsForRoom($breakoutRoom); + $participants = $this->participantService->getSessionsAndParticipantsForRoom($breakoutRoom); foreach ($participants as $participant) { $session = $participant->getSession(); if ($session) { @@ -417,42 +421,16 @@ private static function notifyParticipantsAfterBreakoutRoomStopped(Room $room): } if (!empty($sessionIds)) { - $notifier->switchToRoom($breakoutRoom, $room->getToken(), $sessionIds); + $this->externalSignaling->switchToRoom($breakoutRoom, $room->getToken(), $sessionIds); } } } - public static function notifyParticipantsAfterNameUpdated(ModifyParticipantEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - - $sessionIds = []; - - $sessionService = Server::get(SessionService::class); - $sessions = $sessionService->getAllSessionsForAttendee($event->getParticipant()->getAttendee()); - foreach ($sessions as $session) { - $sessionIds[] = $session->getSessionId(); - } - - if (!empty($sessionIds)) { - $notifier->participantsModified($event->getRoom(), $sessionIds); - } - } - - public static function notifyUsersViaExternalSignalingToRefreshTheChat(ChatEvent $event): void { - if (self::isUsingInternalSignaling()) { + protected function notifyMessageSent(AMessageSentEvent $event): void { + if ($event instanceof ASystemMessageSentEvent && $event->shouldSkipLastActivityUpdate()) { return; } - if ($event->shouldSkipLastActivityUpdate()) { - return; - } - - $notifier = Server::get(BackendNotifier::class); - $room = $event->getRoom(); $message = [ 'type' => 'chat', @@ -460,26 +438,6 @@ public static function notifyUsersViaExternalSignalingToRefreshTheChat(ChatEvent 'refresh' => true, ], ]; - $notifier->sendRoomMessage($room, $message); - } - - public static function sendSignalingMessageWhenToggleRecording(ModifyRoomEvent $event): void { - if (self::isUsingInternalSignaling()) { - return; - } - if ($event->getParameter() !== 'callRecording') { - return; - } - - $room = $event->getRoom(); - $message = [ - 'type' => 'recording', - 'recording' => [ - 'status' => $event->getNewValue(), - ], - ]; - - $notifier = Server::get(BackendNotifier::class); - $notifier->sendRoomMessage($room, $message); + $this->externalSignaling->sendRoomMessage($room, $message); } } diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 63d587e2ab4..3539dd1d730 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -133,6 +133,10 @@ public static function getAttendeeIdForPhoneNumber(string $identifier, string $p return self::$userToAttendeeId[$identifier]['phones'][self::$phoneNumberToActorId[$phoneNumber]]; } + public static function getSessionIdForUser(string $user): string { + return self::$userToSessionId[$user]; + } + public function getAttendeeId(string $type, string $id, string $room, string $user = null) { if (!isset(self::$userToAttendeeId[$room][$type][$id])) { if ($user !== null) { @@ -1087,6 +1091,19 @@ public function userCreatesThePasswordRequestRoomForLastShare(string $user, int * @param TableNode|null $formData */ public function userJoinsRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void { + $this->userJoinsRoomWithNamedSession($user, $identifier, $statusCode, $apiVersion, '', $formData); + } + + /** + * @Then /^user "([^"]*)" joins room "([^"]*)" with (\d+) \((v4)\) session name "([^"]*)"$/ + * + * @param string $user + * @param string $identifier + * @param int $statusCode + * @param string $apiVersion + * @param TableNode|null $formData + */ + public function userJoinsRoomWithNamedSession(string $user, string $identifier, int $statusCode, string $apiVersion, string $sessionName, TableNode $formData = null): void { $this->setCurrentUser($user, $identifier); $this->sendRequest( 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active', @@ -1106,6 +1123,9 @@ public function userJoinsRoom(string $user, string $identifier, int $statusCode, // in chat messages is a hashed version instead. self::$sessionIdToUser[sha1($response['sessionId'])] = $user; self::$userToSessionId[$user] = $response['sessionId']; + if ($sessionName) { + self::$userToSessionId[$user . '#' . $sessionName] = $response['sessionId']; + } if (!isset(self::$userToAttendeeId[$identifier][$response['actorType']])) { self::$userToAttendeeId[$identifier][$response['actorType']] = []; } diff --git a/tests/integration/features/bootstrap/RecordingTrait.php b/tests/integration/features/bootstrap/RecordingTrait.php index 5237618da90..192ed3c582a 100644 --- a/tests/integration/features/bootstrap/RecordingTrait.php +++ b/tests/integration/features/bootstrap/RecordingTrait.php @@ -323,10 +323,43 @@ public function fakeServerReceivedTheFollowingRequests(string $server, TableNode return; } - $expected = array_map(static function (array $request) { + $count = count($formData->getHash()); + Assert::assertCount($count, $requests, 'Request count does not match' . "\n" . json_encode($requests)); + + $requests = array_map(static function (array $actual) { + $actualDataJson = json_decode($actual['data'], true); + $write = false; + + // Fix sorting of postgres + if (isset($actualDataJson['update']['userids'])) { + sort($actualDataJson['update']['userids']); + $write = true; + } + if (isset($actualDataJson['participants']['users'])) { + usort($actualDataJson['participants']['users'], static fn (array $u1, array $u2) => $u1['userId'] <=> $u2['userId']); + $write = true; + } + + if ($write) { + $actual['data'] = json_encode($actualDataJson); + } + + return $actual; + }, $requests); + + $expected = array_map(static function (array $request, array $actual) { $identifier = $request['token']; $request['token'] = FeatureContext::getTokenForIdentifier($identifier); + $matched = preg_match('/ROOM\(([^)]+)\)/', $request['data'], $matches); + if ($matched) { + $request['data'] = str_replace( + 'ROOM(' . $matches[1] . ')', + FeatureContext::getTokenForIdentifier($matches[1]), + $request['data'] + ); + } + $matched = preg_match('/PHONE\((\+\d+)\)/', $request['data'], $matches); if ($matched) { $request['data'] = str_replace( @@ -345,11 +378,41 @@ public function fakeServerReceivedTheFollowingRequests(string $server, TableNode ); } - return $request; - }, $formData->getHash()); + $matched = preg_match('/SESSION\(([^)]+)\)/', $request['data'], $matches); + if ($matched) { + $request['data'] = str_replace( + 'SESSION(' . $matches[1] . ')', + str_replace('/', '\/', FeatureContext::getSessionIdForUser($matches[1])), + $request['data'] + ); + } - $count = count($expected); - Assert::assertCount($count, $requests, 'Request count does not match'); + $matched = preg_match('/"lastPing":LAST_PING\(\)/', $request['data'], $matches); + if ($matched) { + $matched = preg_match('/"lastPing":(\d+)/', $actual['data'], $matches); + if ($matched) { + $request['data'] = str_replace( + '"lastPing":LAST_PING()', + $matches[0], + $request['data'] + ); + } + } + + $matched = preg_match('/"active-since":\{"date":"ACTIVE_SINCE\(\)","timezone_type":3,"timezone":"UTC"}/', $request['data'], $matches); + if ($matched) { + $matched = preg_match('/"active-since":\{"date":"([\d\- :.]+)","timezone_type":3,"timezone":"UTC"}/', $actual['data'], $matches); + if ($matched) { + $request['data'] = str_replace( + 'ACTIVE_SINCE()', + $matches[1], + $request['data'] + ); + } + } + + return $request; + }, $formData->getHash(), $requests); Assert::assertEquals($expected, $requests); } diff --git a/tests/integration/features/callapi/recording.feature b/tests/integration/features/callapi/recording.feature index f86fa446cc8..bf819b9885d 100644 --- a/tests/integration/features/callapi/recording.feature +++ b/tests/integration/features/callapi/recording.feature @@ -5,15 +5,21 @@ Feature: callapi/recording Scenario: Start and stop video recording Given recording server is started + Given signaling server is started And user "participant1" creates room "room1" (v4) | roomType | 2 | | roomName | room1 | And user "participant1" joins room "room1" with 200 (v4) And user "participant1" joins call "room1" with 200 (v4) + And reset signaling server requests When user "participant1" starts "video" recording in room "room1" with 200 (v1) And recording server received the following requests | token | data | | room1 | {"type":"start","start":{"status":1,"owner":"participant1","actor":{"type":"users","id":"participant1"}}} | + Then signaling server received the following requests + | token | data | + | room1 | {"type":"message","message":{"data":{"type":"recording","recording":{"status":3}}}} | + | room1 | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":{"date":"ACTIVE_SINCE()","timezone_type":3,"timezone":"UTC"},"sip-enabled":0,"description":""}}} | And user "participant1" is participant of the following unordered rooms (v4) | type | name | callRecording | | 2 | room1 | 3 | @@ -26,14 +32,23 @@ Feature: callapi/recording And user "participant1" is participant of the following unordered rooms (v4) | type | name | callRecording | | 2 | room1 | 1 | + And reset signaling server requests When user "participant1" stops recording in room "room1" with 200 (v1) And recording server received the following requests | token | data | | room1 | {"type":"stop","stop":{"actor":{"type":"users","id":"participant1"}}} | + Then signaling server received the following requests + # Nothing changes until the recording backend confirms the stop And user "participant1" is participant of the following unordered rooms (v4) | type | name | callRecording | | 2 | room1 | 1 | + And reset signaling server requests And recording server sent stopped request for recording in room "room1" as "participant1" with 200 + Then signaling server received the following requests + | token | data | + | room1 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room1 | {"type":"message","message":{"data":{"type":"recording","recording":{"status":0}}}} | + | room1 | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":{"date":"ACTIVE_SINCE()","timezone_type":3,"timezone":"UTC"},"sip-enabled":0,"description":""}}} | Then user "participant1" sees the following system messages in room "room1" with 200 (v1) | room | actorType | actorId | actorDisplayName | systemMessage | | room1 | users | participant1 | participant1-displayname | recording_stopped | diff --git a/tests/integration/features/callapi/update-call-flags.feature b/tests/integration/features/callapi/update-call-flags.feature index ff2bde498dd..28372592349 100644 --- a/tests/integration/features/callapi/update-call-flags.feature +++ b/tests/integration/features/callapi/update-call-flags.feature @@ -6,6 +6,7 @@ Feature: callapi/update-call-flags And user "not invited but joined user" exists Scenario: all participants can update their call flags when in a call + Given signaling server is started Given user "owner" creates room "public room" (v4) | roomType | 3 | | roomName | room | @@ -20,13 +21,22 @@ Feature: callapi/update-call-flags And user "moderator" joins room "public room" with 200 (v4) And user "invited user" joins room "public room" with 200 (v4) And user "not invited but joined user" joins room "public room" with 200 (v4) + And reset signaling server requests And user "owner" joins call "public room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | public room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | public room | {"type":"incall","incall":{"incall":7,"changed":[{"inCall":7,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":254,"userId":"owner"}],"users":[{"inCall":7,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":254,"userId":"owner"}]}} | + And reset signaling server requests + When user "owner" updates call flags in room "public room" to "1" with 200 (v4) + Then signaling server received the following requests + | token | data | + | public room | {"type":"incall","incall":{"incall":1,"changed":[{"inCall":1,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":254,"userId":"owner"}],"users":[{"inCall":1,"lastPing":LAST_PING(),"sessionId":"SESSION(owner)","nextcloudSessionId":"SESSION(owner)","participantType":1,"participantPermissions":254,"userId":"owner"}]}} | And user "moderator" joins call "public room" with 200 (v4) And user "invited user" joins call "public room" with 200 (v4) And user "not invited but joined user" joins call "public room" with 200 (v4) And user "guest moderator" joins call "public room" with 200 (v4) And user "guest" joins call "public room" with 200 (v4) - When user "owner" updates call flags in room "public room" to "1" with 200 (v4) And user "moderator" updates call flags in room "public room" to "1" with 200 (v4) And user "invited user" updates call flags in room "public room" to "1" with 200 (v4) And user "not invited but joined user" updates call flags in room "public room" to "1" with 200 (v4) diff --git a/tests/integration/features/chat/group-read-only.feature b/tests/integration/features/chat/group-read-only.feature index 6cb626239ac..ef0c2bf9aaa 100644 --- a/tests/integration/features/chat/group-read-only.feature +++ b/tests/integration/features/chat/group-read-only.feature @@ -7,6 +7,7 @@ Feature: chat/group-read-only And user "participant2" is member of group "attendees1" Scenario: owner can send and receive chat messages to and from group room + Given signaling server is started Given user "participant1" creates room "group room" (v4) | roomType | 2 | | invite | attendees1 | @@ -14,12 +15,22 @@ Feature: chat/group-read-only Then user "participant1" sees the following messages in room "group room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | | group room | users | participant1 | participant1-displayname | Message 1 | [] | + And reset signaling server requests When user "participant1" locks room "group room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | group room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | group room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":1,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | When user "participant1" sends message "Message 2" to room "group room" with 403 Then user "participant1" sees the following messages in room "group room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | | group room | users | participant1 | participant1-displayname | Message 1 | [] | + And reset signaling server requests When user "participant1" unlocks room "group room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | group room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | group room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | When user "participant1" sends message "Message 3" to room "group room" with 201 Then user "participant1" sees the following messages in room "group room" with 200 | room | actorType | actorId | actorDisplayName | message | messageParameters | diff --git a/tests/integration/features/command/user-remove.feature b/tests/integration/features/command/user-remove.feature index dc08d3b3793..bd22872cedc 100644 --- a/tests/integration/features/command/user-remove.feature +++ b/tests/integration/features/command/user-remove.feature @@ -63,6 +63,7 @@ Feature: command/user-remove | users | participant1 | 1 | Scenario: Remove a user after there was a missed call + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 1 | | invite | participant2 | @@ -70,8 +71,19 @@ Feature: command/user-remove Then user "participant1" joins call "room" with 200 (v4) Then user "participant1" leaves call "room" with 200 (v4) Then user "participant1" leaves room "room" with 200 (v4) + And reset signaling server requests And invoking occ with "talk:user:remove --user participant2" - And the command output contains the text "Users successfully removed from all rooms" + Then signaling server received the following requests + | token | data | + | room | {"type":"disinvite","disinvite":{"userids":["participant2"],"alluserids":["participant1"],"properties":{"name":"Private conversation","type":1,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + # Type changed + | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + # Name changed + | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + # Read only changed + | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":5,"lobby-state":0,"lobby-timer":null,"read-only":1,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + And the command output contains the text "Users successfully removed from all rooms" Then the command was successful And user "participant2" is participant of the following rooms (v4) And user "participant1" is participant of the following rooms (v4) diff --git a/tests/integration/features/conversation-2/promotion-demotion.feature b/tests/integration/features/conversation-2/promotion-demotion.feature index 9ecd964fd17..9913f479cf5 100644 --- a/tests/integration/features/conversation-2/promotion-demotion.feature +++ b/tests/integration/features/conversation-2/promotion-demotion.feature @@ -5,6 +5,7 @@ Feature: conversation-2/promotion-demotion Given user "participant3" exists Scenario: Owner promotes/demotes moderator + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | @@ -13,7 +14,16 @@ Feature: conversation-2/promotion-demotion | id | type | participantType | | room | 3 | 3 | And user "participant1" loads attendees attendee ids in room "room" (v4) + And reset signaling server requests When user "participant1" promotes "participant2" in room "room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + # TODO remove handler with "roomModified" in favour of handler with + # "participantsModified" once the clients no longer expect a + # "roomModified" message for participant type changes. + | room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | room | {"type":"participants","participants":{"changed":[],"users":[{"inCall":0,"lastPing":0,"sessionId":"0","participantType":1,"participantPermissions":1,"displayName":"participant1-displayname","userId":"participant1"},{"inCall":0,"lastPing":0,"sessionId":"0","participantType":2,"participantPermissions":1,"displayName":"participant2-displayname","userId":"participant2"}]}} | And user "participant2" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 2 | diff --git a/tests/integration/features/conversation-2/public-private.feature b/tests/integration/features/conversation-2/public-private.feature index a685286a961..08b98fff777 100644 --- a/tests/integration/features/conversation-2/public-private.feature +++ b/tests/integration/features/conversation-2/public-private.feature @@ -5,17 +5,28 @@ Feature: conversation-2/public-private Given user "participant3" exists Scenario: Owner makes room private/public + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | And user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 1 | + And reset signaling server requests When user "participant1" makes room "room" private with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Then user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 2 | 1 | + And reset signaling server requests When user "participant1" makes room "room" public with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Then user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 1 | diff --git a/tests/integration/features/conversation-2/remove-participant.feature b/tests/integration/features/conversation-2/remove-participant.feature index 7d4cad2b280..3e88dd29c9f 100644 --- a/tests/integration/features/conversation-2/remove-participant.feature +++ b/tests/integration/features/conversation-2/remove-participant.feature @@ -92,12 +92,18 @@ Feature: conversation-2/remove-participant And user "participant3" is participant of room "room" (v4) Scenario: Moderator removes self participant from public room when there are other moderators in the room + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | And user "participant1" adds user "participant2" to room "room" with 200 (v4) And user "participant1" promotes "participant2" in room "room" with 200 (v4) + And reset signaling server requests And user "participant1" removes "participant1" from room "room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"disinvite","disinvite":{"userids":["participant1"],"alluserids":["participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | And user "participant1" is not participant of room "room" (v4) And user "participant2" adds user "participant3" to room "room" with 200 (v4) And user "participant2" promotes "participant3" in room "room" with 200 (v4) diff --git a/tests/integration/features/conversation-2/rename-room.feature b/tests/integration/features/conversation-2/rename-room.feature index e8b591ca584..8539fc4f7c3 100644 --- a/tests/integration/features/conversation-2/rename-room.feature +++ b/tests/integration/features/conversation-2/rename-room.feature @@ -13,6 +13,7 @@ Feature: conversation-2/rename-room Then user "participant1" is participant of room "room" (v4) Scenario: Moderator renames + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | @@ -20,7 +21,12 @@ Feature: conversation-2/rename-room And user "participant1" adds user "participant2" to room "room" with 200 (v4) And user "participant2" is participant of room "room" (v4) And user "participant1" promotes "participant2" in room "room" with 200 (v4) + And reset signaling server requests When user "participant2" renames room "room" to "new name" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Scenario: User renames Given user "participant1" creates room "room" (v4) diff --git a/tests/integration/features/conversation-2/set-description.feature b/tests/integration/features/conversation-2/set-description.feature index 0445439f866..bc5dab67622 100644 --- a/tests/integration/features/conversation-2/set-description.feature +++ b/tests/integration/features/conversation-2/set-description.feature @@ -8,10 +8,16 @@ Feature: conversation-2/set-description Given user "not joined user" exists Scenario: a description of 500 characters can be set + Given signaling server is started Given user "owner" creates room "group room" (v4) | roomType | 2 | | roomName | room | + And reset signaling server requests When user "owner" sets description for room "group room" to "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C" with 200 (v4) + Then signaling server received the following requests + | token | data | + | group room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | group room | {"type":"update","update":{"userids":["owner"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":"012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C"}}} | Then user "owner" is participant of room "group room" (v4) | description | | 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678C | diff --git a/tests/integration/features/conversation-2/set-listable.feature b/tests/integration/features/conversation-2/set-listable.feature index 8c7aac78010..a5c648a29b0 100644 --- a/tests/integration/features/conversation-2/set-listable.feature +++ b/tests/integration/features/conversation-2/set-listable.feature @@ -24,6 +24,7 @@ Feature: conversation-2/set-listable Then user "creator" allows listing room "room" for "5" with 400 (v4) Scenario: Only moderators and owners can change listable attribute + Given signaling server is started Given user "creator" creates room "room" (v4) | roomType | 3 | | roomName | room | @@ -32,11 +33,21 @@ Feature: conversation-2/set-listable And user "user-guest@example.com" is a guest account user And user "creator" adds user "regular-user" to room "room" with 200 (v4) And user "creator" adds user "moderator" to room "room" with 200 (v4) + And reset signaling server requests And user "creator" allows listing room "room" for "all" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["creator","moderator","regular-user"],"properties":{"name":"room","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":2,"active-since":null,"sip-enabled":0,"description":""}}} | When user "creator" promotes "moderator" in room "room" with 200 (v4) And user "user-guest@example.com" joins room "room" with 200 (v4) And user "guest" joins room "room" with 200 (v4) + And reset signaling server requests Then user "moderator" allows listing room "room" for "none" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["creator","moderator","regular-user","user-guest@example.com"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And user "regular-user" allows listing room "room" for "users" with 403 (v4) And user "user-guest@example.com" allows listing room "room" for "users" with 403 (v4) And user "guest" allows listing room "room" for "users" with 401 (v4) diff --git a/tests/integration/features/conversation-2/set-password.feature b/tests/integration/features/conversation-2/set-password.feature index 2d8627cd75d..054041705e6 100644 --- a/tests/integration/features/conversation-2/set-password.feature +++ b/tests/integration/features/conversation-2/set-password.feature @@ -5,13 +5,19 @@ Feature: conversation-2/set-password Given user "participant3" exists Scenario: Owner sets a room password + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | And user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 1 | + And reset signaling server requests When user "participant1" sets password "foobar" for room "room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["participant1"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Then user "participant3" joins room "room" with 403 (v4) Then user "participant3" joins room "room" with 200 (v4) | password | foobar | diff --git a/tests/integration/features/conversation/add-participant.feature b/tests/integration/features/conversation/add-participant.feature index 6799e9556c2..680c493d43a 100644 --- a/tests/integration/features/conversation/add-participant.feature +++ b/tests/integration/features/conversation/add-participant.feature @@ -25,10 +25,16 @@ Feature: conversation/add-participant | users | participant2 | 3 | Scenario: User invites a user + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | + And reset signaling server requests And user "participant1" adds user "participant2" to room "room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"invite","invite":{"userids":["participant2"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | And user "participant1" is participant of the following rooms (v4) | id | type | participantType | | room | 3 | 1 | diff --git a/tests/integration/features/conversation/breakout-rooms.feature b/tests/integration/features/conversation/breakout-rooms.feature index 81d91b8c471..01932206af8 100644 --- a/tests/integration/features/conversation/breakout-rooms.feature +++ b/tests/integration/features/conversation/breakout-rooms.feature @@ -7,6 +7,7 @@ Feature: conversation/breakout-rooms Given group "group1" exists Scenario: Teacher creates manual breakout rooms + Given signaling server is started Given user "participant1" creates room "class room" (v4) | roomType | 2 | | roomName | class room | @@ -63,6 +64,40 @@ Feature: conversation/breakout-rooms | class room | users | participant4 | 3 | | Room 1 | users | participant1 | 1 | | Room 1 | users | participant2 | 3 | + And user "participant2" joins room "class room" with 200 (v4) + And user "participant3" joins room "class room" with 200 (v4) + And user "participant4" joins room "class room" with 200 (v4) + And reset signaling server requests + And user "participant1" starts breakout rooms in room "class room" with 200 (v1) + Then signaling server received the following requests + | token | data | + | Room 1 | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 2 | {"type":"update","update":{"userids":["participant1","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 3 | {"type":"update","update":{"userids":["participant1","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | class room | {"type":"switchto","switchto":{"roomid":"ROOM(Room 1)","sessions":["SESSION(participant2)"]}} | + | class room | {"type":"switchto","switchto":{"roomid":"ROOM(Room 2)","sessions":["SESSION(participant3)"]}} | + | class room | {"type":"switchto","switchto":{"roomid":"ROOM(Room 3)","sessions":["SESSION(participant4)"]}} | + | class room | {"type":"update","update":{"userids":["participant1","participant2","participant3","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + And user "participant2" leaves room "class room" with 200 (v4) + And user "participant3" leaves room "class room" with 200 (v4) + And user "participant4" leaves room "class room" with 200 (v4) + And user "participant2" joins room "Room 1" with 200 (v4) + And user "participant3" joins room "Room 2" with 200 (v4) + And user "participant4" joins room "Room 3" with 200 (v4) + And reset signaling server requests + And user "participant1" stops breakout rooms in room "class room" with 200 (v1) + Then signaling server received the following requests + | token | data | + | Room 1 | {"type":"switchto","switchto":{"roomid":"ROOM(class room)","sessions":["SESSION(participant2)"]}} | + | Room 2 | {"type":"switchto","switchto":{"roomid":"ROOM(class room)","sessions":["SESSION(participant3)"]}} | + | Room 3 | {"type":"switchto","switchto":{"roomid":"ROOM(class room)","sessions":["SESSION(participant4)"]}} | + | class room | {"type":"update","update":{"userids":["participant1","participant2","participant3","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 1 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | Room 1 | {"type":"update","update":{"userids":["participant1","participant2"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 2 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | Room 2 | {"type":"update","update":{"userids":["participant1","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + | Room 3 | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | Room 3 | {"type":"update","update":{"userids":["participant1","participant4"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | Scenario: Teacher creates automatic breakout rooms Given user "participant1" creates room "class room" (v4) diff --git a/tests/integration/features/conversation/delete-room.feature b/tests/integration/features/conversation/delete-room.feature index 70c5f7caded..c9b2f8b6543 100644 --- a/tests/integration/features/conversation/delete-room.feature +++ b/tests/integration/features/conversation/delete-room.feature @@ -5,6 +5,7 @@ Feature: conversation/delete-room Given user "participant3" exists Scenario: Owner deletes + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | @@ -13,7 +14,11 @@ Feature: conversation/delete-room | room | 3 | 1 | And user "participant2" is not participant of room "room" (v4) And user "participant3" is not participant of room "room" (v4) + And reset signaling server requests When user "participant1" deletes room "room" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"delete","delete":{"userids":["participant1"]}} | Then user "participant1" is not participant of room "room" (v4) Scenario: Moderator deletes diff --git a/tests/integration/features/conversation/join-leave.feature b/tests/integration/features/conversation/join-leave.feature index 6fa273df007..89b025f9fbf 100644 --- a/tests/integration/features/conversation/join-leave.feature +++ b/tests/integration/features/conversation/join-leave.feature @@ -74,18 +74,38 @@ Feature: conversation/join-leave And user "guest" is participant of room "room" (v4) Scenario: leave a public room + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 3 | | roomName | room | And user "participant1" adds user "participant2" to room "room" with 200 (v4) - And user "participant1" joins room "room" with 200 (v4) + And reset signaling server requests + And user "participant1" joins room "room" with 200 (v4) session name "old" + And user "participant1" joins room "room" with 200 (v4) session name "new" + # Rejoining the same room disinvites the previous session + Then signaling server received the following requests + | token | data | + | room | {"type":"disinvite","disinvite":{"sessionids":["SESSION(participant1#old)"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | And user "participant2" joins room "room" with 200 (v4) And user "participant3" joins room "room" with 200 (v4) And user "guest" joins room "room" with 200 (v4) + And reset signaling server requests When user "participant1" leaves room "room" with 200 (v4) + # No signaling message when a normal user leaves + Then signaling server received the following requests And user "participant2" leaves room "room" with 200 (v4) + And reset signaling server requests And user "participant3" leaves room "room" with 200 (v4) + # Signaling message when a self-joined user leaves + Then signaling server received the following requests + | token | data | + | room | {"type":"disinvite","disinvite":{"userids":["participant3"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | + And reset signaling server requests And user "guest" leaves room "room" with 200 (v4) + # Signaling message when a guest leaves + Then signaling server received the following requests + | token | data | + | room | {"type":"disinvite","disinvite":{"sessionids":["SESSION(guest)"],"alluserids":["participant1","participant2"],"properties":{"name":"Private conversation","type":3,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"participant-list":"refresh"}}} | Then user "participant1" is participant of room "room" (v4) And user "participant2" is participant of room "room" (v4) And user "participant3" is not participant of room "room" (v4) diff --git a/tests/integration/features/conversation/lobby.feature b/tests/integration/features/conversation/lobby.feature index 206327089f5..8ea69ed0955 100644 --- a/tests/integration/features/conversation/lobby.feature +++ b/tests/integration/features/conversation/lobby.feature @@ -7,14 +7,25 @@ Feature: conversation/lobby Given user "participant4" exists Scenario: set lobby state in group room + Given signaling server is started Given user "participant1" creates room "room" (v4) | roomType | 2 | | roomName | room | And user "participant1" adds user "participant2" to room "room" with 200 (v4) And user "participant1" promotes "participant2" in room "room" with 200 (v4) And user "participant1" adds user "participant3" to room "room" with 200 (v4) + And reset signaling server requests When user "participant1" sets lobby state for room "room" to "non moderators" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["participant1","participant2","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":1,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | + And reset signaling server requests And user "participant1" sets lobby state for room "room" to "no lobby" with 200 (v4) + Then signaling server received the following requests + | token | data | + | room | {"type":"message","message":{"data":{"type":"chat","chat":{"refresh":true}}}} | + | room | {"type":"update","update":{"userids":["participant1","participant2","participant3"],"properties":{"name":"Private conversation","type":2,"lobby-state":0,"lobby-timer":null,"read-only":0,"listable":0,"active-since":null,"sip-enabled":0,"description":""}}} | And user "participant2" sets lobby state for room "room" to "non moderators" with 200 (v4) And user "participant2" sets lobby state for room "room" to "no lobby" with 200 (v4) And user "participant3" sets lobby state for room "room" to "non moderators" with 403 (v4) diff --git a/tests/php/Signaling/BackendNotifierTest.php b/tests/php/Signaling/BackendNotifierTest.php deleted file mode 100644 index 6f6c59e3e39..00000000000 --- a/tests/php/Signaling/BackendNotifierTest.php +++ /dev/null @@ -1,1527 +0,0 @@ -. - * - */ - -namespace OCA\Talk\Tests\php\Signaling; - -use OCA\Talk\AppInfo\Application; -use OCA\Talk\Chat\ChatManager; -use OCA\Talk\Chat\CommentsManager; -use OCA\Talk\Config; -use OCA\Talk\Events\SignalingRoomPropertiesEvent; -use OCA\Talk\Manager; -use OCA\Talk\Model\Attendee; -use OCA\Talk\Model\AttendeeMapper; -use OCA\Talk\Model\BreakoutRoom; -use OCA\Talk\Model\SessionMapper; -use OCA\Talk\Participant; -use OCA\Talk\Room; -use OCA\Talk\Service\BreakoutRoomService; -use OCA\Talk\Service\MembershipService; -use OCA\Talk\Service\ParticipantService; -use OCA\Talk\Service\RoomService; -use OCA\Talk\Service\SessionService; -use OCA\Talk\Signaling\BackendNotifier; -use OCA\Talk\TalkSession; -use OCA\Talk\Webinary; -use OCP\App\IAppManager; -use OCP\AppFramework\Utility\ITimeFactory; -use OCP\BackgroundJob\IJobList; -use OCP\EventDispatcher\IEventDispatcher; -use OCP\Http\Client\IClientService; -use OCP\Http\Client\IResponse; -use OCP\ICacheFactory; -use OCP\IGroupManager; -use OCP\IL10N; -use OCP\IURLGenerator; -use OCP\IUser; -use OCP\IUserManager; -use OCP\Notification\IManager as INotificationManager; -use OCP\Security\IHasher; -use OCP\Security\ISecureRandom; -use OCP\Share\IManager; -use PHPUnit\Framework\MockObject\MockObject; -use Psr\Log\LoggerInterface; -use Test\TestCase; - -class CustomBackendNotifier extends BackendNotifier { - private array $requests = []; - - public function getRequests(): array { - return $this->requests; - } - - public function clearRequests() { - $this->requests = []; - } - - protected function doRequest(string $url, array $params, int $retries = 3): ?IResponse { - $this->requests[] = [ - 'url' => $url, - 'params' => $params, - ]; - return null; - } -} - -/** - * @group DB - */ -class BackendNotifierTest extends TestCase { - private ?Config $config = null; - private ?ISecureRandom $secureRandom = null; - /** @var ITimeFactory|MockObject */ - private $timeFactory; - /** @var ParticipantService|MockObject */ - private $participantService; - /** @var \OCA\Talk\Signaling\Manager|MockObject */ - private $signalingManager; - /** @var IURLGenerator|MockObject */ - private $urlGenerator; - /** @var IUserManager|MockObject */ - private $userManager; - private ?\OCA\Talk\Tests\php\Signaling\CustomBackendNotifier $controller = null; - - private ?Manager $manager = null; - private ?RoomService $roomService = null; - private ?BreakoutRoomService $breakoutRoomService = null; - - private ?string $userId = null; - private ?string $displayName = null; - private ?string $signalingSecret = null; - private ?string $baseUrl = null; - - protected Application $app; - protected BackendNotifier $originalBackendNotifier; - private ?IEventDispatcher $dispatcher = null; - /** @var IJobList|MockObject */ - private IJobList $jobList; - - public function setUp(): void { - parent::setUp(); - - $this->userId = 'testUser'; - $this->displayName = 'testUserDisplayName'; - $this->secureRandom = \OC::$server->getSecureRandom(); - $this->timeFactory = $this->createMock(ITimeFactory::class); - $this->urlGenerator = $this->createMock(IURLGenerator::class); - $groupManager = $this->createMock(IGroupManager::class); - $this->userManager = $this->createMock(IUserManager::class); - $config = \OC::$server->getConfig(); - $this->signalingSecret = 'the-signaling-secret'; - $this->baseUrl = 'https://localhost/signaling'; - $config->setAppValue('spreed', 'signaling_servers', json_encode([ - 'secret' => $this->signalingSecret, - 'servers' => [ - [ - 'server' => $this->baseUrl, - ], - ], - ])); - $config->setAppValue('spreed', 'recording_servers', json_encode([ - 'secret' => $this->signalingSecret, - 'servers' => [ - [ - 'server' => $this->baseUrl, - ], - ], - ])); - - $this->signalingManager = $this->createMock(\OCA\Talk\Signaling\Manager::class); - $this->signalingManager->expects($this->any()) - ->method('getSignalingServerForConversation') - ->willReturn(['server' => $this->baseUrl]); - - $this->dispatcher = \OC::$server->get(IEventDispatcher::class); - $this->config = new Config($config, $this->secureRandom, $groupManager, $this->userManager, $this->urlGenerator, $this->timeFactory, $this->dispatcher); - - $dbConnection = \OC::$server->getDatabaseConnection(); - $this->participantService = new ParticipantService( - $config, - $this->config, - \OC::$server->get(AttendeeMapper::class), - \OC::$server->get(SessionMapper::class), - \OC::$server->get(SessionService::class), - $this->secureRandom, - $dbConnection, - $this->dispatcher, - $this->userManager, - $groupManager, - \OC::$server->get(MembershipService::class), - \OC::$server->get(\OCA\Talk\Federation\BackendNotifier::class), - $this->timeFactory, - \OC::$server->get(ICacheFactory::class) - ); - - $this->recreateBackendNotifier(); - - $this->overwriteService(BackendNotifier::class, $this->controller); - - $this->manager = new Manager( - $dbConnection, - $config, - $this->config, - \OC::$server->get(IAppManager::class), - \OC::$server->get(AttendeeMapper::class), - \OC::$server->get(SessionMapper::class), - $this->participantService, - $this->secureRandom, - $this->createMock(IUserManager::class), - $groupManager, - $this->createMock(CommentsManager::class), - $this->createMock(TalkSession::class), - $this->dispatcher, - $this->timeFactory, - $this->createMock(IHasher::class), - $this->createMock(IL10N::class) - ); - $this->jobList = $this->createMock(IJobList::class); - - $this->roomService = new RoomService( - $this->manager, - $this->participantService, - $dbConnection, - $this->timeFactory, - $this->createMock(IManager::class), - $this->config, - $this->createMock(IHasher::class), - $this->dispatcher, - $this->jobList - ); - - $l = $this->createMock(IL10N::class); - $l->expects($this->any()) - ->method('t') - ->willReturnCallback(function ($text, $parameters = []) { - return vsprintf($text, $parameters); - }); - - $this->breakoutRoomService = new BreakoutRoomService( - $this->config, - $this->manager, - $this->roomService, - $this->participantService, - $this->createMock(ChatManager::class), - $this->createMock(INotificationManager::class), - $this->dispatcher, - $l - ); - } - - public function tearDown(): void { - $config = \OC::$server->getConfig(); - $config->deleteAppValue('spreed', 'signaling_servers'); - $config->deleteAppValue('spreed', 'recording_servers'); - $this->restoreService(BackendNotifier::class); - parent::tearDown(); - } - - private function recreateBackendNotifier() { - $this->controller = new CustomBackendNotifier( - $this->config, - $this->createMock(LoggerInterface::class), - $this->createMock(IClientService::class), - $this->secureRandom, - $this->signalingManager, - $this->participantService, - $this->urlGenerator - ); - } - - private function calculateBackendChecksum($data, $random) { - if (empty($random) || strlen($random) < 32) { - return false; - } - return hash_hmac('sha256', $random . $data, $this->signalingSecret); - } - - private function validateBackendRequest($expectedUrl, $request) { - $this->assertTrue(isset($request)); - $this->assertEquals($expectedUrl, $request['url']); - $headers = $request['params']['headers']; - $this->assertEquals('application/json', $headers['Content-Type']); - $random = $headers['Spreed-Signaling-Random']; - $checksum = $headers['Spreed-Signaling-Checksum']; - $body = $request['params']['body']; - $this->assertEquals($this->calculateBackendChecksum($body, $random), $checksum); - return $body; - } - - private function assertMessageCount(Room $room, string $messageType, int $expectedCount): void { - $expectedUrl = $this->baseUrl . '/api/v1/room/' . $room->getToken(); - - $requests = $this->controller->getRequests(); - $requests = array_filter($requests, function ($request) use ($expectedUrl) { - return $request['url'] === $expectedUrl; - }); - $bodies = array_map(function ($request) use ($expectedUrl) { - return json_decode($this->validateBackendRequest($expectedUrl, $request), true); - }, $requests); - - $bodies = array_filter($bodies, function (array $body) use ($messageType) { - return $body['type'] === $messageType; - }); - - $this->assertCount($expectedCount, $bodies, json_encode($bodies, JSON_PRETTY_PRINT)); - } - - private function assertMessageWasSent(Room $room, array $message): void { - $expectedUrl = $this->baseUrl . '/api/v1/room/' . $room->getToken(); - - $requests = $this->controller->getRequests(); - $requests = array_filter($requests, function ($request) use ($expectedUrl) { - return $request['url'] === $expectedUrl; - }); - $bodies = array_map(function ($request) use ($expectedUrl) { - return json_decode($this->validateBackendRequest($expectedUrl, $request), true); - }, $requests); - - $bodies = array_filter($bodies, function (array $body) use ($message) { - return $body['type'] === $message['type']; - }); - - $bodies = array_map([$this, 'sortParticipantUsers'], $bodies); - $message = $this->sortParticipantUsers($message); - $this->assertContainsEquals($message, $bodies, json_encode($bodies, JSON_PRETTY_PRINT)); - } - - private function assertNoMessageOfTypeWasSent(Room $room, string $messageType): void { - $requests = $this->controller->getRequests(); - $bodies = array_map(function ($request) use ($room) { - return json_decode($this->validateBackendRequest($this->baseUrl . '/api/v1/room/' . $room->getToken(), $request), true); - }, $requests); - - $bodies = array_filter($bodies, function (array $body) use ($messageType) { - return $body['type'] === $messageType; - }); - - $this->assertEmpty($bodies); - } - - private function sortParticipantUsers(array $message): array { - if ($message['type'] === 'participants') { - usort($message['participants']['users'], static function ($a, $b) { - return - [$a['userId'] ?? '', $a['participantType'], $a['sessionId'], $a['lastPing']] - <=> - [$b['userId'] ?? '', $b['participantType'], $b['sessionId'], $b['lastPing']] - ; - }); - } - if ($message['type'] === 'incall') { - usort($message['incall']['users'], static function ($a, $b) { - return - [$a['userId'] ?? '', $a['participantType'], $a['sessionId'], $a['lastPing']] - <=> - [$b['userId'] ?? '', $b['participantType'], $b['sessionId'], $b['lastPing']] - ; - }); - } - if ($message['type'] === 'switchto') { - usort($message['switchto']['sessions'], static function ($a, $b) { - return $a <=> $b; - }); - } - return $message; - } - - public function testRoomInvite() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - - $this->assertMessageWasSent($room, [ - 'type' => 'invite', - 'invite' => [ - 'userids' => [ - $this->userId, - ], - 'alluserids' => [ - $this->userId, - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'participant-list' => 'refresh', - ], - ], - ]); - } - - public function testRoomDisinvite() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - $this->controller->clearRequests(); - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - $this->participantService->removeUser($room, $testUser, Room::PARTICIPANT_REMOVED); - - $this->assertMessageWasSent($room, [ - 'type' => 'disinvite', - 'disinvite' => [ - 'userids' => [ - $this->userId, - ], - 'alluserids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'participant-list' => 'refresh', - ], - ], - ]); - } - - public function testRoomDisinviteOnRemovalOfGuest() { - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $participant = $this->participantService->joinRoomAsNewGuest($roomService, $room, ''); - $this->controller->clearRequests(); - $this->participantService->removeAttendee($room, $participant, Room::PARTICIPANT_REMOVED); - - $this->assertMessageWasSent($room, [ - 'type' => 'disinvite', - 'disinvite' => [ - 'sessionids' => [ - $participant->getSession()->getSessionId(), - ], - 'alluserids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'participant-list' => 'refresh', - ], - ], - ]); - } - - public function testNoRoomDisinviteOnLeaveOfNormalUser() { - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); - $this->controller->clearRequests(); - $this->participantService->leaveRoomAsSession($room, $participant); - - $this->assertNoMessageOfTypeWasSent($room, 'disinvite'); - } - - public function testRoomDisinviteOnLeaveOfNormalUserWithDuplicatedSession() { - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); - $this->controller->clearRequests(); - $this->participantService->leaveRoomAsSession($room, $participant, true); - - $this->assertMessageWasSent($room, [ - 'type' => 'disinvite', - 'disinvite' => [ - 'sessionids' => [ - $participant->getSession()->getSessionId(), - ], - 'alluserids' => [ - $this->userId, - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'participant-list' => 'refresh', - ], - ], - ]); - } - - public function testRoomDisinviteOnLeaveOfSelfJoinedUser() { - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); - $this->controller->clearRequests(); - - $this->userManager->expects($this->once()) - ->method('get') - ->with($this->userId) - ->willReturn($testUser); - - $this->participantService->leaveRoomAsSession($room, $participant); - - $this->assertMessageWasSent($room, [ - 'type' => 'disinvite', - 'disinvite' => [ - 'userids' => [ - $this->userId, - ], - 'alluserids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'participant-list' => 'refresh', - ], - ], - ]); - } - - public function testRoomDisinviteOnLeaveOfGuest() { - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $participant = $this->participantService->joinRoomAsNewGuest($roomService, $room, ''); - $this->controller->clearRequests(); - $this->participantService->leaveRoomAsSession($room, $participant); - - $this->assertMessageWasSent($room, [ - 'type' => 'disinvite', - 'disinvite' => [ - 'sessionids' => [ - $participant->getSession()->getSessionId(), - ], - 'alluserids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'participant-list' => 'refresh', - ], - ], - ]); - } - - public function testRoomNameChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setName($room, 'Test room'); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => '', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - ], - ], - ]); - } - - public function testRoomDescriptionChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setDescription($room, 'The description'); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => 'The description', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - ], - ], - ]); - } - - public function testRoomPasswordChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setPassword($room, 'password'); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => '', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - ], - ], - ]); - } - - public function testRoomTypeChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setType($room, Room::TYPE_GROUP); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => '', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - ], - ], - ]); - } - - public function testRoomReadOnlyChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setReadOnly($room, Room::READ_ONLY); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => '', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_ONLY, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - ], - ], - ]); - } - - public function testRoomListableChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setListable($room, Room::LISTABLE_ALL); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_ALL, - 'active-since' => null, - 'sip-enabled' => 0, - 'description' => '', - ], - ], - ]); - } - - public function testRoomLobbyStateChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setLobby($room, Webinary::LOBBY_NON_MODERATORS, null); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => '', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NON_MODERATORS, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - ], - ], - ]); - } - - public function testRoomDelete(): void { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - $this->roomService->deleteRoom($room); - - $this->assertMessageWasSent($room, [ - 'type' => 'delete', - 'delete' => [ - 'userids' => [ - $this->userId, - ], - ], - ]); - } - - public function testRoomInCallChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); - $userSession = $participant->getSession()->getSessionId(); - $participant = $this->participantService->getParticipantBySession($room, $userSession); - - $this->participantService->changeInCall($room, $participant, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO); - - $this->assertMessageWasSent($room, [ - 'type' => 'incall', - 'incall' => [ - 'incall' => 7, - 'changed' => [ - [ - 'inCall' => 7, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'nextcloudSessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - ], - ], - 'users' => [ - [ - 'inCall' => 7, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'nextcloudSessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - ], - ], - ], - ]); - - $this->controller->clearRequests(); - - $guestParticipant = $this->participantService->joinRoomAsNewGuest($roomService, $room, ''); - $guestSession = $guestParticipant->getSession()->getSessionId(); - $guestParticipant = $this->participantService->getParticipantBySession($room, $guestSession); - $this->participantService->changeInCall($room, $guestParticipant, Participant::FLAG_IN_CALL); - - $this->assertMessageWasSent($room, [ - 'type' => 'incall', - 'incall' => [ - 'incall' => 1, - 'changed' => [ - [ - 'inCall' => 1, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'nextcloudSessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - ], - ], - 'users' => [ - [ - 'inCall' => 7, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'nextcloudSessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - ], - [ - 'inCall' => 1, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'nextcloudSessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - ], - ], - ], - ]); - - $this->controller->clearRequests(); - $this->participantService->changeInCall($room, $participant, Participant::FLAG_DISCONNECTED); - - $this->assertMessageWasSent($room, [ - 'type' => 'incall', - 'incall' => [ - 'incall' => 0, - 'changed' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'nextcloudSessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - ], - ], - 'users' => [ - [ - 'inCall' => 1, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'nextcloudSessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - ], - ], - ], - ]); - } - - public function testRoomInCallChangedWhenLeavingConversationWhileInCall() { - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - ]]); - $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); - $userSession = $participant->getSession()->getSessionId(); - $this->participantService->changeInCall($room, $participant, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO); - $this->controller->clearRequests(); - $this->participantService->leaveRoomAsSession($room, $participant); - - $this->assertMessageWasSent($room, [ - 'type' => 'incall', - 'incall' => [ - 'incall' => 0, - 'changed' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'nextcloudSessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - ], - ], - 'users' => [ - ], - ], - ]); - } - - public function testRoomPropertiesEvent(): void { - $listener = static function (SignalingRoomPropertiesEvent $event) { - $room = $event->getRoom(); - $event->setProperty('foo', 'bar'); - $event->setProperty('room', $room->getToken()); - }; - - $this->dispatcher->addListener(Room::EVENT_BEFORE_SIGNALING_PROPERTIES, $listener); - - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->controller->clearRequests(); - $this->roomService->setName($room, 'Test room'); - - $this->assertMessageWasSent($room, [ - 'type' => 'update', - 'update' => [ - 'userids' => [ - ], - 'properties' => [ - 'name' => $room->getDisplayName(''), - 'description' => '', - 'type' => $room->getType(), - 'lobby-state' => Webinary::LOBBY_NONE, - 'lobby-timer' => null, - 'read-only' => Room::READ_WRITE, - 'listable' => Room::LISTABLE_NONE, - 'active-since' => null, - 'sip-enabled' => 0, - 'foo' => 'bar', - 'room' => $room->getToken(), - ], - ], - ]); - } - - public function testParticipantsTypeChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $this->userId, - 'displayName' => $this->displayName, - ]]); - - /** @var IUser|MockObject $testUser */ - $testUser = $this->createMock(IUser::class); - $testUser->expects($this->any()) - ->method('getUID') - ->willReturn($this->userId); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); - $userSession = $participant->getSession()->getSessionId(); - $participant = $this->participantService->getParticipantBySession($room, $userSession); - - $this->participantService->updateParticipantType($room, $participant, Participant::MODERATOR); - - $this->assertMessageWasSent($room, [ - 'type' => 'participants', - 'participants' => [ - 'changed' => [ - [ - 'permissions' => ['publish-audio', 'publish-video', 'publish-screen', 'control'], - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - ], - 'users' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - ], - ], - ]); - - $this->controller->clearRequests(); - - $guestParticipant = $this->participantService->joinRoomAsNewGuest($roomService, $room, ''); - $guestSession = $guestParticipant->getSession()->getSessionId(); - $guestParticipant = $this->participantService->getParticipantBySession($room, $guestSession); - - $guestDisplayName = 'GuestDisplayName'; - $guestParticipant->getAttendee()->setDisplayName($guestDisplayName); - - $this->participantService->updateParticipantType($room, $guestParticipant, Participant::GUEST_MODERATOR); - - $this->assertMessageWasSent($room, [ - 'type' => 'participants', - 'participants' => [ - 'changed' => [ - [ - 'permissions' => ['publish-audio', 'publish-video', 'publish-screen'], - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST_MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'displayName' => $guestDisplayName, - ], - ], - 'users' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST_MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'displayName' => $guestDisplayName, - ], - ], - ], - ]); - - $this->controller->clearRequests(); - $notJoinedUserId = 'not-joined-user-id'; - $notJoinedDisplayName = 'not-joined-display-name'; - $this->participantService->addUsers($room, [[ - 'actorType' => 'users', - 'actorId' => $notJoinedUserId, - 'displayName' => $notJoinedDisplayName, - ]]); - - $notJoinedParticipant = $this->participantService->getParticipant($room, $notJoinedUserId); - $this->participantService->updateParticipantType($room, $notJoinedParticipant, Participant::MODERATOR); - - $this->assertMessageWasSent($room, [ - 'type' => 'participants', - 'participants' => [ - 'changed' => [ - ], - 'users' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => 0, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_CUSTOM, - 'userId' => $notJoinedUserId, - 'displayName' => $notJoinedDisplayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST_MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'displayName' => $guestDisplayName, - ], - ], - ], - ]); - - $this->controller->clearRequests(); - $this->participantService->updateParticipantType($room, $participant, Participant::USER); - - $this->assertMessageWasSent($room, [ - 'type' => 'participants', - 'participants' => [ - 'changed' => [ - [ - 'permissions' => ['publish-audio', 'publish-video', 'publish-screen'], - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - ], - 'users' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => 0, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_CUSTOM, - 'userId' => $notJoinedUserId, - 'displayName' => $notJoinedDisplayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST_MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT, - 'displayName' => $guestDisplayName, - ], - ], - ], - ]); - - $this->controller->clearRequests(); - $this->participantService->updateParticipantType($room, $guestParticipant, Participant::GUEST); - - $this->assertMessageWasSent($room, [ - 'type' => 'participants', - 'participants' => [ - 'changed' => [ - [ - 'permissions' => ['publish-audio', 'publish-video', 'publish-screen'], - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'displayName' => $guestDisplayName, - ], - ], - 'users' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => 0, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_CUSTOM, - 'userId' => $notJoinedUserId, - 'displayName' => $notJoinedDisplayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'displayName' => $guestDisplayName, - ], - ], - ], - ]); - - $this->controller->clearRequests(); - $this->participantService->updatePermissions($room, $guestParticipant, Attendee::PERMISSIONS_MODIFY_SET, Attendee::PERMISSIONS_CUSTOM); - - $this->assertMessageWasSent($room, [ - 'type' => 'participants', - 'participants' => [ - 'changed' => [ - [ - 'permissions' => [], - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => Attendee::PERMISSIONS_CUSTOM, - 'displayName' => $guestDisplayName, - ], - ], - 'users' => [ - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $userSession, - 'participantType' => Participant::USER, - 'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE), - 'userId' => $this->userId, - 'displayName' => $this->displayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => 0, - 'participantType' => Participant::MODERATOR, - 'participantPermissions' => Attendee::PERMISSIONS_CUSTOM, - 'userId' => $notJoinedUserId, - 'displayName' => $notJoinedDisplayName, - ], - [ - 'inCall' => 0, - 'lastPing' => 0, - 'sessionId' => $guestSession, - 'participantType' => Participant::GUEST, - 'participantPermissions' => Attendee::PERMISSIONS_CUSTOM, - 'displayName' => $guestDisplayName, - ], - ], - ], - ]); - } - - public function testBreakoutRoomStart() { - $room = $this->manager->createRoom(Room::TYPE_GROUP); - $this->participantService->addUsers($room, [ - [ - 'actorType' => 'users', - 'actorId' => 'userId1', - ], - [ - 'actorType' => 'users', - 'actorId' => 'userId2', - ], - [ - 'actorType' => 'users', - 'actorId' => 'userId3', - ], - [ - 'actorType' => 'users', - 'actorId' => 'userIdModerator1', - ], - ]); - - /** @var IUser|MockObject $user1 */ - $user1 = $this->createMock(IUser::class); - $user1->expects($this->any()) - ->method('getUID') - ->willReturn('userId1'); - - /** @var IUser|MockObject $user2 */ - $user2 = $this->createMock(IUser::class); - $user2->expects($this->any()) - ->method('getUID') - ->willReturn('userId2'); - - /** @var IUser|MockObject $user3 */ - $user3 = $this->createMock(IUser::class); - $user3->expects($this->any()) - ->method('getUID') - ->willReturn('userId3'); - - /** @var IUser|MockObject $userModerator1 */ - $userModerator1 = $this->createMock(IUser::class); - $userModerator1->expects($this->any()) - ->method('getUID') - ->willReturn('userIdModerator1'); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $participant1 = $this->participantService->joinRoom($roomService, $room, $user1, ''); - $sessionId1 = $participant1->getSession()->getSessionId(); - $participant1 = $this->participantService->getParticipantBySession($room, $sessionId1); - - $participant2 = $this->participantService->joinRoom($roomService, $room, $user2, ''); - $sessionId2 = $participant2->getSession()->getSessionId(); - $participant2 = $this->participantService->getParticipantBySession($room, $sessionId2); - - $participant3 = $this->participantService->joinRoom($roomService, $room, $user3, ''); - $sessionId3 = $participant3->getSession()->getSessionId(); - $participant3 = $this->participantService->getParticipantBySession($room, $sessionId3); - - $participant3b = $this->participantService->joinRoom($roomService, $room, $user3, ''); - $sessionId3b = $participant3b->getSession()->getSessionId(); - $participant3b = $this->participantService->getParticipantBySession($room, $sessionId3b); - - $participantModerator1 = $this->participantService->joinRoom($roomService, $room, $userModerator1, ''); - $sessionIdModerator1 = $participantModerator1->getSession()->getSessionId(); - $participantModerator1 = $this->participantService->getParticipantBySession($room, $sessionIdModerator1); - - $this->participantService->updateParticipantType($room, $participantModerator1, Participant::MODERATOR); - - // Third room is explicitly empty. - $attendeeMap = []; - $attendeeMap[$participant1->getSession()->getAttendeeId()] = 0; - $attendeeMap[$participant2->getSession()->getAttendeeId()] = 1; - $attendeeMap[$participant3->getSession()->getAttendeeId()] = 0; - $attendeeMap[$participantModerator1->getSession()->getAttendeeId()] = 0; - - $breakoutRooms = $this->breakoutRoomService->setupBreakoutRooms($room, BreakoutRoom::MODE_MANUAL, 3, json_encode($attendeeMap)); - - $this->controller->clearRequests(); - - $this->breakoutRoomService->startBreakoutRooms($room); - - $this->assertMessageCount($room, 'switchto', 2); - - $this->assertMessageWasSent($room, [ - 'type' => 'switchto', - 'switchto' => [ - 'roomid' => $breakoutRooms[0]->getToken(), - 'sessions' => [ - $sessionId1, - $sessionId3, - $sessionId3b, - ], - ], - ]); - - $this->assertMessageWasSent($room, [ - 'type' => 'switchto', - 'switchto' => [ - 'roomid' => $breakoutRooms[1]->getToken(), - 'sessions' => [ - $sessionId2, - ], - ], - ]); - } - - public function testBreakoutRoomStop() { - $room = $this->manager->createRoom(Room::TYPE_GROUP); - $this->participantService->addUsers($room, [ - [ - 'actorType' => 'users', - 'actorId' => 'userId1', - ], - [ - 'actorType' => 'users', - 'actorId' => 'userId2', - ], - [ - 'actorType' => 'users', - 'actorId' => 'userId3', - ], - [ - 'actorType' => 'users', - 'actorId' => 'userIdModerator1', - ], - ]); - - /** @var IUser|MockObject $user1 */ - $user1 = $this->createMock(IUser::class); - $user1->expects($this->any()) - ->method('getUID') - ->willReturn('userId1'); - - /** @var IUser|MockObject $user2 */ - $user2 = $this->createMock(IUser::class); - $user2->expects($this->any()) - ->method('getUID') - ->willReturn('userId2'); - - /** @var IUser|MockObject $user3 */ - $user3 = $this->createMock(IUser::class); - $user3->expects($this->any()) - ->method('getUID') - ->willReturn('userId3'); - - /** @var IUser|MockObject $userModerator1 */ - $userModerator1 = $this->createMock(IUser::class); - $userModerator1->expects($this->any()) - ->method('getUID') - ->willReturn('userIdModerator1'); - - $roomService = $this->createMock(RoomService::class); - $roomService->method('verifyPassword') - ->willReturn(['result' => true, 'url' => '']); - - $participant1 = $this->participantService->joinRoom($roomService, $room, $user1, ''); - $sessionId1 = $participant1->getSession()->getSessionId(); - $participant1 = $this->participantService->getParticipantBySession($room, $sessionId1); - - $participant2 = $this->participantService->joinRoom($roomService, $room, $user2, ''); - $sessionId2 = $participant2->getSession()->getSessionId(); - $participant2 = $this->participantService->getParticipantBySession($room, $sessionId2); - - $participant3 = $this->participantService->joinRoom($roomService, $room, $user3, ''); - $sessionId3 = $participant3->getSession()->getSessionId(); - $participant3 = $this->participantService->getParticipantBySession($room, $sessionId3); - - $participantModerator1 = $this->participantService->joinRoom($roomService, $room, $userModerator1, ''); - $sessionIdModerator1 = $participantModerator1->getSession()->getSessionId(); - $participantModerator1 = $this->participantService->getParticipantBySession($room, $sessionIdModerator1); - - $this->participantService->updateParticipantType($room, $participantModerator1, Participant::MODERATOR); - - // Third room is explicitly empty. - $attendeeMap = []; - $attendeeMap[$participant1->getSession()->getAttendeeId()] = 0; - $attendeeMap[$participant2->getSession()->getAttendeeId()] = 1; - $attendeeMap[$participant3->getSession()->getAttendeeId()] = 0; - $attendeeMap[$participantModerator1->getSession()->getAttendeeId()] = 0; - - $breakoutRooms = $this->breakoutRoomService->setupBreakoutRooms($room, BreakoutRoom::MODE_MANUAL, 3, json_encode($attendeeMap)); - - $this->breakoutRoomService->startBreakoutRooms($room); - - $participant1 = $this->participantService->joinRoom($roomService, $breakoutRooms[0], $user1, ''); - $sessionId1 = $participant1->getSession()->getSessionId(); - - $participant2 = $this->participantService->joinRoom($roomService, $breakoutRooms[1], $user2, ''); - $sessionId2 = $participant2->getSession()->getSessionId(); - - $participant3 = $this->participantService->joinRoom($roomService, $breakoutRooms[0], $user3, ''); - $sessionId3 = $participant3->getSession()->getSessionId(); - - $participant3b = $this->participantService->joinRoom($roomService, $breakoutRooms[0], $user3, ''); - $sessionId3b = $participant3b->getSession()->getSessionId(); - - $participantModerator1 = $this->participantService->joinRoom($roomService, $breakoutRooms[0], $userModerator1, ''); - $sessionIdModerator1 = $participantModerator1->getSession()->getSessionId(); - - $this->controller->clearRequests(); - - $this->breakoutRoomService->stopBreakoutRooms($room); - - $this->assertMessageCount($breakoutRooms[0], 'switchto', 1); - $this->assertMessageCount($breakoutRooms[1], 'switchto', 1); - $this->assertMessageCount($breakoutRooms[2], 'switchto', 0); - - $this->assertMessageWasSent($breakoutRooms[0], [ - 'type' => 'switchto', - 'switchto' => [ - 'roomid' => $room->getToken(), - 'sessions' => [ - $sessionId1, - $sessionId3, - $sessionId3b, - $sessionIdModerator1, - ], - ], - ]); - - $this->assertMessageWasSent($breakoutRooms[1], [ - 'type' => 'switchto', - 'switchto' => [ - 'roomid' => $room->getToken(), - 'sessions' => [ - $sessionId2, - ], - ], - ]); - } - - public function testRecordingStatusChanged() { - $room = $this->manager->createRoom(Room::TYPE_PUBLIC); - $this->roomService->setCallRecording($room, Room::RECORDING_VIDEO); - - $this->assertMessageWasSent($room, [ - 'type' => 'message', - 'message' => [ - 'data' => [ - 'type' => 'recording', - 'recording' => [ - 'status' => Room::RECORDING_VIDEO, - ], - ], - ], - ]); - } -} diff --git a/tests/php/Signaling/ListenerTest.php b/tests/php/Signaling/ListenerTest.php new file mode 100644 index 00000000000..f1ccaeab6e7 --- /dev/null +++ b/tests/php/Signaling/ListenerTest.php @@ -0,0 +1,328 @@ +. + * + */ + +namespace OCA\Talk\Tests\php\Signaling; + +use OCA\Talk\Config; +use OCA\Talk\Events\ARoomModifiedEvent; +use OCA\Talk\Events\BeforeRoomDeletedEvent; +use OCA\Talk\Events\ChatMessageSentEvent; +use OCA\Talk\Events\GuestsCleanedUpEvent; +use OCA\Talk\Events\LobbyModifiedEvent; +use OCA\Talk\Events\RoomModifiedEvent; +use OCA\Talk\Events\SystemMessageSentEvent; +use OCA\Talk\Events\SystemMessagesMultipleSentEvent; +use OCA\Talk\Manager; +use OCA\Talk\Room; +use OCA\Talk\Service\ParticipantService; +use OCA\Talk\Service\SessionService; +use OCA\Talk\Signaling\BackendNotifier; +use OCA\Talk\Signaling\Listener; +use OCA\Talk\Signaling\Messages; +use OCA\Talk\Webinary; +use OCP\Comments\IComment; +use Test\TestCase; + +/** + * @group DB + */ +class ListenerTest extends TestCase { + protected ?BackendNotifier $backendNotifier = null; + protected ?Manager $manager = null; + protected ?ParticipantService $participantService = null; + protected ?SessionService $sessionService = null; + protected ?Listener $listener = null; + + public function setUp(): void { + parent::setUp(); + + $this->backendNotifier = $this->createMock(BackendNotifier::class); + $this->manager = $this->createMock(Manager::class); + $this->participantService = $this->createMock(ParticipantService::class); + $this->sessionService = $this->createMock(SessionService::class); + + $this->listener = new Listener( + $this->createMock(Config::class), + $this->createMock(Messages::class), + $this->backendNotifier, + $this->manager, + $this->participantService, + $this->sessionService, + ); + } + + public function dataRoomModified(): array { + return [ + [ + ARoomModifiedEvent::PROPERTY_NAME, + 'Test room', + 'name', + ], + [ + ARoomModifiedEvent::PROPERTY_DESCRIPTION, + 'The description', + '', + ], + [ + ARoomModifiedEvent::PROPERTY_PASSWORD, + 'password', + null, + ], + [ + ARoomModifiedEvent::PROPERTY_TYPE, + Room::TYPE_PUBLIC, + Room::TYPE_GROUP, + ], + [ + ARoomModifiedEvent::PROPERTY_READ_ONLY, + Room::READ_ONLY, + Room::READ_WRITE, + ], + [ + ARoomModifiedEvent::PROPERTY_LISTABLE, + Room::LISTABLE_ALL, + Room::LISTABLE_NONE, + ], + ]; + } + + /** + * @dataProvider dataRoomModified + * @param string $property + * @param mixed $newValue + * @param mixed $oldValue + */ + public function testRoomModified(string $property, mixed $newValue, mixed $oldValue): void { + $room = $this->createMock(Room::class); + + $event = new RoomModifiedEvent( + $room, + $property, + $newValue, + $oldValue, + null, + ); + + $this->backendNotifier->expects($this->once()) + ->method('roomModified') + ->with($room); + $this->listener->handle($event); + } + + public function testRecordingStatusChanged(): void { + $room = $this->createMock(Room::class); + + $event = new RoomModifiedEvent( + $room, + ARoomModifiedEvent::PROPERTY_CALL_RECORDING, + Room::RECORDING_VIDEO, + Room::RECORDING_NONE, + null, + ); + + $this->backendNotifier->expects($this->once()) + ->method('sendRoomMessage') + ->with($room, [ + 'type' => 'recording', + 'recording' => [ + 'status' => $event->getNewValue(), + ], + ]); + $this->listener->handle($event); + } + + public function dataRoomLobbyModified(): array { + return [ + [ + Webinary::LOBBY_NON_MODERATORS, + Webinary::LOBBY_NONE, + null, + true, + ], + [ + Webinary::LOBBY_NONE, + Webinary::LOBBY_NON_MODERATORS, + null, + false, + ], + [ + Webinary::LOBBY_NONE, + Webinary::LOBBY_NON_MODERATORS, + new \DateTime(), + false, + ], + ]; + } + + /** + * @dataProvider dataRoomLobbyModified + * @param int $newValue + * @param int $oldValue + * @param \DateTime|null $lobbyTimer + * @param bool $timerReached + */ + public function testRoomLobbyModified(int $newValue, int $oldValue, ?\DateTime $lobbyTimer, bool $timerReached): void { + $room = $this->createMock(Room::class); + + $event = new LobbyModifiedEvent( + $room, + $newValue, + $oldValue, + $lobbyTimer, + $timerReached, + ); + + $this->backendNotifier->expects($this->once()) + ->method('roomModified') + ->with($room); + $this->listener->handle($event); + } + + public function testRoomLobbyRemoved() { + $room = $this->createMock(Room::class); + + $event = new LobbyModifiedEvent( + $room, + Webinary::LOBBY_NONE, + Webinary::LOBBY_NON_MODERATORS, + null, + true, + ); + + $this->backendNotifier->expects($this->once()) + ->method('roomModified') + ->with($room); + $this->listener->handle($event); + } + + public function testRoomDelete(): void { + $room = $this->createMock(Room::class); + + $event = new BeforeRoomDeletedEvent( + $room + ); + + $this->participantService->method('getParticipantUserIds') + ->with($room) + ->willReturn(['user1', 'user2']); + + $this->backendNotifier->expects($this->once()) + ->method('roomDeleted') + ->with($room, ['user1', 'user2']); + + $this->listener->handle($event); + } + + public function testGuestsCleanedUpEvent(): void { + $room = $this->createMock(Room::class); + + $event = new GuestsCleanedUpEvent( + $room + ); + + $this->backendNotifier->expects($this->once()) + ->method('participantsModified') + ->with($room, []); + + $this->listener->handle($event); + } + + public function testChatMessageSentEvent(): void { + $room = $this->createMock(Room::class); + $comment = $this->createMock(IComment::class); + + $event = new ChatMessageSentEvent( + $room, + $comment, + ); + + $this->backendNotifier->expects($this->once()) + ->method('sendRoomMessage') + ->with($room, [ + 'type' => 'chat', + 'chat' => [ + 'refresh' => true, + ], + ]); + + $this->listener->handle($event); + } + + public function testSystemMessageSentEvent(): void { + $room = $this->createMock(Room::class); + $comment = $this->createMock(IComment::class); + + $event = new SystemMessageSentEvent( + $room, + $comment, + skipLastActivityUpdate: false + ); + + $this->backendNotifier->expects($this->once()) + ->method('sendRoomMessage') + ->with($room, [ + 'type' => 'chat', + 'chat' => [ + 'refresh' => true, + ], + ]); + + $this->listener->handle($event); + } + + public function testSystemMessageSentEventSkippingUpdate(): void { + $room = $this->createMock(Room::class); + $comment = $this->createMock(IComment::class); + + $event = new SystemMessageSentEvent( + $room, + $comment, + skipLastActivityUpdate: true + ); + + $this->backendNotifier->expects($this->never()) + ->method('sendRoomMessage'); + + $this->listener->handle($event); + } + + public function testSystemMessagesMultipleSentEvent(): void { + $room = $this->createMock(Room::class); + $comment = $this->createMock(IComment::class); + + $event = new SystemMessagesMultipleSentEvent( + $room, + $comment, + ); + + $this->backendNotifier->expects($this->once()) + ->method('sendRoomMessage') + ->with($room, [ + 'type' => 'chat', + 'chat' => [ + 'refresh' => true, + ], + ]); + + $this->listener->handle($event); + } +}