diff options
author | Tobias Markmann <tm@ayena.de> | 2016-09-19 09:35:57 (GMT) |
---|---|---|
committer | Tobias Markmann <tm@ayena.de> | 2017-04-20 10:16:56 (GMT) |
commit | c6daa0af52934c46ae3c26f9e9149d18e44c203e (patch) | |
tree | 28f11a1cc288932afe15ab0b9a4825af4506bdcf /Swift | |
parent | 57a611f942f2387e497334888a4bc4f68b9275c9 (diff) | |
download | swift-c6daa0af52934c46ae3c26f9e9149d18e44c203e.zip swift-c6daa0af52934c46ae3c26f9e9149d18e44c203e.tar.bz2 |
Verify message IDs during last message correction
Previously we did not check the ID in the replace tag against
the ID of the last message from that JID because some MUC
components change the message ID.
In case the ID of the last message and the ID in the replace
tag do not match, the message is simply treated as a normal
message.
Test-Information:
Added unit tests to verify the new behavior and adjusted
existing test cases for new behavior.
Added test cases to ChatsManagerTest.cpp that test verification
of replacement IDs for 1-to-1 chats and test non-verification
of replacement IDs for MUC.
All tests pass on OS X 10.11.6.
Change-Id: I85b1d2138b056b445a663f3ee3ab89a56cef4a2a
Diffstat (limited to 'Swift')
-rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 14 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatController.h | 2 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.cpp | 16 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.h | 12 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCController.cpp | 14 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCController.h | 3 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 145 |
7 files changed, 187 insertions, 19 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 3e58e40..b6ca984 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -513,60 +513,74 @@ void ChatController::handlePresenceChange(std::shared_ptr<Presence> newPresence) } if (!relevantPresence) { return; } if (!newPresence) { newPresence = std::make_shared<Presence>(); newPresence->setType(Presence::Unavailable); } lastShownStatus_ = newPresence->getShow(); chatStateTracker_->handlePresenceChange(newPresence); chatStateNotifier_->setContactIsOnline(newPresence->getType() == Presence::Available); std::string newStatusChangeString = getStatusChangeString(newPresence); if (newStatusChangeString != lastStatusChangeString_) { if (lastWasPresence_) { chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); } else { chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); } lastStatusChangeString_ = newStatusChangeString; lastWasPresence_ = true; } } boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(std::shared_ptr<Message> message) const { return message->getTimestamp(); } +void ChatController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + lastMessagesIDs_[from.toBare()] = {messageID, addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp)}; +} + +void ChatController::handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + auto lastMessage = lastMessagesIDs_.find(from.toBare()); + if ((lastMessage != lastMessagesIDs_.end()) && (lastMessage->second.idInStream == idToReplace)) { + replaceMessage(message, lastMessage->second.idInWindow, timeStamp); + } + else { + addMessageHandleIncomingMessage(from, message, messageID, senderIsSelf, label, timeStamp); + } +} + void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) { HistoryMessage::Type type; if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { type = HistoryMessage::PrivateMessage; } else { type = HistoryMessage::Chat; } if (historyController_) { historyController_->addMessage(message, fromJID, toJID, type, timeStamp); } } bool ChatController::shouldIgnoreMessage(std::shared_ptr<Message> message) { if (!message->getID().empty()) { if (message->getID() == lastHandledMessageID_) { return true; } else { lastHandledMessageID_ = message->getID(); } } return false; } JID ChatController::messageCorrectionJID(const JID& fromJID) { return fromJID.toBare(); } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 1deb0bb..d5011e4 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -29,60 +29,62 @@ namespace Swift { class TimerFactory; class UIEvent; class ChatController : public ChatControllerBase { public: ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); virtual ~ChatController(); virtual void setToJID(const JID& jid) SWIFTEN_OVERRIDE; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; virtual void setOnline(bool online) SWIFTEN_OVERRIDE; virtual void handleNewFileTransferController(FileTransferController* ftc); virtual void handleWhiteboardSessionRequest(bool senderIsSelf); virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) SWIFTEN_OVERRIDE; virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE; virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; protected: virtual void cancelReplaces() SWIFTEN_OVERRIDE; virtual JID getBaseJID() SWIFTEN_OVERRIDE; virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; virtual bool shouldIgnoreMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE; private: void handlePresenceChange(std::shared_ptr<Presence> newPresence); std::string getStatusChangeString(std::shared_ptr<Presence> presence); virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual void postSendMessage(const std::string &body, std::shared_ptr<Stanza> sentStanza) SWIFTEN_OVERRIDE; virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent) SWIFTEN_OVERRIDE; + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) SWIFTEN_OVERRIDE; + virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) SWIFTEN_OVERRIDE; virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; virtual void preSendMessageRequest(std::shared_ptr<Message>) SWIFTEN_OVERRIDE; virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const SWIFTEN_OVERRIDE; void handleStanzaAcked(std::shared_ptr<Stanza> stanza); virtual void dayTicked() SWIFTEN_OVERRIDE { lastWasPresence_ = false; } void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); virtual void handleBareJIDCapsChanged(const JID& jid) SWIFTEN_OVERRIDE; void handleFileTransferCancel(std::string /* id */); void handleFileTransferStart(std::string /* id */, std::string /* description */); void handleFileTransferAccept(std::string /* id */, std::string /* filename */); void handleSendFileRequest(std::string filename); void handleWhiteboardSessionAccept(); void handleWhiteboardSessionCancel(); void handleWhiteboardWindowShow(); void handleSettingChanged(const std::string& settingPath); void checkForDisplayingDisplayReceiptsAlert(); void handleBlockingStateChanged(); void handleBlockUserRequest(); void handleUnblockUserRequest(); void handleInviteToChat(const std::vector<JID>& droppedJIDs); void handleWindowClosed(); diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 7ae7dbd..0fc735a 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -249,85 +249,75 @@ void ChatControllerBase::handleIncomingMessage(std::shared_ptr<MessageEvent> mes return; } else { if (!messageEvent->isReadable()) { return; } showChatWindow(); JID from = message->getFrom(); std::vector<std::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { if (!delayPayloads[i]->getFrom()) { continue; } boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); std::ostringstream s; s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); } std::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); // Determine the timestamp boost::posix_time::ptime timeStamp = boost::posix_time::microsec_clock::universal_time(); boost::optional<boost::posix_time::ptime> messageTimeStamp = getMessageTimestamp(message); if (messageTimeStamp) { timeStamp = *messageTimeStamp; } onActivity(body); std::shared_ptr<Replace> replace = message->getPayload<Replace>(); bool senderIsSelf = isIncomingMessageFromMe(message); + chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); if (replace) { - // Should check if the user has a previous message - std::map<JID, std::string>::iterator lastMessage; - lastMessage = lastMessagesUIID_.find(messageCorrectionJID(from)); - if (lastMessage != lastMessagesUIID_.end()) { - chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); - replaceMessage(chatMessage, lastMessagesUIID_[messageCorrectionJID(from)], timeStamp); - } + handleIncomingReplaceMessage(from, chatMessage, message->getID(), replace->getID(), senderIsSelf, label, timeStamp); } else { - chatMessage = buildChatWindowChatMessage(body, senderHighlightNameFromMessage(from), senderIsSelf); - addMessageHandleIncomingMessage(from, chatMessage, senderIsSelf, label, timeStamp); + addMessageHandleIncomingMessage(from, chatMessage, message->getID(), senderIsSelf, label, timeStamp); } logMessage(body, from, selfJID_, timeStamp, true); } chatWindow_->show(); updateMessageCount(); postHandleIncomingMessage(messageEvent, chatMessage); } -void ChatControllerBase::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { - lastMessagesUIID_[messageCorrectionJID(from)] = addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), timeStamp); -} - std::string ChatControllerBase::getErrorMessage(std::shared_ptr<ErrorPayload> error) { std::string defaultMessage = QT_TRANSLATE_NOOP("", "Error sending message"); if (!error->getText().empty()) { return error->getText(); } else { switch (error->getCondition()) { case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index d10df8f..88cb95c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -21,126 +21,134 @@ #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Elements/SecurityLabelsCatalog.h> #include <Swiften/Elements/Stanza.h> #include <Swiften/JID/JID.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Queries/IQRouter.h> #include <Swift/Controllers/Highlighting/HighlightManager.h> #include <Swift/Controllers/HistoryController.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> #include <Swift/Controllers/XMPPEvents/MessageEvent.h> namespace Swift { class AutoAcceptMUCInviteDecider; class AvatarManager; class ChatMessageParser; class ChatWindowFactory; class EntityCapsProvider; class EventController; class HighlightManager; class Highlighter; class IQRouter; class NickResolver; class StanzaChannel; class UIEventStream; class ChatControllerBase : public boost::signals2::trackable { public: + class StreamWindowMessageIDPair { + public: + std::string idInStream; + std::string idInWindow; + }; + + public: virtual ~ChatControllerBase(); void showChatWindow(); void activateChatWindow(); bool hasOpenWindow() const; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info); virtual void handleIncomingOwnMessage(std::shared_ptr<Message> /*message*/) {} void handleIncomingMessage(std::shared_ptr<MessageEvent> message); std::string addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time); void replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time); virtual void setOnline(bool online); void setEnabled(bool enabled); virtual void setToJID(const JID& jid) {toJID_ = jid;} /** Used for determining when something is recent.*/ boost::signals2::signal<void (const std::string& /*activity*/)> onActivity; boost::signals2::signal<void ()> onUnreadCountChanged; boost::signals2::signal<void ()> onWindowClosed; int getUnreadCount(); const JID& getToJID() {return toJID_;} void handleCapsChanged(const JID& jid); void setCanStartImpromptuChats(bool supportsImpromptu); virtual ChatWindow* detachChatWindow(); boost::signals2::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; protected: ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); /** * Pass the Message appended, and the stanza used to send it. */ virtual void postSendMessage(const std::string&, std::shared_ptr<Stanza>) {} virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; virtual std::string senderHighlightNameFromMessage(const JID& from) = 0; virtual bool isIncomingMessageFromMe(std::shared_ptr<Message>) = 0; virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) {} - virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time); + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) = 0; + virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& chatMessage, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) = 0; virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage&) {} virtual void preSendMessageRequest(std::shared_ptr<Message>) {} virtual bool isFromContact(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message>) const = 0; virtual void dayTicked() {} virtual void handleBareJIDCapsChanged(const JID& jid) = 0; std::string getErrorMessage(std::shared_ptr<ErrorPayload>); virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} virtual void cancelReplaces() = 0; /** JID any iq for account should go to - bare except for PMs */ virtual JID getBaseJID(); virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; ChatWindow::ChatMessage buildChatWindowChatMessage(const std::string& message, const std::string& senderName, bool senderIsSelf); void updateMessageCount(); virtual bool shouldIgnoreMessage(std::shared_ptr<Message> /* message */) { return false; } /** * What JID should be used for last message correction (XEP-0308) tracking. */ virtual JID messageCorrectionJID(const JID& fromJID) = 0; private: IDGenerator idGenerator_; std::string lastSentMessageStanzaID_; void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); void handleAllMessagesRead(); void handleSecurityLabelsCatalogResponse(std::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error); void handleMUCInvitation(Message::ref message); void handleMediatedMUCInvitation(Message::ref message); void handleGeneralMUCInvitation(MUCInviteEvent::ref event); void handleContinuationsBroken(); protected: JID selfJID_; std::vector<std::shared_ptr<StanzaEvent> > unreadMessages_; std::vector<std::shared_ptr<StanzaEvent> > targetedUnreadMessages_; StanzaChannel* stanzaChannel_; IQRouter* iqRouter_; ChatWindowFactory* chatWindowFactory_; ChatWindow* chatWindow_; JID toJID_; bool labelsEnabled_; - std::map<JID, std::string> lastMessagesUIID_; + std::map<JID, StreamWindowMessageIDPair> lastMessagesIDs_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; bool useDelayForLatency_; EventController* eventController_; EntityCapsProvider* entityCapsProvider_; SecurityLabelsCatalog::Item lastLabel_; HistoryController* historyController_; MUCRegistry* mucRegistry_; Highlighter* highlighter_; std::shared_ptr<ChatMessageParser> chatMessageParser_; AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; UIEventStream* eventStream_; bool lastWasPresence_ = false; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index b685e04..fd3dc37 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -560,66 +560,76 @@ void MUCController::preHandleIncomingMessage(std::shared_ptr<MessageEvent> messa if (joined_) { std::string nick = message->getFrom().getResource(); if (nick != nick_ && currentOccupants_.find(nick) != currentOccupants_.end()) { completer_->addWord(nick); } } /*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/ receivedActivity(); joined_ = true; if (message->hasSubject() && !message->getPayload<Body>() && !message->getPayload<Thread>()) { if (!isInitialJoin_) { displaySubjectIfChanged(message->getSubject()); } isInitialJoin_ = false; chatWindow_->setSubject(message->getSubject()); doneGettingHistory_ = true; subject_ = message->getSubject(); } if (!doneGettingHistory_ && !message->getPayload<Delay>()) { doneGettingHistory_ = true; } if (!doneGettingHistory_) { checkDuplicates(message); messageEvent->conclude(); } } -void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) { +void MUCController::addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) { if (from.isBare()) { chatWindow_->addSystemMessage(message, ChatWindow::DefaultDirection); } else { - ChatControllerBase::addMessageHandleIncomingMessage(from, message, senderIsSelf, label, time); + lastMessagesIDs_[from] = {messageID, addMessage(message, senderDisplayNameFromMessage(from), senderIsSelf, label, avatarManager_->getAvatarPath(from), time)}; + } +} + +void MUCController::handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& /*idToReplace*/, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) { + auto lastMessage = lastMessagesIDs_.find(from); + if (lastMessage != lastMessagesIDs_.end()) { + replaceMessage(message, lastMessage->second.idInWindow, timeStamp); + } + else { + addMessageHandleIncomingMessage(from, message, messageID, senderIsSelf, label, timeStamp); } } void MUCController::postHandleIncomingMessage(std::shared_ptr<MessageEvent> messageEvent, const ChatWindow::ChatMessage& chatMessage) { std::shared_ptr<Message> message = messageEvent->getStanza(); if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { highlighter_->handleSystemNotifications(chatMessage, messageEvent); if (!messageEvent->getNotifications().empty()) { eventController_->handleIncomingEvent(messageEvent); } if (!messageEvent->getConcluded()) { highlighter_->handleSoundNotifications(chatMessage); } } } void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant, const MUCOccupant::Role& oldRole) { clearPresenceQueue(); receivedActivity(); JID jid(nickToJID(nick)); roster_->removeContactFromGroup(jid, roleToGroupName(oldRole)); JID realJID; if (occupant.getRealJID()) { realJID = occupant.getRealJID().get(); } std::string group(roleToGroupName(occupant.getRole())); roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index ba68ec6..b8f2aac 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -54,61 +54,62 @@ namespace Swift { class MUCController : public ChatControllerBase { public: MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* xmppRoster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager, MUCBookmarkManager* mucBookmarkManager); virtual ~MUCController(); boost::signals2::signal<void ()> onUserLeft; boost::signals2::signal<void ()> onUserJoined; boost::signals2::signal<void ()> onImpromptuConfigCompleted; boost::signals2::signal<void (const std::string&, const std::string& )> onUserNicknameChanged; virtual void setOnline(bool online) SWIFTEN_OVERRIDE; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info) SWIFTEN_OVERRIDE; void rejoin(); static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname); bool isJoined(); const std::string& getNick(); const boost::optional<std::string> getPassword() const; bool isImpromptu() const; std::map<std::string, JID> getParticipantJIDs() const; void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; protected: virtual void preSendMessageRequest(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual bool isIncomingMessageFromMe(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; virtual std::string senderHighlightNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual std::string senderDisplayNameFromMessage(const JID& from) SWIFTEN_OVERRIDE; virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(std::shared_ptr<Message> message) const SWIFTEN_OVERRIDE; virtual void preHandleIncomingMessage(std::shared_ptr<MessageEvent>) SWIFTEN_OVERRIDE; - virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE; + virtual void addMessageHandleIncomingMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE; + virtual void handleIncomingReplaceMessage(const JID& from, const ChatWindow::ChatMessage& message, const std::string& messageID, const std::string& idToReplace, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::posix_time::ptime& timeStamp) SWIFTEN_OVERRIDE; virtual void postHandleIncomingMessage(std::shared_ptr<MessageEvent>, const ChatWindow::ChatMessage& chatMessage) SWIFTEN_OVERRIDE; virtual void cancelReplaces() SWIFTEN_OVERRIDE; virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) SWIFTEN_OVERRIDE; virtual JID messageCorrectionJID(const JID& fromJID) SWIFTEN_OVERRIDE; private: void setAvailableRoomActions(const MUCOccupant::Affiliation& affiliation, const MUCOccupant::Role& role); void clearPresenceQueue(); void addPresenceMessage(const std::string& message); void handleWindowOccupantSelectionChanged(ContactRosterItem* item); void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item); void handleWindowClosed(); void handleAvatarChanged(const JID& jid); void handleOccupantJoined(const MUCOccupant& occupant); void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname); void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason); void handleOccupantPresenceChange(std::shared_ptr<Presence> presence); void handleOccupantRoleChanged(const std::string& nick, const MUCOccupant& occupant,const MUCOccupant::Role& oldRole); void handleOccupantAffiliationChanged(const std::string& nick, const MUCOccupant::Affiliation& affiliation,const MUCOccupant::Affiliation& oldAffiliation); void handleJoinComplete(const std::string& nick); void handleJoinFailed(std::shared_ptr<ErrorPayload> error); void handleJoinTimeoutTick(); void handleChangeSubjectRequest(const std::string&); void handleBookmarkRequest(); std::string roleToGroupName(MUCOccupant::Role role); std::string roleToSortName(MUCOccupant::Role role); JID nickToJID(const std::string& nick); std::string roleToFriendlyName(MUCOccupant::Role role); void receivedActivity(); bool messageTargetsMe(std::shared_ptr<Message> message); diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 0356c6a..bf645d0 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -6,69 +6,69 @@ #include <map> #include <set> #include <boost/bind.hpp> #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <hippomocks.h> #include <Swiften/Avatars/AvatarMemoryStorage.h> #include <Swiften/Avatars/NullAvatarManager.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Client/Client.h> #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/DummyStanzaChannel.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Crypto/CryptoProvider.h> #include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/Disco/DummyEntityCapsProvider.h> #include <Swiften/Elements/CarbonsReceived.h> #include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> #include <Swiften/Elements/Forwarded.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> #include <Swiften/Jingle/JingleSessionManager.h> #include <Swiften/MUC/MUCManager.h> +#include <Swiften/Network/DummyTimerFactory.h> #include <Swiften/Presence/DirectedPresenceSender.h> #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Presence/StanzaChannelPresenceSender.h> #include <Swiften/Queries/DummyIQChannel.h> #include <Swiften/Roster/XMPPRosterImpl.h> #include <Swiften/VCards/VCardManager.h> #include <Swiften/VCards/VCardMemoryStorage.h> #include <Swiften/Whiteboard/WhiteboardSessionManager.h> -#include <Swiften/Network/DummyTimerFactory.h> #include <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatsManager.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h> #include <Swift/Controllers/EventNotifier.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Settings/DummySettingsProvider.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> #include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> #include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> #include <Swift/Controllers/UnitTest/MockChatWindow.h> #include <Swift/Controllers/WhiteboardManager.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <SwifTools/Notifier/Notifier.h> using namespace Swift; class DummyNotifier : public Notifier { public: virtual void showMessage( @@ -112,62 +112,65 @@ class ChatsManagerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster); CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster); CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth); CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom); CPPUNIT_TEST(testChatControllerFullJIDBindingOnMessageAndNotReceipt); CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive); CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect); CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite); CPPUNIT_TEST(testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications); // MUC PM Tests CPPUNIT_TEST(testChatControllerPMPresenceHandling); CPPUNIT_TEST(testChatControllerMucPmUnavailableErrorHandling); // Highlighting tests CPPUNIT_TEST(testChatControllerHighlightingNotificationTesting); CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds); CPPUNIT_TEST(testChatControllerHighlightingNotificationKeyword); CPPUNIT_TEST(testChatControllerMeMessageHandling); CPPUNIT_TEST(testRestartingMUCComponentCrash); CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC); // Carbons tests CPPUNIT_TEST(testCarbonsForwardedIncomingMessageToSecondResource); CPPUNIT_TEST(testCarbonsForwardedOutgoingMessageFromSecondResource); CPPUNIT_TEST(testCarbonsForwardedIncomingDuplicates); // Message correction tests + CPPUNIT_TEST(testChatControllerMessageCorrectionCorrectReplaceID); + CPPUNIT_TEST(testChatControllerMessageCorrectionIncorrectReplaceID); CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceBySameResource); CPPUNIT_TEST(testChatControllerMessageCorrectionReplaceByOtherResource); + CPPUNIT_TEST(testMUCControllerMessageCorrectionNoIDMatchRequired); CPPUNIT_TEST_SUITE_END(); public: void setUp() { mocks_ = new MockRepository(); notifier_ = std::unique_ptr<DummyNotifier>(new DummyNotifier()); jid_ = JID("test@test.com/resource"); stanzaChannel_ = new DummyStanzaChannel(); iqRouter_ = new IQRouter(stanzaChannel_); eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>(); xmppRoster_ = new XMPPRosterImpl(); mucRegistry_ = new MUCRegistry(); nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_); presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); serverDiscoInfo_ = std::make_shared<DiscoInfo>(); presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_); uiEventStream_ = new UIEventStream(); entityCapsProvider_ = new DummyEntityCapsProvider(); chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>(); settings_ = new DummySettingsProvider(); profileSettings_ = new ProfileSettingsProvider("a", settings_); chatListWindow_ = new MockChatListWindow(); ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); @@ -1282,112 +1285,252 @@ public: std::string body("This is a legible message. >HEH@)oeueu"); message->setBody(body); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); // incoming carbons message from another resource and duplicate of it { auto originalMessage = std::make_shared<Message>(); originalMessage->setFrom(messageJID); originalMessage->setTo(jid2); originalMessage->setID("BDD82F0B-2523-48BF-B8CA-17B23A314BC2"); originalMessage->setType(Message::Chat); std::string forwardedBody = "Some further text."; originalMessage->setBody(forwardedBody); auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); manager_->handleIncomingMessage(messageWrapper); CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_); window->resetLastMessages(); messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); manager_->handleIncomingMessage(messageWrapper); CPPUNIT_ASSERT_EQUAL(std::string(), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_); } } + /** + * This test case ensures correct handling of the ideal case where the replace + * message refers to a message with a known ID. This results in the last + * message being replaced. + */ + void testChatControllerMessageCorrectionCorrectReplaceID() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + auto message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text before edit"); + message->setID("someID"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text after edit"); + message->addPayload(std::make_shared<Replace>("someID")); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); + } + + /** + * This test case ensures correct handling of the case where the replace + * message refers to a message with a unknown ID. The replace message should + * be treated like a non-repalce message in this case, with no replacement + * occuring. + */ + void testChatControllerMessageCorrectionIncorrectReplaceID() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + auto message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text before edit"); + message->setID("someID"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + message = std::make_shared<Message>(); + message->setFrom(messageJID); + message->setTo(jid_); + message->setType(Message::Chat); + message->setBody("text after failed edit"); + message->addPayload(std::make_shared<Replace>("wrongID")); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(std::string("text after failed edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); + } + void testChatControllerMessageCorrectionReplaceBySameResource() { JID messageJID("testling@test.com/resource1"); MockChatWindow* window = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); auto message = std::make_shared<Message>(); message->setFrom(messageJID); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text before edit"); + message->setID("someID"); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); message = std::make_shared<Message>(); message->setFrom(messageJID); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text after edit"); message->addPayload(std::make_shared<Replace>("someID")); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); } void testChatControllerMessageCorrectionReplaceByOtherResource() { JID messageJID("testling@test.com/resource1"); MockChatWindow* window = new MockChatWindow(); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); auto message = std::make_shared<Message>(); message->setFrom(messageJID); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text before edit"); + message->setID("someID"); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text before edit"), MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); message = std::make_shared<Message>(); message->setFrom(messageJID.toBare().withResource("resource2")); message->setTo(jid_); message->setType(Message::Chat); message->setBody("text after edit"); message->addPayload(std::make_shared<Replace>("someID")); manager_->handleIncomingMessage(message); CPPUNIT_ASSERT_EQUAL(std::string("text after edit"), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_)); } + void testMUCControllerMessageCorrectionNoIDMatchRequired() { + JID mucJID("SomeMUCRoom@test.com"); + manager_->setOnline(true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + + std::vector<JID> jids; + jids.emplace_back("foo@test.com"); + jids.emplace_back("bar@test.com"); + + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + auto nickname = std::string("SomeNickName"); + // Join room + { + auto joinRoomEvent = std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname); + uiEventStream_->send(joinRoomEvent); + } + + auto genRemoteMUCPresence = [=]() { + auto presence = Presence::create(); + presence->setFrom(mucJID.withResource(nickname)); + presence->setTo(jid_); + return presence; + }; + + { + auto presence = genRemoteMUCPresence(); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addStatusCode(110); + userPayload->addItem(MUCItem(MUCOccupant::Owner, jid_, MUCOccupant::Moderator)); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + + { + auto presence = genRemoteMUCPresence(); + presence->setFrom(mucJID.withResource("someDifferentNickname")); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addItem(MUCItem(MUCOccupant::Member, JID("foo@bar.com"), MUCOccupant::Moderator)); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource(nickname)); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setID("fooBlaID_1"); + mucMirrored->setBody("Some misssssspelled message."); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("Some misssssspelled message."), window->bodyFromMessage(window->lastAddedMessage_)); + + // Replace message with non-matching ID + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource(nickname)); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setID("fooBlaID_3"); + mucMirrored->setBody("Some correctly spelled message."); + mucMirrored->addPayload(std::make_shared<Replace>("fooBlaID_2")); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("Some correctly spelled message."), window->bodyFromMessage(window->lastReplacedMessage_)); + } + + private: std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { std::shared_ptr<Message> message = std::make_shared<Message>(); message->setFrom(from); message->setID(id); message->setBody("This will cause the window to open"); message->addPayload(std::make_shared<DeliveryReceiptRequest>()); return message; } size_t st(int i) { return static_cast<size_t>(i); } void handleHighlightAction(const HighlightAction& action) { handledHighlightActions_++; if (action.getSoundFilePath()) { soundsPlayed_.insert(action.getSoundFilePath().get_value_or("")); } } private: JID jid_; std::unique_ptr<DummyNotifier> notifier_; ChatsManager* manager_; DummyStanzaChannel* stanzaChannel_; IQRouter* iqRouter_; EventController* eventController_; ChatWindowFactory* chatWindowFactory_; JoinMUCWindowFactory* joinMUCWindowFactory_; |