2019-06-24 22:01:18 +08:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2019-06-03 02:22:35 +08:00
|
|
|
#include "src/core/icorefriendmessagesender.h"
|
|
|
|
#include "src/model/friend.h"
|
|
|
|
#include "src/model/friendmessagedispatcher.h"
|
|
|
|
#include "src/model/message.h"
|
|
|
|
|
|
|
|
#include <QObject>
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
|
2019-11-17 18:19:57 +08:00
|
|
|
#include <set>
|
2019-06-03 02:22:35 +08:00
|
|
|
#include <deque>
|
|
|
|
|
2022-03-11 18:18:35 +08:00
|
|
|
namespace {
|
|
|
|
constexpr uint64_t testMaxExtendedMessageSize = 10 * 1024 * 1024;
|
|
|
|
}
|
2019-06-03 02:22:35 +08:00
|
|
|
|
2019-11-13 12:12:05 +08:00
|
|
|
class MockCoreExtPacket : public ICoreExtPacket
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
|
2022-03-11 13:17:14 +08:00
|
|
|
MockCoreExtPacket(uint64_t& numSentMessages_, uint64_t& currentReceiptId_)
|
|
|
|
: numSentMessages(numSentMessages_)
|
|
|
|
, currentReceiptId(currentReceiptId_)
|
2019-11-13 12:12:05 +08:00
|
|
|
{}
|
|
|
|
|
2022-03-11 13:17:14 +08:00
|
|
|
uint64_t addExtendedMessage(QString message_) override;
|
2019-11-13 12:12:05 +08:00
|
|
|
|
2021-10-15 13:26:40 +08:00
|
|
|
bool send() override;
|
2019-11-13 12:12:05 +08:00
|
|
|
|
|
|
|
uint64_t& numSentMessages;
|
|
|
|
uint64_t& currentReceiptId;
|
|
|
|
QDateTime senderTimestamp;
|
|
|
|
QString message;
|
|
|
|
};
|
|
|
|
|
2022-03-11 13:17:14 +08:00
|
|
|
uint64_t MockCoreExtPacket::addExtendedMessage(QString message_)
|
2021-10-15 13:26:40 +08:00
|
|
|
{
|
2022-03-11 13:17:14 +08:00
|
|
|
message = message_;
|
2021-10-15 13:26:40 +08:00
|
|
|
return currentReceiptId++;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockCoreExtPacket::send()
|
|
|
|
{
|
|
|
|
numSentMessages++;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-11-13 12:12:05 +08:00
|
|
|
class MockCoreExtPacketAllocator : public ICoreExtPacketAllocator
|
|
|
|
{
|
|
|
|
public:
|
2021-10-15 13:26:40 +08:00
|
|
|
std::unique_ptr<ICoreExtPacket> getPacket(uint32_t friendId) override;
|
2019-11-13 12:12:05 +08:00
|
|
|
|
|
|
|
uint64_t numSentMessages;
|
|
|
|
uint64_t currentReceiptId;
|
|
|
|
};
|
|
|
|
|
2021-10-15 13:26:40 +08:00
|
|
|
std::unique_ptr<ICoreExtPacket> MockCoreExtPacketAllocator::getPacket(uint32_t friendId)
|
|
|
|
{
|
2022-03-11 15:53:13 +08:00
|
|
|
std::ignore = friendId;
|
2021-10-15 13:26:40 +08:00
|
|
|
return std::unique_ptr<MockCoreExtPacket>(new MockCoreExtPacket(numSentMessages, currentReceiptId));
|
|
|
|
}
|
|
|
|
|
2019-06-03 02:22:35 +08:00
|
|
|
class MockFriendMessageSender : public ICoreFriendMessageSender
|
|
|
|
{
|
|
|
|
public:
|
2021-10-15 13:26:40 +08:00
|
|
|
bool sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt) override;
|
2019-06-03 02:22:35 +08:00
|
|
|
|
2021-10-15 13:26:40 +08:00
|
|
|
bool sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt) override;
|
2019-06-03 02:22:35 +08:00
|
|
|
|
|
|
|
bool canSend = true;
|
|
|
|
ReceiptNum receiptNum{0};
|
|
|
|
size_t numSentActions = 0;
|
|
|
|
size_t numSentMessages = 0;
|
|
|
|
};
|
2021-10-15 13:26:40 +08:00
|
|
|
|
|
|
|
bool MockFriendMessageSender::sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt)
|
|
|
|
{
|
2022-03-11 15:53:13 +08:00
|
|
|
std::ignore = friendId;
|
|
|
|
std::ignore = action;
|
2021-10-15 13:26:40 +08:00
|
|
|
if (canSend) {
|
|
|
|
numSentActions++;
|
|
|
|
receipt = receiptNum;
|
|
|
|
receiptNum.get() += 1;
|
|
|
|
}
|
|
|
|
return canSend;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MockFriendMessageSender::sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt)
|
|
|
|
{
|
2022-03-11 15:53:13 +08:00
|
|
|
std::ignore = friendId;
|
|
|
|
std::ignore = message;
|
2021-10-15 13:26:40 +08:00
|
|
|
if (canSend) {
|
|
|
|
numSentMessages++;
|
|
|
|
receipt = receiptNum;
|
|
|
|
receiptNum.get() += 1;
|
|
|
|
}
|
|
|
|
return canSend;
|
|
|
|
}
|
|
|
|
|
2019-06-03 02:22:35 +08:00
|
|
|
class TestFriendMessageDispatcher : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
TestFriendMessageDispatcher();
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
void init();
|
|
|
|
void testSignals();
|
|
|
|
void testMessageSending();
|
|
|
|
void testOfflineMessages();
|
|
|
|
void testFailedMessage();
|
2019-11-13 12:12:05 +08:00
|
|
|
void testNegotiationFailure();
|
|
|
|
void testNegotiationSuccess();
|
2019-11-17 18:19:57 +08:00
|
|
|
void testOfflineExtensionMessages();
|
|
|
|
void testSentMessageExtensionSetReduced();
|
|
|
|
void testActionMessagesSplitWithExtensions();
|
2019-06-03 02:22:35 +08:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2022-03-11 15:53:13 +08:00
|
|
|
std::ignore = sender;
|
2019-06-03 02:22:35 +08:00
|
|
|
receivedMessages.push_back(std::move(message));
|
|
|
|
}
|
|
|
|
|
2022-03-15 18:39:26 +08:00
|
|
|
void onMessageBroken(DispatchedMessageId id, BrokenMessageReason reason)
|
2019-11-17 18:19:57 +08:00
|
|
|
{
|
2022-03-15 18:39:26 +08:00
|
|
|
std::ignore = reason;
|
2019-11-17 18:19:57 +08:00
|
|
|
brokenMessages.insert(id);
|
|
|
|
}
|
|
|
|
|
2019-06-03 02:22:35 +08:00
|
|
|
private:
|
|
|
|
// All unique_ptrs to make construction/init() easier to manage
|
|
|
|
std::unique_ptr<Friend> f;
|
|
|
|
std::unique_ptr<MockFriendMessageSender> messageSender;
|
2019-11-13 12:12:05 +08:00
|
|
|
std::unique_ptr<MockCoreExtPacketAllocator> coreExtPacketAllocator;
|
2019-06-06 23:41:21 +08:00
|
|
|
std::unique_ptr<MessageProcessor::SharedParams> sharedProcessorParams;
|
|
|
|
std::unique_ptr<MessageProcessor> messageProcessor;
|
2019-06-03 02:22:35 +08:00
|
|
|
std::unique_ptr<FriendMessageDispatcher> friendMessageDispatcher;
|
|
|
|
std::map<DispatchedMessageId, Message> outgoingMessages;
|
2019-11-17 18:19:57 +08:00
|
|
|
std::set<DispatchedMessageId> brokenMessages;
|
2019-06-03 02:22:35 +08:00
|
|
|
std::deque<Message> receivedMessages;
|
|
|
|
};
|
|
|
|
|
|
|
|
TestFriendMessageDispatcher::TestFriendMessageDispatcher() {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Test initialization. Resets all member variables for a fresh test state
|
|
|
|
*/
|
|
|
|
void TestFriendMessageDispatcher::init()
|
|
|
|
{
|
|
|
|
f = std::unique_ptr<Friend>(new Friend(0, ToxPk()));
|
|
|
|
f->setStatus(Status::Status::Online);
|
2019-11-17 18:19:57 +08:00
|
|
|
f->onNegotiationComplete();
|
2019-06-03 02:22:35 +08:00
|
|
|
messageSender = std::unique_ptr<MockFriendMessageSender>(new MockFriendMessageSender());
|
2019-11-13 12:12:05 +08:00
|
|
|
coreExtPacketAllocator = std::unique_ptr<MockCoreExtPacketAllocator>(new MockCoreExtPacketAllocator());
|
2019-06-06 23:41:21 +08:00
|
|
|
sharedProcessorParams =
|
2021-01-25 06:49:24 +08:00
|
|
|
std::unique_ptr<MessageProcessor::SharedParams>(new MessageProcessor::SharedParams(tox_max_message_length(), testMaxExtendedMessageSize));
|
|
|
|
|
2019-06-06 23:41:21 +08:00
|
|
|
messageProcessor = std::unique_ptr<MessageProcessor>(new MessageProcessor(*sharedProcessorParams));
|
|
|
|
friendMessageDispatcher = std::unique_ptr<FriendMessageDispatcher>(
|
2019-11-13 12:12:05 +08:00
|
|
|
new FriendMessageDispatcher(*f, *messageProcessor, *messageSender, *coreExtPacketAllocator));
|
2019-06-03 02:22:35 +08:00
|
|
|
|
|
|
|
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageSent, this,
|
|
|
|
&TestFriendMessageDispatcher::onMessageSent);
|
|
|
|
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageComplete, this,
|
|
|
|
&TestFriendMessageDispatcher::onMessageComplete);
|
|
|
|
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageReceived, this,
|
|
|
|
&TestFriendMessageDispatcher::onMessageReceived);
|
2019-11-17 18:19:57 +08:00
|
|
|
connect(friendMessageDispatcher.get(), &FriendMessageDispatcher::messageBroken, this,
|
|
|
|
&TestFriendMessageDispatcher::onMessageBroken);
|
2019-06-03 02:22:35 +08:00
|
|
|
|
|
|
|
outgoingMessages = std::map<DispatchedMessageId, Message>();
|
|
|
|
receivedMessages = std::deque<Message>();
|
2019-11-17 18:19:57 +08:00
|
|
|
brokenMessages = std::set<DispatchedMessageId>();
|
2019-06-03 02:22:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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);
|
2019-11-17 18:19:57 +08:00
|
|
|
f->onNegotiationComplete();
|
2019-06-03 02:22:35 +08:00
|
|
|
|
|
|
|
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);
|
2019-11-17 18:19:57 +08:00
|
|
|
f->onNegotiationComplete();
|
2019-06-03 02:22:35 +08:00
|
|
|
|
|
|
|
QVERIFY(messageSender->numSentMessages == 1);
|
|
|
|
}
|
|
|
|
|
2019-11-13 12:12:05 +08:00
|
|
|
void TestFriendMessageDispatcher::testNegotiationFailure()
|
|
|
|
{
|
2019-11-17 18:19:57 +08:00
|
|
|
f->setStatus(Status::Status::Offline);
|
|
|
|
f->setStatus(Status::Status::Online);
|
|
|
|
|
|
|
|
QVERIFY(f->getStatus() == Status::Status::Negotiating);
|
|
|
|
|
2019-11-13 12:12:05 +08:00
|
|
|
friendMessageDispatcher->sendMessage(false, "test");
|
|
|
|
|
2019-11-17 18:19:57 +08:00
|
|
|
QVERIFY(messageSender->numSentMessages == 0);
|
|
|
|
|
|
|
|
f->onNegotiationComplete();
|
|
|
|
|
2019-11-13 12:12:05 +08:00
|
|
|
QVERIFY(messageSender->numSentMessages == 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TestFriendMessageDispatcher::testNegotiationSuccess()
|
|
|
|
{
|
2019-11-17 18:19:57 +08:00
|
|
|
f->setStatus(Status::Status::Offline);
|
|
|
|
f->setStatus(Status::Status::Online);
|
|
|
|
|
2019-11-13 12:12:05 +08:00
|
|
|
f->setExtendedMessageSupport(true);
|
2019-11-17 18:19:57 +08:00
|
|
|
f->onNegotiationComplete();
|
2019-11-13 12:12:05 +08:00
|
|
|
|
|
|
|
friendMessageDispatcher->sendMessage(false, "test");
|
|
|
|
|
|
|
|
QVERIFY(coreExtPacketAllocator->numSentMessages == 1);
|
|
|
|
|
|
|
|
friendMessageDispatcher->sendMessage(false, "test");
|
|
|
|
QVERIFY(coreExtPacketAllocator->numSentMessages == 2);
|
|
|
|
QVERIFY(messageSender->numSentMessages == 0);
|
|
|
|
}
|
|
|
|
|
2019-11-17 18:19:57 +08:00
|
|
|
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");
|
|
|
|
|
2021-01-25 06:49:24 +08:00
|
|
|
for (uint64_t i = 0; i < testMaxExtendedMessageSize + 50; ++i) {
|
2022-04-03 16:17:08 +08:00
|
|
|
reallyLongMessage += QString().number(i);
|
2019-11-17 18:19:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
friendMessageDispatcher->sendMessage(true, reallyLongMessage);
|
|
|
|
|
|
|
|
QVERIFY(coreExtPacketAllocator->numSentMessages == 0);
|
|
|
|
QVERIFY(messageSender->numSentMessages == 0);
|
|
|
|
QVERIFY(messageSender->numSentActions > 1);
|
|
|
|
}
|
|
|
|
|
2019-06-03 02:22:35 +08:00
|
|
|
QTEST_GUILESS_MAIN(TestFriendMessageDispatcher)
|
|
|
|
#include "friendmessagedispatcher_test.moc"
|