syncthingtray/connector/tests/connectiontests.cpp

382 lines
16 KiB
C++
Raw Normal View History

2017-03-09 23:06:03 +01:00
#include "../syncthingconnection.h"
2016-09-21 21:05:31 +02:00
#include "../testhelper/helper.h"
#include "../testhelper/syncthingtestinstance.h"
2016-09-21 21:05:31 +02:00
#include <c++utilities/tests/testutils.h>
#include <cppunit/TestFixture.h>
#include <QDir>
2017-07-02 21:47:59 +02:00
#include <QJsonArray>
#include <QJsonDocument>
2017-07-02 22:08:30 +02:00
#include <QStringBuilder>
2016-09-21 21:05:31 +02:00
using namespace std;
using namespace Data;
using namespace TestUtilities;
2017-06-01 11:11:38 +02:00
using namespace TestUtilities::Literals;
2016-09-21 21:05:31 +02:00
using namespace CPPUNIT_NS;
/*!
* \brief The ConnectionTests class tests the SyncthingConnector.
*/
2017-05-04 22:48:45 +02:00
class ConnectionTests : public TestFixture, private SyncthingTestInstance {
2016-09-21 21:05:31 +02:00
CPPUNIT_TEST_SUITE(ConnectionTests);
CPPUNIT_TEST(testConnection);
CPPUNIT_TEST_SUITE_END();
public:
ConnectionTests();
void testConnection();
2017-07-02 21:47:59 +02:00
void testErrorCases();
void testInitialConnection();
void waitForAllDirsAndDevsReady();
2017-07-02 21:47:59 +02:00
void checkDevices();
void checkDirectories() const;
void testReconnecting();
void testResumingAllDevices();
void testResumingDirectory();
void testPausingDirectory();
void testDisconnecting();
2016-09-21 21:05:31 +02:00
void setUp();
void tearDown();
private:
2017-07-02 22:08:30 +02:00
template <typename Action, typename... SignalInfos> void waitForConnection(Action action, int timeout, const SignalInfos &... signalInfos);
template <typename Signal, typename Handler = function<void(void)>>
SignalInfo<Signal, Handler> connectionSignal(
Signal signal, const Handler &handler = function<void(void)>(), bool *correctSignalEmitted = nullptr);
2017-07-02 21:47:59 +02:00
static void (SyncthingConnection::*defaultConnect())(void);
static void (SyncthingConnection::*defaultReconnect())(void);
static void (SyncthingConnection::*defaultDisconnect())(void);
template <typename Handler> TemporaryConnection handleNewDevices(Handler handler, bool *ok);
template <typename Handler> TemporaryConnection handleNewDirs(Handler handler, bool *ok);
2016-09-21 21:05:31 +02:00
SyncthingConnection m_connection;
2017-07-02 21:47:59 +02:00
QString m_ownDevId;
2016-09-21 21:05:31 +02:00
};
CPPUNIT_TEST_SUITE_REGISTRATION(ConnectionTests);
ConnectionTests::ConnectionTests()
2017-05-04 22:48:45 +02:00
{
}
2016-09-21 21:05:31 +02:00
//
// test setup
//
/*!
* \brief Starts Syncthing and prepares connecting.
*/
void ConnectionTests::setUp()
{
SyncthingTestInstance::start();
2016-09-21 21:05:31 +02:00
cerr << "\n - Preparing connection ..." << endl;
m_connection.setSyncthingUrl(QStringLiteral("http://localhost:") + syncthingPort());
2016-09-21 21:05:31 +02:00
// keep track of status changes
2017-05-04 22:48:45 +02:00
QObject::connect(&m_connection, &SyncthingConnection::statusChanged,
[this] { cerr << " - Connection status changed to: " << m_connection.statusText().toLocal8Bit().data() << endl; });
2017-07-02 21:47:59 +02:00
// log configuration change
QObject::connect(&m_connection, &SyncthingConnection::newConfig,
2017-07-02 22:08:30 +02:00
[](const QJsonObject &config) { cerr << " - New config: " << QJsonDocument(config).toJson(QJsonDocument::Compact).data() << endl; });
2016-09-21 21:05:31 +02:00
}
/*!
* \brief Terminates Syncthing and prints stdout/stderr from Syncthing.
*/
void ConnectionTests::tearDown()
{
SyncthingTestInstance::stop();
2016-09-21 21:05:31 +02:00
}
//
// test helper
//
/*!
* \brief Variant of waitForSignal() where sender is the connection and the action is a method of the connection.
*/
2017-07-02 21:47:59 +02:00
template <typename Action, typename... SignalInfos>
2017-07-02 22:08:30 +02:00
void ConnectionTests::waitForConnection(Action action, int timeout, const SignalInfos &... signalInfos)
2016-09-21 21:05:31 +02:00
{
2017-07-02 21:47:59 +02:00
waitForSignals(bind(action, &m_connection), timeout, signalInfos...);
2016-09-21 21:05:31 +02:00
}
2017-07-02 22:08:30 +02:00
template <typename Signal, typename Handler>
2017-07-02 21:47:59 +02:00
SignalInfo<Signal, Handler> ConnectionTests::connectionSignal(Signal signal, const Handler &handler, bool *correctSignalEmitted)
2016-09-21 21:05:31 +02:00
{
2017-07-02 21:47:59 +02:00
return SignalInfo<Signal, Handler>(&m_connection, signal, handler, correctSignalEmitted);
}
void (SyncthingConnection::*ConnectionTests::defaultConnect())(void)
{
2017-07-02 22:08:30 +02:00
return static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect);
2017-07-02 21:47:59 +02:00
}
void (SyncthingConnection::*ConnectionTests::defaultReconnect())(void)
{
2017-07-02 22:08:30 +02:00
return static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::reconnect);
2017-07-02 21:47:59 +02:00
}
void (SyncthingConnection::*ConnectionTests::defaultDisconnect())(void)
{
2017-07-02 22:08:30 +02:00
return static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::disconnect);
}
/*!
* \brief Helps handling newDevices() signal when waiting for device change.
*/
2017-07-02 21:47:59 +02:00
template <typename Handler> TemporaryConnection ConnectionTests::handleNewDevices(Handler handler, bool *ok)
{
2017-05-04 22:48:45 +02:00
return QObject::connect(&m_connection, &SyncthingConnection::newDevices, [ok, &handler](const std::vector<SyncthingDev> &devs) {
for (const SyncthingDev &dev : devs) {
handler(dev, 0);
}
});
}
/*!
* \brief Helps handling newDirs() signal when waiting for directory change.
*/
2017-07-02 21:47:59 +02:00
template <typename Handler> TemporaryConnection ConnectionTests::handleNewDirs(Handler handler, bool *ok)
{
2017-05-04 22:48:45 +02:00
return QObject::connect(&m_connection, &SyncthingConnection::newDirs, [ok, &handler](const std::vector<SyncthingDir> &dirs) {
for (const SyncthingDir &dir : dirs) {
handler(dir, 0);
}
});
2016-09-21 21:05:31 +02:00
}
//
// actual test
//
/*!
* \brief Tests basic behaviour of the SyncthingConnection class.
*/
void ConnectionTests::testConnection()
{
2017-07-02 21:47:59 +02:00
testErrorCases();
testInitialConnection();
waitForAllDirsAndDevsReady();
2017-07-02 21:47:59 +02:00
checkDevices();
checkDirectories();
testReconnecting();
testResumingAllDevices();
testResumingDirectory();
testPausingDirectory();
testDisconnecting();
}
void ConnectionTests::testErrorCases()
{
cerr << "\n - Error handling in case of insufficient conficuration ..." << endl;
2017-07-02 22:08:30 +02:00
waitForConnection(defaultConnect(), 1000, connectionSignal(&SyncthingConnection::error, [](const QString &errorMessage) {
CPPUNIT_ASSERT_EQUAL(QStringLiteral("Connection configuration is insufficient."), errorMessage);
2017-07-02 21:47:59 +02:00
}));
2016-09-21 21:05:31 +02:00
2017-07-02 21:47:59 +02:00
cerr << "\n - Error handling in case of inavailability and wrong API key ..." << endl;
2016-09-21 21:05:31 +02:00
m_connection.setApiKey(QByteArray("wrong API key"));
2017-03-09 23:06:03 +01:00
bool syncthingAvailable = false;
2017-05-04 22:48:45 +02:00
const function<void(const QString &errorMessage)> errorHandler = [this, &syncthingAvailable](const QString &errorMessage) {
if (errorMessage == QStringLiteral("Unable to request Syncthing config: Connection refused")
|| errorMessage == QStringLiteral("Unable to request Syncthing status: Connection refused")) {
return; // Syncthing not ready yet
}
syncthingAvailable = true;
if (errorMessage
!= QStringLiteral("Unable to request Syncthing config: Error transferring ") % m_connection.syncthingUrl()
% QStringLiteral("/rest/system/config - server replied: Forbidden")
&& errorMessage
!= QStringLiteral("Unable to request Syncthing status: Error transferring ") % m_connection.syncthingUrl()
% QStringLiteral("/rest/system/status - server replied: Forbidden")) {
CPPUNIT_FAIL(argsToString("wrong error message in case of wrong API key: ", errorMessage.toLocal8Bit().data()));
}
};
while (!syncthingAvailable) {
2017-07-02 21:47:59 +02:00
waitForConnection(defaultConnect(), 5000, connectionSignal(&SyncthingConnection::error, errorHandler));
2017-03-09 23:06:03 +01:00
}
2017-07-02 21:47:59 +02:00
}
2016-09-21 21:05:31 +02:00
2017-07-02 21:47:59 +02:00
void ConnectionTests::testInitialConnection()
{
cerr << "\n - Connecting initially ..." << endl;
m_connection.setApiKey(apiKey().toUtf8());
2017-07-02 21:47:59 +02:00
waitForConnection(defaultConnect(), 5000, connectionSignal(&SyncthingConnection::statusChanged));
2017-05-04 22:48:45 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE(
"connected and paused (one dev is initially paused)", QStringLiteral("connected, paused"), m_connection.statusText());
2017-07-02 21:47:59 +02:00
CPPUNIT_ASSERT_MESSAGE("no dirs out-of-sync", !m_connection.hasOutOfSyncDirs());
}
2016-09-21 21:05:31 +02:00
void ConnectionTests::waitForAllDirsAndDevsReady()
{
bool allDirsReady, allDevsReady;
const function<void()> checkAllDirsReady([this, &allDirsReady] {
for (const SyncthingDir &dir : m_connection.dirInfo()) {
if (dir.status == SyncthingDirStatus::Unknown) {
allDirsReady = false;
return;
}
}
allDirsReady = true;
});
const function<void()> checkAllDevsReady([this, &allDevsReady] {
for (const SyncthingDev &dev : m_connection.devInfo()) {
if (dev.status == SyncthingDevStatus::Unknown) {
allDevsReady = false;
return;
}
}
allDevsReady = true;
});
checkAllDirsReady();
checkAllDevsReady();
if (allDirsReady && allDevsReady) {
return;
}
waitForSignals(noop, 1000, connectionSignal(&SyncthingConnection::dirStatusChanged, checkAllDirsReady, &allDirsReady),
connectionSignal(&SyncthingConnection::newDirs, checkAllDirsReady, &allDirsReady),
connectionSignal(&SyncthingConnection::dirStatusChanged, checkAllDevsReady, &allDevsReady),
connectionSignal(&SyncthingConnection::newDevices, checkAllDevsReady, &allDevsReady));
}
2017-07-02 21:47:59 +02:00
void ConnectionTests::checkDevices()
{
2016-09-21 21:05:31 +02:00
const auto &devInfo = m_connection.devInfo();
2017-06-01 11:11:38 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("3 devs present", 3_st, devInfo.size());
2017-05-04 22:48:45 +02:00
for (const SyncthingDev &dev : devInfo) {
if (dev.id != QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7")
&& dev.id != QStringLiteral("6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4")) {
2017-03-09 23:06:03 +01:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("own device", QStringLiteral("own device"), dev.statusString());
2017-07-02 21:47:59 +02:00
m_ownDevId = dev.id;
2017-03-09 23:06:03 +01:00
}
}
2017-05-04 22:48:45 +02:00
for (const SyncthingDev &dev : devInfo) {
if (dev.id == QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7")) {
2017-03-09 23:06:03 +01:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("paused device", QStringLiteral("paused"), dev.statusString());
CPPUNIT_ASSERT_EQUAL_MESSAGE("name", QStringLiteral("Test dev 2"), dev.name);
CPPUNIT_ASSERT_MESSAGE("no introducer", !dev.introducer);
CPPUNIT_ASSERT_EQUAL(1, dev.addresses.size());
CPPUNIT_ASSERT_EQUAL(QStringLiteral("tcp://192.168.2.2:22000"), dev.addresses.front());
2017-05-04 22:48:45 +02:00
} else if (dev.id == QStringLiteral("6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4")) {
2017-03-09 23:06:03 +01:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("disconnected device", QStringLiteral("disconnected"), dev.statusString());
CPPUNIT_ASSERT_EQUAL_MESSAGE("name", QStringLiteral("Test dev 1"), dev.name);
CPPUNIT_ASSERT_MESSAGE("introducer", dev.introducer);
CPPUNIT_ASSERT_EQUAL(1, dev.addresses.size());
CPPUNIT_ASSERT_EQUAL(QStringLiteral("dynamic"), dev.addresses.front());
}
}
2017-07-02 21:47:59 +02:00
}
2017-03-09 23:06:03 +01:00
2017-07-02 21:47:59 +02:00
void ConnectionTests::checkDirectories() const
{
2017-03-09 23:06:03 +01:00
const auto &dirInfo = m_connection.dirInfo();
2017-06-01 11:11:38 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("2 dirs present", 2_st, dirInfo.size());
2017-03-09 23:06:03 +01:00
const SyncthingDir &dir1 = dirInfo.front();
CPPUNIT_ASSERT_EQUAL(QStringLiteral("test1"), dir1.id);
CPPUNIT_ASSERT_EQUAL(QStringLiteral(""), dir1.label);
CPPUNIT_ASSERT_EQUAL(QStringLiteral("test1"), dir1.displayName());
CPPUNIT_ASSERT_EQUAL(QStringLiteral("/tmp/some/path/1/"), dir1.path);
CPPUNIT_ASSERT(!dir1.readOnly);
CPPUNIT_ASSERT(!dir1.paused);
2017-05-04 22:48:45 +02:00
CPPUNIT_ASSERT_EQUAL(dir1.devices.toSet(), QSet<QString>({ QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7"),
2017-07-02 21:47:59 +02:00
QStringLiteral("6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4"), m_ownDevId }));
2017-03-09 23:06:03 +01:00
const SyncthingDir &dir2 = dirInfo.back();
CPPUNIT_ASSERT_EQUAL(QStringLiteral("test2"), dir2.id);
CPPUNIT_ASSERT_EQUAL(QStringLiteral("Test dir 2"), dir2.label);
CPPUNIT_ASSERT_EQUAL(QStringLiteral("Test dir 2"), dir2.displayName());
CPPUNIT_ASSERT_EQUAL(QStringLiteral("/tmp/some/path/2/"), dir2.path);
CPPUNIT_ASSERT(!dir2.readOnly);
CPPUNIT_ASSERT(dir2.paused);
2017-05-04 22:48:45 +02:00
CPPUNIT_ASSERT_EQUAL(
2017-07-02 21:47:59 +02:00
dir2.devices.toSet(), QSet<QString>({ QStringLiteral("MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7"), m_ownDevId }));
}
void ConnectionTests::testReconnecting()
{
2016-09-21 21:05:31 +02:00
cerr << "\n - Reconnecting ..." << endl;
2017-07-02 21:47:59 +02:00
waitForConnection(defaultReconnect(), 1000, connectionSignal(&SyncthingConnection::statusChanged));
2016-09-21 21:05:31 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("reconnecting", QStringLiteral("reconnecting"), m_connection.statusText());
2017-07-02 21:47:59 +02:00
waitForSignals(noop, 1000, connectionSignal(&SyncthingConnection::statusChanged));
2017-05-04 22:48:45 +02:00
if (m_connection.isConnected() && m_connection.status() != SyncthingStatus::Paused) {
2017-07-02 21:47:59 +02:00
// FIXME: Maybe it takes one further update to recognize paused dev?
waitForSignals(noop, 1000, connectionSignal(&SyncthingConnection::statusChanged));
2017-03-09 23:06:03 +01:00
}
2016-09-21 21:05:31 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("connected again", QStringLiteral("connected, paused"), m_connection.statusText());
2017-07-02 21:47:59 +02:00
}
2016-09-21 21:05:31 +02:00
2017-07-02 21:47:59 +02:00
void ConnectionTests::testResumingAllDevices()
{
cerr << "\n - Resuming all devices ..." << endl;
bool devResumed = false;
2017-05-04 22:48:45 +02:00
const function<void(const SyncthingDev &, int)> devResumedHandler = [&devResumed](const SyncthingDev &dev, int) {
if (dev.name == QStringLiteral("Test dev 2") && !dev.paused) {
devResumed = true;
2017-03-09 23:06:03 +01:00
}
};
2017-07-02 22:08:30 +02:00
const function<void(const QStringList &)> devResumedTriggeredHandler
= [this](const QStringList &devIds) { CPPUNIT_ASSERT_EQUAL(m_connection.deviceIds(), devIds); };
2017-07-02 21:47:59 +02:00
const auto newDevsConnection = handleNewDevices(devResumedHandler, &devResumed);
waitForConnection(&SyncthingConnection::resumeAllDevs, 3000,
2017-07-02 22:08:30 +02:00
connectionSignal(&SyncthingConnection::devStatusChanged, devResumedHandler, &devResumed),
connectionSignal(&SyncthingConnection::deviceResumeTriggered, devResumedTriggeredHandler));
2017-07-02 21:47:59 +02:00
CPPUNIT_ASSERT(devResumed);
2017-03-09 23:06:03 +01:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("not paused anymore", QStringLiteral("connected"), m_connection.statusText());
2017-07-02 21:47:59 +02:00
for (const QJsonValue &devValue : m_connection.m_rawConfig.value(QStringLiteral("devices")).toArray()) {
const QJsonObject &devObj(devValue.toObject());
CPPUNIT_ASSERT(!devObj.isEmpty());
CPPUNIT_ASSERT_MESSAGE("raw config updated accordingly", !devObj.value(QStringLiteral("paused")).toBool(true));
}
}
2017-03-09 23:06:03 +01:00
2017-07-02 21:47:59 +02:00
void ConnectionTests::testResumingDirectory()
{
cerr << "\n - Resuming all dirs ..." << endl;
bool dirResumed = false;
2017-05-04 22:48:45 +02:00
const function<void(const SyncthingDir &, int)> dirResumedHandler = [&dirResumed](const SyncthingDir &dir, int) {
if (dir.id == QStringLiteral("test2") && !dir.paused) {
dirResumed = true;
}
};
2017-07-02 21:47:59 +02:00
const auto newDirsConnection = handleNewDirs(dirResumedHandler, &dirResumed);
2017-07-02 22:08:30 +02:00
waitForConnection(
&SyncthingConnection::resumeAllDirs, 3000, connectionSignal(&SyncthingConnection::dirStatusChanged, dirResumedHandler, &dirResumed));
2017-07-02 21:47:59 +02:00
CPPUNIT_ASSERT(dirResumed);
2017-06-01 11:11:38 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("still 2 dirs present", 2_st, m_connection.dirInfo().size());
2017-07-02 21:47:59 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("still not paused anymore", QStringLiteral("connected"), m_connection.statusText());
}
2017-07-02 21:47:59 +02:00
void ConnectionTests::testPausingDirectory()
{
cerr << "\n - Pause dir 1 ..." << endl;
2017-03-09 23:06:03 +01:00
bool dirPaused = false;
2017-05-04 22:48:45 +02:00
const function<void(const SyncthingDir &, int)> dirPausedHandler = [&dirPaused](const SyncthingDir &dir, int) {
if (dir.id == QStringLiteral("test1") && dir.paused) {
2017-03-09 23:06:03 +01:00
dirPaused = true;
}
};
2017-07-02 21:47:59 +02:00
const auto newDirsConnection = handleNewDirs(dirPausedHandler, &dirPaused);
waitForSignals(bind(&SyncthingConnection::pauseDirectories, &m_connection, QStringList({ QStringLiteral("test1") })), 3000,
2017-07-02 22:08:30 +02:00
connectionSignal(&SyncthingConnection::dirStatusChanged, dirPausedHandler, &dirPaused));
2017-07-02 21:47:59 +02:00
CPPUNIT_ASSERT(dirPaused);
2017-06-01 11:11:38 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("still 2 dirs present", 2_st, m_connection.dirInfo().size());
2017-07-02 21:47:59 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("still not paused anymore", QStringLiteral("connected"), m_connection.statusText());
}
2017-03-09 23:06:03 +01:00
2017-07-02 21:47:59 +02:00
void ConnectionTests::testDisconnecting()
{
2016-09-21 21:05:31 +02:00
cerr << "\n - Disconnecting ..." << endl;
2017-07-02 21:47:59 +02:00
waitForConnection(defaultDisconnect(), 1000, connectionSignal(&SyncthingConnection::statusChanged));
2016-09-21 21:05:31 +02:00
CPPUNIT_ASSERT_EQUAL_MESSAGE("disconnected", QStringLiteral("disconnected"), m_connection.statusText());
}