testremotediscovery.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include <QtTest>
  8. #include "syncenginetestutils.h"
  9. #include <syncengine.h>
  10. #include <localdiscoverytracker.h>
  11. using namespace OCC;
  12. struct FakeBrokenXmlPropfindReply : FakePropfindReply {
  13. FakeBrokenXmlPropfindReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op,
  14. const QNetworkRequest &request, QObject *parent)
  15. : FakePropfindReply(remoteRootFileInfo, op, request, parent) {
  16. QVERIFY(payload.size() > 50);
  17. // turncate the XML
  18. payload.chop(20);
  19. }
  20. };
  21. struct MissingPermissionsPropfindReply : FakePropfindReply {
  22. MissingPermissionsPropfindReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op,
  23. const QNetworkRequest &request, QObject *parent)
  24. : FakePropfindReply(remoteRootFileInfo, op, request, parent) {
  25. // If the propfind contains a single file without permissions, this is a server error
  26. const char toRemove[] = "<oc:permissions>RDNVCKW</oc:permissions>";
  27. auto pos = payload.indexOf(toRemove, payload.size()/2);
  28. QVERIFY(pos > 0);
  29. payload.remove(pos, sizeof(toRemove) - 1);
  30. }
  31. };
  32. enum ErrorKind : int {
  33. // Lower code are corresponding to HTML error code
  34. InvalidXML = 1000,
  35. Timeout,
  36. };
  37. Q_DECLARE_METATYPE(ErrorCategory)
  38. class TestRemoteDiscovery : public QObject
  39. {
  40. Q_OBJECT
  41. private slots:
  42. void testRemoteDiscoveryError_data()
  43. {
  44. qRegisterMetaType<ErrorCategory>();
  45. QTest::addColumn<int>("errorKind");
  46. QTest::addColumn<QString>("expectedErrorString");
  47. QTest::addColumn<bool>("syncSucceeds");
  48. QString itemErrorMessage = "Internal Server Fake Error";
  49. QTest::newRow("400") << 400 << itemErrorMessage << false;
  50. QTest::newRow("401") << 401 << itemErrorMessage << false;
  51. QTest::newRow("403") << 403 << itemErrorMessage << true;
  52. QTest::newRow("404") << 404 << itemErrorMessage << true;
  53. QTest::newRow("500") << 500 << itemErrorMessage << true;
  54. QTest::newRow("503") << 503 << itemErrorMessage << true;
  55. // 200 should be an error since propfind should return 207
  56. QTest::newRow("200") << 200 << itemErrorMessage << false;
  57. QTest::newRow("InvalidXML") << +InvalidXML << "Unknown error" << false;
  58. QTest::newRow("Timeout") << +Timeout << "Operation canceled" << false;
  59. }
  60. // Check what happens when there is an error.
  61. void testRemoteDiscoveryError()
  62. {
  63. QFETCH(int, errorKind);
  64. QFETCH(QString, expectedErrorString);
  65. QFETCH(bool, syncSucceeds);
  66. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  67. // Do Some change as well
  68. fakeFolder.localModifier().insert("A/z1");
  69. fakeFolder.localModifier().insert("B/z1");
  70. fakeFolder.localModifier().insert("C/z1");
  71. fakeFolder.remoteModifier().insert("A/z2");
  72. fakeFolder.remoteModifier().insert("B/z2");
  73. fakeFolder.remoteModifier().insert("C/z2");
  74. auto oldLocalState = fakeFolder.currentLocalState();
  75. auto oldRemoteState = fakeFolder.currentRemoteState();
  76. QString errorFolder = "dav/files/admin/B";
  77. QString fatalErrorPrefix = "Server replied with an error while reading directory \"B\" : ";
  78. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *)
  79. -> QNetworkReply *{
  80. if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith(errorFolder)) {
  81. if (errorKind == InvalidXML) {
  82. return new FakeBrokenXmlPropfindReply(fakeFolder.remoteModifier(), op, req, this);
  83. } else if (errorKind == Timeout) {
  84. return new FakeHangingReply(op, req, this);
  85. } else if (errorKind < 1000) {
  86. return new FakeErrorReply(op, req, this, errorKind);
  87. }
  88. }
  89. return nullptr;
  90. });
  91. // So the test that test timeout finishes fast
  92. QScopedValueRollback<int> setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1 : 10000);
  93. ItemCompletedSpy completeSpy(fakeFolder);
  94. QSignalSpy errorSpy(&fakeFolder.syncEngine(), &SyncEngine::syncError);
  95. QCOMPARE(fakeFolder.syncOnce(), syncSucceeds);
  96. // The folder B should not have been sync'ed (and in particular not removed)
  97. QCOMPARE(oldLocalState.children["B"], fakeFolder.currentLocalState().children["B"]);
  98. QCOMPARE(oldRemoteState.children["B"], fakeFolder.currentRemoteState().children["B"]);
  99. if (!syncSucceeds) {
  100. QCOMPARE(errorSpy.size(), 1);
  101. QCOMPARE(errorSpy[0][0].toString(), QString(fatalErrorPrefix + expectedErrorString));
  102. } else {
  103. QCOMPARE(completeSpy.findItem("B")->_instruction, CSYNC_INSTRUCTION_IGNORE);
  104. QVERIFY(completeSpy.findItem("B")->_errorString.contains(expectedErrorString));
  105. // The other folder should have been sync'ed as the sync just ignored the faulty dir
  106. QCOMPARE(fakeFolder.currentRemoteState().children["A"], fakeFolder.currentLocalState().children["A"]);
  107. QCOMPARE(fakeFolder.currentRemoteState().children["C"], fakeFolder.currentLocalState().children["C"]);
  108. QCOMPARE(completeSpy.findItem("A/z1")->_instruction, CSYNC_INSTRUCTION_NEW);
  109. }
  110. //
  111. // Check the same discovery error on the sync root
  112. //
  113. errorFolder = "dav/files/admin/";
  114. fatalErrorPrefix = "Server replied with an error while reading directory \"\" : ";
  115. errorSpy.clear();
  116. QVERIFY(!fakeFolder.syncOnce());
  117. QCOMPARE(errorSpy.size(), 1);
  118. QCOMPARE(errorSpy[0][0].toString(), QString(fatalErrorPrefix + expectedErrorString));
  119. }
  120. void testMissingData()
  121. {
  122. FakeFolder fakeFolder{ FileInfo() };
  123. fakeFolder.remoteModifier().insert("good");
  124. fakeFolder.remoteModifier().insert("noetag");
  125. fakeFolder.remoteModifier().find("noetag")->etag.clear();
  126. fakeFolder.remoteModifier().insert("nofileid");
  127. fakeFolder.remoteModifier().find("nofileid")->fileId.clear();
  128. fakeFolder.remoteModifier().mkdir("nopermissions");
  129. fakeFolder.remoteModifier().insert("nopermissions/A");
  130. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *)
  131. -> QNetworkReply *{
  132. if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith("nopermissions"))
  133. return new MissingPermissionsPropfindReply(fakeFolder.remoteModifier(), op, req, this);
  134. return nullptr;
  135. });
  136. ItemCompletedSpy completeSpy(fakeFolder);
  137. QVERIFY(!fakeFolder.syncOnce());
  138. QCOMPARE(completeSpy.findItem("good")->_instruction, CSYNC_INSTRUCTION_NEW);
  139. QCOMPARE(completeSpy.findItem("noetag")->_instruction, CSYNC_INSTRUCTION_ERROR);
  140. QCOMPARE(completeSpy.findItem("nofileid")->_instruction, CSYNC_INSTRUCTION_ERROR);
  141. QCOMPARE(completeSpy.findItem("nopermissions")->_instruction, CSYNC_INSTRUCTION_NEW);
  142. QCOMPARE(completeSpy.findItem("nopermissions/A")->_instruction, CSYNC_INSTRUCTION_ERROR);
  143. QVERIFY(completeSpy.findItem("noetag")->_errorString.contains("ETag"));
  144. QVERIFY(completeSpy.findItem("nofileid")->_errorString.contains("file id"));
  145. QVERIFY(completeSpy.findItem("nopermissions/A")->_errorString.contains("permission"));
  146. }
  147. };
  148. QTEST_GUILESS_MAIN(TestRemoteDiscovery)
  149. #include "testremotediscovery.moc"