From 56166deb76f56dcc12502bbbadf8a5d10a12c0a2 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Wed, 25 Jul 2018 12:21:29 +0200 Subject: [PATCH] Added test that checks what happens when there is an error in the remote discovery (Many of the expected error string are left empty because the current error message is not insterresting --- test/CMakeLists.txt | 1 + test/syncenginetestutils.h | 9 ++- test/testremotediscovery.cpp | 128 +++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 test/testremotediscovery.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index abad72e8c6..0b923b85ce 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,6 +52,7 @@ owncloud_add_test(UploadReset "syncenginetestutils.h") owncloud_add_test(AllFilesDeleted "syncenginetestutils.h") owncloud_add_test(Blacklist "syncenginetestutils.h") owncloud_add_test(LocalDiscovery "syncenginetestutils.h") +owncloud_add_test(RemoteDiscovery "syncenginetestutils.h") owncloud_add_test(Permissions "syncenginetestutils.h") owncloud_add_test(FolderWatcher "${FolderWatcher_SRC}") diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index 536521b707..59bccc12a7 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -773,7 +773,14 @@ public: open(QIODevice::ReadOnly); } - void abort() override {} + void abort() override { + // Follow more or less the implementation of QNetworkReplyImpl::abort + close(); + setError(OperationCanceledError, tr("Operation canceled")); + emit error(OperationCanceledError); + setFinished(true); + emit finished(); + } qint64 readData(char *, qint64) override { return 0; } }; diff --git a/test/testremotediscovery.cpp b/test/testremotediscovery.cpp new file mode 100644 index 0000000000..4945bbf4cd --- /dev/null +++ b/test/testremotediscovery.cpp @@ -0,0 +1,128 @@ +/* + * This software is in the public domain, furnished "as is", without technical + * support, and with no warranty, express or implied, as to its usefulness for + * any purpose. + * + */ + +#include +#include "syncenginetestutils.h" +#include +#include + +using namespace OCC; + +struct FakeBrokenXmlPropfindReply : FakePropfindReply { + FakeBrokenXmlPropfindReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, + const QNetworkRequest &request, QObject *parent) + : FakePropfindReply(remoteRootFileInfo, op, request, parent) { + QVERIFY(payload.size() > 50); + // turncate the XML + payload.chop(20); + } +}; + +struct MissingPermissionsPropfindReply : FakePropfindReply { + MissingPermissionsPropfindReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, + const QNetworkRequest &request, QObject *parent) + : FakePropfindReply(remoteRootFileInfo, op, request, parent) { + // If the propfind contains a single file without permissions, this is a server error + const char toRemove[] = "RDNVCKW"; + auto pos = payload.indexOf(toRemove, payload.size()/2); + QVERIFY(pos > 0); + payload.remove(pos, sizeof(toRemove) - 1); + } +}; + + +enum ErrorKind : int { + // Lower code are corresponding to HTML error code + InvalidXML = 1000, + MissingPermissions, + Timeout, +}; + +Q_DECLARE_METATYPE(ErrorCategory) + +class TestRemoteDiscovery : public QObject +{ + Q_OBJECT + +private slots: + + void testRemoteDiscoveryError_data() + { + qRegisterMetaType(); + QTest::addColumn("errorKind"); + QTest::addColumn("expectedErrorString"); + + QTest::newRow("404") << 404 << "B"; // The filename should be in the error message + QTest::newRow("500") << 500 << "Internal Server Fake Error"; // the message from FakeErrorReply + QTest::newRow("503") << 503 << ""; + QTest::newRow("200") << 200 << ""; // 200 should be an error since propfind should return 207 + QTest::newRow("InvalidXML") << +InvalidXML << ""; + QTest::newRow("MissingPermissions") << +MissingPermissions << "missing data"; + QTest::newRow("Timeout") << +Timeout << ""; + } + + + // Check what happens when there is an error. + void testRemoteDiscoveryError() + { + QFETCH(int, errorKind); + QFETCH(QString, expectedErrorString); + bool syncSucceeds = errorKind == 503; // 503 just ignore the temporarily unavailable directory + + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + + // Do Some change as well + fakeFolder.localModifier().insert("A/z1"); + fakeFolder.localModifier().insert("B/z1"); + fakeFolder.localModifier().insert("C/z1"); + fakeFolder.remoteModifier().insert("A/z2"); + fakeFolder.remoteModifier().insert("B/z2"); + fakeFolder.remoteModifier().insert("C/z2"); + + auto oldLocalState = fakeFolder.currentLocalState(); + auto oldRemoteState = fakeFolder.currentRemoteState(); + + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) + -> QNetworkReply *{ + if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith("/B")) { + if (errorKind == InvalidXML) { + return new FakeBrokenXmlPropfindReply(fakeFolder.remoteModifier(), op, req, this); + } else if (errorKind == MissingPermissions) { + return new MissingPermissionsPropfindReply(fakeFolder.remoteModifier(), op, req, this); + } else if (errorKind == Timeout) { + return new FakeHangingReply(op, req, this); + } else if (errorKind < 1000) { + return new FakeErrorReply(op, req, this, errorKind); + } + } + return nullptr; + }); + + // So the test that test timeout finishes fast + QScopedValueRollback setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1 : 10000); + + QSignalSpy errorSpy(&fakeFolder.syncEngine(), &SyncEngine::syncError); + QCOMPARE(fakeFolder.syncOnce(), false); + qDebug() << "errorSpy=" << errorSpy; + + // The folder B should not have been sync'ed (and in particular not removed) + QCOMPARE(oldLocalState.children["B"], fakeFolder.currentLocalState().children["B"]); + QCOMPARE(oldRemoteState.children["B"], fakeFolder.currentRemoteState().children["B"]); + if (!syncSucceeds) { + // Check we got the right error + QCOMPARE(errorSpy.count(), 1); + QVERIFY(errorSpy[0][0].toString().contains(expectedErrorString)); + } else { + // The other folder should have been sync'ed as the sync just ignored the faulty dir + QCOMPARE(fakeFolder.currentRemoteState().children["A"], fakeFolder.currentLocalState().children["A"]); + QCOMPARE(fakeFolder.currentRemoteState().children["C"], fakeFolder.currentLocalState().children["C"]); + } + } +}; + +QTEST_GUILESS_MAIN(TestRemoteDiscovery) +#include "testremotediscovery.moc"