/* Copyright © 2019 by The qTox Project Contributors This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "src/core/icoregroupmessagesender.h" #include "src/model/group.h" #include "src/model/groupmessagedispatcher.h" #include "src/model/message.h" #include "src/persistence/settings.h" #include #include #include #include class MockGroupMessageSender : public ICoreGroupMessageSender { public: void sendGroupAction(int groupId, const QString& action) override { numSentActions++; } void sendGroupMessage(int groupId, const QString& message) override { numSentMessages++; } size_t numSentActions = 0; size_t numSentMessages = 0; }; /** * Mock 1 peer at group number 0 */ class MockGroupQuery : public ICoreGroupQuery { public: GroupId getGroupPersistentId(uint32_t groupNumber) const override { return GroupId(0); } uint32_t getGroupNumberPeers(int groupId) const override { if (emptyGroup) { return 1; } return 2; } QString getGroupPeerName(int groupId, int peerId) const override { return QString("peer") + peerId; } ToxPk getGroupPeerPk(int groupId, int peerId) const override { uint8_t id[TOX_PUBLIC_KEY_SIZE] = {static_cast(peerId)}; return ToxPk(id); } QStringList getGroupPeerNames(int groupId) const override { if (emptyGroup) { return QStringList({QString("me")}); } return QStringList({QString("me"), QString("other")}); } bool getGroupAvEnabled(int groupId) const override { return false; } void setAsEmptyGroup() { emptyGroup = true; } void setAsFunctionalGroup() { emptyGroup = false; } private: bool emptyGroup = false; }; class MockCoreIdHandler : public ICoreIdHandler { public: ToxId getSelfId() const override { std::terminate(); return ToxId(); } ToxPk getSelfPublicKey() const override { static uint8_t id[TOX_PUBLIC_KEY_SIZE] = {0}; return ToxPk(id); } QString getUsername() const override { return "me"; } }; class MockGroupSettings : public IGroupSettings { public: QStringList getBlackList() const override { return blacklist; } void setBlackList(const QStringList& blist) override { blacklist = blist; } bool getGroupAlwaysNotify() const override { return false; } void setGroupAlwaysNotify(bool newValue) override {} private: QStringList blacklist; }; class TestGroupMessageDispatcher : public QObject { Q_OBJECT public: TestGroupMessageDispatcher(); private slots: void init(); void testSignals(); void testMessageSending(); void testEmptyGroup(); void testSelfReceive(); void testBlacklist(); void onMessageSent(DispatchedMessageId id, Message message) { auto it = outgoingMessages.find(id); QVERIFY(it == outgoingMessages.end()); outgoingMessages.emplace(id); sentMessages.push_back(std::move(message)); } void onMessageComplete(DispatchedMessageId id) { auto it = outgoingMessages.find(id); QVERIFY(it != outgoingMessages.end()); outgoingMessages.erase(it); } void onMessageReceived(const ToxPk& sender, Message message) { receivedMessages.push_back(std::move(message)); } private: // All unique_ptrs to make construction/init() easier to manage std::unique_ptr groupSettings; std::unique_ptr groupQuery; std::unique_ptr coreIdHandler; std::unique_ptr g; std::unique_ptr messageSender; std::unique_ptr sharedProcessorParams; std::unique_ptr messageProcessor; std::unique_ptr groupMessageDispatcher; std::set outgoingMessages; std::deque sentMessages; std::deque receivedMessages; }; TestGroupMessageDispatcher::TestGroupMessageDispatcher() {} /** * @brief Test initialization. Resets all members to initial state */ void TestGroupMessageDispatcher::init() { groupSettings = std::unique_ptr(new MockGroupSettings()); groupQuery = std::unique_ptr(new MockGroupQuery()); coreIdHandler = std::unique_ptr(new MockCoreIdHandler()); g = std::unique_ptr( new Group(0, GroupId(0), "TestGroup", false, "me", *groupQuery, *coreIdHandler)); messageSender = std::unique_ptr(new MockGroupMessageSender()); sharedProcessorParams = std::unique_ptr(new MessageProcessor::SharedParams()); messageProcessor = std::unique_ptr(new MessageProcessor(*sharedProcessorParams)); groupMessageDispatcher = std::unique_ptr( new GroupMessageDispatcher(*g, *messageProcessor, *coreIdHandler, *messageSender, *groupSettings)); connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageSent, this, &TestGroupMessageDispatcher::onMessageSent); connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageComplete, this, &TestGroupMessageDispatcher::onMessageComplete); connect(groupMessageDispatcher.get(), &GroupMessageDispatcher::messageReceived, this, &TestGroupMessageDispatcher::onMessageReceived); outgoingMessages = std::set(); sentMessages = std::deque(); receivedMessages = std::deque(); } /** * @brief Tests that the signals emitted by the dispatcher are all emitted at the correct times */ void TestGroupMessageDispatcher::testSignals() { groupMessageDispatcher->sendMessage(false, "test"); // For groups we pair our sent and completed signals since we have no receiver reports QVERIFY(outgoingMessages.size() == 0); QVERIFY(!sentMessages.empty()); QVERIFY(sentMessages.front().isAction == false); QVERIFY(sentMessages.front().content == "test"); // If signals are emitted correctly we should have one message in our received message buffer QVERIFY(receivedMessages.empty()); groupMessageDispatcher->onMessageReceived(ToxPk(), false, "test2"); QVERIFY(!receivedMessages.empty()); QVERIFY(receivedMessages.front().isAction == false); QVERIFY(receivedMessages.front().content == "test2"); } /** * @brief Tests that sent messages actually go through to core */ void TestGroupMessageDispatcher::testMessageSending() { groupMessageDispatcher->sendMessage(false, "Test"); QVERIFY(messageSender->numSentMessages == 1); QVERIFY(messageSender->numSentActions == 0); groupMessageDispatcher->sendMessage(true, "Test"); QVERIFY(messageSender->numSentMessages == 1); QVERIFY(messageSender->numSentActions == 1); } /** * @brief Tests that if we are the only member in a group we do _not_ send messages to core. Toxcore * isn't too happy if we send messages and we're the only one in the group */ void TestGroupMessageDispatcher::testEmptyGroup() { groupQuery->setAsEmptyGroup(); g->regeneratePeerList(); groupMessageDispatcher->sendMessage(false, "Test"); groupMessageDispatcher->sendMessage(true, "Test"); QVERIFY(messageSender->numSentMessages == 0); QVERIFY(messageSender->numSentActions == 0); } /** * @brief Tests that we do not emit any signals if we receive a message from ourself. Toxcore will send us back messages we sent */ void TestGroupMessageDispatcher::testSelfReceive() { uint8_t selfId[TOX_PUBLIC_KEY_SIZE] = {0}; groupMessageDispatcher->onMessageReceived(ToxPk(selfId), false, "Test"); QVERIFY(receivedMessages.size() == 0); uint8_t id[TOX_PUBLIC_KEY_SIZE] = {1}; groupMessageDispatcher->onMessageReceived(ToxPk(id), false, "Test"); QVERIFY(receivedMessages.size() == 1); } /** * @brief Tests that messages from blacklisted peers do not get propogated from the dispatcher */ void TestGroupMessageDispatcher::testBlacklist() { uint8_t id[TOX_PUBLIC_KEY_SIZE] = {1}; auto otherPk = ToxPk(id); groupMessageDispatcher->onMessageReceived(otherPk, false, "Test"); QVERIFY(receivedMessages.size() == 1); groupSettings->setBlackList({otherPk.toString()}); groupMessageDispatcher->onMessageReceived(otherPk, false, "Test"); QVERIFY(receivedMessages.size() == 1); } // Cannot be guiless due to a settings instance in GroupMessageDispatcher QTEST_GUILESS_MAIN(TestGroupMessageDispatcher) #include "groupmessagedispatcher_test.moc"