From c3bda61b09597a7944fbc382366bcdf998540e82 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sat, 26 Dec 2009 22:52:16 +0000
Subject: More thoroughly test the chat routing.

This adds in the behaviour for unbinding chats when the resource goes offline (only if there isn't already an unbound chatwindow).

Resolves: #155

diff --git a/Swift/Controllers/ChatsManager.cpp b/Swift/Controllers/ChatsManager.cpp
index ef187f4..fe8efb3 100644
--- a/Swift/Controllers/ChatsManager.cpp
+++ b/Swift/Controllers/ChatsManager.cpp
@@ -1,5 +1,7 @@
 #include "Swift/Controllers/ChatsManager.h"
 
+#include <boost/bind.hpp>
+
 #include "Swiften/Client/Client.h"
 
 #include "Swift/Controllers/ChatController.h"
@@ -23,6 +25,7 @@ ChatsManager::ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRo
 	avatarManager_ = NULL;
 	serverDiscoInfo_ = serverDiscoInfo;
 	presenceSender_ = presenceSender;
+	presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1, _2));
 }
 
 ChatsManager::~ChatsManager() {
@@ -35,6 +38,20 @@ ChatsManager::~ChatsManager() {
 
 }
 
+/**
+ * If a resource goes offline, release bound chatdialog to that resource.
+ */
+void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> /*oldPresence*/, boost::shared_ptr<Presence> newPresence) {
+	if (newPresence->getType() != Presence::Unavailable) return;
+	JID fullJID(newPresence->getFrom());
+	std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
+	if (it == chatControllers_.end()) return;
+	JID bareJID(fullJID.toBare());
+	//It doesn't make sense to have two unbound dialogs.
+	if (chatControllers_.find(bareJID) != chatControllers_.end()) return;
+	rebindControllerJID(fullJID, bareJID);
+}
+
 void ChatsManager::setAvatarManager(AvatarManager* avatarManager) {
 	avatarManager_ = avatarManager;
 }
@@ -83,19 +100,20 @@ void ChatsManager::handleChatRequest(const String &contact) {
 }
 
 ChatController* ChatsManager::getChatController(const JID &contact) {
-	JID lookupContact(contact);
-	if (chatControllers_.find(lookupContact) == chatControllers_.end()) {
-		lookupContact = JID(contact.toBare());
-	}
-	if (chatControllers_.find(lookupContact) == chatControllers_.end()) {
-		chatControllers_[contact] = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_);
-		chatControllers_[contact]->setAvailableServerFeatures(serverDiscoInfo_);
-		lookupContact = contact;
+	if (chatControllers_.find(contact) == chatControllers_.end()) {
+		//Need to look for an unboud window to bind first
+		JID bare(contact.toBare());
+		if (chatControllers_.find(bare) != chatControllers_.end()) {
+			rebindControllerJID(bare, contact);
+		} else {
+			chatControllers_[contact] = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_);
+			chatControllers_[contact]->setAvailableServerFeatures(serverDiscoInfo_);
+		}
 	}
-	return chatControllers_[lookupContact];
+	return chatControllers_[contact];
 }
 
-void ChatsManager::handleChatControllerJIDChanged(const JID& from, const JID& to) {
+void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
 	chatControllers_[to] = chatControllers_[from];
 	chatControllers_.erase(from);
 }
diff --git a/Swift/Controllers/ChatsManager.h b/Swift/Controllers/ChatsManager.h
index e897e59..260bd1d 100644
--- a/Swift/Controllers/ChatsManager.h
+++ b/Swift/Controllers/ChatsManager.h
@@ -7,6 +7,7 @@
 #include "Swiften/Base/String.h"
 #include "Swiften/Elements/DiscoInfo.h"
 #include "Swiften/Elements/Message.h"
+#include "Swiften/Elements/Presence.h"
 #include "Swiften/JID/JID.h"
 #include "Swiften/MUC/MUCRegistry.h"
 
@@ -33,7 +34,8 @@ namespace Swift {
 			void handleChatRequest(const String& contact);
 			void handleJoinMUCRequest(const JID& muc, const String& nick);
 		private:
-			void handleChatControllerJIDChanged(const JID& from, const JID& to);
+			void rebindControllerJID(const JID& from, const JID& to);
+			void handlePresenceChange(boost::shared_ptr<Presence> oldPresence, boost::shared_ptr<Presence> newPresence);
 			ChatController* getChatController(const JID &contact);
 			virtual bool isMUC(const JID& muc) const;
 
diff --git a/Swift/Controllers/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/UnitTest/ChatsManagerTest.cpp
index 071cd5b..9df244f 100644
--- a/Swift/Controllers/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/UnitTest/ChatsManagerTest.cpp
@@ -27,9 +27,12 @@ class ChatsManagerTest : public CppUnit::TestFixture
 {
 	CPPUNIT_TEST_SUITE(ChatsManagerTest);
 	CPPUNIT_TEST(testFirstOpenWindowIncoming);
+	CPPUNIT_TEST(testSecondOpenWindowIncoming);
 	CPPUNIT_TEST(testFirstOpenWindowOutgoing);
 	CPPUNIT_TEST(testFirstOpenWindowBareToFull);
 	CPPUNIT_TEST(testSecondWindow);
+	CPPUNIT_TEST(testUnbindRebind);
+	CPPUNIT_TEST(testNoDuplicateUnbind);
 	CPPUNIT_TEST_SUITE_END();
 	
 public:
@@ -70,13 +73,41 @@ public:
 	void testFirstOpenWindowIncoming() {
 		JID messageJID("testling@test.com/resource1");
 		
-		ChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID).Return(window);
 
 		boost::shared_ptr<Message> message(new Message());
 		message->setFrom(messageJID);
-		message->setBody("This is a legible message.");
+		String body("This is a legible message. >HEH@)oeueu");
+		message->setBody(body);
 		manager_->handleIncomingMessage(message);
+		CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_);
+	}
+
+	void testSecondOpenWindowIncoming() {
+		JID messageJID1("testling@test.com/resource1");
+		
+		MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1).Return(window1);
+
+		boost::shared_ptr<Message> message1(new Message());
+		message1->setFrom(messageJID1);
+		String body1("This is a legible message. >HEH@)oeueu");
+		message1->setBody(body1);
+		manager_->handleIncomingMessage(message1);
+		CPPUNIT_ASSERT_EQUAL(body1, window1->lastMessageBody_);
+
+		JID messageJID2("testling@test.com/resource2");
+		
+		MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2).Return(window2);
+
+		boost::shared_ptr<Message> message2(new Message());
+		message2->setFrom(messageJID2);
+		String body2("This is a legible message. .cmaulm.chul");
+		message2->setBody(body2);
+		manager_->handleIncomingMessage(message2);
+		CPPUNIT_ASSERT_EQUAL(body2, window2->lastMessageBody_);
 	}
 
 	void testFirstOpenWindowOutgoing() {
@@ -99,28 +130,122 @@ public:
 
 		boost::shared_ptr<Message> message(new Message());
 		message->setFrom(JID(fullJIDString));
-		message->setBody("This is a legible message.");
+		String body("This is a legible message. mjuga3089gm8G(*>M)@*(");
+		message->setBody(body);
 		manager_->handleIncomingMessage(message);
-		/*FIXME: check the message got through. For now, checking that there isn't a new window created is useful enough.*/
-		//CPPUNIT_ASSERT_EQUAL(fullJIDString, window->name_);
+		CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_);
 	}
 
 	void testSecondWindow() {
 		String messageJIDString1("testling1@test.com");
-		
 		ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1)).Return(window1);
-
 		manager_->handleChatRequest(messageJIDString1);
 
 		String messageJIDString2("testling2@test.com");
-		
 		ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2)).Return(window2);
 
 		manager_->handleChatRequest(messageJIDString2);
 	}
 
+	/** Complete cycle.
+		Create unbound window.
+		Bind it.
+		Unbind it.
+		Rebind it.
+	 */
+	void testUnbindRebind() {
+		String bareJIDString("testling@test.com");
+		String fullJIDString1("testling@test.com/resource1");
+		String fullJIDString2("testling@test.com/resource2");
+		
+		MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString)).Return(window);
+		manager_->handleChatRequest(bareJIDString);
+
+		boost::shared_ptr<Message> message1(new Message());
+		message1->setFrom(JID(fullJIDString1));
+		String messageBody1("This is a legible message.");
+		message1->setBody(messageBody1);
+		manager_->handleIncomingMessage(message1);
+		CPPUNIT_ASSERT_EQUAL(messageBody1, window->lastMessageBody_);
+		
+		boost::shared_ptr<Presence> jid1Online(new Presence());
+		jid1Online->setFrom(JID(fullJIDString1));
+		boost::shared_ptr<Presence> jid1Offline(new Presence());
+		jid1Offline->setFrom(JID(fullJIDString1));
+		jid1Offline->setType(Presence::Unavailable);
+		presenceOracle_->onPresenceChange(jid1Online, jid1Offline);
+
+		boost::shared_ptr<Message> message2(new Message());
+		message2->setFrom(JID(fullJIDString2));
+		String messageBody2("This is another legible message.");
+		message2->setBody(messageBody2);
+		manager_->handleIncomingMessage(message2);
+		CPPUNIT_ASSERT_EQUAL(messageBody2, window->lastMessageBody_);
+	}
+
+	/**
+	   Test that a second window isn't unbound where there's already an unbound one.
+	   Bind 1
+	   Bind 2
+	   Unbind 1
+	   Unbind 2 (but it doesn't)
+	   Sent to bound 2
+	   Rebind 1
+	 */
+	void testNoDuplicateUnbind() {
+		JID messageJID1("testling@test.com/resource1");
+		
+		MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1).Return(window1);
+
+		boost::shared_ptr<Message> message1(new Message());
+		message1->setFrom(messageJID1);
+		message1->setBody("This is a legible message1.");
+		manager_->handleIncomingMessage(message1);
+
+		JID messageJID2("testling@test.com/resource2");
+		
+		MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2).Return(window2);
+
+		boost::shared_ptr<Message> message2(new Message());
+		message2->setFrom(messageJID2);
+		message2->setBody("This is a legible message2.");
+		manager_->handleIncomingMessage(message2);
+
+		boost::shared_ptr<Presence> jid1Online(new Presence());
+		jid1Online->setFrom(JID(messageJID1));
+		boost::shared_ptr<Presence> jid1Offline(new Presence());
+		jid1Offline->setFrom(JID(messageJID1));
+		jid1Offline->setType(Presence::Unavailable);
+		presenceOracle_->onPresenceChange(jid1Online, jid1Offline);
+ 
+		boost::shared_ptr<Presence> jid2Online(new Presence());
+		jid2Online->setFrom(JID(messageJID2));
+		boost::shared_ptr<Presence> jid2Offline(new Presence());
+		jid2Offline->setFrom(JID(messageJID2));
+		jid2Offline->setType(Presence::Unavailable);
+		presenceOracle_->onPresenceChange(jid2Online, jid2Offline);
+
+		JID messageJID3("testling@test.com/resource3");
+
+		boost::shared_ptr<Message> message3(new Message());
+		message3->setFrom(messageJID3);
+		String body3("This is a legible message3.");
+		message3->setBody(body3);
+		manager_->handleIncomingMessage(message3);
+		CPPUNIT_ASSERT_EQUAL(body3, window1->lastMessageBody_);
+
+		boost::shared_ptr<Message> message2b(new Message());
+		message2b->setFrom(messageJID2);
+		String body2b("This is a legible message2b.");
+		message2b->setBody(body2b);
+		manager_->handleIncomingMessage(message2b);
+		CPPUNIT_ASSERT_EQUAL(body2b, window2->lastMessageBody_);
+	}
 	
 private:
 	JID jid_;
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 015bb9b..2625553 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -8,7 +8,7 @@ namespace Swift {
 			MockChatWindow() {};
 			virtual ~MockChatWindow();
 
-			virtual void addMessage(const String& /*message*/, const String& /*senderName*/, bool /*senderIsSelf*/, const boost::optional<SecurityLabel>& /*label*/, const String& /*avatarPath*/) {};
+			virtual void addMessage(const String& message, const String& /*senderName*/, bool /*senderIsSelf*/, const boost::optional<SecurityLabel>& /*label*/, const String& /*avatarPath*/) {lastMessageBody_ = message;};
 			virtual void addSystemMessage(const String& /*message*/) {};
 			virtual void addErrorMessage(const String& /*message*/) {};
 
@@ -29,6 +29,7 @@ namespace Swift {
 			boost::signal<void (const String&)> onSendMessageRequest;
 
 			String name_;
+			String lastMessageBody_;
 			std::vector<SecurityLabel> labels_;
 			bool labelsEnabled_;
 	};
-- 
cgit v0.10.2-6-g49f6