/*
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/icorefriendmessagesender.h"
#include "src/model/friend.h"
#include "src/model/friendmessagedispatcher.h"
#include "src/model/message.h"
#include
#include
#include
#include
namespace {
constexpr uint64_t testMaxExtendedMessageSize = 10 * 1024 * 1024;
}
class MockCoreExtPacket : public ICoreExtPacket
{
public:
MockCoreExtPacket(uint64_t& numSentMessages_, uint64_t& currentReceiptId_)
: numSentMessages(numSentMessages_)
, currentReceiptId(currentReceiptId_)
{}
uint64_t addExtendedMessage(QString message_) override;
bool send() override;
uint64_t& numSentMessages;
uint64_t& currentReceiptId;
QDateTime senderTimestamp;
QString message;
};
uint64_t MockCoreExtPacket::addExtendedMessage(QString message_)
{
message = message_;
return currentReceiptId++;
}
bool MockCoreExtPacket::send()
{
numSentMessages++;
return true;
}
class MockCoreExtPacketAllocator : public ICoreExtPacketAllocator
{
public:
std::unique_ptr getPacket(uint32_t friendId) override;
uint64_t numSentMessages;
uint64_t currentReceiptId;
};
std::unique_ptr MockCoreExtPacketAllocator::getPacket(uint32_t friendId)
{
std::ignore = friendId;
return std::unique_ptr(new MockCoreExtPacket(numSentMessages, currentReceiptId));
}
class MockFriendMessageSender : public ICoreFriendMessageSender
{
public:
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override;
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override;
bool canSend = true;
ReceiptNum receiptNum{0};
size_t numSentActions = 0;
size_t numSentMessages = 0;
};
bool MockFriendMessageSender::sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt)
{
std::ignore = friendId;
std::ignore = action;
if (canSend) {
numSentActions++;
receipt = receiptNum;
receiptNum.get() += 1;
}
return canSend;
}
bool MockFriendMessageSender::sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt)
{
std::ignore = friendId;
std::ignore = message;
if (canSend) {
numSentMessages++;
receipt = receiptNum;
receiptNum.get() += 1;
}
return canSend;
}
class TestFriendMessageDispatcher : public QObject
{
Q_OBJECT
public:
TestFriendMessageDispatcher();
private slots:
void init();
void testSignals();
void testMessageSending();
void testOfflineMessages();
void testFailedMessage();
void testNegotiationFailure();
void testNegotiationSuccess();
void testOfflineExtensionMessages();
void testSentMessageExtensionSetReduced();
void testActionMessagesSplitWithExtensions();
void onMessageSent(DispatchedMessageId id, Message message)
{
auto it = outgoingMessages.find(id);
QVERIFY(it == outgoingMessages.end());
outgoingMessages.emplace(id, 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)
{
std::ignore = sender;
receivedMessages.push_back(std::move(message));
}
void onMessageBroken(DispatchedMessageId id, BrokenMessageReason reason)
{
std::ignore = reason;
brokenMessages.insert(id);
}
private:
// All unique_ptrs to make construction/init() easier to manage
std::unique_ptr f;
std::unique_ptr messageSender;
std::unique_ptr coreExtPacketAllocator;
std::unique_ptr sharedProcessorParams;
std::unique_ptr messageProcessor;
std::unique_ptr friendMessageDispatcher;
std::map outgoingMessages;
std::set brokenMessages;
std::deque receivedMessages;
};
TestFriendMessageDispatcher::TestFriendMessageDispatcher() {}
/**
* @brief Test initialization. Resets all member variables for a fresh test state
*/
void TestFriendMessageDispatcher::init()
{
f = std::unique_ptr(new Friend(0, ToxPk()));
f->setStatus(Status::Status::Online);
f->onNegotiationComplete();
messageSender = std::unique_ptr(new MockFriendMessageSender());
coreExtPacketAllocator = std::unique_ptr(new MockCoreExtPacketAllocator());
sharedProcessorParams =
std::unique_ptr(new MessageProcessor::SharedParams(tox_max_message_length(), testMaxExtendedMessageSize));
messageProcessor = std::unique_ptr(new MessageProcessor(*sharedProcessorParams));
friendMessageDispatcher = std::unique_ptr(
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender, *coreExtPacketAllocator));
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this,
&TestFriendMessageDispatcher::onMessageSent);
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageComplete, this,
&TestFriendMessageDispatcher::onMessageComplete);
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageReceived, this,
&TestFriendMessageDispatcher::onMessageReceived);
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageBroken, this,
&TestFriendMessageDispatcher::onMessageBroken);
outgoingMessages = std::map();
receivedMessages = std::deque();
brokenMessages = std::set();
}
/**
* @brief Tests that the signals emitted by the dispatcher are all emitted at the correct times
*/
void TestFriendMessageDispatcher::testSignals()
{
auto startReceiptNum = messageSender->receiptNum;
auto sentIds = friendMessageDispatcher->sendMessage(false, "test");
auto endReceiptNum = messageSender->receiptNum;
// We should have received some message ids in our callbacks
QVERIFY(sentIds.first == sentIds.second);
QVERIFY(outgoingMessages.find(sentIds.first) != outgoingMessages.end());
QVERIFY(startReceiptNum.get() != endReceiptNum.get());
QVERIFY(outgoingMessages.size() == 1);
QVERIFY(outgoingMessages.begin()->second.isAction == false);
QVERIFY(outgoingMessages.begin()->second.content == "test");
for (auto i = startReceiptNum; i < endReceiptNum; ++i.get()) {
friendMessageDispatcher->onReceiptReceived(i);
}
// If our completion ids were hooked up right this should be empty
QVERIFY(outgoingMessages.empty());
// If signals are emitted correctly we should have one message in our received message buffer
QVERIFY(receivedMessages.empty());
friendMessageDispatcher->onMessageReceived(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 TestFriendMessageDispatcher::testMessageSending()
{
friendMessageDispatcher->sendMessage(false, "Test");
QVERIFY(messageSender->numSentMessages == 1);
QVERIFY(messageSender->numSentActions == 0);
friendMessageDispatcher->sendMessage(true, "Test");
QVERIFY(messageSender->numSentMessages == 1);
QVERIFY(messageSender->numSentActions == 1);
}
/**
* @brief Tests that messages dispatched while a friend is offline are sent later
*/
void TestFriendMessageDispatcher::testOfflineMessages()
{
f->setStatus(Status::Status::Offline);
auto firstReceipt = messageSender->receiptNum;
friendMessageDispatcher->sendMessage(false, "test");
friendMessageDispatcher->sendMessage(false, "test2");
friendMessageDispatcher->sendMessage(true, "test3");
QVERIFY(messageSender->numSentActions == 0);
QVERIFY(messageSender->numSentMessages == 0);
QVERIFY(outgoingMessages.size() == 3);
f->setStatus(Status::Status::Online);
f->onNegotiationComplete();
QVERIFY(messageSender->numSentActions == 1);
QVERIFY(messageSender->numSentMessages == 2);
QVERIFY(outgoingMessages.size() == 3);
auto lastReceipt = messageSender->receiptNum;
for (auto i = firstReceipt; i < lastReceipt; ++i.get()) {
friendMessageDispatcher->onReceiptReceived(i);
}
QVERIFY(messageSender->numSentActions == 1);
QVERIFY(messageSender->numSentMessages == 2);
QVERIFY(outgoingMessages.size() == 0);
}
/**
* @brief Tests that messages that failed to send due to toxcore are resent later
*/
void TestFriendMessageDispatcher::testFailedMessage()
{
messageSender->canSend = false;
friendMessageDispatcher->sendMessage(false, "test");
QVERIFY(messageSender->numSentMessages == 0);
messageSender->canSend = true;
f->setStatus(Status::Status::Offline);
f->setStatus(Status::Status::Online);
f->onNegotiationComplete();
QVERIFY(messageSender->numSentMessages == 1);
}
void TestFriendMessageDispatcher::testNegotiationFailure()
{
f->setStatus(Status::Status::Offline);
f->setStatus(Status::Status::Online);
QVERIFY(f->getStatus() == Status::Status::Negotiating);
friendMessageDispatcher->sendMessage(false, "test");
QVERIFY(messageSender->numSentMessages == 0);
f->onNegotiationComplete();
QVERIFY(messageSender->numSentMessages == 1);
}
void TestFriendMessageDispatcher::testNegotiationSuccess()
{
f->setStatus(Status::Status::Offline);
f->setStatus(Status::Status::Online);
f->setExtendedMessageSupport(true);
f->onNegotiationComplete();
friendMessageDispatcher->sendMessage(false, "test");
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
friendMessageDispatcher->sendMessage(false, "test");
QVERIFY(coreExtPacketAllocator->numSentMessages == 2);
QVERIFY(messageSender->numSentMessages == 0);
}
void TestFriendMessageDispatcher::testOfflineExtensionMessages()
{
f->setStatus(Status::Status::Offline);
auto requiredExtensions = ExtensionSet();
requiredExtensions[ExtensionType::messages] = true;
friendMessageDispatcher->sendExtendedMessage("Test", requiredExtensions);
f->setStatus(Status::Status::Online);
f->setExtendedMessageSupport(true);
f->onNegotiationComplete();
// Ensure that when our friend came online with the desired extensions we
// were able to send them our message over the extended message path
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
f->setStatus(Status::Status::Offline);
friendMessageDispatcher->sendExtendedMessage("Test", requiredExtensions);
f->setStatus(Status::Status::Online);
f->setExtendedMessageSupport(false);
f->onNegotiationComplete();
// Here we want to make sure that when they do _not_ support extensions
// we discard the message instead of attempting to send it over either
// channel
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
QVERIFY(messageSender->numSentMessages == 0);
}
void TestFriendMessageDispatcher::testSentMessageExtensionSetReduced()
{
f->setStatus(Status::Status::Online);
f->setExtendedMessageSupport(true);
f->onNegotiationComplete();
friendMessageDispatcher->sendMessage(false, "Test");
f->setStatus(Status::Status::Offline);
f->setStatus(Status::Status::Online);
f->setExtendedMessageSupport(false);
f->onNegotiationComplete();
// Ensure that when we reduce our extension set we correctly emit the
// "messageBroken" signal
QVERIFY(brokenMessages.size() == 1);
}
void TestFriendMessageDispatcher::testActionMessagesSplitWithExtensions()
{
f->setStatus(Status::Status::Online);
f->setExtendedMessageSupport(true);
f->onNegotiationComplete();
auto reallyLongMessage = QString("a");
for (uint64_t i = 0; i < testMaxExtendedMessageSize + 50; ++i) {
reallyLongMessage += QString().number(i);
}
friendMessageDispatcher->sendMessage(true, reallyLongMessage);
QVERIFY(coreExtPacketAllocator->numSentMessages == 0);
QVERIFY(messageSender->numSentMessages == 0);
QVERIFY(messageSender->numSentActions > 1);
}
QTEST_GUILESS_MAIN(TestFriendMessageDispatcher)
#include "friendmessagedispatcher_test.moc"