#include "./syncthingconnectionmockhelpers.h" #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace CppUtilities; using namespace CppUtilities::EscapeCodes; namespace Data { /*! * \brief Contains test data for mocked SyncthingConnection. */ namespace TestData { static bool initialized = false; static string config, status, folderStats, deviceStats, errors, folderStatus, folderStatus2, folderStatus3, pullErrors, connections, version, empty; static string events[7]; } // namespace TestData /*! * \brief Returns the contents of the specified file and exits with an error message if an error occurs. */ string readMockFile(const string &filePath) { try { return readFile(filePath); } catch (const std::ios_base::failure &failure) { cerr << Phrases::Error << "An IO error occurred when reading mock config file \"" << filePath << "\": " << failure.what() << Phrases::EndFlush; exit(-2); } } /*! * \brief Loads test files for mocked configuration using TestApplication::testFilePath(). * \remarks * - So TEST_FILE_PATH must be set to "$synthingtray_checkout/connector/testfiles" so this function can * find the required files. * - In the error case, the application will be terminated. */ void setupTestData() { // skip if already initialized using namespace TestData; if (initialized) { return; } // use a TestApplication to locate the test files const TestApplication testApp(0, nullptr); // read mock files for REST-API const char *const fileNames[] = { "config", "status", "folderstats", "devicestats", "errors", "folderstatus-01", "folderstatus-02", "folderstatus-03", "pullerrors-01", "connections", "version", "empty" }; const char *const *fileName = fileNames; for (string *testDataVariable : { &config, &status, &folderStats, &deviceStats, &errors, &folderStatus, &folderStatus2, &folderStatus3, &pullErrors, &connections, &version, &empty }) { *testDataVariable = readMockFile(testApp.testFilePath(argsToString("mocks/", *fileName, ".json"))); ++fileName; } // read mock files for Event-API unsigned int index = 1; for (string &event : events) { const char *const pad = index < 10 ? "0" : ""; event = readMockFile(testApp.testFilePath(argsToString("mocks/events-", pad, index, ".json"))); ++index; } initialized = true; } MockedReply::MockedReply(const string &buffer, int delay, QObject *parent) : QNetworkReply(parent) , m_buffer(buffer) , m_pos(buffer.data()) , m_bytesLeft(static_cast(m_buffer.size())) { setOpenMode(QIODevice::ReadOnly); QTimer::singleShot(delay, this, &MockedReply::emitFinished); } MockedReply::~MockedReply() { } void MockedReply::abort() { close(); } void MockedReply::close() { } qint64 MockedReply::bytesAvailable() const { return m_bytesLeft; } bool MockedReply::isSequential() const { return true; } qint64 MockedReply::size() const { return static_cast(m_buffer.size()); } qint64 MockedReply::readData(char *data, qint64 maxlen) { if (!m_bytesLeft) { return -1; } const qint64 bytesToRead = static_cast(min(m_bytesLeft, maxlen)); if (!bytesToRead) { return 0; } copy(m_pos, m_pos + bytesToRead, data); m_pos += bytesToRead; m_bytesLeft -= bytesToRead; return bytesToRead; } int MockedReply::s_eventIndex = 0; MockedReply *MockedReply::forRequest(const QString &method, const QString &path, const QUrlQuery &query, bool rest) { CPP_UTILITIES_UNUSED(query) CPP_UTILITIES_UNUSED(rest) // set "mock URL" QUrl url((rest ? QStringLiteral("mock://rest/") : QStringLiteral("mock://")) + path); url.setQuery(query); // find the correct buffer for the request static const string emptyBuffer; const string *buffer = &emptyBuffer; int delay = 5; { using namespace TestData; if (method == QLatin1String("GET")) { if (path == QLatin1String("system/config")) { buffer = &config; } else if (path == QLatin1String("system/status")) { buffer = &status; } else if (path == QLatin1String("stats/folder")) { buffer = &folderStats; } else if (path == QLatin1String("stats/device")) { buffer = &deviceStats; } else if (path == QLatin1String("system/error")) { buffer = &errors; } else if (path == QLatin1String("db/status")) { const QString folder(query.queryItemValue(QStringLiteral("folder"))); if (folder == QLatin1String("GXWxf-3zgnU")) { buffer = &folderStatus; } else if (folder == QLatin1String("zX8xfl3ygn-")) { buffer = &folderStatus2; } else if (folder == QLatin1String("forever-alone")) { buffer = &folderStatus3; } } else if (path == QLatin1String("folder/pullerrors")) { const QString folder(query.queryItemValue(QStringLiteral("folder"))); if (folder == QLatin1String("GXWxf-3zgnU") && s_eventIndex >= 6) { buffer = &pullErrors; } } else if (path == QLatin1String("system/connections")) { buffer = &connections; } else if (path == QLatin1String("system/version")) { buffer = &version; } else if (path == QLatin1String("events")) { buffer = &events[s_eventIndex]; cerr << "mocking: at event index " << s_eventIndex << endl; // "emit" the first event almost immediately and further events each 2.5 seconds switch (s_eventIndex) { case 0: delay = 200; ++s_eventIndex; break; case 6: // continue emitting the last event every 10 seconds delay = 10000; break; default: delay = 2000; ++s_eventIndex; } } else if (path == QLatin1String("events/disk")) { buffer = ∅ delay = 5000; } } } // construct reply auto *const reply = new MockedReply(*buffer, delay); reply->setRequest(QNetworkRequest(url)); return reply; } void MockedReply::emitFinished() { if (m_buffer.empty()) { setError(QNetworkReply::InternalServerError, QStringLiteral("No mockup reply available for this request.")); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404); setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, QLatin1String("Not found")); } else { setError(QNetworkReply::NoError, QString()); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, QLatin1String("OK")); } setFinished(true); emit finished(); } } // namespace Data