From cf90536b5b015289ce8834b5417ba0fc7636cee6 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Tue, 17 May 2016 21:05:30 +0200
Subject: Fix MUC invitation request being overwritten by presence change

After an initial presence change, when a client received a
MUC invite, a potential following offline presence could
replace the previous MUC invite request in the chat view.

This commit fixes the issue.

Test-Information:

Added unit test verifying the new behavior.
Verified absence of described bug in Swift GUI.
All tests pass on OS X 10.11.5.

Change-Id: I8fd9c7ad3f5f5009f48fc3d86017cd94e1998f01

diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 3291190..206ee71 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -90,7 +90,6 @@ namespace Swift {
             ChatStateTracker* chatStateTracker_;
             std::string myLastMessageUIID_;
             bool isInMUC_;
-            bool lastWasPresence_;
             std::string lastStatusChangeString_;
             std::map<std::shared_ptr<Stanza>, std::string> unackedStanzas_;
             std::map<std::string, std::string> requestedReceipts_;
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 15f6112..a54c89e 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -97,6 +97,7 @@ void ChatControllerBase::handleDayChangeTick() {
     dateChangeTimer_->stop();
     boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
     chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
+    lastWasPresence_ = false;
     dayTicked();
     createDayChangeTimer();
 }
@@ -382,6 +383,7 @@ void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) {
     updateMessageCount();
     chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu());
     eventController_->handleIncomingEvent(event);
+    lastWasPresence_ = false;
 }
 
 void ChatControllerBase::handleMUCInvitation(Message::ref message) {
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 71390d9..b97d7af 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -138,5 +138,6 @@ namespace Swift {
             std::shared_ptr<ChatMessageParser> chatMessageParser_;
             AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
             UIEventStream* eventStream_;
+            bool lastWasPresence_ = false;
     };
 }
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index ec3f0c4..e0ffd7e 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -153,7 +153,6 @@ namespace Swift {
             TabComplete* completer_;
             bool parting_;
             bool joined_;
-            bool lastWasPresence_;
             bool shouldJoinOnReconnect_;
             bool doneGettingHistory_;
             boost::signals2::scoped_connection avatarChangedConnection_;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 434ac8e..f3908d6 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -25,6 +25,7 @@
 #include <Swiften/Disco/DummyEntityCapsProvider.h>
 #include <Swiften/Elements/DeliveryReceipt.h>
 #include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/Elements/MUCInvitationPayload.h>
 #include <Swiften/Elements/MUCUserPayload.h>
 #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
 #include <Swiften/Jingle/JingleSessionManager.h>
@@ -83,6 +84,7 @@ class ChatsManagerTest : public CppUnit::TestFixture {
     CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds);
     CPPUNIT_TEST(testChatControllerMeMessageHandling);
     CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC);
+    CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite);
     CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -103,7 +105,6 @@ public:
         directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
         mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_);
         uiEventStream_ = new UIEventStream();
-//        entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_);
         entityCapsProvider_ = new DummyEntityCapsProvider();
         chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
         mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
@@ -190,9 +191,6 @@ public:
 
         JID messageJID2("testling@test.com/resource2");
 
-        //MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
-        //mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2, uiEventStream_).Return(window2);
-
         std::shared_ptr<Message> message2(new Message());
         message2->setFrom(messageJID2);
         std::string body2("This is a legible message. .cmaulm.chul");
@@ -332,9 +330,6 @@ public:
 
         JID messageJID2("testling@test.com/resource2");
 
-        //MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
-        //mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2, uiEventStream_).Return(window2);
-
         std::shared_ptr<Message> message2(new Message());
         message2->setFrom(messageJID2);
         message2->setBody("This is a legible message2.");
@@ -883,6 +878,49 @@ public:
         CPPUNIT_ASSERT_EQUAL(std::string("says hello with a test message with foo and foo"), window->bodyFromMessage(window->lastAddedAction_));
     }
 
+    void testPresenceChangeDoesNotReplaceMUCInvite() {
+        JID messageJID("testling@test.com/resource1");
+
+        auto generateIncomingPresence = [=](Presence::Type type) {
+            auto presence = std::make_shared<Presence>();
+            presence->setType(type);
+            presence->setFrom(messageJID);
+            presence->setTo(jid_);
+            return presence;
+        };
+
+        stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available));
+
+        MockChatWindow* window = new MockChatWindow();
+        mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+
+        std::shared_ptr<Message> message(new Message());
+        message->setFrom(messageJID);
+        std::string body("This is a legible message. >HEH@)oeueu");
+        message->setBody(body);
+        manager_->handleIncomingMessage(message);
+        CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_));
+
+        auto incomingMUCInvite = std::make_shared<Message>();
+        incomingMUCInvite->setFrom(messageJID);
+
+        auto invitePayload = std::make_shared<MUCInvitationPayload>();
+        invitePayload->setJID("room@muc.service.com");
+        incomingMUCInvite->addPayload(invitePayload);
+
+        stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable));
+        stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Available));
+
+        window->resetLastMessages();
+
+        manager_->handleIncomingMessage(incomingMUCInvite);
+        CPPUNIT_ASSERT_EQUAL(JID("room@muc.service.com"), window->lastMUCInvitationJID_);
+
+        stanzaChannel_->onPresenceReceived(generateIncomingPresence(Presence::Unavailable));
+        CPPUNIT_ASSERT_EQUAL(std::string(""), MockChatWindow::bodyFromMessage(window->lastReplacedMessage_));
+        CPPUNIT_ASSERT_EQUAL(std::string("testling@test.com has gone offline."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_));
+    }
+
 private:
     std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
         std::shared_ptr<Message> message = std::make_shared<Message>();
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index f9b183d..4f874e1 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -43,7 +43,9 @@ namespace Swift {
             virtual void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour /*timestampBehaviour*/) {
                 lastReplacedMessage_ = message;
             }
-            virtual void replaceSystemMessage(const ChatMessage& /*message*/, const std::string& /*id*/, const TimestampBehaviour /*timestampBehaviour*/) {}
+            virtual void replaceSystemMessage(const ChatMessage& message, const std::string& /*id*/, const TimestampBehaviour /*timestampBehaviour*/) {
+                lastReplacedSystemMessage_ = message;
+            }
 
             // File transfer related stuff
             virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/, const std::string& /*description*/) { return nullptr; }
@@ -77,7 +79,9 @@ namespace Swift {
             void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {}
             void setSubject(const std::string& /*subject*/) {}
             virtual void showRoomConfigurationForm(Form::ref) {}
-            virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true, bool = false, bool = false) {}
+            virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& jid, const std::string& /*reason*/, const std::string& /*password*/, bool = true, bool = false, bool = false) {
+                lastMUCInvitationJID_ = jid;
+            }
 
             virtual std::string addWhiteboardRequest(bool) {return "";}
             virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){}
@@ -109,7 +113,7 @@ namespace Swift {
             }
 
             void resetLastMessages() {
-                lastAddedMessage_ = lastAddedAction_ = lastAddedPresence_ = lastReplacedMessage_ = lastAddedSystemMessage_ = ChatMessage();
+                lastAddedMessage_ = lastAddedAction_ = lastAddedPresence_ = lastReplacedMessage_ = lastAddedSystemMessage_ = lastReplacedSystemMessage_ = ChatMessage();
             }
 
             std::string name_;
@@ -118,6 +122,8 @@ namespace Swift {
             ChatMessage lastAddedPresence_;
             ChatMessage lastReplacedMessage_;
             ChatMessage lastAddedSystemMessage_;
+            ChatMessage lastReplacedSystemMessage_;
+            JID lastMUCInvitationJID_;
             std::vector<SecurityLabelsCatalog::Item> labels_;
             bool labelsEnabled_;
             bool impromptuMUCSupported_;
-- 
cgit v0.10.2-6-g49f6