diff options
Diffstat (limited to 'Swift/Controllers/Chat/UnitTest')
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp | 162 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp | 292 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp | 344 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 2339 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp | 140 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp | 1301 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/MockChatListWindow.h | 35 |
7 files changed, 3714 insertions, 899 deletions
diff --git a/Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp new file mode 100644 index 0000000..e010656 --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChatControllerTest.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <memory> +#include <string> + +#include <gtest/gtest.h> +#include <hippomocks.h> + +#include <Swiften/Avatars/NullAvatarManager.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Disco/DummyEntityCapsProvider.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/Queries/DummyIQChannel.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/VCards/VCardMemoryStorage.h> + +#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> +#include <Swift/Controllers/Chat/ChatController.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Chat/Chattables.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UnitTest/MockChatWindow.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + +using namespace Swift; + +/** + * Most of the ChatController tests are in ChatsManagerTest. + * New tests related with ChatController should be added here, + * and old tests should be migrated when possible. + */ + +class ExtendedChatController : public ChatController { +public: + ExtendedChatController(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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, std::shared_ptr<ChatMessageParser> chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, SettingsProvider* settings, Chattables& chattables) : + ChatController(self, stanzaChannel, iqRouter, chatWindowFactory, contact, nickResolver, presenceOracle, avatarManager, isInMUC, useDelayForLatency, eventStream, timerFactory, eventController, entityCapsProvider, userWantsReceipts, historyController, mucRegistry, highlightManager, clientBlockListManager, chatMessageParser, autoAcceptMUCInviteDecider, settings, chattables) { + } + + std::map<std::shared_ptr<Stanza>, std::string> getUnackedStanzas() { return unackedStanzas_; } + std::map<std::string, std::shared_ptr<Stanza>> getFailedStanzas() { return failedStanzas_; } +}; + +class ChatControllerTest : public ::testing::Test { +protected: + virtual void SetUp() { + self_ = JID("alice@wonderland.lit"); + other_ = JID("whiterabbit@wonderland.lit"); + stanzaChannel_ = new DummyStanzaChannel(); + iqChannel_ = new DummyIQChannel(); + iqRouter_ = new IQRouter(iqChannel_); + eventController_ = new EventController(); + xmppRoster_ = new XMPPRosterImpl(); + vCardManager_ = new VCardManager(self_, iqRouter_, vCardMemoryStorage_); + mucRegistry_ = new MUCRegistry(); + nickResolver_ = new NickResolver(self_, xmppRoster_, vCardManager_, mucRegistry_); + presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); + avatarManager_ = new NullAvatarManager(); + uiEventStream_ = new UIEventStream(); + timerFactory_ = new DummyTimerFactory(); + entityCapsProvider_ = new DummyEntityCapsProvider(); + settings_ = new DummySettingsProvider(); + highlightManager_ = new HighlightManager(settings_); + highlightManager_->resetToDefaultConfiguration(); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(self_.getDomain(), xmppRoster_, settings_); + chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat); + mocks_ = new MockRepository(); + window_ = new MockChatWindow(); + chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(other_, uiEventStream_).Return(window_); + chattables_ = std::make_unique<Chattables>(); + + controller_ = new ExtendedChatController(self_, stanzaChannel_, iqRouter_, chatWindowFactory_, other_, nickResolver_, presenceOracle_, avatarManager_, false, false, uiEventStream_, timerFactory_, eventController_, entityCapsProvider_, false, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, nullptr, settings_, *chattables_); + } + virtual void TearDown() { + delete controller_; + chattables_.reset(); + chatMessageParser_.reset(); + delete autoAcceptMUCInviteDecider_; + delete clientBlockListManager_; + delete highlightManager_; + delete settings_; + delete entityCapsProvider_; + delete timerFactory_; + delete uiEventStream_; + delete avatarManager_; + delete presenceOracle_; + delete nickResolver_; + delete mucRegistry_; + delete vCardManager_; + delete xmppRoster_; + delete eventController_; + delete iqRouter_; + delete iqChannel_; + delete stanzaChannel_; + } + + JID self_, other_; + AvatarManager* avatarManager_ = nullptr; + ExtendedChatController* controller_ = nullptr; + ChatWindowFactory* chatWindowFactory_; + ClientBlockListManager* clientBlockListManager_; + EventController* eventController_ = nullptr; + EntityCapsProvider* entityCapsProvider_ = nullptr; + IQChannel* iqChannel_ = nullptr; + IQRouter* iqRouter_ = nullptr; + MockRepository* mocks_; + MockChatWindow* window_; + MUCRegistry* mucRegistry_ = nullptr; + NickResolver* nickResolver_ = nullptr; + PresenceOracle* presenceOracle_ = nullptr; + DummyStanzaChannel* stanzaChannel_ = nullptr; + TimerFactory* timerFactory_; + XMPPRosterImpl* xmppRoster_ = nullptr; + UIEventStream* uiEventStream_; + VCardManager* vCardManager_ = nullptr; + VCardMemoryStorage* vCardMemoryStorage_ = nullptr; + DummySettingsProvider* settings_; + HighlightManager* highlightManager_; + std::shared_ptr<ChatMessageParser> chatMessageParser_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + std::unique_ptr<Chattables> chattables_; + +}; + +TEST_F(ChatControllerTest, testResendMessage) { + std::string msgBody("TestMsg"); + stanzaChannel_->setStreamManagementEnabled(true); + window_->onSendMessageRequest(msgBody, false); + { + auto failedStanzas = controller_->getFailedStanzas(); + auto unackedStanzas = controller_->getUnackedStanzas(); + ASSERT_EQ(failedStanzas.size(), 0); + ASSERT_EQ(unackedStanzas.size(), 1); + } + //Disconnecting to fail the stanza + controller_->setOnline(false); + controller_->setOnline(true); + { + auto failedStanzas = controller_->getFailedStanzas(); + auto unackedStanzas = controller_->getUnackedStanzas(); + ASSERT_EQ(failedStanzas.size(), 1); + ASSERT_EQ(unackedStanzas.size(), 0); + } + window_->onResendMessageRequest("id"); + { + auto failedStanzas = controller_->getFailedStanzas(); + auto unackedStanzas = controller_->getUnackedStanzas(); + ASSERT_EQ(failedStanzas.size(), 0); + ASSERT_EQ(unackedStanzas.size(), 1); + } +} diff --git a/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp new file mode 100644 index 0000000..9561e2b --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <memory> +#include <string> +#include <vector> + +#include <boost/algorithm/string.hpp> +#include <boost/version.hpp> + +#include <gtest/gtest.h> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/StringCodecs/Base64.h> + +#include <Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> + +using namespace Swift; + +class ChatListWindowChatTest : public ::testing::Test { +protected: + virtual void SetUp() {} + virtual void TearDown() {} + + void testOptionalPasswordValue(const boost::optional<std::string>& value1, const std::string& value2) { + auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION(); + if (archiveLibraryVersion != Swift::BoostArchiveSkipVersion) { + EXPECT_EQ(value1.get_value_or(""), value2); + } + else { + EXPECT_EQ(value1.get_value_or(""), ""); + } + } + + std::string chatsSerialise(const std::vector<ChatListWindow::Chat>& chats) { + std::stringstream serializeStream; + boost::archive::text_oarchive oa(serializeStream); + oa & chats; + std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); + return serializedStr; + } + + std::vector<ChatListWindow::Chat> chatsDeserialise(const std::string& b64chats) { + ByteArray debase64 = Base64::decode(b64chats); + std::vector<ChatListWindow::Chat> recentChats; + std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size())); + try { + boost::archive::text_iarchive ia(deserializeStream); + ia >> recentChats; + } + catch (const boost::archive::archive_exception& e) { + EXPECT_TRUE(false) << "Archive Version:" << boost::archive::BOOST_ARCHIVE_VERSION() << " " << e.what() << std::endl; + recentChats.clear(); + return recentChats; + } + recentChats.erase(std::remove(recentChats.begin(), recentChats.end(), ChatListWindow::Chat()), recentChats.end()); + return recentChats; + } +}; + +TEST_F(ChatListWindowChatTest, testNormalSerialization) { + ChatListWindow::Chat chat1("swift@rooms.swift.im", "swift@rooms.swift.im", "Some text 0", 0, StatusShow::None, "", false, false, "Nick Name"); + ChatListWindow::Chat chat2("testuser1@domain.com", "swift@rooms2.swift.im", "Some text 1", 0, StatusShow::None, "", false, false, "Nick Name", std::string("pass")); + ChatListWindow::Chat chat3("testuser2@domain.com", "room 2", "Some text 2", 0, StatusShow::None, "", true, false, "Nick Name"); + ChatListWindow::Chat chat4("testuser3@domain.com", "room 3", "Some text 2", 0, StatusShow::None, "", true, false, "Nick Name", std::string("pass")); + + std::map<std::string, JID> impromptuJIDs; + impromptuJIDs["testuser1@domain.com"] = "testuser1@domain.com"; + impromptuJIDs["testuser2@domain.com"] = "testuser2@domain.com"; + std::map<JID, std::string> inviteesNames; + inviteesNames["user1@domain.com"] = "user1@domain.com"; + + chat3.impromptuJIDs = impromptuJIDs; + chat3.inviteesNames = inviteesNames; + chat4.impromptuJIDs = impromptuJIDs; + chat4.inviteesNames = inviteesNames; + + std::vector<ChatListWindow::Chat> chats; + chats.push_back(chat1); + chats.push_back(chat2); + chats.push_back(chat3); + chats.push_back(chat4); + + auto base64 = chatsSerialise(chats); + ASSERT_TRUE(base64.size() > 0); + auto restoredChats = chatsDeserialise(base64); + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + testOptionalPasswordValue(restoredChats[0].password, ""); + EXPECT_EQ(restoredChats[0].inviteesNames.size(), 0); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + testOptionalPasswordValue(restoredChats[1].password, "pass"); + EXPECT_EQ(restoredChats[1].inviteesNames.size(), 0); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + testOptionalPasswordValue(restoredChats[2].password, ""); + ASSERT_EQ(restoredChats[2].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[2].inviteesNames["user1@domain.com"], "user1@domain.com"); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + testOptionalPasswordValue(restoredChats[3].password, "pass"); + ASSERT_EQ(restoredChats[3].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[3].inviteesNames["user1@domain.com"], "user1@domain.com"); +} + +TEST_F(ChatListWindowChatTest, testVersionsSerialization) { + auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION(); + /* + The following strings are base64 serialised vectors with these Swift::ChatListWindow::Chat elements: + + Chat1: Jid = "swift@rooms.swift.im", ChatName = "swift@rooms.swift.im", Activity = "Some text 0", isMUC = false, nick="Nick Name" + Chat2: Jid = "testuser1@domain.com", ChatName = "swift@rooms2.swift.im", Activity = "Some text 1", isMUC = false, nick="Nick Name", password = "pass" + Chat3: Jid = "testuser2@domain.com", ChatName = "room2", Activity = "Some text 2", isMUC = true, nick="Nick Name", impromptuJIDs, inviteesNames + Chat4: Jid = "testuser3@domain.com", ChatName = "room3", Activity = "Some text 2", isMUC = true, nick="Nick Name", impromptuJIDs, password = "pass", inviteesNames + + impromptuJIDs = {("testuser1@domain.com","testuser1@domain.com"), ("testuser2@domain.com", "testuser2@domain.com")} + inviteesNames = {("user1@domain.com","user1@domain.com")} + */ + std::string serializedChats_BoostArchiveV10_ClassVersion_0 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAwIDAgMCAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIxIHN3aWZ0QHJvb21zMi5zd2lmdC5pbSAxMSBTb21lIHRleHQgMSAwIDkgTmljayBOYW1lIDAgMCAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSA2IHJvb20gMiAxMSBTb21lIHRleHQgMiAxIDkgTmljayBOYW1lIDIgMCAwIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIzQGRvbWFpbi5jb20gNiByb29tIDMgMTEgU29tZSB0ZXh0IDIgMSA5IE5pY2sgTmFtZSAyIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20="; + { + auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_0); + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + ASSERT_EQ(restoredChats.size(), 0); + } + else { + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + } + } + + std::string serializedChats_BoostArchiveV10_ClassVersion_1 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAxIDAgMSAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDAgMCAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIxIHN3aWZ0QHJvb21zMi5zd2lmdC5pbSAxMSBTb21lIHRleHQgMSAwIDkgTmljayBOYW1lIDAgMCAxIDAgNCBwYXNzIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDYgcm9vbSAyIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDAgMCAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAwIDIwIHRlc3R1c2VyM0Bkb21haW4uY29tIDYgcm9vbSAzIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDEgMCA0IHBhc3M="; + { + auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_1); + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + ASSERT_EQ(restoredChats.size(), 0); + } + else { + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[0].password.get_value_or(""), ""); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[1].password.get_value_or(""), "pass"); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].password.get_value_or(""), ""); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[3].password.get_value_or(""), "pass"); + } + } + + std::string serializedChats_BoostArchiveV10_ClassVersion_2 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAyIDAgMiAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjEgc3dpZnRAcm9vbXMyLnN3aWZ0LmltIDExIFNvbWUgdGV4dCAxIDAgOSBOaWNrIE5hbWUgMCAwIDEgMCA0IHBhc3MgMCAwIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDYgcm9vbSAyIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDAgMCAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAwIDEgMCAwIDAgMTYgdXNlcjFAZG9tYWluLmNvbSAxNiB1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyM0Bkb21haW4uY29tIDYgcm9vbSAzIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDEgMCA0IHBhc3MgMSAwIDE2IHVzZXIxQGRvbWFpbi5jb20gMTYgdXNlcjFAZG9tYWluLmNvbQ=="; + { + auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_2); + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + ASSERT_EQ(restoredChats.size(), 0); + } + else { + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[0].password.get_value_or(""), ""); + EXPECT_EQ(restoredChats[0].inviteesNames.size(), 0); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[1].password.get_value_or(""), "pass"); + EXPECT_EQ(restoredChats[1].inviteesNames.size(), 0); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].password.get_value_or(""), ""); + ASSERT_EQ(restoredChats[2].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[2].inviteesNames["user1@domain.com"], "user1@domain.com"); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[3].password.get_value_or(""), "pass"); + ASSERT_EQ(restoredChats[3].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[3].inviteesNames["user1@domain.com"], "user1@domain.com"); + } + } +} + diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp index 44d7834..163a38a 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -1,74 +1,292 @@ /* - * Copyright (c) 2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2013-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> -#include <hippomocks.h> +#include <gtest/gtest.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Highlighting/HighlightConfiguration.h> +#include <Swift/Controllers/Highlighting/HighlightManager.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> using namespace Swift; -class ChatMessageParserTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(ChatMessageParserTest); - CPPUNIT_TEST(testFullBody); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() { - smile1_ = ":)"; - smile1Path_ = "/blah/smile1.png"; - smile2_ = ":("; - smile2Path_ = "/blah/smile2.jpg"; - emoticons_[smile1_] = smile1Path_; - emoticons_[smile2_] = smile2Path_; - } - - void tearDown() { - emoticons_.clear(); - } - - void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { - boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->text); - } - - void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { - boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); - CPPUNIT_ASSERT_EQUAL(path, part->imagePath); - } - - void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { - boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); - CPPUNIT_ASSERT_EQUAL(text, part->target); - } - - void testFullBody() { - ChatMessageParser testling(emoticons_); - ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom"); - assertEmoticon(result, 0, smile1_, smile1Path_); - assertText(result, 1, " shiny "); - assertEmoticon(result, 2, smile2_, smile2Path_); - assertText(result, 3, " "); - assertEmoticon(result, 4, smile1_, smile1Path_); - assertText(result, 5, " "); - assertURL(result, 6, "http://wonderland.lit/blah"); - assertText(result, 7, " "); - assertURL(result, 8, "http://denmark.lit"); - assertText(result, 9, " boom boom"); - } - -private: - std::map<std::string, std::string> emoticons_; - std::string smile1_; - std::string smile1Path_; - std::string smile2_; - std::string smile2Path_; +// Common test state +class ChatMessageParserTest : public ::testing::Test { +protected: + virtual void SetUp() { + smile1_ = ":)"; + smile1Path_ = "/blah/smile1.png"; + smile2_ = ":("; + smile2Path_ = "/blah/smile2.jpg"; + emoticons_[smile1_] = smile1Path_; + emoticons_[smile2_] = smile2Path_; + } + + virtual void TearDown() { + emoticons_.clear(); + } + + static void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + ASSERT_LT(index, result.getParts().size()); + std::shared_ptr<ChatWindow::ChatTextMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); + ASSERT_EQ(text, part->text); + } + + static void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { + ASSERT_LT(index, result.getParts().size()); + std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); + ASSERT_NE(nullptr, part); + ASSERT_EQ(text, part->alternativeText); + ASSERT_EQ(path, part->imagePath); + } + + static void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + ASSERT_LT(index, result.getParts().size()); + std::shared_ptr<ChatWindow::ChatURIMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); + ASSERT_EQ(text, part->target); + } + + void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const HighlightAction& action) { + ASSERT_LT(index, result.getParts().size()); + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); + ASSERT_EQ(std::string(text), part->text); + ASSERT_EQ(action, part->action); + } + + static const std::shared_ptr<HighlightConfiguration> highlightConfigFromKeyword(const std::string& keyword, bool matchCase) { + std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>(); + HighlightConfiguration::KeywordHightlight keywordHighlight; + keywordHighlight.keyword = keyword; + keywordHighlight.matchCaseSensitive = matchCase; + keywordHighlight.action.setFrontColor(std::string("#121212")); + config->keywordHighlights.push_back(keywordHighlight); + return config; + } + + static const std::shared_ptr<HighlightConfiguration> mergeHighlightConfig(const std::shared_ptr<HighlightConfiguration>& configA, const std::shared_ptr<HighlightConfiguration>& configB) { + std::shared_ptr<HighlightConfiguration> config = std::make_shared<HighlightConfiguration>(); + config->keywordHighlights.insert(config->keywordHighlights.end(), configA->keywordHighlights.begin(), configA->keywordHighlights.end()); + config->keywordHighlights.insert(config->keywordHighlights.end(), configB->keywordHighlights.begin(), configB->keywordHighlights.end()); + return config; + } + + std::map<std::string, std::string> emoticons_; + std::string smile1_; + std::string smile1Path_; + std::string smile2_; + std::string smile2Path_; }; -CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); +TEST_F(ChatMessageParserTest, testNoHighlightingWithEmtpyConfiguration) { + const std::string no_special_message = "a message with no special content"; + ChatMessageParser testling(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(no_special_message); + assertText(result, 0, no_special_message); +} + +TEST_F(ChatMessageParserTest, testSimpleHighlightAndEmojiAndUrlParsing) { + auto highlightConfig = highlightConfigFromKeyword("trigger", false); + auto testling = ChatMessageParser(emoticons_, highlightConfig); + auto result = testling.parseMessageBody(":) shiny :( trigger :) http://wonderland.lit/blah http://denmark.lit boom boom"); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, " shiny "); + assertEmoticon(result, 2, smile2_, smile2Path_); + assertText(result, 3, " "); + assertHighlight(result, 4, "trigger", highlightConfig->keywordHighlights[0].action); + assertText(result, 5, " "); + assertEmoticon(result, 6, smile1_, smile1Path_); + assertText(result, 7, " "); + assertURL(result, 8, "http://wonderland.lit/blah"); + assertText(result, 9, " "); + assertURL(result, 10, "http://denmark.lit"); + assertText(result, 11, " boom boom"); +} + +TEST_F(ChatMessageParserTest, testNoKeywordHighlightAsPartOfLongerWords) { + auto testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false)); + auto result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "testtriggermessage"); +} + +TEST_F(ChatMessageParserTest, testCaseInsensitiveKeyordHighlight) { + auto config = highlightConfigFromKeyword("trigger", true); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("TrIgGeR"); + assertText(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, highlightConfigFromKeyword("trigger", false)); + result = testling.parseMessageBody("TrIgGeR"); + assertHighlight(result, 0, "TrIgGeR", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testMultipleKeywordHighlights) { + auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false)); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("zero one two three"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "one", config->keywordHighlights[0].action); + assertText(result, 2, " two "); + assertHighlight(result, 3, "three", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testMultipleCaseInsensitiveKeywordHighlights) { + auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", false)); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action); + assertText(result, 2, " two "); + assertHighlight(result, 3, "tHrEe", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testMultipleCaseSensitiveKeywordHighlights) { + auto config = mergeHighlightConfig(highlightConfigFromKeyword("one", false), highlightConfigFromKeyword("three", true)); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe", config->keywordHighlights[0].action); + assertText(result, 2, " two tHrEe"); + + config = mergeHighlightConfig(highlightConfigFromKeyword("one", true), highlightConfigFromKeyword("three", false)); + testling = ChatMessageParser(emoticons_, config); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero oNe two "); + assertHighlight(result, 1, "tHrEe", config->keywordHighlights[0].action); +} + +TEST_F(ChatMessageParserTest, testOneEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(" :) "); + assertText(result, 0, " "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, " "); +} + +TEST_F(ChatMessageParserTest, testBareEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(":)"); + assertEmoticon(result, 0, smile1_, smile1Path_); +} + +TEST_F(ChatMessageParserTest, testHiddenEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody("b:)a"); + assertText(result, 0, "b:)a"); +} + +TEST_F(ChatMessageParserTest, testEndlineEmoticon) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody("Lazy:)"); + assertText(result, 0, "Lazy"); + assertEmoticon(result, 1, smile1_, smile1Path_); +} + +TEST_F(ChatMessageParserTest, testBoundedEmoticons) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody(":)Lazy:("); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, "Lazy"); + assertEmoticon(result, 2, smile2_, smile2Path_); +} + +TEST_F(ChatMessageParserTest, testEmoticonParenthesis) { + auto testling = ChatMessageParser(emoticons_, std::make_shared<HighlightConfiguration>()); + auto result = testling.parseMessageBody("(Like this :))"); + assertText(result, 0, "(Like this "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, ")"); +} + +TEST_F(ChatMessageParserTest, testSenderAndKeywordHighlighting) { + auto config = std::make_shared<HighlightConfiguration>(); + auto contactHighlight = HighlightConfiguration::ContactHighlight(); + contactHighlight.action.setFrontColor(std::string("#f0f0f0")); + contactHighlight.action.setBackColor(std::string("#0f0f0f")); + contactHighlight.name = "Romeo"; + config->contactHighlights.push_back(contactHighlight); + auto keywordHighlight = HighlightConfiguration::KeywordHightlight(); + keywordHighlight.action.setFrontColor(std::string("#abcdef")); + keywordHighlight.action.setBackColor(std::string("#fedcba")); + keywordHighlight.keyword = "XMPP"; + config->keywordHighlights.push_back(keywordHighlight); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo"); + assertText(result, 0, "Heard any news about "); + assertHighlight(result, 1, "xmpp", keywordHighlight.action); + assertText(result, 2, " recently?"); + ASSERT_EQ(contactHighlight.action, result.getHighlightActionSender()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testKeywordWithEmptyActionIsIgnored) { + auto config = std::make_shared<HighlightConfiguration>(); + auto keywordHighlight = HighlightConfiguration::KeywordHightlight(); + keywordHighlight.keyword = "XMPP"; + config->keywordHighlights.push_back(keywordHighlight); + auto testling = ChatMessageParser(emoticons_, config); + auto result = testling.parseMessageBody("Heard any news about xmpp recently?", "Romeo"); + assertText(result, 0, "Heard any news about xmpp recently?"); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testMeMessageAndOwnMention) { + auto config = std::make_shared<HighlightConfiguration>(); + config->ownMentionAction.setFrontColor(std::string("#f0f0f0")); + config->ownMentionAction.setBackColor(std::string("#0f0f0f")); + config->ownMentionAction.setSoundFilePath(std::string("someSoundFile.wav")); + auto ownMentionActionForPart = config->ownMentionAction; + ownMentionActionForPart.setSoundFilePath(boost::optional<std::string>()); + auto testling = ChatMessageParser(emoticons_, config); + testling.setNick("Juliet"); + auto result = testling.parseMessageBody("/me wonders when Juliet is coming?", "Romeo"); + assertText(result, 0, "wonders when "); + assertHighlight(result, 1, "Juliet", ownMentionActionForPart); + assertText(result, 2, " is coming?"); + ASSERT_EQ(true, result.isMeCommand()); + ASSERT_EQ(config->ownMentionAction, result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testSoundAndNotificationOnDirectMessage) { + auto defaultConfiguration = std::make_shared<HighlightConfiguration>(); + defaultConfiguration->playSoundOnIncomingDirectMessages = true; + defaultConfiguration->showNotificationOnIncomingDirectMessages = true; + defaultConfiguration->ownMentionAction.setFrontColor(std::string("black")); + defaultConfiguration->ownMentionAction.setBackColor(std::string("yellow")); + defaultConfiguration->ownMentionAction.setSoundFilePath(std::string("")); + defaultConfiguration->ownMentionAction.setSystemNotificationEnabled(true); + + auto testling = ChatMessageParser(emoticons_, defaultConfiguration, ChatMessageParser::Mode::Chat); + auto result = testling.parseMessageBody("I wonder when Juliet is coming?", "Romeo"); + + ASSERT_EQ(std::string(""), result.getHighlightActionDirectMessage().getSoundFilePath().get_value_or(std::string("somethingElse"))); + ASSERT_EQ(true, result.getHighlightActionDirectMessage().isSystemNotificationEnabled()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); +} + +TEST_F(ChatMessageParserTest, testWithDefaultConfiguration) { + DummySettingsProvider settings; + HighlightManager manager(&settings); + manager.resetToDefaultConfiguration(); + auto testling = ChatMessageParser(emoticons_, manager.getConfiguration(), ChatMessageParser::Mode::GroupChat); + testling.setNick("Juliet"); + auto result = testling.parseMessageBody("Hello, how is it going?", "Romeo"); + assertText(result, 0, "Hello, how is it going?"); + ASSERT_EQ(HighlightAction(), result.getHighlightActionOwnMention()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionSender()); + result = testling.parseMessageBody("Juliet, seen the new interface design?", "Romeo"); + auto mentionKeywordAction = manager.getConfiguration()->ownMentionAction; + mentionKeywordAction.setSoundFilePath(boost::optional<std::string>()); + mentionKeywordAction.setSystemNotificationEnabled(false); + assertHighlight(result, 0, "Juliet", mentionKeywordAction); + assertText(result, 1, ", seen the new interface design?"); + ASSERT_EQ(manager.getConfiguration()->ownMentionAction, result.getHighlightActionOwnMention()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionDirectMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionGroupMessage()); + ASSERT_EQ(HighlightAction(), result.getHighlightActionSender()); +} diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index f5a3003..954dd2f 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -1,495 +1,1904 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ +#include <map> +#include <set> + +#include <boost/bind.hpp> + #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <hippomocks.h> -#include <boost/bind.hpp> +#include <Swiften/Avatars/AvatarMemoryStorage.h> +#include <Swiften/Avatars/NullAvatarManager.h> +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/LogSerializers.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/Elements/PrivateStorage.h> +#include <Swiften/Elements/Storage.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swiften/MUC/UnitTest/MockMUC.h> +#include <Swiften/MUC/MUCBookmarkManager.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 <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatsManager.h> - +#include <Swift/Controllers/Chat/Chattables.h> +#include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h> -#include <Swift/Controllers/UIInterfaces/ChatWindow.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/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.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/WhiteboardWindowFactory.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 <Swiften/Client/Client.h> -#include <Swiften/Disco/EntityCapsManager.h> -#include <Swiften/Disco/CapsProvider.h> -#include <Swiften/MUC/MUCManager.h> -#include <Swift/Controllers/Chat/ChatController.h> -#include <Swift/Controllers/XMPPEvents/EventController.h> -#include <Swift/Controllers/Chat/MUCController.h> -#include <Swiften/Presence/StanzaChannelPresenceSender.h> -#include <Swiften/Avatars/NullAvatarManager.h> -#include <Swiften/Avatars/AvatarMemoryStorage.h> -#include <Swiften/VCards/VCardManager.h> -#include <Swiften/VCards/VCardMemoryStorage.h> -#include <Swiften/Client/NickResolver.h> -#include <Swiften/Presence/DirectedPresenceSender.h> -#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> #include <Swift/Controllers/UnitTest/MockChatWindow.h> -#include <Swiften/Client/DummyStanzaChannel.h> -#include <Swiften/Queries/DummyIQChannel.h> -#include <Swiften/Presence/PresenceOracle.h> -#include <Swiften/Jingle/JingleSessionManager.h> -#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> -#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> -#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> -#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include <Swift/Controllers/ProfileSettingsProvider.h> -#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> -#include <Swiften/Elements/DeliveryReceiptRequest.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Base/Algorithm.h> -#include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/WhiteboardManager.h> -#include <Swiften/Whiteboard/WhiteboardSessionManager.h> -#include <Swiften/Client/ClientBlockListManager.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + +#include <SwifTools/Notifier/Notifier.h> + +#include <Swift/QtUI/QtSwiftUtil.h> using namespace Swift; -class DummyCapsProvider : public CapsProvider { - DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());} +class DummyNotifier : public Notifier { + public: + virtual void showMessage( + Type type, + const std::string& subject, + const std::string& description, + const boost::filesystem::path& picture, + boost::function<void()> callback) { + notifications.push_back({type, subject, description, picture, callback}); + } + + /** Remove any pending callbacks. */ + virtual void purgeCallbacks() { + + } + + public: + struct Notification { + Type type; + std::string subject; + std::string description; + boost::filesystem::path picture; + boost::function<void()> callback; + }; + + public: + std::vector<Notification> notifications; +}; + +class ExtendedChatsManager : public ChatsManager { +public: + ExtendedChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, VCardManager* vcardManager, Chattables& chattables) : + ChatsManager(jid, stanzaChannel, iqRouter, eventController, chatWindowFactory, joinMUCWindowFactory, nickResolver, presenceOracle, presenceSender, uiEventStream, chatListWindowFactory, useDelayForLatency, timerFactory, mucRegistry, entityCapsProvider, mucManager, mucSearchWindowFactory, profileSettings, ftOverview, roster, eagleMode, settings, historyController_, whiteboardManager, highlightManager, clientBlockListManager, emoticons, vcardManager, chattables) { + } + MUCBookmarkManager* getBookmarkManager() { + return mucBookmarkManager_; + } }; + 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(testThreeMUCWindows); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth); - CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom); - CPPUNIT_TEST_SUITE_END(); - + 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(testThreeMUCWindows); + 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); + + // Chat window title tests + CPPUNIT_TEST(testImpromptuChatTitle); + CPPUNIT_TEST(testImpromptuChatWindowTitle); + CPPUNIT_TEST(testStandardMUCChatWindowTitle); + + // Bookmark tests + CPPUNIT_TEST(testReceivingBookmarksWithDomainJID); + CPPUNIT_TEST(testReceivingBookmarksWithBareJID); + CPPUNIT_TEST(testReceivingBookmarksWithFullJID); + CPPUNIT_TEST(testAutoJoinBookmarksAndChattables); + CPPUNIT_TEST(testJoinNoAutojoinBookmark); + CPPUNIT_TEST(testJoinAndBookmarkMUC); + CPPUNIT_TEST(testReceivingNoBookmarks); + CPPUNIT_TEST(testReceivingNullBookmarks); + CPPUNIT_TEST(testReceivingBookmarksError); + + CPPUNIT_TEST_SUITE_END(); + public: - void setUp() { - mocks_ = new MockRepository(); - jid_ = JID("test@test.com/resource"); - stanzaChannel_ = new DummyStanzaChannel(); - iqChannel_ = new DummyIQChannel(); - iqRouter_ = new IQRouter(iqChannel_); - capsProvider_ = new DummyCapsProvider(); - eventController_ = new EventController(); - chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); - joinMUCWindowFactory_ = mocks_->InterfaceMock<JoinMUCWindowFactory>(); - xmppRoster_ = new XMPPRosterImpl(); - mucRegistry_ = new MUCRegistry(); - nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, NULL, mucRegistry_); - presenceOracle_ = new PresenceOracle(stanzaChannel_); - serverDiscoInfo_ = boost::make_shared<DiscoInfo>(); - presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); - directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); - mucManager_ = new MUCManager(stanzaChannel_, iqRouter_, directedPresenceSender_, mucRegistry_); - uiEventStream_ = new UIEventStream(); - entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); - 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_); - avatarManager_ = new NullAvatarManager(); - wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_); - wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); - highlightManager_ = new HighlightManager(settings_); - - mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); - clientBlockListManager_ = new ClientBlockListManager(iqRouter_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL); - - manager_->setAvatarManager(avatarManager_); - } - - void tearDown() { - delete highlightManager_; - //delete chatListWindowFactory - delete profileSettings_; - delete avatarManager_; - delete manager_; - delete clientBlockListManager_; - delete ftOverview_; - delete ftManager_; - delete wbSessionManager_; - delete wbManager_; - delete directedPresenceSender_; - delete presenceSender_; - delete presenceOracle_; - delete nickResolver_; - delete mucRegistry_; - delete stanzaChannel_; - delete eventController_; - delete iqRouter_; - delete iqChannel_; - delete uiEventStream_; - delete mucManager_; - delete xmppRoster_; - delete entityCapsManager_; - delete capsProvider_; - delete chatListWindow_; - delete mocks_; - delete settings_; - } - - void testFirstOpenWindowIncoming() { - JID messageJID("testling@test.com/resource1"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - - boost::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, window->lastMessageBody_); - } - - void testSecondOpenWindowIncoming() { - JID messageJID1("testling@test.com/resource1"); - - MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); - - boost::shared_ptr<Message> message1(new Message()); - message1->setFrom(messageJID1); - std::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, uiEventStream_).Return(window2); - - boost::shared_ptr<Message> message2(new Message()); - message2->setFrom(messageJID2); - std::string body2("This is a legible message. .cmaulm.chul"); - message2->setBody(body2); - manager_->handleIncomingMessage(message2); - CPPUNIT_ASSERT_EQUAL(body2, window1->lastMessageBody_); - } - - void testFirstOpenWindowOutgoing() { - std::string messageJIDString("testling@test.com"); - - ChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString), uiEventStream_).Return(window); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString)))); - } - - - void testFirstOpenWindowBareToFull() { - std::string bareJIDString("testling@test.com"); - std::string fullJIDString("testling@test.com/resource1"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(bareJIDString)))); - - boost::shared_ptr<Message> message(new Message()); - message->setFrom(JID(fullJIDString)); - std::string body("This is a legible message. mjuga3089gm8G(*>M)@*("); - message->setBody(body); - manager_->handleIncomingMessage(message); - CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_); - } - - void testSecondWindow() { - std::string messageJIDString1("testling1@test.com"); - ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1)))); - - std::string messageJIDString2("testling2@test.com"); - ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString2)))); - } - - /** Complete cycle. - Create unbound window. - Bind it. - Unbind it. - Rebind it. - */ - void testUnbindRebind() { - std::string bareJIDString("testling@test.com"); - std::string fullJIDString1("testling@test.com/resource1"); - std::string fullJIDString2("testling@test.com/resource2"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(bareJIDString)))); - - boost::shared_ptr<Message> message1(new Message()); - message1->setFrom(JID(fullJIDString1)); - std::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(jid1Offline); - - boost::shared_ptr<Message> message2(new Message()); - message2->setFrom(JID(fullJIDString2)); - std::string messageBody2("This is another legible message."); - message2->setBody(messageBody2); - manager_->handleIncomingMessage(message2); - CPPUNIT_ASSERT_EQUAL(messageBody2, window->lastMessageBody_); - } - - /** - * Test that MUC PMs get opened in the right windows - */ - void testThreeMUCWindows() { - JID muc("testling@test.com"); - ChatWindow* mucWindow = new MockChatWindow(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc, uiEventStream_).Return(mucWindow); - uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(muc, std::string("nick"))); - - - std::string messageJIDString1("testling@test.com/1"); - ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1)))); - - std::string messageJIDString2("testling@test.com/2"); - ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString2)))); - - std::string messageJIDString3("testling@test.com/3"); - ChatWindow* window3 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString3), uiEventStream_).Return(window3); - - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString3)))); - - /* Refetch an earlier window */ - /* We do not expect a new window to be created */ - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(JID(messageJIDString1)))); - - } - - /** - 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, uiEventStream_).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, uiEventStream_).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(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(jid2Offline); - - JID messageJID3("testling@test.com/resource3"); - - boost::shared_ptr<Message> message3(new Message()); - message3->setFrom(messageJID3); - std::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); - std::string body2b("This is a legible message2b."); - message2b->setBody(body2b); - manager_->handleIncomingMessage(message2b); - CPPUNIT_ASSERT_EQUAL(body2b, window1->lastMessageBody_); - } - - /** - * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster. - */ - void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() { - JID messageJID("testling@test.com/resource1"); - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); - manager_->handleIncomingMessage(message); - Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); - - xmppRoster_->removeContact(messageJID); - - message->setID("2"); - manager_->handleIncomingMessage(message); - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - } - - /** - * Test that ChatController sends receipts after the contact has been added to the roster. - */ - void testChatControllerPresenceAccessUpdatedOnAddToRoster() { - JID messageJID("testling@test.com/resource1"); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->sentStanzas.size()); - - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); - message->setID("2"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); - CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); - } - - /** - * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both. - */ - void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() { - testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both); - } - - /** - * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From. - */ - void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() { - testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From); - } - - void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) { - JID messageJID("testling@test.com/resource1"); - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from); - - MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); - - boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->sentStanzas.size()); - - xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to); - message->setID("2"); - manager_->handleIncomingMessage(message); - - CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->sentStanzas.size()); - Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0); - CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0); - } + void setUp() { + mocks_ = new MockRepository(); + notifier_ = std::make_unique<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_); + avatarManager_ = new NullAvatarManager(); + eventNotifier_ = new EventNotifier(eventController_, notifier_.get(), avatarManager_, nickResolver_); + wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsProvider_); + wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); + highlightManager_ = new HighlightManager(settings_); + highlightManager_->resetToDefaultConfiguration(); + handledHighlightActions_ = 0; + soundsPlayed_.clear(); + highlightManager_->onHighlight.connect(boost::bind(&ChatsManagerTest::handleHighlightAction, this, _1)); + + crypto_ = PlatformCryptoProvider::create(); + vcardStorage_ = new VCardMemoryStorage(crypto_); + vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_); + mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + timerFactory_ = new DummyTimerFactory(); + chattables_ = std::make_unique<Chattables>(); + manager_ = new ExtendedChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, timerFactory_, mucRegistry_, entityCapsProvider_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, nullptr, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, vcardManager_, *chattables_); + + manager_->setAvatarManager(avatarManager_); + } + + void tearDown() { + delete highlightManager_; + delete profileSettings_; + delete eventNotifier_; + delete avatarManager_; + delete manager_; + delete timerFactory_; + delete clientBlockListManager_; + delete vcardManager_; + delete vcardStorage_; + delete crypto_; + delete ftOverview_; + delete ftManager_; + delete wbSessionManager_; + delete wbManager_; + delete directedPresenceSender_; + delete presenceSender_; + delete presenceOracle_; + delete nickResolver_; + delete mucRegistry_; + delete iqRouter_; + delete stanzaChannel_; + delete eventController_; + delete uiEventStream_; + delete mucManager_; + delete xmppRoster_; + delete entityCapsProvider_; + delete chatListWindow_; + delete mocks_; + delete settings_; + } + + void testFirstOpenWindowIncoming() { + JID messageJID("testling@test.com/resource1"); + + 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_)); + } + + void testSecondOpenWindowIncoming() { + JID messageJID1("testling@test.com/resource1"); + + MockChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); + + std::shared_ptr<Message> message1(new Message()); + message1->setFrom(messageJID1); + std::string body1("This is a legible message. >HEH@)oeueu"); + message1->setBody(body1); + manager_->handleIncomingMessage(message1); + CPPUNIT_ASSERT_EQUAL(body1, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + + JID messageJID2("testling@test.com/resource2"); + + std::shared_ptr<Message> message2(new Message()); + message2->setFrom(messageJID2); + std::string body2("This is a legible message. .cmaulm.chul"); + message2->setBody(body2); + manager_->handleIncomingMessage(message2); + CPPUNIT_ASSERT_EQUAL(body2, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + } + + void testFirstOpenWindowOutgoing() { + std::string messageJIDString("testling@test.com"); + + ChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString), uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString))); + } + + + void testFirstOpenWindowBareToFull() { + std::string bareJIDString("testling@test.com"); + std::string fullJIDString("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(bareJIDString))); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(JID(fullJIDString)); + std::string body("This is a legible message. mjuga3089gm8G(*>M)@*("); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + } + + void testSecondWindow() { + std::string messageJIDString1("testling1@test.com"); + ChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1))); + + std::string messageJIDString2("testling2@test.com"); + ChatWindow* window2 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString2))); + } + + /** Complete cycle. + Create unbound window. + Bind it. + Unbind it. + Rebind it. + */ + void testUnbindRebind() { + std::string bareJIDString("testling@test.com"); + std::string fullJIDString1("testling@test.com/resource1"); + std::string fullJIDString2("testling@test.com/resource2"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString), uiEventStream_).Return(window); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(bareJIDString))); + + std::shared_ptr<Message> message1(new Message()); + message1->setFrom(JID(fullJIDString1)); + std::string messageBody1("This is a legible message."); + message1->setBody(messageBody1); + manager_->handleIncomingMessage(message1); + CPPUNIT_ASSERT_EQUAL(messageBody1, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + std::shared_ptr<Presence> jid1Online(new Presence()); + jid1Online->setFrom(JID(fullJIDString1)); + std::shared_ptr<Presence> jid1Offline(new Presence()); + jid1Offline->setFrom(JID(fullJIDString1)); + jid1Offline->setType(Presence::Unavailable); + presenceOracle_->onPresenceChange(jid1Offline); + + std::shared_ptr<Message> message2(new Message()); + message2->setFrom(JID(fullJIDString2)); + std::string messageBody2("This is another legible message."); + message2->setBody(messageBody2); + manager_->handleIncomingMessage(message2); + CPPUNIT_ASSERT_EQUAL(messageBody2, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + } + + /** + * Test that MUC PMs get opened in the right windows + */ + void testThreeMUCWindows() { + JID muc("testling@test.com"); + ChatWindow* mucWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc, uiEventStream_).Return(mucWindow); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(muc, std::string("nick"))); + + + std::string messageJIDString1("testling@test.com/1"); + ChatWindow* window1 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1), uiEventStream_).Return(window1); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1))); + + std::string messageJIDString2("testling@test.com/2"); + ChatWindow* window2 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2), uiEventStream_).Return(window2); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString2))); + + std::string messageJIDString3("testling@test.com/3"); + ChatWindow* window3 = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString3), uiEventStream_).Return(window3); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString3))); + + /* Refetch an earlier window */ + /* We do not expect a new window to be created */ + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(messageJIDString1))); + + } + + /** + 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_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1, uiEventStream_).Return(window1); + + std::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"); + + std::shared_ptr<Message> message2(new Message()); + message2->setFrom(messageJID2); + message2->setBody("This is a legible message2."); + manager_->handleIncomingMessage(message2); + + std::shared_ptr<Presence> jid1Online(new Presence()); + jid1Online->setFrom(JID(messageJID1)); + std::shared_ptr<Presence> jid1Offline(new Presence()); + jid1Offline->setFrom(JID(messageJID1)); + jid1Offline->setType(Presence::Unavailable); + presenceOracle_->onPresenceChange(jid1Offline); + + std::shared_ptr<Presence> jid2Online(new Presence()); + jid2Online->setFrom(JID(messageJID2)); + std::shared_ptr<Presence> jid2Offline(new Presence()); + jid2Offline->setFrom(JID(messageJID2)); + jid2Offline->setType(Presence::Unavailable); + presenceOracle_->onPresenceChange(jid2Offline); + + JID messageJID3("testling@test.com/resource3"); + + std::shared_ptr<Message> message3(new Message()); + message3->setFrom(messageJID3); + std::string body3("This is a legible message3."); + message3->setBody(body3); + manager_->handleIncomingMessage(message3); + CPPUNIT_ASSERT_EQUAL(body3, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + + std::shared_ptr<Message> message2b(new Message()); + message2b->setFrom(messageJID2); + std::string body2b("This is a legible message2b."); + message2b->setBody(body2b); + manager_->handleIncomingMessage(message2b); + CPPUNIT_ASSERT_EQUAL(body2b, MockChatWindow::bodyFromMessage(window1->lastAddedMessage_)); + } + + /** + * Test that ChatController doesn't send receipts anymore after removal of the contact from the roster. + */ + void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() { + JID messageJID("testling@test.com/resource1"); + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); + + MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1); + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr); + + xmppRoster_->removeContact(messageJID); + + message->setID("2"); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + } + + /** + * Test that ChatController sends receipts after the contact has been added to the roster. + */ + void testChatControllerPresenceAccessUpdatedOnAddToRoster() { + JID messageJID("testling@test.com/resource1"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>()); + + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both); + message->setID("2"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr); + } + + /** + * Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both. + */ + void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() { + testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both); + } + + /** + * Test that ChatController sends receipts if requested after change from subscription state To to subscription state From. + */ + void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() { + testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From); + } + + void testChatControllerFullJIDBindingOnMessageAndNotReceipt() { + JID ownJID("test@test.com/resource"); + JID sender("foo@test.com"); + std::vector<JID> senderResource; + senderResource.push_back(sender.withResource("resourceA")); + senderResource.push_back(sender.withResource("resourceB")); + + // We support delivery receipts. + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender)); + + for (const auto& senderJID : senderResource) { + // The sender supports delivery receipts. + DiscoInfo::ref disco = std::make_shared<DiscoInfo>(); + disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); + entityCapsProvider_->caps[senderJID] = disco; + + // The sender is online. + Presence::ref senderPresence = std::make_shared<Presence>(); + senderPresence->setFrom(senderJID); + senderPresence->setTo(ownJID); + stanzaChannel_->onPresenceReceived(senderPresence); + + entityCapsProvider_->onCapsChanged(senderJID); + } + + // Send first message. + window->onSendMessageRequest("hello there", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref receiptReply = std::make_shared<Message>(); + receiptReply->setFrom(senderJID); + receiptReply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); + receiptReply->addPayload(receipt); + manager_->handleIncomingMessage(receiptReply); + } + + // Send second message. + window->onSendMessageRequest("how are you?", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref receiptReply = std::make_shared<Message>(); + receiptReply->setFrom(senderJID); + receiptReply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); + receiptReply->addPayload(receipt); + manager_->handleIncomingMessage(receiptReply); + } + + // Reply with a message including a body text. + Message::ref reply = std::make_shared<Message>(); + reply->setFrom(senderResource[0]); + reply->setTo(ownJID); + reply->setBody("fine."); + manager_->handleIncomingMessage(reply); + + // Send third message. + window->onSendMessageRequest("great to hear.", false); + + // The chat session is bound to the full JID of the first resource. + CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(2)->getPayload<DeliveryReceiptRequest>()); + + // Receive random receipt from second sender resource. + reply = std::make_shared<Message>(); + reply->setFrom(senderResource[1]); + reply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(2)->getID()); + reply->addPayload(receipt); + manager_->handleIncomingMessage(reply); + + // Send forth message. + window->onSendMessageRequest("what else is new?", false); + + // The chat session is bound to the full JID of the first resource. + CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>()); + + // Reply with a message including a body text from second resource. + reply = std::make_shared<Message>(); + reply->setFrom(senderResource[1]); + reply->setTo(ownJID); + reply->setBody("nothing."); + manager_->handleIncomingMessage(reply); + + // Send fifth message. + window->onSendMessageRequest("okay", false); + + // The chat session is now bound to the full JID of the second resource. + CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(5)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(5)->getPayload<DeliveryReceiptRequest>()); + } + + void testChatControllerFullJIDBindingOnTypingAndNotActive() { + JID ownJID("test@test.com/resource"); + JID sender("foo@test.com"); + std::vector<JID> senderResource; + senderResource.push_back(sender.withResource("resourceA")); + senderResource.push_back(sender.withResource("resourceB")); + + // We support delivery receipts. + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender)); + + for (const auto& senderJID : senderResource) { + // The sender supports delivery receipts. + DiscoInfo::ref disco = std::make_shared<DiscoInfo>(); + disco->addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); + entityCapsProvider_->caps[senderJID] = disco; + + // The sender is online. + Presence::ref senderPresence = std::make_shared<Presence>(); + senderPresence->setFrom(senderJID); + senderPresence->setTo(ownJID); + stanzaChannel_->onPresenceReceived(senderPresence); + + entityCapsProvider_->onCapsChanged(senderJID); + } + + // Send first message. + window->onSendMessageRequest("hello there", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref reply = std::make_shared<Message>(); + reply->setFrom(senderJID); + reply->setTo(ownJID); + + std::shared_ptr<ChatState> csn = std::make_shared<ChatState>(); + csn->setChatState(ChatState::Active); + reply->addPayload(csn); + manager_->handleIncomingMessage(reply); + } + + // Send second message. + window->onSendMessageRequest("how are you?", false); + + // A bare message is send because no resources is bound. + CPPUNIT_ASSERT_EQUAL(sender, stanzaChannel_->getStanzaAtIndex<Message>(1)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(1)->getPayload<DeliveryReceiptRequest>()); + + // Two resources respond with message receipts. + for (const auto& senderJID : senderResource) { + Message::ref receiptReply = std::make_shared<Message>(); + receiptReply->setFrom(senderJID); + receiptReply->setTo(ownJID); + + std::shared_ptr<DeliveryReceipt> receipt = std::make_shared<DeliveryReceipt>(); + receipt->setReceivedID(stanzaChannel_->getStanzaAtIndex<Message>(1)->getID()); + receiptReply->addPayload(receipt); + manager_->handleIncomingMessage(receiptReply); + } + + // Reply with a message including a CSN. + Message::ref reply = std::make_shared<Message>(); + reply->setFrom(senderResource[0]); + reply->setTo(ownJID); + + std::shared_ptr<ChatState> csn = std::make_shared<ChatState>(); + csn->setChatState(ChatState::Composing); + reply->addPayload(csn); + manager_->handleIncomingMessage(reply); + + // Send third message. + window->onSendMessageRequest("great to hear.", false); + + // The chat session is now bound to the full JID of the first resource due to its recent composing message. + CPPUNIT_ASSERT_EQUAL(senderResource[0], stanzaChannel_->getStanzaAtIndex<Message>(3)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(3)->getPayload<DeliveryReceiptRequest>()); + + // Reply with a message including a CSN from the other resource. + reply = std::make_shared<Message>(); + reply->setFrom(senderResource[1]); + reply->setTo(ownJID); + + csn = std::make_shared<ChatState>(); + csn->setChatState(ChatState::Composing); + reply->addPayload(csn); + manager_->handleIncomingMessage(reply); + + // Send third message. + window->onSendMessageRequest("ping.", false); + + // The chat session is now bound to the full JID of the second resource due to its recent composing message. + CPPUNIT_ASSERT_EQUAL(senderResource[1], stanzaChannel_->getStanzaAtIndex<Message>(4)->getTo()); + CPPUNIT_ASSERT(stanzaChannel_->getStanzaAtIndex<Message>(4)->getPayload<DeliveryReceiptRequest>()); + } + + void testChatControllerPMPresenceHandling() { + JID participantA = JID("test@rooms.test.com/participantA"); + JID participantB = JID("test@rooms.test.com/participantB"); + + mucRegistry_->addMUC("test@rooms.test.com"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(JID(participantA))); + + Presence::ref presence = Presence::create(); + presence->setFrom(participantA); + presence->setShow(StatusShow::Online); + stanzaChannel_->onPresenceReceived(presence); + CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); + + presence = Presence::create(); + presence->setFrom(participantB); + presence->setShow(StatusShow::Away); + stanzaChannel_->onPresenceReceived(presence); + + presence = Presence::create(); + presence->setFrom(participantA); + presence->setShow(StatusShow::None); + presence->setType(Presence::Unavailable); + stanzaChannel_->onPresenceReceived(presence); + CPPUNIT_ASSERT_EQUAL(std::string("participantA has gone offline."), MockChatWindow::bodyFromMessage(window->lastReplacedLastMessage_)); + } + + void testChatControllerMucPmUnavailableErrorHandling() { + auto mucJID = JID("test@rooms.test.com"); + auto participantA = mucJID.withResource("participantA"); + auto participantB = mucJID.withResource("participantB"); + + auto mucWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(mucWindow); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, participantB.getResource())); + CPPUNIT_ASSERT_EQUAL(true, mucWindow->mucType_.is_initialized()); + + auto window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(participantA, uiEventStream_).Return(window); + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(participantA)); + CPPUNIT_ASSERT_EQUAL(false, window->mucType_.is_initialized()); + + Presence::ref presence = Presence::create(); + presence->setFrom(participantA); + presence->setShow(StatusShow::Online); + stanzaChannel_->onPresenceReceived(presence); + CPPUNIT_ASSERT_EQUAL(std::string("participantA has become available."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); + + // send message to participantA + auto messageBody = std::string("message body to send"); + window->onSendMessageRequest(messageBody, false); + auto sendMessageStanza = stanzaChannel_->getStanzaAtIndex<Message>(3); + CPPUNIT_ASSERT_EQUAL(messageBody, *sendMessageStanza->getBody()); + + // receive reply with error + auto messageErrorReply = std::make_shared<Message>(); + messageErrorReply->setID(stanzaChannel_->getNewIQID()); + messageErrorReply->setType(Message::Error); + messageErrorReply->setFrom(participantA); + messageErrorReply->setTo(jid_); + messageErrorReply->addPayload(std::make_shared<ErrorPayload>(ErrorPayload::ItemNotFound, ErrorPayload::Cancel, "Recipient not in room")); + + auto lastMUCWindowErrorMessageBeforeError = MockChatWindow::bodyFromMessage(mucWindow->lastAddedErrorMessage_); + manager_->handleIncomingMessage(messageErrorReply); + + // assert that error is not routed to MUC window + CPPUNIT_ASSERT_EQUAL(lastMUCWindowErrorMessageBeforeError, MockChatWindow::bodyFromMessage(mucWindow->lastAddedErrorMessage_)); + // assert that error is routed to PM + CPPUNIT_ASSERT_EQUAL(std::string("This user could not be found in the room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_)); + } + + void testLocalMUCServiceDiscoveryResetOnDisconnect() { + JID ownJID("test@test.com/resource"); + JID sender("foo@test.com"); + + manager_->setOnline(true); + + // Open chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(sender, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<RequestChatUIEvent>(sender)); + + CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_); + + std::shared_ptr<IQ> infoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]); + std::shared_ptr<IQ> infoResponse = IQ::createResult(infoRequest->getFrom(), infoRequest->getTo(), infoRequest->getID()); + + DiscoInfo info; + info.addIdentity(DiscoInfo::Identity("Shakespearean Chat Service", "conference", "text")); + info.addFeature("http://jabber.org/protocol/muc"); + infoResponse->addPayload(std::make_shared<DiscoInfo>(info)); + stanzaChannel_->onIQReceived(infoResponse); + + CPPUNIT_ASSERT_EQUAL(true, window->impromptuMUCSupported_); + manager_->setOnline(false); + CPPUNIT_ASSERT_EQUAL(false, window->impromptuMUCSupported_); + } + + void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) { + JID messageJID("testling@test.com/resource1"); + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from); + + MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, true); + + std::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(0), stanzaChannel_->countSentStanzaOfType<Message>()); + + xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to); + message->setID("2"); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(st(1), stanzaChannel_->countSentStanzaOfType<Message>()); + Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(1); + CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != nullptr); + } + + void testChatControllerHighlightingNotificationTesting() { + HighlightConfiguration::KeywordHightlight keywordRuleA; + keywordRuleA.keyword = "Romeo"; + keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow")); + keywordRuleA.action.setSoundFilePath(boost::optional<std::string>("")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA); + + HighlightConfiguration::KeywordHightlight keywordRuleB; + keywordRuleB.keyword = "Juliet"; + keywordRuleB.action.setFrontColor(boost::optional<std::string>("green")); + keywordRuleB.action.setSoundFilePath(boost::optional<std::string>("/tmp/someotherfile.wav")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB); + + JID messageJID = JID("testling@test.com"); + + 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 message should cause two sounds: Juliet and Romeo."); + message->setBody(body); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(2, handledHighlightActions_); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); + CPPUNIT_ASSERT_EQUAL(size_t(1), notifier_->notifications.size()); + } + + void testChatControllerHighlightingNotificationDeduplicateSounds() { + auto keywordRuleA = HighlightConfiguration::KeywordHightlight(); + keywordRuleA.keyword = "Romeo"; + keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow")); + keywordRuleA.action.setSoundFilePath(boost::optional<std::string>("")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA); + + auto keywordRuleB = HighlightConfiguration::KeywordHightlight(); + keywordRuleB.keyword = "Juliet"; + keywordRuleB.action.setFrontColor(boost::optional<std::string>("green")); + keywordRuleB.action.setSoundFilePath(boost::optional<std::string>("")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleB); + + JID messageJID = JID("testling@test.com"); + + 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 message should cause one sound, because both actions have the same sound: Juliet and Romeo."); + message->setBody(body); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(1, handledHighlightActions_); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleA.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); + CPPUNIT_ASSERT(soundsPlayed_.find(keywordRuleB.action.getSoundFilePath().get_value_or("")) != soundsPlayed_.end()); + CPPUNIT_ASSERT_EQUAL(size_t(1), notifier_->notifications.size()); + } + + void testChatControllerHighlightingNotificationKeyword() { + auto keywordRuleA = HighlightConfiguration::KeywordHightlight(); + keywordRuleA.keyword = "Swift"; + keywordRuleA.action.setFrontColor(boost::optional<std::string>("yellow")); + keywordRuleA.action.setSoundFilePath(boost::optional<std::string>("")); + keywordRuleA.action.setSystemNotificationEnabled(true); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordRuleA); + + JID messageJID = JID("testling@test.com"); + + 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("Let's see if the Swift highlight kicks off a system notification."); + message->setBody(body); + manager_->handleIncomingMessage(message); + + CPPUNIT_ASSERT_EQUAL(size_t(2), notifier_->notifications.size()); + } + + void testChatControllerMeMessageHandling() { + JID messageJID("testling@test.com/resource1"); + + 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("/me is feeling delighted."); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(std::string("is feeling delighted."), window->bodyFromMessage(window->lastAddedAction_)); + } + + void testRestartingMUCComponentCrash() { + JID mucJID = JID("teaparty@rooms.wonderland.lit"); + JID self = JID("girl@wonderland.lit/rabbithole"); + std::string nick = "aLiCe"; + + MockChatWindow* window; + + auto genRemoteMUCPresence = [=]() { + auto presence = Presence::create(); + presence->setFrom(mucJID.withResource(nick)); + presence->setTo(self); + return presence; + }; + + // User rejoins. + window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + // Join room + { + auto joinRoomEvent = std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nick); + uiEventStream_->send(joinRoomEvent); + } + + { + auto firstPresence = genRemoteMUCPresence(); + firstPresence->setType(Presence::Unavailable); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addItem(MUCItem(MUCOccupant::Owner, MUCOccupant::NoRole)); + firstPresence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(firstPresence); + } + CPPUNIT_ASSERT_EQUAL(std::string("Couldn't enter room: Unable to enter this room."), MockChatWindow::bodyFromMessage(window->lastAddedErrorMessage_)); + + { + auto presence = genRemoteMUCPresence(); + presence->setType(Presence::Unavailable); + auto userPayload = std::make_shared<MUCUserPayload>(); + userPayload->addStatusCode(303); + auto item = MUCItem(MUCOccupant::Owner, self, MUCOccupant::Moderator); + item.nick = nick; + userPayload->addItem(item); + userPayload->addStatusCode(110); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + } + + void testChatControllerMeMessageHandlingInMUC() { + JID mucJID("mucroom@rooms.test.com"); + std::string nickname = "toodles"; + + //highlightManager_->resetToDefaultConfiguration(); + + // add highlight rule for 'foo' + HighlightConfiguration::KeywordHightlight keywordHighlight; + keywordHighlight.keyword = "foo"; + keywordHighlight.action.setBackColor(boost::optional<std::string>("green")); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordHighlight); + + HighlightConfiguration::KeywordHightlight keywordHighlightNotification; + keywordHighlightNotification.keyword = "Swift"; + keywordHighlightNotification.action.setBackColor(boost::optional<std::string>("green")); + keywordHighlightNotification.action.setSystemNotificationEnabled(true); + highlightManager_->getConfiguration()->keywordHighlights.push_back(keywordHighlightNotification); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname)); + + 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); + } + + window->onSendMessageRequest("/me sends a test message with foo", false); + + window->resetLastMessages(); + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource(nickname)); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setBody("/me sends a test message with foo"); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("sends a test message with foo"), window->bodyFromMessage(window->lastAddedAction_)); + + window->resetLastMessages(); + { + Message::ref mucMirrored = std::make_shared<Message>(); + mucMirrored->setFrom(mucJID.withResource("someDifferentNickname")); + mucMirrored->setTo(jid_); + mucMirrored->setType(Message::Groupchat); + mucMirrored->setBody("/me says hello with a test message with foo and foo"); + manager_->handleIncomingMessage(mucMirrored); + } + CPPUNIT_ASSERT_EQUAL(std::string("says hello with a test message with foo and foo"), window->bodyFromMessage(window->lastAddedAction_)); + + window->resetLastMessages(); + { + Message::ref keywordMessage = std::make_shared<Message>(); + keywordMessage->setFrom(mucJID.withResource("someOtherDifferentNickname")); + keywordMessage->setTo(jid_); + keywordMessage->setType(Message::Groupchat); + keywordMessage->setBody("Let's mention Swift and see what happens."); + manager_->handleIncomingMessage(keywordMessage); + } + CPPUNIT_ASSERT_EQUAL(std::string("Let's mention Swift and see what happens."), window->bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(size_t(1), notifier_->notifications.size()); + } + + 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->lastReplacedLastMessage_)); + CPPUNIT_ASSERT_EQUAL(std::string("testling@test.com has gone offline."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); + } + + void testNotSplittingMUCPresenceJoinLeaveLinesOnChatStateNotifications() { + JID mucJID("mucroom@rooms.test.com"); + std::string nickname = "toodles"; + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname)); + + 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>(); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname has entered the room."), window->bodyFromMessage(window->lastAddedPresence_)); + CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastReplacedLastMessage_)); + window->resetLastMessages(); + + { + auto presence = genRemoteMUCPresence(); + presence->setFrom(mucJID.withResource("Romeo")); + auto userPayload = std::make_shared<MUCUserPayload>(); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastAddedPresence_)); + CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname and Romeo have entered the room"), window->bodyFromMessage(window->lastReplacedLastMessage_)); + window->resetLastMessages(); + + { + auto message = std::make_shared<Message>(); + message->setFrom(mucJID.withResource("Romeo")); + message->setTo(mucJID); + message->setType(Message::Groupchat); + message->addPayload(std::make_shared<ChatState>(ChatState::Composing)); + manager_->handleIncomingMessage(message); + } + + { + auto presence = genRemoteMUCPresence(); + presence->setFrom(mucJID.withResource("Juliet")); + auto userPayload = std::make_shared<MUCUserPayload>(); + presence->addPayload(userPayload); + stanzaChannel_->onPresenceReceived(presence); + } + CPPUNIT_ASSERT_EQUAL(std::string(), window->bodyFromMessage(window->lastAddedPresence_)); + CPPUNIT_ASSERT_EQUAL(std::string("someDifferentNickname, Romeo and Juliet have entered the room"), window->bodyFromMessage(window->lastReplacedLastMessage_)); + } + + template <typename CarbonsType> + Message::ref createCarbonsMessage(std::shared_ptr<CarbonsType> carbons, std::shared_ptr<Message> forwardedMessage) { + auto messageWrapper = std::make_shared<Message>(); + messageWrapper->setFrom(jid_.toBare()); + messageWrapper->setTo(jid_); + messageWrapper->setType(Message::Chat); + + messageWrapper->addPayload(carbons); + auto forwarded = std::make_shared<Forwarded>(); + carbons->setForwarded(forwarded); + forwarded->setStanza(forwardedMessage); + return messageWrapper; + } + + void testCarbonsForwardedIncomingMessageToSecondResource() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + 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_)); + + // incoming carbons message from another resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(messageJID); + originalMessage->setTo(jid2); + 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_); + + auto recentChats = manager_->getRecentChats(); + CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1)); + CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, originalMessage->getFrom().toBare()); + CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some further text.")); + } + } + + void testCarbonsForwardedOutgoingMessageFromSecondResource() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + 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_)); + + // incoming carbons message from another resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(jid2); + originalMessage->setTo(messageJID); + originalMessage->setType(Message::Chat); + originalMessage->setID("abcdefg123456"); + std::string forwardedBody = "Some text my other resource sent."; + originalMessage->setBody(forwardedBody); + originalMessage->addPayload(std::make_shared<DeliveryReceiptRequest>()); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsSent>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(true, window->lastAddedMessageSenderIsSelf_); + CPPUNIT_ASSERT_EQUAL(size_t(1), window->receiptChanges_.size()); + CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptRequested, window->receiptChanges_[0].second); + + auto recentChats = manager_->getRecentChats(); + CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1)); + CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, originalMessage->getTo().toBare()); + CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some text my other resource sent.")); + } + + // incoming carbons message for the received delivery receipt to the other resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(messageJID); + originalMessage->setTo(jid2); + originalMessage->setType(Message::Chat); + originalMessage->addPayload(std::make_shared<DeliveryReceipt>("abcdefg123456")); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(size_t(2), window->receiptChanges_.size()); + CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptReceived, window->receiptChanges_[1].second); + + //Delivery receipt should not change the latest recent entry. Checking for the original message. + auto recentChats = manager_->getRecentChats(); + CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1)); + CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, messageJID.toBare()); + CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some text my other resource sent.")); + } + } + + void testCarbonsForwardedIncomingDuplicates() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + 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_)); + + // 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_); + + auto recentChats = manager_->getRecentChats(); + CPPUNIT_ASSERT_EQUAL(recentChats.size(), size_t(1)); + CPPUNIT_ASSERT_EQUAL(recentChats[0].jid, originalMessage->getFrom().toBare()); + CPPUNIT_ASSERT_EQUAL(recentChats[0].activity, std::string("Some further text.")); + } + } + + /** + * 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_)); + } + + void impromptuChatSetup(MockChatWindow* window) { + stanzaChannel_->uniqueIDs_ = true; + JID mucJID("795B7BBE-9099-4A0D-81BA-C816F78E275C@test.com"); + manager_->setOnline(true); + + std::shared_ptr<IQ> infoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]); + CPPUNIT_ASSERT(infoRequest); + + std::shared_ptr<IQ> infoResponse = IQ::createResult(infoRequest->getFrom(), infoRequest->getTo(), infoRequest->getID()); + + DiscoInfo info; + info.addIdentity(DiscoInfo::Identity("Shakespearean Chat Service", "conference", "text")); + info.addFeature("http://jabber.org/protocol/muc"); + infoResponse->addPayload(std::make_shared<DiscoInfo>(info)); + stanzaChannel_->onIQReceived(infoResponse); + + 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); + uiEventStream_->send(std::make_shared<CreateImpromptuMUCUIEvent>(jids, mucJID, "")); + CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle()); + + // Check the MUC security marking request + auto mucInfoRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[2]); + CPPUNIT_ASSERT(mucInfoRequest); + + auto mucJoinPresence = std::dynamic_pointer_cast<Presence>(stanzaChannel_->sentStanzas[3]); + CPPUNIT_ASSERT(mucJoinPresence); + + // MUC presence reply + auto mucResponse = Presence::create(); + mucResponse->setTo(jid_); + mucResponse->setFrom(mucJoinPresence->getTo()); + mucResponse->addPayload([]() { + auto mucUser = std::make_shared<MUCUserPayload>(); + mucUser->addItem(MUCItem(MUCOccupant::Member, MUCOccupant::Participant)); + mucUser->addStatusCode(MUCUserPayload::StatusCode(110)); + mucUser->addStatusCode(MUCUserPayload::StatusCode(210)); + return mucUser; + }()); + stanzaChannel_->onPresenceReceived(mucResponse); + + // Before people join the impromptu room, the title is based on names coming from Roster + CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle()); + + auto mucParticipantJoined = [&](const JID& jid) { + auto participantJoinedPresence = Presence::create(); + participantJoinedPresence->setTo(jid_); + participantJoinedPresence->setFrom(mucJID.withResource(jid.toString())); + auto mucUser = std::make_shared<MUCUserPayload>(); + mucUser->addItem(MUCItem(MUCOccupant::Member, MUCOccupant::Participant)); + participantJoinedPresence->addPayload(mucUser); + return participantJoinedPresence; + }; + + for (const auto& participantJID : jids) { + stanzaChannel_->onPresenceReceived(mucParticipantJoined(participantJID)); + } + } + + void testImpromptuChatTitle() { + // Open a new chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + impromptuChatSetup(window); + // After people joined, the title is the list of participant nicknames or names coming from Roster (if nicknames are unavailable) + CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), manager_->getRecentChats()[0].getTitle()); + } + + void testImpromptuChatWindowTitle() { + // Open a new chat window to a sender. + MockChatWindow* window = new MockChatWindow(); + impromptuChatSetup(window); + // After people joined, the title of chat window is combined of participant nicknames or names coming from Roster (if nicknames are unavailable) + CPPUNIT_ASSERT_EQUAL(std::string("bar@test.com, foo@test.com"), window->name_); + } + + void testStandardMUCChatWindowTitle() { + JID mucJID("mucroom@rooms.test.com"); + std::string nickname = "toodles"; + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(mucJID, uiEventStream_).Return(window); + + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>(mucJID, boost::optional<std::string>(), nickname)); + CPPUNIT_ASSERT_EQUAL(std::string("mucroom"), window->name_); + } + + std::shared_ptr<Storage> createBookmarkStorageWithJID(std::shared_ptr<IQ> bookmarkRequest, const JID& jid, const bool autojoin) { + CPPUNIT_ASSERT(bookmarkRequest); + CPPUNIT_ASSERT_EQUAL(IQ::Get, bookmarkRequest->getType()); + + auto privateStorage = bookmarkRequest->getPayload<PrivateStorage>(); + CPPUNIT_ASSERT(privateStorage); + + auto storage = std::dynamic_pointer_cast<Storage>(privateStorage->getPayload()); + CPPUNIT_ASSERT(storage); + + auto roomsStorage = std::make_shared<Storage>(); + if (jid.isValid()) { + auto room = Storage::Room(); + room.jid = jid; + room.autoJoin = autojoin; + roomsStorage->addRoom(room); + } + return roomsStorage; + } + + void testReceivingBookmarksWithDomainJID() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>(createBookmarkStorageWithJID(bookmarkRequest, "montague.lit", true)) + ); + stanzaChannel_->onIQReceived(response); + } + + void testReceivingBookmarksWithBareJID() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID("example@montague.lit"), uiEventStream_).Return(window); + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>(createBookmarkStorageWithJID(bookmarkRequest, "example@montague.lit", true)) + ); + stanzaChannel_->onIQReceived(response); + } + + void testReceivingNoBookmarks() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>() + ); + stanzaChannel_->onIQReceived(response); + } + + void testReceivingNullBookmarks() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + nullptr + ); + stanzaChannel_->onIQReceived(response); + } + + void testReceivingBookmarksError() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto response = IQ::createError( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + ErrorPayload::Condition::ServiceUnavailable, + ErrorPayload::Type::Cancel, + nullptr + ); + stanzaChannel_->onIQReceived(response); + } + + void testReceivingBookmarksWithFullJID() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>(createBookmarkStorageWithJID(bookmarkRequest, "example@montague.lit/someresource", true)) + ); + stanzaChannel_->onIQReceived(response); + } + + void testAutoJoinBookmarksAndChattables() { + + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto roomsStorage = createBookmarkStorageWithJID(bookmarkRequest, "autojoin@bookmark.lit", true); + auto room = Storage::Room(); + room.jid = "noAutojoin@bookmark.lit"; + roomsStorage->addRoom(room); + + //Only autojoin@bookmark.lit window should open. + MockChatWindow* autojoinBookmarkWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID("autojoin@bookmark.lit"), uiEventStream_).Return(autojoinBookmarkWindow); + + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>(roomsStorage) + ); + stanzaChannel_->onIQReceived(response); + //Both bookmarks should be added to the chattables. + CPPUNIT_ASSERT_EQUAL(size_t(2), chattables_->get().size()); + auto autoJoinState = chattables_->getState("autojoin@bookmark.lit"); + CPPUNIT_ASSERT(autoJoinState.type == Chattables::State::Type::Room); + CPPUNIT_ASSERT_EQUAL(autoJoinState.status, StatusShow::Online); + auto noAutoJoinState = chattables_->getState("noAutojoin@bookmark.lit"); + CPPUNIT_ASSERT(noAutoJoinState.type == Chattables::State::Type::Room); + CPPUNIT_ASSERT_EQUAL(noAutoJoinState.status, StatusShow::None); + } + + void testJoinNoAutojoinBookmark() { + + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto roomsStorage = createBookmarkStorageWithJID(bookmarkRequest, "example@montague.lit", false); + + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>(roomsStorage) + ); + stanzaChannel_->onIQReceived(response); + + //Join previous bookmarked room, expecting no increase in chattables and change of autojoin in bookmark to true + MockChatWindow* newExampleChatWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID("example@montague.lit"), uiEventStream_).Return(newExampleChatWindow); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>("example@montague.lit", boost::optional<std::string>(), boost::optional<std::string>())); + CPPUNIT_ASSERT_EQUAL(size_t(1), chattables_->get().size()); + auto state = chattables_->getState("example@montague.lit"); + CPPUNIT_ASSERT(state.type == Chattables::State::Type::Room); + CPPUNIT_ASSERT_EQUAL(state.status, StatusShow::Online); + + auto bookmarks = manager_->getBookmarkManager()->getBookmarks(); + CPPUNIT_ASSERT_EQUAL(bookmarks.size(), size_t(1)); + CPPUNIT_ASSERT(bookmarks[0].getRoom() == JID("example@montague.lit")); + CPPUNIT_ASSERT(bookmarks[0].getAutojoin()); + } + + void testJoinAndBookmarkMUC() { + auto bookmarkRequest = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]); + auto roomsStorage = createBookmarkStorageWithJID(bookmarkRequest, "", true); + auto response = IQ::createResult( + bookmarkRequest->getFrom(), + bookmarkRequest->getTo(), + bookmarkRequest->getID(), + std::make_shared<PrivateStorage>(roomsStorage) + ); + stanzaChannel_->onIQReceived(response); + + //Join non-bookmarked room expecting for the room to get bookmarked with autojoin to true + MockChatWindow* exampleChatWindow = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With("example@montague.lit", uiEventStream_).Return(exampleChatWindow); + uiEventStream_->send(std::make_shared<JoinMUCUIEvent>("example@montague.lit", boost::optional<std::string>(), boost::optional<std::string>())); + { + CPPUNIT_ASSERT_EQUAL(size_t(1), chattables_->get().size()); + auto state = chattables_->getState("example@montague.lit"); + CPPUNIT_ASSERT(state.type == Chattables::State::Type::Room); + CPPUNIT_ASSERT_EQUAL(state.status, StatusShow::Online); + + auto bookmarks = manager_->getBookmarkManager()->getBookmarks(); + CPPUNIT_ASSERT_EQUAL(bookmarks.size(), size_t(1)); + CPPUNIT_ASSERT(bookmarks[0].getRoom() == JID("example@montague.lit")); + CPPUNIT_ASSERT(bookmarks[0].getAutojoin()); + + } + //Exiting room that is bookmarked, expecting chattable to stay but bookmark autojoin change to false. + exampleChatWindow->onClosed(); + { + CPPUNIT_ASSERT_EQUAL(size_t(1), chattables_->get().size()); + auto bookmarks = manager_->getBookmarkManager()->getBookmarks(); + CPPUNIT_ASSERT_EQUAL(bookmarks.size(), size_t(1)); + CPPUNIT_ASSERT(bookmarks[0].getRoom() == JID("example@montague.lit")); + CPPUNIT_ASSERT(!bookmarks[0].getAutojoin()); + } + } private: - boost::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { - boost::shared_ptr<Message> message = boost::make_shared<Message>(); - message->setFrom(from); - message->setID(id); - message->setBody("This will cause the window to open"); - message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); - return message; - } - - size_t st(int i) { - return static_cast<size_t>(i); - } + 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_; - ChatsManager* manager_; - DummyStanzaChannel* stanzaChannel_; - IQChannel* iqChannel_; - IQRouter* iqRouter_; - EventController* eventController_; - ChatWindowFactory* chatWindowFactory_; - JoinMUCWindowFactory* joinMUCWindowFactory_; - NickResolver* nickResolver_; - PresenceOracle* presenceOracle_; - AvatarManager* avatarManager_; - boost::shared_ptr<DiscoInfo> serverDiscoInfo_; - XMPPRosterImpl* xmppRoster_; - PresenceSender* presenceSender_; - MockRepository* mocks_; - UIEventStream* uiEventStream_; - ChatListWindowFactory* chatListWindowFactory_; - WhiteboardWindowFactory* whiteboardWindowFactory_; - MUCSearchWindowFactory* mucSearchWindowFactory_; - MUCRegistry* mucRegistry_; - DirectedPresenceSender* directedPresenceSender_; - EntityCapsManager* entityCapsManager_; - CapsProvider* capsProvider_; - MUCManager* mucManager_; - DummySettingsProvider* settings_; - ProfileSettingsProvider* profileSettings_; - ChatListWindow* chatListWindow_; - FileTransferOverview* ftOverview_; - FileTransferManager* ftManager_; - WhiteboardSessionManager* wbSessionManager_; - WhiteboardManager* wbManager_; - HighlightManager* highlightManager_; - ClientBlockListManager* clientBlockListManager_; - std::map<std::string, std::string> emoticons_; + JID jid_; + std::unique_ptr<DummyNotifier> notifier_; + ExtendedChatsManager* manager_; + DummyStanzaChannel* stanzaChannel_; + IQRouter* iqRouter_; + EventController* eventController_; + ChatWindowFactory* chatWindowFactory_; + JoinMUCWindowFactory* joinMUCWindowFactory_; + NickResolver* nickResolver_; + PresenceOracle* presenceOracle_; + AvatarManager* avatarManager_; + EventNotifier* eventNotifier_; + std::shared_ptr<DiscoInfo> serverDiscoInfo_; + XMPPRosterImpl* xmppRoster_; + PresenceSender* presenceSender_; + MockRepository* mocks_; + UIEventStream* uiEventStream_; + ChatListWindowFactory* chatListWindowFactory_; + WhiteboardWindowFactory* whiteboardWindowFactory_; + MUCSearchWindowFactory* mucSearchWindowFactory_; + MUCRegistry* mucRegistry_; + DirectedPresenceSender* directedPresenceSender_; + DummyEntityCapsProvider* entityCapsProvider_; + MUCManager* mucManager_; + DummySettingsProvider* settings_; + ProfileSettingsProvider* profileSettings_; + ChatListWindow* chatListWindow_; + FileTransferOverview* ftOverview_; + FileTransferManager* ftManager_; + WhiteboardSessionManager* wbSessionManager_; + WhiteboardManager* wbManager_; + HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; + VCardManager* vcardManager_; + CryptoProvider* crypto_; + VCardStorage* vcardStorage_; + std::map<std::string, std::string> emoticons_; + int handledHighlightActions_; + std::set<std::string> soundsPlayed_; + DummyTimerFactory* timerFactory_; + std::unique_ptr<Chattables> chattables_; + }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); - diff --git a/Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp b/Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp new file mode 100644 index 0000000..f30e3fd --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChattablesTest.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <gtest/gtest.h> +#include <hippomocks.h> + +#include <Swift/Controllers/Chat/Chattables.h> + +// Clang wrongly things that tests for 0 are using 0 as null. +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" + +using namespace Swift; + +class ChattablesTest : public ::testing::Test { + protected: + void SetUp() { + beginListSize_ = 0; + endListSize_ = 0; + callsToBegin_ = 0; + callsToEnd_ = 0; + } + + void TearDown() { + } + + void updateBeginListSize(int listSize); + void updateEndListSize(); + + Chattables chattables_; + int beginListSize_; + int endListSize_; + int callsToBegin_; + int callsToEnd_; +}; + +void ChattablesTest::updateBeginListSize(int listSize) { + beginListSize_ = listSize; + callsToBegin_++; +} + +void ChattablesTest::updateEndListSize() { + endListSize_ = chattables_.get().size(); + callsToEnd_++; +} + +TEST_F(ChattablesTest, testAddJID) { + chattables_.onBeginAdd.connect([this](int listSize){ updateBeginListSize(listSize); }); + chattables_.onAdded.connect([this](){ updateEndListSize(); }); + JID jid("user@swift.jid"); + + chattables_.addJID(jid, Chattables::State::Type::Person); + ASSERT_EQ(0, beginListSize_); + ASSERT_EQ(1, endListSize_); + ASSERT_EQ(1, callsToBegin_); + ASSERT_EQ(1, callsToEnd_); + + ASSERT_EQ(jid, chattables_.get()[0]); + Chattables::State state = chattables_.getState(jid); + ASSERT_EQ(jid, state.jid); + ASSERT_EQ(Chattables::State::Type::Person, state.type); +} + +TEST_F(ChattablesTest, testAddMultipleJIDs) { + chattables_.onBeginAdd.connect([this](int listSize){ updateBeginListSize(listSize); }); + chattables_.onAdded.connect([this](){ updateEndListSize(); }); + JID jid0("user0@swift.jid"); + JID jid1("user1@swift.jid"); + JID jid2("user2@swift.jid"); + + chattables_.addJID(jid0, Chattables::State::Type::Person); + ASSERT_EQ(0, beginListSize_); + ASSERT_EQ(1, endListSize_); + ASSERT_EQ(1, callsToBegin_); + ASSERT_EQ(1, callsToEnd_); + chattables_.addJID(jid1, Chattables::State::Type::Person); + ASSERT_EQ(1, beginListSize_); + ASSERT_EQ(2, endListSize_); + ASSERT_EQ(2, callsToBegin_); + ASSERT_EQ(2, callsToEnd_); + chattables_.addJID(jid2, Chattables::State::Type::Person); + ASSERT_EQ(2, beginListSize_); + ASSERT_EQ(3, endListSize_); + ASSERT_EQ(3, callsToBegin_); + ASSERT_EQ(3, callsToEnd_); +} + +TEST_F(ChattablesTest, testAddRoomJID) { + chattables_.onBeginAdd.connect([this](int listSize){ updateBeginListSize(listSize); }); + chattables_.onAdded.connect([this](){ updateEndListSize(); }); + JID jid("room@swift.jid"); + + chattables_.addJID(jid, Chattables::State::Type::Room); + ASSERT_EQ(0, beginListSize_); + ASSERT_EQ(1, endListSize_); + ASSERT_EQ(1, callsToBegin_); + ASSERT_EQ(1, callsToEnd_); + + ASSERT_EQ(jid, chattables_.get()[0]); + Chattables::State state = chattables_.getState(jid); + ASSERT_EQ(jid, state.jid); + ASSERT_EQ(Chattables::State::Type::Room, state.type); +} + +TEST_F(ChattablesTest, testSetState) { + JID jid("user@swift.jid"); + chattables_.addJID(jid, Chattables::State::Type::Person); + ASSERT_EQ(1, chattables_.get().size()); + ASSERT_EQ(jid, chattables_.get()[0]); + ASSERT_EQ(Chattables::State::Type::Person, chattables_.getState(jid).type); + ASSERT_EQ(StatusShow::None, chattables_.getState(jid).status); + + JID returnedJID; + int returnedIndex; + int callsToChanged = 0; + chattables_.onChanged.connect([&returnedJID, &returnedIndex, &callsToChanged](const JID& jid, int index){ + returnedJID = jid; + returnedIndex = index; + callsToChanged++; + }); + + Chattables::State newState; + newState.jid = jid; + newState.type = Chattables::State::Type::Room; + newState.status = StatusShow::Online; + chattables_.setState(jid, newState); + + auto storedState = chattables_.getState(jid); + + ASSERT_EQ(1, chattables_.get().size()); + ASSERT_EQ(jid, chattables_.get()[0]); + ASSERT_EQ(jid, returnedJID); + ASSERT_EQ(0, returnedIndex); + ASSERT_EQ(1, callsToChanged); + ASSERT_EQ(jid, storedState.jid); + ASSERT_EQ(Chattables::State::Type::Room, storedState.type); + ASSERT_EQ(StatusShow::Online, storedState.status); +} diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index 5ca0687..5339c7b 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,371 +1,964 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/algorithm/string.hpp> + +#include <memory> + +#include <gtest/gtest.h> #include <hippomocks.h> -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swiften/Presence/DirectedPresenceSender.h" -#include "Swiften/Presence/StanzaChannelPresenceSender.h" -#include "Swiften/Avatars/NullAvatarManager.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/Roster/XMPPRoster.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UnitTest/MockChatWindow.h" -#include "Swiften/Client/DummyStanzaChannel.h" -#include "Swiften/Queries/DummyIQChannel.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Network/TimerFactory.h" -#include "Swiften/Elements/MUCUserPayload.h" -#include "Swiften/Disco/DummyEntityCapsProvider.h" -#include <Swiften/VCards/VCardMemoryStorage.h> +#include <Swiften/Avatars/NullAvatarManager.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/MUCUserPayload.h> +#include <Swiften/Elements/Thread.h> +#include <Swiften/MUC/MUCBookmarkManager.h> +#include <Swiften/MUC/UnitTest/MockMUC.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Presence/DirectedPresenceSender.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/StanzaChannelPresenceSender.h> +#include <Swiften/Queries/DummyIQChannel.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/XMPPRosterImpl.h> #include <Swiften/VCards/VCardManager.h> -#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swiften/VCards/VCardMemoryStorage.h> + #include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Chat/Chattables.h> +#include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> -#include <Swiften/Crypto/CryptoProvider.h> +#include <Swift/Controllers/UnitTest/MockChatWindow.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + +// Clang wrongly things that tests for 0 are using 0 as null. +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" using namespace Swift; -class MUCControllerTest : public CppUnit::TestFixture { - CPPUNIT_TEST_SUITE(MUCControllerTest); - CPPUNIT_TEST(testJoinPartStringContructionSimple); - CPPUNIT_TEST(testJoinPartStringContructionMixed); - CPPUNIT_TEST(testAppendToJoinParts); - CPPUNIT_TEST(testAddressedToSelf); - CPPUNIT_TEST(testNotAddressedToSelf); - CPPUNIT_TEST(testAddressedToSelfBySelf); - CPPUNIT_TEST(testMessageWithEmptyLabelItem); - CPPUNIT_TEST(testMessageWithLabelItem); - CPPUNIT_TEST(testCorrectMessageWithLabelItem); - CPPUNIT_TEST_SUITE_END(); - -public: - void setUp() { - crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); - self_ = JID("girl@wonderland.lit/rabbithole"); - nick_ = "aLiCe"; - mucJID_ = JID("teaparty@rooms.wonderland.lit"); - mocks_ = new MockRepository(); - stanzaChannel_ = new DummyStanzaChannel(); - iqChannel_ = new DummyIQChannel(); - iqRouter_ = new IQRouter(iqChannel_); - eventController_ = new EventController(); - chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); - userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); - presenceOracle_ = new PresenceOracle(stanzaChannel_); - presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); - directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); - uiEventStream_ = new UIEventStream(); - avatarManager_ = new NullAvatarManager(); - TimerFactory* timerFactory = NULL; - window_ = new MockChatWindow(); - mucRegistry_ = new MUCRegistry(); - entityCapsProvider_ = new DummyEntityCapsProvider(); - settings_ = new DummySettingsProvider(); - highlightManager_ = new HighlightManager(settings_); - muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_); - mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>()); - vcardStorage_ = new VCardMemoryStorage(crypto_.get()); - vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); - controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL); - } - - void tearDown() { - delete vcardManager_; - delete vcardStorage_; - delete highlightManager_; - delete settings_; - delete entityCapsProvider_; - delete controller_; - delete eventController_; - delete presenceOracle_; - delete mocks_; - delete uiEventStream_; - delete stanzaChannel_; - delete presenceSender_; - delete directedPresenceSender_; - delete iqRouter_; - delete iqChannel_; - delete mucRegistry_; - delete avatarManager_; - delete chatMessageParser_; - } - - void finishJoin() { - Presence::ref presence(new Presence()); - presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); - MUCUserPayload::ref status(new MUCUserPayload()); - MUCUserPayload::StatusCode code; - code.code = 110; - status->addStatusCode(code); - presence->addPayload(status); - stanzaChannel_->onPresenceReceived(presence); - } - - void testAddressedToSelf() { - finishJoin(); - Message::ref message(new Message()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); - message->setBody("basic " + nick_ + " test."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); - message->setBody(nick_ + ": hi there"); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size()); - - message->setFrom(JID(muc_->getJID().toString() + "/other")); - message->setBody("Hi there " + nick_); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other2")); - message->setBody("Hi " + boost::to_lower_copy(nick_) + "."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other3")); - message->setBody("Hi bert."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); - - message = Message::ref(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other2")); - message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie."); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size()); - } - - void testNotAddressedToSelf() { - finishJoin(); - Message::ref message(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/other3")); - message->setBody("Hi there Hatter"); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); - } - - void testAddressedToSelfBySelf() { - finishJoin(); - Message::ref message(new Message()); - message->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); - message->setBody("Hi there " + nick_); - message->setType(Message::Groupchat); - controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); - CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size()); - } - - void testMessageWithEmptyLabelItem() { - SecurityLabelsCatalog::Item label; - label.setSelector("Bob"); - window_->label_ = label; - boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); - features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); - controller_->setAvailableServerFeatures(features); - IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; - SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); - labelPayload->addItem(label); - IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); - iqChannel_->onIQReceived(result); - std::string messageBody("agamemnon"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); - CPPUNIT_ASSERT(window_->labelsEnabled_); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); - CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>()); - } - - void testMessageWithLabelItem() { - SecurityLabel::ref label = boost::make_shared<SecurityLabel>(); - label->setLabel("a"); - SecurityLabelsCatalog::Item labelItem; - labelItem.setSelector("Bob"); - labelItem.setLabel(label); - window_->label_ = labelItem; - boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); - features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); - controller_->setAvailableServerFeatures(features); - IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; - SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); - labelPayload->addItem(labelItem); - IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); - iqChannel_->onIQReceived(result); - std::string messageBody("agamemnon"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); - CPPUNIT_ASSERT(window_->labelsEnabled_); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - } - - void testCorrectMessageWithLabelItem() { - SecurityLabel::ref label = boost::make_shared<SecurityLabel>(); - label->setLabel("a"); - SecurityLabelsCatalog::Item labelItem; - labelItem.setSelector("Bob"); - labelItem.setLabel(label); - SecurityLabel::ref label2 = boost::make_shared<SecurityLabel>(); - label->setLabel("b"); - SecurityLabelsCatalog::Item labelItem2; - labelItem2.setSelector("Charlie"); - labelItem2.setLabel(label2); - window_->label_ = labelItem; - boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); - features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); - controller_->setAvailableServerFeatures(features); - IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; - SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); - labelPayload->addItem(labelItem); - IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); - iqChannel_->onIQReceived(result); - std::string messageBody("agamemnon"); - window_->onSendMessageRequest(messageBody, false); - boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); - CPPUNIT_ASSERT(window_->labelsEnabled_); - CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ - CPPUNIT_ASSERT(message); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - window_->label_ = labelItem2; - window_->onSendMessageRequest(messageBody, true); - rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; - message = boost::dynamic_pointer_cast<Message>(rawStanza); - CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); - CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); - } - - void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) { - CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size()); - for (size_t i = 0; i < expected.size(); i++) { - CPPUNIT_ASSERT_EQUAL(expected[i].nick, actual[i].nick); - CPPUNIT_ASSERT_EQUAL(expected[i].type, actual[i].type); - } - } - - void testAppendToJoinParts() { - std::vector<NickJoinPart> list; - std::vector<NickJoinPart> gold; - MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); - gold.push_back(NickJoinPart("Kev", Join)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join)); - gold.push_back(NickJoinPart("Remko", Join)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join)); - gold.push_back(NickJoinPart("Bert", Join)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part)); - gold[2].type = JoinThenPart; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part)); - gold[0].type = JoinThenPart; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part)); - gold[1].type = JoinThenPart; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); - gold.push_back(NickJoinPart("Ernie", Part)); - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join)); - gold[3].type = PartThenJoin; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); - gold[0].type = Join; - checkEqual(gold, list); - MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); - gold[3].type = Part; - checkEqual(gold, list); - - } - - void testJoinPartStringContructionSimple() { - std::vector<NickJoinPart> list; - list.push_back(NickJoinPart("Kev", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Bert", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Ernie", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); - } - - void testJoinPartStringContructionMixed() { - std::vector<NickJoinPart> list; - list.push_back(NickJoinPart("Kev", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Bert", PartThenJoin)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); - list.push_back(NickJoinPart("Ernie", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); - } - -private: - JID self_; - JID mucJID_; - MUC::ref muc_; - std::string nick_; - DummyStanzaChannel* stanzaChannel_; - DummyIQChannel* iqChannel_; - IQRouter* iqRouter_; - EventController* eventController_; - ChatWindowFactory* chatWindowFactory_; - UserSearchWindowFactory* userSearchWindowFactory_; - MUCController* controller_; -// NickResolver* nickResolver_; - PresenceOracle* presenceOracle_; - AvatarManager* avatarManager_; - StanzaChannelPresenceSender* presenceSender_; - DirectedPresenceSender* directedPresenceSender_; - MockRepository* mocks_; - UIEventStream* uiEventStream_; - MockChatWindow* window_; - MUCRegistry* mucRegistry_; - DummyEntityCapsProvider* entityCapsProvider_; - DummySettingsProvider* settings_; - HighlightManager* highlightManager_; - ChatMessageParser* chatMessageParser_; - boost::shared_ptr<CryptoProvider> crypto_; - VCardManager* vcardManager_; - VCardMemoryStorage* vcardStorage_; +class MUCControllerTest : public ::testing::Test { + + protected: + void SetUp() { + crypto_ = std::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); + self_ = JID("girl@wonderland.lit/rabbithole"); + nick_ = "aLiCe"; + mucJID_ = JID("teaparty@rooms.wonderland.lit"); + mocks_ = new MockRepository(); + stanzaChannel_ = new DummyStanzaChannel(); + iqChannel_ = new DummyIQChannel(); + iqRouter_ = new IQRouter(iqChannel_); + eventController_ = new EventController(); + chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); + xmppRoster_ = new XMPPRosterImpl(); + presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); + presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); + directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); + uiEventStream_ = new UIEventStream(); + avatarManager_ = new NullAvatarManager(); + TimerFactory* timerFactory = nullptr; + window_ = new MockChatWindow(); + mucRegistry_ = new MUCRegistry(); + entityCapsProvider_ = new DummyEntityCapsProvider(); + settings_ = new DummySettingsProvider(); + highlightManager_ = new HighlightManager(settings_); + highlightManager_->resetToDefaultConfiguration(); + muc_ = std::make_shared<MockMUC>(mucJID_); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); + chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getConfiguration(), ChatMessageParser::Mode::GroupChat); + vcardStorage_ = new VCardMemoryStorage(crypto_.get()); + vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); + nickResolver_ = new NickResolver(self_, xmppRoster_, vcardManager_, mucRegistry_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); + chattables_ = std::make_unique<Chattables>(); + controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, nickResolver_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_, settings_, *chattables_); + } + + void TearDown() { + delete controller_; + delete mucBookmarkManager_; + delete clientBlockListManager_; + delete nickResolver_; + delete vcardManager_; + delete vcardStorage_; + delete highlightManager_; + delete settings_; + delete entityCapsProvider_; + delete eventController_; + delete presenceOracle_; + delete xmppRoster_; + delete mocks_; + delete uiEventStream_; + delete stanzaChannel_; + delete presenceSender_; + delete directedPresenceSender_; + delete iqRouter_; + delete iqChannel_; + delete mucRegistry_; + delete avatarManager_; + } + + void finishJoin() { + Presence::ref presence(new Presence()); + presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); + MUCUserPayload::ref status(new MUCUserPayload()); + MUCUserPayload::StatusCode code; + code.code = 110; + status->addStatusCode(code); + presence->addPayload(status); + stanzaChannel_->onPresenceReceived(presence); + } + + void joinCompleted() { + std::string messageBody("test message"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + ASSERT_TRUE(message); + ASSERT_EQ(messageBody, message->getBody().get_value_or("")); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("Initial"); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + } + } + + void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) { + ASSERT_EQ(expected.size(), actual.size()); + for (size_t i = 0; i < expected.size(); i++) { + ASSERT_EQ(expected[i].nick, actual[i].nick); + ASSERT_EQ(expected[i].type, actual[i].type); + } + } + + JID jidFromOccupant(const MUCOccupant& occupant) { + return JID(mucJID_.toString()+"/"+occupant.getNick()); + } + + void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) { + /* verify that the roster is in sync */ + GroupRosterItem* group = window_->getRosterModel()->getRoot(); + for (auto rosterItem : group->getChildren()) { + GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem); + ASSERT_TRUE(child); + for (auto childItem : child->getChildren()) { + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); + ASSERT_TRUE(item); + std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); + ASSERT_TRUE(occupant != occupants.end()); + ASSERT_TRUE(item->getMUCRole() == occupant->second.getRole()); + ASSERT_TRUE(item->getMUCAffiliation() == occupant->second.getAffiliation()); + } + } + } + + void setMUCSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue, const bool includeFormTypeField = true) { + auto form = std::make_shared<Form>(Form::Type::ResultType); + + if (includeFormTypeField) { + std::shared_ptr<FormField> formTypeField = std::make_shared<FormField>(FormField::Type::HiddenType, "http://jabber.org/protocol/muc#roominfo"); + formTypeField->setName("FORM_TYPE"); + form->addField(formTypeField); + } + + auto markingField = std::make_shared<FormField>(FormField::Type::TextSingleType, markingValue); + auto markingForegroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, markingForegroundColorValue); + auto markingBackgroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, markingBackgroundColorValue); + + markingField->setName("x-isode#roominfo_marking"); + markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color"); + markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color"); + + form->addField(markingField); + form->addField(markingForegroundColorField); + form->addField(markingBackgroundColorField); + + auto discoInfoRef = std::make_shared<DiscoInfo>(); + discoInfoRef->addExtension(form); + + auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef); + iqChannel_->onIQReceived(infoResponse); + } + + Message::ref createTestMessageWithoutSecurityLabel() { + auto message = std::make_shared<Message>(); + message->setType(Message::Type::Groupchat); + message->setID("test-id"); + message->setTo(self_); + message->setFrom(mucJID_.withResource("TestNickname")); + message->setBody("Do Not Read This Message"); + return message; + } + + JID self_; + JID mucJID_; + MockMUC::ref muc_; + std::string nick_; + DummyStanzaChannel* stanzaChannel_; + DummyIQChannel* iqChannel_; + IQRouter* iqRouter_; + EventController* eventController_; + ChatWindowFactory* chatWindowFactory_; + UserSearchWindowFactory* userSearchWindowFactory_; + MUCController* controller_; + NickResolver* nickResolver_; + PresenceOracle* presenceOracle_; + AvatarManager* avatarManager_; + StanzaChannelPresenceSender* presenceSender_; + DirectedPresenceSender* directedPresenceSender_; + MockRepository* mocks_; + UIEventStream* uiEventStream_; + MockChatWindow* window_; + MUCRegistry* mucRegistry_; + DummyEntityCapsProvider* entityCapsProvider_; + DummySettingsProvider* settings_; + HighlightManager* highlightManager_; + std::shared_ptr<ChatMessageParser> chatMessageParser_; + std::shared_ptr<CryptoProvider> crypto_; + VCardManager* vcardManager_; + VCardMemoryStorage* vcardStorage_; + ClientBlockListManager* clientBlockListManager_; + MUCBookmarkManager* mucBookmarkManager_; + XMPPRoster* xmppRoster_; + std::unique_ptr<Chattables> chattables_; }; -CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); +TEST_F(MUCControllerTest, testAddressedToSelf) { + finishJoin(); + Message::ref message(new Message()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); + message->setBody("basic " + nick_ + " test."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(1, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/otherperson")); + message->setBody(nick_ + ": hi there"); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(2, eventController_->getEvents().size()); + + message->setFrom(JID(muc_->getJID().toString() + "/other")); + message->setBody("Hi there " + nick_); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(3, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other2")); + message->setBody("Hi " + boost::to_lower_copy(nick_) + "."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + + // The last message is ignored because self-mention highlights are matched case + // sensitive against the nickname. + ASSERT_EQ(3, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other3")); + message->setBody("Hi bert."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(3, eventController_->getEvents().size()); + + message = Message::ref(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other2")); + message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie."); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(3, eventController_->getEvents().size()); +} + +TEST_F(MUCControllerTest, testNotAddressedToSelf) { + finishJoin(); + Message::ref message(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/other3")); + message->setBody("Hi there Hatter"); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(0, eventController_->getEvents().size()); +} + +TEST_F(MUCControllerTest, testAddressedToSelfBySelf) { + finishJoin(); + Message::ref message(new Message()); + message->setFrom(JID(muc_->getJID().toString() + "/" + nick_)); + message->setBody("Hi there " + nick_); + message->setType(Message::Groupchat); + controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message))); + ASSERT_EQ(0, eventController_->getEvents().size()); +} + +TEST_F(MUCControllerTest, testMessageWithEmptyLabelItem) { + SecurityLabelsCatalog::Item label; + label.setSelector("Bob"); + window_->label_ = label; + std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(label); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + ASSERT_EQ(iq->getTo(), result->getFrom()); + ASSERT_TRUE(window_->labelsEnabled_); + ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + ASSERT_TRUE(message); + ASSERT_EQ(messageBody, message->getBody().get()); + ASSERT_FALSE(message->getPayload<SecurityLabel>()); +} + +TEST_F(MUCControllerTest, testMessageWithLabelItem) { + std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>(); + label->setLabel("a"); + SecurityLabelsCatalog::Item labelItem; + labelItem.setSelector("Bob"); + labelItem.setLabel(label); + window_->label_ = labelItem; + std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(labelItem); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + ASSERT_EQ(iq->getTo(), result->getFrom()); + ASSERT_TRUE(window_->labelsEnabled_); + ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + ASSERT_TRUE(message); + ASSERT_EQ(messageBody, message->getBody().get()); + ASSERT_EQ(label, message->getPayload<SecurityLabel>()); +} + +TEST_F(MUCControllerTest, testCorrectMessageWithLabelItem) { + std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>(); + label->setLabel("a"); + SecurityLabelsCatalog::Item labelItem; + labelItem.setSelector("Bob"); + labelItem.setLabel(label); + std::shared_ptr<SecurityLabel> label2 = std::make_shared<SecurityLabel>(); + label->setLabel("b"); + SecurityLabelsCatalog::Item labelItem2; + labelItem2.setSelector("Charlie"); + labelItem2.setLabel(label2); + window_->label_ = labelItem; + std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(labelItem); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza); + ASSERT_EQ(iq->getTo(), result->getFrom()); + ASSERT_TRUE(window_->labelsEnabled_); + ASSERT_TRUE(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + ASSERT_TRUE(message); + ASSERT_EQ(messageBody, message->getBody().get()); + ASSERT_EQ(label, message->getPayload<SecurityLabel>()); + window_->label_ = labelItem2; + window_->onSendMessageRequest(messageBody, true); + rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + message = std::dynamic_pointer_cast<Message>(rawStanza); + ASSERT_EQ(messageBody, message->getBody().get()); + ASSERT_EQ(label, message->getPayload<SecurityLabel>()); +} + +TEST_F(MUCControllerTest, testAppendToJoinParts) { + std::vector<NickJoinPart> list; + std::vector<NickJoinPart> gold; + MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); + gold.push_back(NickJoinPart("Kev", Join)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join)); + gold.push_back(NickJoinPart("Remko", Join)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join)); + gold.push_back(NickJoinPart("Bert", Join)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part)); + gold[2].type = JoinThenPart; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part)); + gold[0].type = JoinThenPart; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part)); + gold[1].type = JoinThenPart; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); + gold.push_back(NickJoinPart("Ernie", Part)); + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join)); + gold[3].type = PartThenJoin; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join)); + gold[0].type = Join; + checkEqual(gold, list); + MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part)); + gold[3].type = Part; + checkEqual(gold, list); + +} + +TEST_F(MUCControllerTest, testJoinPartStringContructionSimple) { + std::vector<NickJoinPart> list; + list.push_back(NickJoinPart("Kev", Join)); + ASSERT_EQ(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Remko", Part)); + ASSERT_EQ(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Bert", Join)); + ASSERT_EQ(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Ernie", Join)); + ASSERT_EQ(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); +} + +TEST_F(MUCControllerTest, testJoinPartStringContructionMixed) { + std::vector<NickJoinPart> list; + list.push_back(NickJoinPart("Kev", JoinThenPart)); + ASSERT_EQ(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Remko", Part)); + ASSERT_EQ(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Bert", PartThenJoin)); + ASSERT_EQ(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); + list.push_back(NickJoinPart("Ernie", JoinThenPart)); + ASSERT_EQ(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); +} + +TEST_F(MUCControllerTest, testRoleAffiliationStates) { + + typedef std::map<std::string, MUCOccupant> occupant_map; + occupant_map occupants; + occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner))); + + /* populate the MUC with fake users */ + for (auto&& occupant : occupants) { + muc_->insertOccupant(occupant.second); + } + + std::vector<MUCOccupant> alterations; + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner)); + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast)); + + for (const auto& alteration : alterations) { + /* perform an alteration to a user's role and affiliation */ + occupant_map::iterator occupant = occupants.find(alteration.getNick()); + ASSERT_TRUE(occupant != occupants.end()); + const JID jid = jidFromOccupant(occupant->second); + /* change the affiliation, leave the role in place */ + muc_->changeAffiliation(jid, alteration.getAffiliation()); + occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + /* change the role, leave the affiliation in place */ + muc_->changeOccupantRole(jid, alteration.getRole()); + occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + } +} + +TEST_F(MUCControllerTest, testSubjectChangeCorrect) { + joinCompleted(); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID("3FB99C56-7C92-4755-91B0-9C0098BC7AE0"); + message->setSubject("New Room Subject"); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + ASSERT_EQ(std::string("The room subject is now: New Room Subject"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } +} + +/* + * Test that message stanzas with subject element and non-empty body element do not cause a subject change. + */ +TEST_F(MUCControllerTest, testSubjectChangeIncorrectA) { + joinCompleted(); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + message->setBody("Some body text that prevents this stanza from being a subject change."); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + ASSERT_EQ(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } +} + +/* + * Test that message stanzas with subject element and thread element do not cause a subject change. + */ +TEST_F(MUCControllerTest, testSubjectChangeIncorrectB) { + joinCompleted(); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + message->addPayload(std::make_shared<Thread>("Thread that prevents the subject change.")); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + ASSERT_EQ(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } +} + +/* + * Test that message stanzas with subject element and empty body element do not cause a subject change. + */ +TEST_F(MUCControllerTest, testSubjectChangeIncorrectC) { + joinCompleted(); + + { + Message::ref message = std::make_shared<Message>(); + message->setType(Message::Groupchat); + message->setTo(self_); + message->setFrom(mucJID_.withResource("SomeNickname")); + message->setID(iqChannel_->getNewIQID()); + message->setSubject("New Room Subject"); + message->setBody(""); + + controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message)); + ASSERT_EQ(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text); + } +} + +TEST_F(MUCControllerTest, testHandleOccupantNicknameChanged) { + const auto occupantCount = [&](const std::string & nick) { + auto roster = window_->getRosterModel(); + const auto currentOccupantsJIDs = roster->getJIDs(); + int count = 0; + for (auto & p : currentOccupantsJIDs) { + if (p.getResource() == nick) { + ++count; + } + } + return count; + }; + + muc_->insertOccupant(MUCOccupant("TestUserOne", MUCOccupant::Participant, MUCOccupant::Owner)); + muc_->insertOccupant(MUCOccupant("TestUserTwo", MUCOccupant::Participant, MUCOccupant::Owner)); + muc_->insertOccupant(MUCOccupant("TestUserThree", MUCOccupant::Participant, MUCOccupant::Owner)); + + muc_->onOccupantNicknameChanged("TestUserOne", "TestUserTwo"); + + ASSERT_EQ(0, occupantCount("TestUserOne")); + ASSERT_EQ(1, occupantCount("TestUserTwo")); + ASSERT_EQ(1, occupantCount("TestUserThree")); +} + +TEST_F(MUCControllerTest, testHandleOccupantNicknameChangedRoster) { + const auto occupantCount = [&](const std::string & nick) { + auto roster = window_->getRosterModel(); + const auto participants = roster->getGroup("Participants"); + const auto displayedParticipants = participants->getDisplayedChildren(); + int count = 0; + for (auto & p : displayedParticipants) { + if (p->getDisplayName() == nick) { + ++count; + } + } + return count; + }; + + muc_->insertOccupant(MUCOccupant("TestUserOne", MUCOccupant::Participant, MUCOccupant::Owner)); + muc_->insertOccupant(MUCOccupant("TestUserTwo", MUCOccupant::Participant, MUCOccupant::Owner)); + muc_->insertOccupant(MUCOccupant("TestUserThree", MUCOccupant::Participant, MUCOccupant::Owner)); + ASSERT_EQ(1, occupantCount("TestUserOne")); + ASSERT_EQ(1, occupantCount("TestUserTwo")); + ASSERT_EQ(1, occupantCount("TestUserThree")); + + muc_->onOccupantNicknameChanged("TestUserOne", "TestUserTwo"); + + ASSERT_EQ(0, occupantCount("TestUserOne")); + ASSERT_EQ(1, occupantCount("TestUserTwo")); + ASSERT_EQ(1, occupantCount("TestUserThree")); +} + +TEST_F(MUCControllerTest, testHandleChangeSubjectRequest) { + std::string testStr("New Subject"); + ASSERT_EQ(std::string(""), muc_->newSubjectSet_); + window_->onChangeSubjectRequest(testStr); + ASSERT_EQ(testStr, muc_->newSubjectSet_); +} + +TEST_F(MUCControllerTest, testNonImpromptuMUCWindowTitle) { + ASSERT_EQ(muc_->getJID().getNode(), window_->name_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestCompleteMarking) { + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red", true); + + ASSERT_EQ(std::string("Test|Highest Possible Security"), window_->markingValue_); + ASSERT_EQ(std::string("Black"), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string("Red"), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestCompleteMarkingWithExtraForm) { + auto formTypeField = std::make_shared<FormField>(FormField::Type::HiddenType, "http://jabber.org/protocol/muc#roominfo"); + auto markingField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Test|Highest Possible Security"); + auto markingForegroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Black"); + auto markingBackgroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Red"); + formTypeField->setName("FORM_TYPE"); + markingField->setName("x-isode#roominfo_marking"); + markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color"); + markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color"); + + auto extraForm = std::make_shared<Form>(Form::Type::ResultType); + auto form = std::make_shared<Form>(Form::Type::ResultType); + form->addField(formTypeField); + form->addField(markingField); + form->addField(markingForegroundColorField); + form->addField(markingBackgroundColorField); + + auto discoInfoRef = std::make_shared<DiscoInfo>(); + discoInfoRef->addExtension(extraForm); + discoInfoRef->addExtension(form); + + auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef); + iqChannel_->onIQReceived(infoResponse); + ASSERT_EQ(std::string("Test|Highest Possible Security"), window_->markingValue_); + ASSERT_EQ(std::string("Black"), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string("Red"), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestNoColorsInMarking) { + auto formTypeField = std::make_shared<FormField>(FormField::Type::HiddenType, "http://jabber.org/protocol/muc#roominfo"); + auto markingField = std::make_shared<FormField>(FormField::Type::TextSingleType, "Test|Highest Possible Security"); + auto markingForegroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, ""); + auto markingBackgroundColorField = std::make_shared<FormField>(FormField::Type::TextSingleType, ""); + formTypeField->setName("FORM_TYPE"); + markingField->setName("x-isode#roominfo_marking"); + markingForegroundColorField->setName("x-isode#roominfo_marking_fg_color"); + markingBackgroundColorField->setName("x-isode#roominfo_marking_bg_color"); + + auto form = std::make_shared<Form>(Form::Type::ResultType); + form->addField(formTypeField); + form->addField(markingField); + form->addField(markingForegroundColorField); + form->addField(markingBackgroundColorField); + + auto discoInfoRef = std::make_shared<DiscoInfo>(); + discoInfoRef->addExtension(form); + + auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef); + iqChannel_->onIQReceived(infoResponse); + ASSERT_EQ(std::string("Test|Highest Possible Security"), window_->markingValue_); + ASSERT_EQ(std::string("Black"), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string("White"), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestEmptyMarking) { + setMUCSecurityMarking("", "", "", true); + + ASSERT_EQ(std::string(""), window_->markingValue_); + ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestWithMarkingNoFormType) { + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red", false); + + ASSERT_EQ(std::string(""), window_->markingValue_); + ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestNoMarking) { + auto form = std::make_shared<Form>(Form::Type::ResultType); + + auto discoInfoRef = std::make_shared<DiscoInfo>(); + discoInfoRef->addExtension(form); + + auto infoResponse = IQ::createResult(self_, mucJID_, "test-id", discoInfoRef); + iqChannel_->onIQReceived(infoResponse); + ASSERT_EQ(std::string(""), window_->markingValue_); + ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestNoForm) { + auto discoInfoRef = std::make_shared<DiscoInfo>(); + + auto infoResponse = IQ::createResult( self_, mucJID_, "test-id", discoInfoRef); + iqChannel_->onIQReceived(infoResponse); + ASSERT_EQ(std::string(""), window_->markingValue_); + ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingRequestError) { + auto errorPayload = std::make_shared<ErrorPayload>(ErrorPayload::Condition::NotAuthorized, ErrorPayload::Type::Auth); + + auto infoResponse = IQ::createResult( self_, mucJID_, "test-id", errorPayload); + iqChannel_->onIQReceived(infoResponse); + ASSERT_EQ(std::string(""), window_->markingValue_); + ASSERT_EQ(std::string(""), window_->markingForegroundColorValue_); + ASSERT_EQ(std::string(""), window_->markingBackgroundColorValue_); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_NoRoomMarkingA) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_NoRoomMarkingB) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking(""); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingA) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + // Test the first message matching MUC marking. This message SHOULD have a marking + + auto sentMessageEvent1 = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent1); + + auto storedSecurityLabel1 = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel1 == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel1->getDisplayMarking()); + + // Test a consecutive message matching MUC marking. This message SHOULD NOT have a marking + + auto sentMessageEvent2 = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent2); + + auto storedSecurityLabel2 = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel2 == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string(""), storedSecurityLabel2->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingB) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("Test|Lower Security Marking", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_Elision_WithRoomMarkingC) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, true); + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking(""); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Unmarked"), storedSecurityLabel->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_NoRoomMarkingA) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false); + setMUCSecurityMarking("", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_NoRoomMarkingB) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false); + setMUCSecurityMarking("", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking(""); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_WithRoomMarkingA) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false); + setMUCSecurityMarking("Test|Highest Possible Security", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking("Test|Highest Possible Security"); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + // Test the first message matching MUC marking. This message SHOULD have a marking + + auto sentMessageEvent1 = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent1); + + auto storedSecurityLabel1 = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel1 == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel1->getDisplayMarking()); + + // Test a consecutive message matching MUC marking. This message SHOULD ALSO have a marking + + auto sentMessageEvent2 = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent2); + + auto storedSecurityLabel2 = window_->lastAddedMessageSecurityLabel_; + + ASSERT_EQ(false, storedSecurityLabel2 == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string("Test|Highest Possible Security"), storedSecurityLabel2->getDisplayMarking()); +} + +TEST_F(MUCControllerTest, testSecurityMarkingAddedToMessage_NoElision_WithRoomMarkingB) { + settings_->storeSetting(SettingConstants::MUC_MARKING_ELISION, false); + setMUCSecurityMarking("", "Black", "Red"); + + auto messageLabel = std::make_shared<SecurityLabel>(); + messageLabel->setDisplayMarking(""); + + auto sentMessage = createTestMessageWithoutSecurityLabel(); + sentMessage->addPayload(messageLabel); + + auto sentMessageEvent = std::make_shared<MessageEvent>(sentMessage); + controller_->handleIncomingMessage(sentMessageEvent); + + auto storedSecurityLabel = window_->lastAddedMessageSecurityLabel_; + ASSERT_EQ(false, storedSecurityLabel == nullptr); + // This is the potentially altered security label that is displayed on the screen + ASSERT_EQ(std::string(""), storedSecurityLabel->getDisplayMarking()); +} diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h index 5fa264d..1d980d3 100644 --- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -1,27 +1,28 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/Controllers/UIInterfaces/ChatListWindow.h" +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> namespace Swift { - class MockChatListWindow : public ChatListWindow { - public: - MockChatListWindow() {} - virtual ~MockChatListWindow() {} - void addMUCBookmark(const MUCBookmark& /*bookmark*/) {} - void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {} - void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {} - void removeWhiteboardSession(const JID& /*jid*/) {} - void setBookmarksEnabled(bool /*enabled*/) {} - void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} - void setUnreadCount(int /*unread*/) {} - void clearBookmarks() {} - }; + class MockChatListWindow : public ChatListWindow { + public: + MockChatListWindow() {} + virtual ~MockChatListWindow() {} + void addMUCBookmark(const MUCBookmark& /*bookmark*/) {} + void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {} + void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {} + void removeWhiteboardSession(const JID& /*jid*/) {} + void setBookmarksEnabled(bool /*enabled*/) {} + void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} + void setUnreadCount(size_t /*unread*/) {} + void clearBookmarks() {} + void setOnline(bool /*isOnline*/) {} + }; } |