diff --git a/docs/chat.md b/docs/chat.md index 4438c17360f..22064c73e8d 100644 --- a/docs/chat.md +++ b/docs/chat.md @@ -40,6 +40,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1` `actorDisplayName` | string | Display name of the message author `timestamp` | int | Timestamp in seconds and UTC time zone `systemMessage` | string | empty for normal chat message or the type of the system message (untranslated) + `messageType` | string | Currently known types are `comment`, `system` and `command` `message` | string | Message string with placeholders (see [Rich Object String](https://github.com/nextcloud/server/issues/1706)) `messageParameters` | array | Message parameters for `message` (see [Rich Object String](https://github.com/nextcloud/server/issues/1706)) @@ -53,6 +54,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1` ------|------|------------ `message` | string | The message the user wants to say `actorDisplayName` | string | Guest display name (ignored for logged in users) + `replyTo` | int | The message ID this message is a reply to (only allowed for messages from the same conversation and when the message type is not `system` or `command`) * Response: - Header: diff --git a/js/models/chatmessage.js b/js/models/chatmessage.js index dd27de33cf2..b435642d728 100644 --- a/js/models/chatmessage.js +++ b/js/models/chatmessage.js @@ -49,7 +49,8 @@ actorDisplayName: '', timestamp: 0, message: '', - messageParameters: [] + messageParameters: [], + replyTo: 0 }, url: function() { diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php index 11a101f465b..36550ef4a9d 100644 --- a/lib/Chat/ChatManager.php +++ b/lib/Chat/ChatManager.php @@ -138,10 +138,11 @@ public function addChangelogMessage(Room $chat, string $message): IComment { * @param string $actorType * @param string $actorId * @param string $message + * @param IComment|null $replyTo * @param \DateTime $creationDateTime * @return IComment */ - public function sendMessage(Room $chat, Participant $participant, string $actorType, string $actorId, string $message, \DateTime $creationDateTime): IComment { + public function sendMessage(Room $chat, Participant $participant, string $actorType, string $actorId, string $message, \DateTime $creationDateTime, ?IComment $replyTo): IComment { $comment = $this->commentsManager->create($actorType, $actorId, 'chat', (string) $chat->getId()); $comment->setMessage($message, self::MAX_CHAT_LENGTH); $comment->setCreationDateTime($creationDateTime); @@ -149,6 +150,10 @@ public function sendMessage(Room $chat, Participant $participant, string $actorT // comment $comment->setVerb('comment'); + if ($replyTo instanceof IComment) { + $comment->setParentId($replyTo->getId()); + } + $this->dispatcher->dispatch(self::class . '::preSendMessage', new GenericEvent($chat, [ 'comment' => $comment, 'room' => $chat, @@ -161,13 +166,18 @@ public function sendMessage(Room $chat, Participant $participant, string $actorT // Update last_message $chat->setLastMessage($comment); - $mentionedUsers = $this->notifier->notifyMentionedUsers($chat, $comment); - if (!empty($mentionedUsers)) { - $chat->markUsersAsMentioned($mentionedUsers, (int) $comment->getId()); + $alreadyNotifiedUsers = []; + if ($replyTo instanceof IComment) { + $alreadyNotifiedUsers = $this->notifier->notifyReplyToAuthor($chat, $comment, $replyTo); + } + + $alreadyNotifiedUsers = $this->notifier->notifyMentionedUsers($chat, $comment, $alreadyNotifiedUsers); + if (!empty($alreadyNotifiedUsers)) { + $chat->markUsersAsMentioned($alreadyNotifiedUsers, (int) $comment->getId()); } // User was not mentioned, send a normal notification - $this->notifier->notifyOtherParticipant($chat, $comment, $mentionedUsers); + $this->notifier->notifyOtherParticipant($chat, $comment, $alreadyNotifiedUsers); $this->dispatcher->dispatch(self::class . '::sendMessage', new GenericEvent($chat, [ 'comment' => $comment, @@ -180,6 +190,22 @@ public function sendMessage(Room $chat, Participant $participant, string $actorT return $comment; } + /** + * @param Room $chat + * @param string $parentId + * @return IComment + * @throws NotFoundException + */ + public function getParentComment(Room $chat, string $parentId): IComment { + $comment = $this->commentsManager->get($parentId); + + if ($comment->getObjectType() !== 'chat' || $comment->getObjectId() !== (string) $chat->getId()) { + throw new NotFoundException('Parent not found in the right context'); + } + + return $comment; + } + public function getLastReadMessageFromLegacy(Room $chat, IUser $user): int { $marker = $this->commentsManager->getReadMark('chat', $chat->getId(), $user); if ($marker === null) { diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php index 4f44d034b41..8b744bee0f9 100644 --- a/lib/Chat/Notifier.php +++ b/lib/Chat/Notifier.php @@ -76,9 +76,10 @@ public function __construct(INotificationManager $notificationManager, * * @param Room $chat * @param IComment $comment + * @param string[] $alreadyNotifiedUsers * @return string[] Users that were mentioned */ - public function notifyMentionedUsers(Room $chat, IComment $comment): array { + public function notifyMentionedUsers(Room $chat, IComment $comment, array $alreadyNotifiedUsers): array { $mentionedUserIds = $this->getMentionedUserIds($comment); if (empty($mentionedUserIds)) { return []; @@ -92,13 +93,48 @@ public function notifyMentionedUsers(Room $chat, IComment $comment): array { $notification = $this->createNotification($chat, $comment, 'mention'); foreach ($mentionedUserIds as $mentionedUserId) { + if (in_array($mentionedUserId, $alreadyNotifiedUsers, true)) { + continue; + } + if ($this->shouldUserBeNotified($mentionedUserId, $comment)) { $notification->setUser($mentionedUserId); $this->notificationManager->notify($notification); + $alreadyNotifiedUsers[] = $mentionedUserId; } } - return $mentionedUserIds; + return $alreadyNotifiedUsers; + } + + /** + * Notifies the author that wrote the comment which was replied to + * + * The comment must be a chat message comment. That is, its "objectId" must + * be the room ID. + * + * The author of the message is notified only if he is still able to participate in the room + * + * @param Room $chat + * @param IComment $comment + * @param IComment $replyTo + * @return string[] Users that were mentioned + */ + public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $replyTo): array { + if ($replyTo->getActorType() !== 'users') { + // No reply notification when the replyTo-author was not a user + return []; + } + + if (!$this->shouldUserBeNotified($replyTo->getActorId(), $comment)) { + return []; + } + + $notification = $this->createNotification($chat, $comment, 'reply'); + $notification->setUser($replyTo->getActorId()); + $this->notificationManager->notify($notification); + + return [$replyTo->getActorId()]; } /** @@ -112,9 +148,9 @@ public function notifyMentionedUsers(Room $chat, IComment $comment): array { * * @param Room $chat * @param IComment $comment - * @param string[] $mentionedUsers + * @param string[] $alreadyNotifiedUsers */ - public function notifyOtherParticipant(Room $chat, IComment $comment, array $mentionedUsers): void { + public function notifyOtherParticipant(Room $chat, IComment $comment, array $alreadyNotifiedUsers): void { $participants = $chat->getParticipantsByNotificationLevel(Participant::NOTIFY_ALWAYS); $notification = $this->createNotification($chat, $comment, 'chat'); @@ -128,7 +164,7 @@ public function notifyOtherParticipant(Room $chat, IComment $comment, array $men continue; } - if (\in_array($participant->getUser(), $mentionedUsers, true)) { + if (\in_array($participant->getUser(), $alreadyNotifiedUsers, true)) { continue; } @@ -154,7 +190,7 @@ public function notifyOtherParticipant(Room $chat, IComment $comment, array $men continue; } - if (\in_array($participant->getUser(), $mentionedUsers, true)) { + if (\in_array($participant->getUser(), $alreadyNotifiedUsers, true)) { continue; } diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php index 2fac2964e30..57ba2c8ec27 100644 --- a/lib/Chat/Parser/SystemMessage.php +++ b/lib/Chat/Parser/SystemMessage.php @@ -235,8 +235,7 @@ public function parseMessage(Message $chatMessage): void { throw new \OutOfBoundsException('Unknown subject'); } - $comment->setMessage($message, ChatManager::MAX_CHAT_LENGTH); - $chatMessage->setMessage($parsedMessage, $parsedParameters); + $chatMessage->setMessage($parsedMessage, $parsedParameters, $message); } /** diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index 155b0528f34..293b5c9a065 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -28,6 +28,7 @@ use OCA\Spreed\Chat\ChatManager; use OCA\Spreed\Chat\MessageParser; use OCA\Spreed\GuestManager; +use OCA\Spreed\Model\Message; use OCA\Spreed\Room; use OCA\Spreed\TalkSession; use OCP\AppFramework\Http; @@ -37,6 +38,7 @@ use OCP\Collaboration\Collaborators\ISearchResult; use OCP\Comments\IComment; use OCP\Comments\MessageTooLongException; +use OCP\Comments\NotFoundException; use OCP\IL10N; use OCP\IRequest; use OCP\IUserManager; @@ -118,11 +120,12 @@ public function __construct(string $appName, * * @param string $message the message to send * @param string $actorDisplayName for guests + * @param int $replyTo Parent id which this message is a reply to * @return DataResponse the status code is "201 Created" if successful, and * "404 Not found" if the room or session for a guest user was not * found". */ - public function sendMessage(string $message, string $actorDisplayName = ''): DataResponse { + public function sendMessage(string $message, string $actorDisplayName = '', int $replyTo = 0): DataResponse { if ($this->userId === null) { $actorType = 'guests'; @@ -146,11 +149,27 @@ public function sendMessage(string $message, string $actorDisplayName = ''): Dat return new DataResponse([], Http::STATUS_NOT_FOUND); } + $parent = $parentMessage = null; + if ($replyTo !== 0) { + try { + $parent = $this->chatManager->getParentComment($this->room, (string) $replyTo); + } catch (NotFoundException $e) { + // Someone is trying to reply cross-rooms or to a non-existing message + return new DataResponse([], Http::STATUS_BAD_REQUEST); + } + + $parentMessage = $this->messageParser->createMessage($this->room, $this->participant, $parent, $this->l); + $this->messageParser->parseMessage($parentMessage); + if ($parentMessage->getMessageType() === 'system' || $parentMessage->getMessageType() === 'command') { + return new DataResponse([], Http::STATUS_BAD_REQUEST); + } + } + $this->room->ensureOneToOneRoomIsFilled(); $creationDateTime = $this->timeFactory->getDateTime('now', new \DateTimeZone('UTC')); try { - $comment = $this->chatManager->sendMessage($this->room, $this->participant, $actorType, $actorId, $message, $creationDateTime); + $comment = $this->chatManager->sendMessage($this->room, $this->participant, $actorType, $actorId, $message, $creationDateTime, $parent); } catch (MessageTooLongException $e) { return new DataResponse([], Http::STATUS_REQUEST_ENTITY_TOO_LARGE); } catch (\Exception $e) { @@ -164,17 +183,11 @@ public function sendMessage(string $message, string $actorDisplayName = ''): Dat return new DataResponse([], Http::STATUS_CREATED); } - return new DataResponse([ - 'id' => (int) $comment->getId(), - 'token' => $this->room->getToken(), - 'actorType' => $chatMessage->getActorType(), - 'actorId' => $chatMessage->getActorId(), - 'actorDisplayName' => $chatMessage->getActorDisplayName(), - 'timestamp' => $comment->getCreationDateTime()->getTimestamp(), - 'message' => $chatMessage->getMessage(), - 'messageParameters' => $chatMessage->getMessageParameters(), - 'systemMessage' => $chatMessage->getMessageType() === 'system' ? $comment->getMessage() : '', - ], Http::STATUS_CREATED); + $data = $this->messageToData($chatMessage); + if ($parentMessage instanceof Message) { + $data['parent'] = $this->messageToData($parentMessage); + } + return new DataResponse($data, Http::STATUS_CREATED); } /** @@ -241,28 +254,76 @@ public function receiveMessages(int $lookIntoFuture, int $limit = 100, int $last return new DataResponse([], Http::STATUS_NOT_MODIFIED); } - $messages = []; + $i = 0; + $messages = $commentIdToIndex = $parentIds = []; foreach ($comments as $comment) { + $id = (int) $comment->getId(); $message = $this->messageParser->createMessage($this->room, $this->participant, $comment, $this->l); $this->messageParser->parseMessage($message); if (!$message->getVisibility()) { + $commentIdToIndex[$id] = null; continue; } - $messages[] = [ - 'id' => (int) $comment->getId(), - 'token' => $this->room->getToken(), - 'actorType' => $message->getActorType(), - 'actorId' => $message->getActorId(), - 'actorDisplayName' => $message->getActorDisplayName(), - 'timestamp' => $comment->getCreationDateTime()->getTimestamp(), - 'message' => $message->getMessage(), - 'messageParameters' => $message->getMessageParameters(), - 'systemMessage' => $message->getMessageType() === 'system' ? $comment->getMessage() : '', - ]; + if ($comment->getParentId() !== '0') { + $parentIds[$id] = $comment->getParentId(); + } + + $messages[] = $this->messageToData($message); + $commentIdToIndex[$id] = $i; + $i++; } + /** + * Set the parent for reply-messages + */ + $loadedParents = []; + foreach ($parentIds as $commentId => $parentId) { + $commentKey = $commentIdToIndex[$commentId]; + + // Parent is already parsed in the message list + if (!empty($commentIdToIndex[$parentId])) { + $parentKey = $commentIdToIndex[$parentId]; + $messages[$commentKey]['parent'] = $messages[$parentKey]; + + // We don't show nested parents… + unset($messages[$commentKey]['parent']['parent']); + continue; + } + + // Parent was already loaded manually for another comment + if (!empty($loadedParents[$parentId])) { + $messages[$commentKey]['parent'] = $loadedParents[$parentId]; + continue; + } + + // Parent was not skipped due to visibility, so we need to manually grab it. + if (!isset($commentIdToIndex[$parentId])) { + try { + $comment = $this->chatManager->getParentComment($this->room, $parentId); + $message = $this->messageParser->createMessage($this->room, $this->participant, $comment, $this->l); + $this->messageParser->parseMessage($message); + + if ($message->getVisibility()) { + $loadedParents[$parentId] = $this->messageToData($message); + $messages[$commentKey]['parent'] = $loadedParents[$parentId]; + } else { + $loadedParents[$parentId] = [ + 'id' => $parentId, + 'deleted' => true, + ]; + } + } catch (NotFoundException $e) { + } + } + + // Message is not visible to the user + $messages[$commentKey]['parent'] = [ + 'id' => $parentId, + 'deleted' => true, + ]; + } $response = new DataResponse($messages, Http::STATUS_OK); @@ -277,6 +338,21 @@ public function receiveMessages(int $lookIntoFuture, int $limit = 100, int $last return $response; } + protected function messageToData(Message $message): array { + return [ + 'id' => (int) $message->getComment()->getId(), + 'token' => $message->getRoom()->getToken(), + 'actorType' => $message->getActorType(), + 'actorId' => $message->getActorId(), + 'actorDisplayName' => $message->getActorDisplayName(), + 'timestamp' => $message->getComment()->getCreationDateTime()->getTimestamp(), + 'message' => $message->getMessage(), + 'messageParameters' => $message->getMessageParameters(), + 'systemMessage' => $message->getMessageType() === 'system' ? $message->getMessageRaw() : '', + 'messageType' => $message->getMessageType(), + ]; + } + /** * @NoAdminRequired * @RequireParticipant diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 5018ae37e86..8277e26009e 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -327,7 +327,8 @@ protected function formatLastMessage(Room $room, Participant $participant, IComm 'timestamp' => $lastMessage->getCreationDateTime()->getTimestamp(), 'message' => $message->getMessage(), 'messageParameters' => $message->getMessageParameters(), - 'systemMessage' => $message->getMessageType() === 'system' ? $lastMessage->getMessage() : '', + 'systemMessage' => $message->getMessageType() === 'system' ? $message->getMessageRaw() : '', + 'messageType' => $message->getMessageType(), ]; } diff --git a/lib/Model/Message.php b/lib/Model/Message.php index acb3848d9e9..a7e1f3ea571 100644 --- a/lib/Model/Message.php +++ b/lib/Model/Message.php @@ -52,6 +52,9 @@ class Message { /** @var string */ protected $message = ''; + /** @var string */ + protected $rawMessage = ''; + /** @var array */ protected $parameters = []; @@ -106,9 +109,10 @@ public function getVisibility(): bool { return $this->visible; } - public function setMessage(string $message, array $parameters): void { + public function setMessage(string $message, array $parameters, string $rawMessage = ''): void { $this->message = $message; $this->parameters = $parameters; + $this->rawMessage = $rawMessage; } public function getMessage(): string { @@ -119,6 +123,10 @@ public function getMessageParameters(): array { return $this->parameters; } + public function getMessageRaw(): string { + return $this->rawMessage; + } + public function setMessageType(string $type): void { $this->type = $type; } diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index 3c11cecab10..0b54a40af86 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -165,7 +165,7 @@ public function prepare(INotification $notification, string $languageCode): INot } return $this->parseCall($notification, $room, $l); } - if ($subject === 'mention' || $subject === 'chat') { + if ($subject === 'reply' || $subject === 'mention' || $subject === 'chat') { return $this->parseChatMessage($notification, $room, $participant, $l); } @@ -251,25 +251,31 @@ protected function parseChatMessage(INotification $notification, Room $room, Par if ($notification->getSubject() === 'chat') { if ($room->getType() === Room::ONE_TO_ONE_CALL) { $subject = $l->t('{user} sent you a private message'); + } else if ($richSubjectUser) { + $subject = $l->t('{user} sent a message in conversation {call}'); + } else if (!$isGuest) { + $subject = $l->t('A deleted user sent a message in conversation {call}'); } else { - if ($richSubjectUser) { - $subject = $l->t('{user} sent a message in conversation {call}'); - } else if (!$isGuest) { - $subject = $l->t('A deleted user sent a message in conversation {call}'); - } else { - $subject = $l->t('A guest sent a message in conversation {call}'); - } + $subject = $l->t('A guest sent a message in conversation {call}'); } - } else if ($room->getType() === Room::ONE_TO_ONE_CALL) { - $subject = $l->t('{user} mentioned you in a private conversation'); - } else { - if ($richSubjectUser) { - $subject = $l->t('{user} mentioned you in conversation {call}'); + } else if ($notification->getSubject() === 'reply') { + if ($room->getType() === Room::ONE_TO_ONE_CALL) { + $subject = $l->t('{user} replied to your private message'); + } else if ($richSubjectUser) { + $subject = $l->t('{user} replied to your message in conversation {call}'); } else if (!$isGuest) { - $subject = $l->t('A deleted user mentioned you in conversation {call}'); + $subject = $l->t('A deleted user replied to your message in conversation {call}'); } else { - $subject = $l->t('A guest mentioned you in conversation {call}'); + $subject = $l->t('A guest replied to your message in conversation {call}'); } + } else if ($room->getType() === Room::ONE_TO_ONE_CALL) { + $subject = $l->t('{user} mentioned you in a private conversation'); + } else if ($richSubjectUser) { + $subject = $l->t('{user} mentioned you in conversation {call}'); + } else if (!$isGuest) { + $subject = $l->t('A deleted user mentioned you in conversation {call}'); + } else { + $subject = $l->t('A guest mentioned you in conversation {call}'); } $notification = $this->addActionButton($notification, $l->t('View chat'), false); diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 62e17349ad1..ec4a08f9788 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -39,6 +39,8 @@ class FeatureContext implements Context, SnippetAcceptingContext { protected static $tokenToIdentifier; /** @var array[] */ protected static $sessionIdToUser; + /** @var array[] */ + protected static $messages; /** @var string */ protected $currentUser; @@ -80,6 +82,7 @@ public function setUp() { self::$identifierToToken = []; self::$tokenToIdentifier = []; self::$sessionIdToUser = []; + self::$messages = []; $this->createdUsers = []; $this->createdGroups = []; @@ -582,6 +585,37 @@ public function userSendsMessageToRoom($user, $message, $identifier, $statusCode ); $this->assertStatusCode($this->response, $statusCode); sleep(1); // make sure Postgres manages the order of the messages + + $response = $this->getDataFromResponse($this->response); + if (isset($response['id'])) { + self::$messages[$message] = $response['id']; + } + } + + /** + * @Then /^user "([^"]*)" sends reply "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)$/ + * + * @param string $user + * @param string $reply + * @param string $message + * @param string $identifier + * @param string $statusCode + */ + public function userSendsReplyToRoom($user, $reply, $message, $identifier, $statusCode) { + $replyTo = self::$messages[$message]; + + $this->setCurrentUser($user); + $this->sendRequest( + 'POST', '/apps/spreed/api/v1/chat/' . self::$identifierToToken[$identifier], + new TableNode([['message', $reply], ['replyTo', $replyTo]]) + ); + $this->assertStatusCode($this->response, $statusCode); + sleep(1); // make sure Postgres manages the order of the messages + + $response = $this->getDataFromResponse($this->response); + if (isset($response['id'])) { + self::$messages[$reply] = $response['id']; + } } /** @@ -605,23 +639,40 @@ public function userSeesTheFollowingMessagesInRoom($user, $identifier, $statusCo } }, $actual); + foreach ($messages as $message) { + // Include the received messages in the list of messages used for + // replies; this is needed to get special messages not explicitly + // sent like those for shared files. + self::$messages[$message['message']] = $message['id']; + } + if ($formData === null) { PHPUnit_Framework_Assert::assertEmpty($messages); return; } + $includeParents = in_array('parentMessage', $formData->getRow(0), true); PHPUnit_Framework_Assert::assertCount(count($formData->getHash()), $messages, 'Message count does not match'); - PHPUnit_Framework_Assert::assertEquals($formData->getHash(), array_map(function($message) { - return [ + for ($i = 0; $i < count($formData->getHash()); $i++) { + if ($formData->getHash()[$i]['messageParameters'] === '"IGNORE"') { + $messages[$i]['messageParameters'] = 'IGNORE'; + } + } + PHPUnit_Framework_Assert::assertEquals($formData->getHash(), array_map(function($message) use($includeParents) { + $data = [ 'room' => self::$tokenToIdentifier[$message['token']], - 'actorType' => (string) $message['actorType'], - 'actorId' => ($message['actorType'] === 'guests')? self::$sessionIdToUser[$message['actorId']]: (string) $message['actorId'], - 'actorDisplayName' => (string) $message['actorDisplayName'], + 'actorType' => $message['actorType'], + 'actorId' => ($message['actorType'] === 'guests')? self::$sessionIdToUser[$message['actorId']]: $message['actorId'], + 'actorDisplayName' => $message['actorDisplayName'], // TODO test timestamp; it may require using Runkit, php-timecop // or something like that. - 'message' => (string) $message['message'], + 'message' => $message['message'], 'messageParameters' => json_encode($message['messageParameters']), ]; + if ($includeParents) { + $data['parentMessage'] = $message['parent']['message'] ?? ''; + } + return $data; }, $messages)); } @@ -642,6 +693,12 @@ public function userSeesTheFollowingSystemMessagesInRoom($user, $identifier, $st return $message['systemMessage'] !== ''; }); + foreach ($messages as $systemMessage) { + // Include the received system messages in the list of messages used + // for replies. + self::$messages[$systemMessage['systemMessage']] = $systemMessage['id']; + } + if ($formData === null) { PHPUnit_Framework_Assert::assertEmpty($messages); return; diff --git a/tests/integration/features/chat/reply.feature b/tests/integration/features/chat/reply.feature new file mode 100644 index 00000000000..4a8c9a56ec1 --- /dev/null +++ b/tests/integration/features/chat/reply.feature @@ -0,0 +1,215 @@ +Feature: chat/reply + Background: + Given user "participant1" exists + Given user "participant2" exists + Given user "participant3" exists + And group "attendees1" exists + And user "participant2" is member of group "attendees1" + + Scenario: user can reply to own message + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + When user "participant1" sends reply "Message 1-1" on message "Message 1" 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 | parentMessage | + | group room | users | participant1 | participant1-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant1 | participant1-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + + Scenario: user can reply to other's messages + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + When user "participant2" sends reply "Message 1-1" on message "Message 1" 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 | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + + Scenario: several users can reply to the same message several times + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + When user "participant1" sends reply "Message 1-1" on message "Message 1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-2" on message "Message 1" to room "group room" with 201 + And user "participant1" sends reply "Message 1-3" on message "Message 1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-4" on message "Message 1" 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 | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-4 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1-3 | [] | Message 1 | + | group room | users | participant2 | participant2-displayname | Message 1-2 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-4 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1-3 | [] | Message 1 | + | group room | users | participant2 | participant2-displayname | Message 1-2 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + + + + Scenario: user can reply to shared file messages + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" shares "welcome.txt" with room "group room" + # The messages need to be got so the file message is added to the list of + # known messages to reply to. + # The file message parameters are not relevant for this test and are quite + # large, so they are simply ignored. + And user "participant1" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant1 | participant1-displayname | {file} | "IGNORE" | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant1 | participant1-displayname | {file} | "IGNORE" | | + When user "participant1" sends reply "Message X-1" on message "{file}" to room "group room" with 201 + And user "participant2" sends reply "Message X-2" on message "{file}" 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 | parentMessage | + | group room | users | participant2 | participant2-displayname | Message X-2 | [] | {file} | + | group room | users | participant1 | participant1-displayname | Message X-1 | [] | {file} | + | group room | users | participant1 | participant1-displayname | {file} | "IGNORE" | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant2 | participant2-displayname | Message X-2 | [] | {file} | + | group room | users | participant1 | participant1-displayname | Message X-1 | [] | {file} | + | group room | users | participant1 | participant1-displayname | {file} | "IGNORE" | | + + Scenario: user can not reply to commands + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "/help" to room "group room" with 201 + # In the tests the reference for the message to reply to is got from the + # messages originally sent, not from how they are returned by the server. + When user "participant1" sends reply "Message X-1" on message "/help" to room "group room" with 400 + Then user "participant1" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | bots | talk | talk-bot | There are currently no commands available. | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + + Scenario: user can not reply to system messages + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + # The system messages need to be got so they are added to the list of known + # messages to reply to. + And user "participant1" sees the following system messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | systemMessage | + | group room | users | participant1 | participant1-displayname | user_added | + | group room | users | participant1 | participant1-displayname | conversation_created | + When user "participant1" sends reply "Message X-1" on message "conversation_created" to room "group room" with 400 + Then user "participant1" sees the following messages in room "group room" with 200 + And user "participant2" sees the following messages in room "group room" with 200 + + + + Scenario: user can reply to own replies + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + And user "participant1" sends reply "Message 1-1" on message "Message 1" to room "group room" with 201 + When user "participant1" sends reply "Message 1-1-1" on message "Message 1-1" 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 | parentMessage | + | group room | users | participant1 | participant1-displayname | Message 1-1-1 | [] | Message 1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant1 | participant1-displayname | Message 1-1-1 | [] | Message 1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + + Scenario: user can reply to other's replies + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-1" on message "Message 1" to room "group room" with 201 + When user "participant1" sends reply "Message 1-1-1" on message "Message 1-1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-1-1-1" on message "Message 1-1-1" 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 | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-1-1-1 | [] | Message 1-1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1-1 | [] | Message 1-1 | + | group room | users | participant2 | participant2-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-1-1-1 | [] | Message 1-1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1-1 | [] | Message 1-1 | + | group room | users | participant2 | participant2-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + + Scenario: several users can reply to the same reply several times + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-1" on message "Message 1" to room "group room" with 201 + When user "participant1" sends reply "Message 1-1-1" on message "Message 1-1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-1-2" on message "Message 1-1" to room "group room" with 201 + And user "participant1" sends reply "Message 1-1-3" on message "Message 1-1" to room "group room" with 201 + And user "participant2" sends reply "Message 1-1-4" on message "Message 1-1" 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 | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-1-4 | [] | Message 1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1-3 | [] | Message 1-1 | + | group room | users | participant2 | participant2-displayname | Message 1-1-2 | [] | Message 1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1-1 | [] | Message 1-1 | + | group room | users | participant2 | participant2-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant2" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant2 | participant2-displayname | Message 1-1-4 | [] | Message 1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1-3 | [] | Message 1-1 | + | group room | users | participant2 | participant2-displayname | Message 1-1-2 | [] | Message 1-1 | + | group room | users | participant1 | participant1-displayname | Message 1-1-1 | [] | Message 1-1 | + | group room | users | participant2 | participant2-displayname | Message 1-1 | [] | Message 1 | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + + + + Scenario: user can not reply when not in the room + Given user "participant1" creates room "group room" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room" with 201 + When user "participant3" sends reply "Message 1-1" on message "Message 1" to room "group room" with 404 + Then user "participant1" sees the following messages in room "group room" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | parentMessage | + | group room | users | participant1 | participant1-displayname | Message 1 | [] | | + And user "participant3" sees the following messages in room "group room" with 404 + + + + Scenario: user can not reply to a message from another room + Given user "participant1" creates room "group room1" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" creates room "group room2" + | roomType | 2 | + | invite | attendees1 | + And user "participant1" sends message "Message 1" to room "group room1" with 201 + When user "participant1" sends reply "Message 1-1" on message "Message 1" to room "group room2" with 400 + Then user "participant1" sees the following messages in room "group room1" with 200 + | room | actorType | actorId | actorDisplayName | message | messageParameters | + | group room1 | users | participant1 | participant1-displayname | Message 1 | [] | + And user "participant1" sees the following messages in room "group room2" with 200 diff --git a/tests/php/Chat/ChatManagerTest.php b/tests/php/Chat/ChatManagerTest.php index ab8774fe836..8398b7596ee 100644 --- a/tests/php/Chat/ChatManagerTest.php +++ b/tests/php/Chat/ChatManagerTest.php @@ -118,7 +118,54 @@ public function testSendMessage() { $participant = $this->createMock(Participant::class); - $this->chatManager->sendMessage($chat, $participant, 'users', 'testUser', 'testMessage', $creationDateTime); + $this->chatManager->sendMessage($chat, $participant, 'users', 'testUser', 'testMessage', $creationDateTime, null); + } + + public function testReplyToMessage() { + $replyTo = $this->createMock(IComment::class); + $comment = $this->createMock(IComment::class); + + $this->commentsManager->expects($this->once()) + ->method('create') + ->with('users', 'testUser', 'chat', 1234) + ->willReturn($comment); + + $comment->expects($this->once()) + ->method('setMessage') + ->with('testMessage'); + + $creationDateTime = new \DateTime(); + $comment->expects($this->once()) + ->method('setCreationDateTime') + ->with($creationDateTime); + + $comment->expects($this->once()) + ->method('setVerb') + ->with('comment'); + + $this->commentsManager->expects($this->once()) + ->method('save') + ->with($comment); + + $chat = $this->createMock(Room::class); + $chat->expects($this->any()) + ->method('getId') + ->willReturn(1234); + + $this->notifier->expects($this->once()) + ->method('notifyMentionedUsers') + ->with($chat, $comment); + + $replyTo->expects($this->once()) + ->method('getId') + ->willReturn('12345'); + + $comment->expects($this->once()) + ->method('setParentId') + ->with('12345'); + + $participant = $this->createMock(Participant::class); + $this->chatManager->sendMessage($chat, $participant, 'users', 'testUser', 'testMessage', $creationDateTime, $replyTo); } public function testGetHistory() { diff --git a/tests/php/Chat/NotifierTest.php b/tests/php/Chat/NotifierTest.php index 3c4941d56fb..0037a76ccf7 100644 --- a/tests/php/Chat/NotifierTest.php +++ b/tests/php/Chat/NotifierTest.php @@ -174,7 +174,35 @@ public function testNotifyMentionedUsers() { ->method('notify') ->with($notification); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); + } + + public function testNotNotifyMentionedUserIfReplyToAuthor() { + $comment = $this->newComment(108, 'users', 'testUser', new \DateTime('@' . 1000000016), 'Mention @anotherUser'); + + $room = $this->createMock(Room::class); + $room->expects($this->any()) + ->method('getToken') + ->willReturn('Token123'); + + $notification = $this->newNotification($room, $comment); + + $this->notificationManager->expects($this->once()) + ->method('createNotification') + ->willReturn($notification); + + $notification->expects($this->never()) + ->method('setUser'); + + $notification->expects($this->once()) + ->method('setMessage') + ->with('comment') + ->willReturnSelf(); + + $this->notificationManager->expects($this->never()) + ->method('notify'); + + $this->notifier->notifyMentionedUsers($room, $comment, ['anotherUser']); } public function testNotifyMentionedUsersByGuest() { @@ -217,7 +245,7 @@ public function testNotifyMentionedUsersByGuest() { ->method('notify') ->with($notification); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersWithLongMessageStartMention() { @@ -261,7 +289,7 @@ public function testNotifyMentionedUsersWithLongMessageStartMention() { ->method('notify') ->with($notification); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersWithLongMessageMiddleMention() { @@ -305,7 +333,7 @@ public function testNotifyMentionedUsersWithLongMessageMiddleMention() { ->method('notify') ->with($notification); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersWithLongMessageEndMention() { @@ -349,7 +377,7 @@ public function testNotifyMentionedUsersWithLongMessageEndMention() { ->method('notify') ->with($notification); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersToSelf() { @@ -369,7 +397,7 @@ public function testNotifyMentionedUsersToSelf() { $this->notificationManager->expects($this->never()) ->method('notify'); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersToUnknownUser() { @@ -390,7 +418,7 @@ public function testNotifyMentionedUsersToUnknownUser() { $this->notificationManager->expects($this->never()) ->method('notify'); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersToUserNotInvitedToChat() { @@ -421,7 +449,7 @@ public function testNotifyMentionedUsersToUserNotInvitedToChat() { $this->notificationManager->expects($this->never()) ->method('notify'); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersNoMentions() { @@ -438,7 +466,7 @@ public function testNotifyMentionedUsersNoMentions() { $this->notificationManager->expects($this->never()) ->method('notify'); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testNotifyMentionedUsersSeveralMentions() { @@ -487,7 +515,7 @@ public function testNotifyMentionedUsersSeveralMentions() { [ $notification ] ); - $this->notifier->notifyMentionedUsers($room, $comment); + $this->notifier->notifyMentionedUsers($room, $comment, []); } public function testRemovePendingNotificationsForRoom() { diff --git a/tests/php/Chat/Parser/SystemMessageTest.php b/tests/php/Chat/Parser/SystemMessageTest.php index 57234c74a39..32ecfcc9f58 100644 --- a/tests/php/Chat/Parser/SystemMessageTest.php +++ b/tests/php/Chat/Parser/SystemMessageTest.php @@ -368,17 +368,13 @@ public function testParseMessage(string $message, array $parameters, $recipientI ->method('getFileFromShare'); } - $comment->expects($this->once()) - ->method('setMessage') - ->with($message); - /** @var Room|MockObject $room */ $room = $this->createMock(Room::class); $chatMessage = new Message($room, $participant, $comment, $this->l); $chatMessage->setMessage(json_encode([ 'message' => $message, 'parameters' => $parameters, - ]), []); + ]), [], $message); $parser->parseMessage($chatMessage); diff --git a/tests/php/Controller/ChatControllerTest.php b/tests/php/Controller/ChatControllerTest.php index 6e9b82b1ffd..6364617c40c 100644 --- a/tests/php/Controller/ChatControllerTest.php +++ b/tests/php/Controller/ChatControllerTest.php @@ -136,6 +136,7 @@ private function newComment($id, $actorType, $actorId, $creationDateTime, $messa $comment->method('getActorId')->willReturn($actorId); $comment->method('getCreationDateTime')->willReturn($creationDateTime); $comment->method('getMessage')->willReturn($message); + $comment->method('getParentId')->willReturn('0'); return $comment; } @@ -179,12 +180,18 @@ public function testSendMessageByUser() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['arg' => 'uments']); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->exactly(2)) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($this->room); $this->messageParser->expects($this->once()) ->method('createMessage') @@ -208,11 +215,214 @@ public function testSendMessageByUser() { 'message' => 'parsedMessage', 'messageParameters' => ['arg' => 'uments'], 'systemMessage' => '', + 'messageType' => 'comment', + ], Http::STATUS_CREATED); + + $this->assertEquals($expected, $response); + } + + public function testSendReplyByUser() { + $participant = $this->createMock(Participant::class); + $this->room->expects($this->exactly(2)) + ->method('getToken') + ->willReturn('testToken'); + + $date = new \DateTime(); + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->willReturn($date); + + /** @var IComment|MockObject $comment */ + $parent = $this->newComment(23, 'user', $this->userId . '2', $date, 'testMessage original'); + + /** @var IComment|MockObject $comment */ + $comment = $this->newComment(42, 'user', $this->userId, $date, 'testMessage'); + $this->chatManager->expects($this->once()) + ->method('sendMessage') + ->with($this->room, + $participant, + 'users', + $this->userId, + 'testMessage', + $this->newMessageDateTimeConstraint, + $parent + ) + ->willReturn($comment); + $this->chatManager->expects($this->once()) + ->method('getParentComment') + ->with($this->room, 23) + ->willReturn($parent); + + $parentMessage = $this->createMock(Message::class); + $parentMessage->expects($this->once()) + ->method('getActorType') + ->willReturn('user'); + $parentMessage->expects($this->once()) + ->method('getActorId') + ->willReturn($this->userId . '2'); + $parentMessage->expects($this->once()) + ->method('getActorDisplayName') + ->willReturn('displayName2'); + $parentMessage->expects($this->once()) + ->method('getMessage') + ->willReturn('parsedMessage2'); + $parentMessage->expects($this->once()) + ->method('getMessageParameters') + ->willReturn(['arg' => 'uments2']); + $parentMessage->expects($this->exactly(4)) + ->method('getMessageType') + ->willReturn('comment'); + $parentMessage->expects($this->exactly(2)) + ->method('getComment') + ->willReturn($parent); + $parentMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($this->room); + + $chatMessage = $this->createMock(Message::class); + $chatMessage->expects($this->once()) + ->method('getActorType') + ->willReturn('user'); + $chatMessage->expects($this->once()) + ->method('getActorId') + ->willReturn($this->userId); + $chatMessage->expects($this->once()) + ->method('getActorDisplayName') + ->willReturn('displayName'); + $chatMessage->expects($this->once()) + ->method('getMessage') + ->willReturn('parsedMessage'); + $chatMessage->expects($this->once()) + ->method('getMessageParameters') + ->willReturn(['arg' => 'uments']); + $chatMessage->expects($this->exactly(2)) + ->method('getMessageType') + ->willReturn('comment'); + $chatMessage->expects($this->once()) + ->method('getVisibility') + ->willReturn(true); + $chatMessage->expects($this->exactly(2)) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($this->room); + + $this->messageParser->expects($this->exactly(2)) + ->method('createMessage') + ->withConsecutive( + [$this->room, $participant, $parent, $this->l], + [$this->room, $participant, $comment, $this->l] + ) + ->willReturnOnConsecutiveCalls($parentMessage, $chatMessage); + + $this->messageParser->expects($this->exactly(2)) + ->method('parseMessage') + ->withConsecutive([$parentMessage], [$chatMessage]); + + $this->controller->setRoom($this->room); + $this->controller->setParticipant($participant); + $response = $this->controller->sendMessage('testMessage', '', 23); + $expected = new DataResponse([ + 'id' => 42, + 'token' => 'testToken', + 'actorType' => 'user', + 'actorId' => $this->userId, + 'actorDisplayName' => 'displayName', + 'timestamp' => $date->getTimestamp(), + 'message' => 'parsedMessage', + 'messageParameters' => ['arg' => 'uments'], + 'systemMessage' => '', + 'messageType' => 'comment', + 'parent' => [ + 'id' => 23, + 'token' => 'testToken', + 'actorType' => 'user', + 'actorId' => $this->userId . '2', + 'actorDisplayName' => 'displayName2', + 'timestamp' => $date->getTimestamp(), + 'message' => 'parsedMessage2', + 'messageParameters' => ['arg' => 'uments2'], + 'systemMessage' => '', + 'messageType' => 'comment', + ] ], Http::STATUS_CREATED); $this->assertEquals($expected, $response); } + public function testSendReplyByUserToCommand() { + $participant = $this->createMock(Participant::class); + + $date = new \DateTime(); + /** @var IComment|MockObject $comment */ + $parent = $this->newComment(23, 'user', $this->userId . '2', $date, 'testMessage original'); + + $this->chatManager->expects($this->never()) + ->method('sendMessage'); + $this->chatManager->expects($this->once()) + ->method('getParentComment') + ->with($this->room, 23) + ->willReturn($parent); + + $parentMessage = $this->createMock(Message::class); + $parentMessage->expects($this->exactly(2)) + ->method('getMessageType') + ->willReturn('command'); + + $this->messageParser->expects($this->once()) + ->method('createMessage') + ->with($this->room, $participant, $parent, $this->l) + ->willReturn($parentMessage); + + $this->messageParser->expects($this->once()) + ->method('parseMessage') + ->with($parentMessage); + + $this->controller->setRoom($this->room); + $this->controller->setParticipant($participant); + $response = $this->controller->sendMessage('testMessage', '', 23); + $expected = new DataResponse([], Http::STATUS_BAD_REQUEST); + + $this->assertEquals($expected, $response); + } + + public function testSendReplyByUserToSystemMessage() { + $participant = $this->createMock(Participant::class); + + $date = new \DateTime(); + /** @var IComment|MockObject $comment */ + $parent = $this->newComment(23, 'user', $this->userId . '2', $date, 'testMessage original'); + + $this->chatManager->expects($this->never()) + ->method('sendMessage'); + $this->chatManager->expects($this->once()) + ->method('getParentComment') + ->with($this->room, 23) + ->willReturn($parent); + + $parentMessage = $this->createMock(Message::class); + $parentMessage->expects($this->once()) + ->method('getMessageType') + ->willReturn('system'); + + $this->messageParser->expects($this->once()) + ->method('createMessage') + ->with($this->room, $participant, $parent, $this->l) + ->willReturn($parentMessage); + + $this->messageParser->expects($this->once()) + ->method('parseMessage') + ->with($parentMessage); + + $this->controller->setRoom($this->room); + $this->controller->setParticipant($participant); + $response = $this->controller->sendMessage('testMessage', '', 23); + $expected = new DataResponse([], Http::STATUS_BAD_REQUEST); + + $this->assertEquals($expected, $response); + } + public function testSendMessageByUserNotJoinedButInRoom() { $participant = $this->createMock(Participant::class); $this->room->expects($this->once()) @@ -252,12 +462,18 @@ public function testSendMessageByUserNotJoinedButInRoom() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['arg' => 'uments2']); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->exactly(2)) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($this->room); $this->messageParser->expects($this->once()) ->method('createMessage') @@ -281,6 +497,7 @@ public function testSendMessageByUserNotJoinedButInRoom() { 'message' => 'parsedMessage2', 'messageParameters' => ['arg' => 'uments2'], 'systemMessage' => '', + 'messageType' => 'comment', ], Http::STATUS_CREATED); $this->assertEquals($expected, $response); @@ -333,12 +550,18 @@ public function testSendMessageByGuest() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['arg' => 'uments3']); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->exactly(2)) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($this->room); $this->messageParser->expects($this->once()) ->method('createMessage') @@ -362,6 +585,7 @@ public function testSendMessageByGuest() { 'message' => 'parsedMessage3', 'messageParameters' => ['arg' => 'uments3'], 'systemMessage' => '', + 'messageType' => 'comment', ], Http::STATUS_CREATED); $this->assertEquals($expected, $response); @@ -411,12 +635,18 @@ public function testReceiveHistoryByUser() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['testMessageParameters' . $i]); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->atLeastOnce()) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($room); $i--; return $chatMessage; @@ -429,10 +659,10 @@ public function testReceiveHistoryByUser() { $this->controller->setParticipant($participant); $response = $this->controller->receiveMessages(0, $limit, $offset); $expected = new DataResponse([ - ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => ''], - ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => ''], - ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => ''], - ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => ''] + ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => '', 'messageType' => 'comment'] ], Http::STATUS_OK); $expected->addHeader('X-Chat-Last-Given', 108); @@ -483,12 +713,18 @@ public function testReceiveMessagesByUserNotJoinedButInRoom() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['testMessageParameters' . $i]); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->atLeastOnce()) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($room); $i--; return $chatMessage; @@ -501,10 +737,10 @@ public function testReceiveMessagesByUserNotJoinedButInRoom() { $this->controller->setParticipant($participant); $response = $this->controller->receiveMessages(0, $limit, $offset); $expected = new DataResponse([ - ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => ''], - ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => ''], - ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => ''], - ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => ''] + ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => '', 'messageType' => 'comment'] ], Http::STATUS_OK); $expected->addHeader('X-Chat-Last-Given', 108); @@ -558,12 +794,18 @@ public function testReceiveMessagesByGuest() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['testMessageParameters' . $i]); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->atLeastOnce()) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($room); $i--; return $chatMessage; @@ -576,10 +818,10 @@ public function testReceiveMessagesByGuest() { $this->controller->setParticipant($participant); $response = $this->controller->receiveMessages(0, $limit, $offset); $expected = new DataResponse([ - ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => ''], - ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => ''], - ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => ''], - ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => ''] + ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => '', 'messageType' => 'comment'] ], Http::STATUS_OK); $expected->addHeader('X-Chat-Last-Given', 108); @@ -641,12 +883,18 @@ public function testWaitForNewMessagesByUser() { $chatMessage->expects($this->once()) ->method('getMessageParameters') ->willReturn(['testMessageParameters' . $i]); - $chatMessage->expects($this->once()) + $chatMessage->expects($this->exactly(2)) ->method('getMessageType') ->willReturn('comment'); $chatMessage->expects($this->once()) ->method('getVisibility') ->willReturn(true); + $chatMessage->expects($this->atLeastOnce()) + ->method('getComment') + ->willReturn($comment); + $chatMessage->expects($this->once()) + ->method('getRoom') + ->willReturn($room); $i++; return $chatMessage; @@ -659,10 +907,10 @@ public function testWaitForNewMessagesByUser() { $this->controller->setParticipant($participant); $response = $this->controller->receiveMessages(1, $limit, $offset, $timeout); $expected = new DataResponse([ - ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => ''], - ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => ''], - ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => ''], - ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => ''], + ['id'=>108, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User1', 'timestamp'=>1000000004, 'message'=>'testMessage1', 'messageParameters'=>['testMessageParameters1'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>109, 'token'=>'testToken', 'actorType'=>'guests', 'actorId'=>'testSpreedSession', 'actorDisplayName'=>'User2', 'timestamp'=>1000000008, 'message'=>'testMessage2', 'messageParameters'=>['testMessageParameters2'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>110, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUnknownUser', 'actorDisplayName'=>'User3', 'timestamp'=>1000000015, 'message'=>'testMessage3', 'messageParameters'=>['testMessageParameters3'], 'systemMessage' => '', 'messageType' => 'comment'], + ['id'=>111, 'token'=>'testToken', 'actorType'=>'users', 'actorId'=>'testUser', 'actorDisplayName'=>'User4', 'timestamp'=>1000000016, 'message'=>'testMessage4', 'messageParameters'=>['testMessageParameters4'], 'systemMessage' => '', 'messageType' => 'comment'], ], Http::STATUS_OK); $expected->addHeader('X-Chat-Last-Given', 111); diff --git a/tests/php/Notification/NotifierTest.php b/tests/php/Notification/NotifierTest.php index 5247bea0361..64820dc051a 100644 --- a/tests/php/Notification/NotifierTest.php +++ b/tests/php/Notification/NotifierTest.php @@ -686,7 +686,7 @@ public function testPrepareChatMessage(bool $isMention, int $roomType, array $su $notification->expects($this->once()) ->method('getApp') ->willReturn('spreed'); - $notification->expects($this->exactly(2)) + $notification->expects($this->atLeast(2)) ->method('getSubject') ->willReturn($isMention ? 'mention' : 'chat'); $notification->expects($this->once())