/* * 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; SyncFileItemPtr findItem(const QSignalSpy &spy, const QString &path) { for (const QList &args : spy) { auto item = args[0].value(); if (item->destination() == path) return item; } return SyncFileItemPtr(new SyncFileItem); } 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, Timeout, }; Q_DECLARE_METATYPE(ErrorCategory) class TestRemoteDiscovery : public QObject { Q_OBJECT private slots: void testRemoteDiscoveryError_data() { qRegisterMetaType(); QTest::addColumn("errorKind"); QTest::addColumn("expectedErrorString"); QString itemErrorMessage = "Internal Server Fake Error"; QTest::newRow("403") << 403 << itemErrorMessage; QTest::newRow("404") << 404 << itemErrorMessage; QTest::newRow("500") << 500 << itemErrorMessage; QTest::newRow("503") << 503 << itemErrorMessage; // 200 should be an error since propfind should return 207 QTest::newRow("200") << 200 << itemErrorMessage; QTest::newRow("InvalidXML") << +InvalidXML << "Unknown error"; QTest::newRow("Timeout") << +Timeout << "Operation canceled"; } // Check what happens when there is an error. void testRemoteDiscoveryError() { QFETCH(int, errorKind); QFETCH(QString, expectedErrorString); // 403/503 just ignore the temporarily unavailable directory bool syncSucceeds = errorKind == 503 || errorKind == 403; 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(); QString errorFolder = "webdav/B"; QString fatalErrorPrefix = "Server replied with an error while reading directory 'B' : "; fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply *{ if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith(errorFolder)) { if (errorKind == InvalidXML) { return new FakeBrokenXmlPropfindReply(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 completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); QSignalSpy errorSpy(&fakeFolder.syncEngine(), &SyncEngine::syncError); QCOMPARE(fakeFolder.syncOnce(), syncSucceeds); // 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) { QCOMPARE(errorSpy.size(), 1); QCOMPARE(errorSpy[0][0].toString(), QString(fatalErrorPrefix + expectedErrorString)); } else { QCOMPARE(findItem(completeSpy, "B")->_instruction, CSYNC_INSTRUCTION_IGNORE); QVERIFY(findItem(completeSpy, "B")->_errorString.contains(expectedErrorString)); // 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"]); QCOMPARE(findItem(completeSpy, "A/z1")->_instruction, CSYNC_INSTRUCTION_NEW); } // // Check the same discovery error on the sync root // errorFolder = "webdav/"; fatalErrorPrefix = "Server replied with an error while reading directory '' : "; errorSpy.clear(); QVERIFY(!fakeFolder.syncOnce()); QCOMPARE(errorSpy.size(), 1); QCOMPARE(errorSpy[0][0].toString(), QString(fatalErrorPrefix + expectedErrorString)); } void testMissingData() { FakeFolder fakeFolder{ FileInfo() }; fakeFolder.remoteModifier().insert("good"); fakeFolder.remoteModifier().insert("noetag"); fakeFolder.remoteModifier().find("noetag")->etag.clear(); fakeFolder.remoteModifier().insert("nofileid"); fakeFolder.remoteModifier().find("nofileid")->fileId.clear(); fakeFolder.remoteModifier().mkdir("nopermissions"); fakeFolder.remoteModifier().insert("nopermissions/A"); fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply *{ if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith("nopermissions")) return new MissingPermissionsPropfindReply(fakeFolder.remoteModifier(), op, req, this); return nullptr; }); QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); QVERIFY(!fakeFolder.syncOnce()); QCOMPARE(findItem(completeSpy, "good")->_instruction, CSYNC_INSTRUCTION_NEW); QCOMPARE(findItem(completeSpy, "noetag")->_instruction, CSYNC_INSTRUCTION_ERROR); QCOMPARE(findItem(completeSpy, "nofileid")->_instruction, CSYNC_INSTRUCTION_ERROR); QCOMPARE(findItem(completeSpy, "nopermissions")->_instruction, CSYNC_INSTRUCTION_NEW); QCOMPARE(findItem(completeSpy, "nopermissions/A")->_instruction, CSYNC_INSTRUCTION_ERROR); QVERIFY(findItem(completeSpy, "noetag")->_errorString.contains("etag")); QVERIFY(findItem(completeSpy, "nofileid")->_errorString.contains("file id")); QVERIFY(findItem(completeSpy, "nopermissions/A")->_errorString.contains("permissions")); } }; QTEST_GUILESS_MAIN(TestRemoteDiscovery) #include "testremotediscovery.moc"