1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
/*
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2018 ownCloud, Inc.
* SPDX-License-Identifier: CC0-1.0
*
* 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 <QtTest>
#include "syncenginetestutils.h"
#include <syncengine.h>
#include <localdiscoverytracker.h>
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[] = "<oc:permissions>GRDNVCKW</oc:permissions>";
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 initTestCase()
{
AbstractNetworkJob::enableTimeout = true;
OCC::Logger::instance()->setLogFlush(true);
OCC::Logger::instance()->setLogDebug(true);
QStandardPaths::setTestModeEnabled(true);
}
void testRemoteDiscoveryError_data()
{
qRegisterMetaType<ErrorCategory>();
QTest::addColumn<int>("errorKind");
QTest::addColumn<QString>("expectedErrorString");
QTest::addColumn<bool>("syncSucceeds");
const auto itemErrorMessage = "An unexpected error occurred. Please try syncing again or contact your server administrator if the issue continues.";
QTest::newRow("400") << 400 << QStringLiteral("We couldn’t process your request. Please try syncing again later. If this keeps happening, contact your server administrator for help.") << false;
QTest::newRow("401") << 401 << QStringLiteral("You need to sign in to continue. If you have trouble with your credentials, please reach out to your server administrator.") << false;
QTest::newRow("403") << 403 << QStringLiteral("You don’t have access to this resource. If you think this is a mistake, please contact your server administrator.") << true;
QTest::newRow("404") << 404 << QStringLiteral("We couldn’t find what you were looking for. It might have been moved or deleted. If you need help, contact your server administrator.") << true;
QTest::newRow("407") << 407 << QStringLiteral("It seems you are using a proxy that required authentication. Please check your proxy settings and credentials. If you need help, contact your server administrator.") << true;
QTest::newRow("408") << 408 << QStringLiteral("The request is taking longer than usual. Please try syncing again. If it still doesn’t work, reach out to your server administrator.") << true;
QTest::newRow("409") << 409 << QStringLiteral("Server files changed while you were working. Please try syncing again. Contact your server administrator if the issue persists.") << true;
QTest::newRow("410") << 410 << QStringLiteral("This folder or file isn’t available anymore. If you need assistance, please contact your server administrator.") << true;
QTest::newRow("412") << 412 << QStringLiteral("The request could not be completed because some required conditions were not met. Please try syncing again later. If you need assistance, please contact your server administrator.") << true;
QTest::newRow("413") << 413 << QStringLiteral("The file is too big to upload. You might need to choose a smaller file or contact your server administrator for assistance.") << true;
QTest::newRow("414") << 414 << QStringLiteral("The address used to make the request is too long for the server to handle. Please try shortening the information you’re sending or contact your server administrator for assistance.") << true;
QTest::newRow("415") << 415 << QStringLiteral("This file type isn’t supported. Please contact your server administrator for assistance.") << true;
QTest::newRow("422") << 422 << QStringLiteral("The server couldn’t process your request because some information was incorrect or incomplete. Please try syncing again later, or contact your server administrator for assistance.") << true;
QTest::newRow("423") << 423 << QStringLiteral("The resource you are trying to access is currently locked and cannot be modified. Please try changing it later, or contact your server administrator for assistance.") << true;
QTest::newRow("428") << 428 << QStringLiteral("This request could not be completed because it is missing some required conditions. Please try again later, or contact your server administrator for help.") << true;
QTest::newRow("429") << 429 << QStringLiteral("You made too many requests. Please wait and try again. If you keep seeing this, your server administrator can help.") << true;
QTest::newRow("500") << 500 << QStringLiteral("Something went wrong on the server. Please try syncing again later, or contact your server administrator if the issue persists.") << true;
QTest::newRow("502") << 502 << QStringLiteral("We’re having trouble connecting to the server. Please try again soon. If the issue persists, your server administrator can help you.") << true;
QTest::newRow("503") << 503 << QStringLiteral("The server is busy right now. Please try syncing again in a few minutes or contact your server administrator if it’s urgent.") << true;
QTest::newRow("504") << 504 << QStringLiteral("It’s taking too long to connect to the server. Please try again later. If you need help, contact your server administrator.") << true;
QTest::newRow("505") << 505 << QStringLiteral("The server does not support the version of the connection being used. Contact your server administrator for help.") << true;
QTest::newRow("507") << 507 << QStringLiteral("The server does not have enough space to complete your request. Please check how much quota your user has by contacting your server administrator.") << true;
QTest::newRow("511") << 511 << QStringLiteral("Your network needs extra authentication. Please check your connection. Contact your server administrator for help if the issue persists.") << true;
QTest::newRow("513") << 513 << QStringLiteral("You don’t have permission to access this resource. If you believe this is an error, contact your server administrator to ask for assistance.") << true;
// 200 should be an error since propfind should return 207
QTest::newRow("200") << 200 << itemErrorMessage << false;
QTest::newRow("InvalidXML") << +InvalidXML << itemErrorMessage << false;
QTest::newRow("Timeout") << +Timeout << QStringLiteral("The server took too long to respond. Check your connection and try syncing again. If it still doesn’t work, reach out to your server administrator.") << false;
}
// Check what happens when there is an error.
void testRemoteDiscoveryError()
{
QFETCH(int, errorKind);
QFETCH(QString, expectedErrorString);
QFETCH(bool, syncSucceeds);
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 = "dav/files/admin/B";
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *)
-> QNetworkReply *{
if (req.attribute(QNetworkRequest::CustomVerbAttribute).toString() == "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<int> setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1 : 10000);
ItemCompletedSpy completeSpy(fakeFolder);
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(), expectedErrorString);
} else {
QCOMPARE(completeSpy.findItem("B")->_instruction, CSYNC_INSTRUCTION_IGNORE);
QVERIFY(completeSpy.findItem("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(completeSpy.findItem("A/z1")->_instruction, CSYNC_INSTRUCTION_NEW);
}
//
// Check the same discovery error on the sync root
//
errorFolder = "dav/files/admin/";
errorSpy.clear();
QVERIFY(!fakeFolder.syncOnce());
QCOMPARE(errorSpy.size(), 1);
QCOMPARE(errorSpy[0][0].toString(), 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).toString() == "PROPFIND" && req.url().path().endsWith("nopermissions"))
return new MissingPermissionsPropfindReply(fakeFolder.remoteModifier(), op, req, this);
return nullptr;
});
ItemCompletedSpy completeSpy(fakeFolder);
QVERIFY(!fakeFolder.syncOnce());
QCOMPARE(completeSpy.findItem("good")->_instruction, CSYNC_INSTRUCTION_NEW);
QCOMPARE(completeSpy.findItem("noetag")->_instruction, CSYNC_INSTRUCTION_ERROR);
QCOMPARE(completeSpy.findItem("nofileid")->_instruction, CSYNC_INSTRUCTION_ERROR);
QCOMPARE(completeSpy.findItem("nopermissions")->_instruction, CSYNC_INSTRUCTION_NEW);
QCOMPARE(completeSpy.findItem("nopermissions/A")->_instruction, CSYNC_INSTRUCTION_ERROR);
QVERIFY(completeSpy.findItem("noetag")->_errorString.contains("ETag"));
QVERIFY(completeSpy.findItem("nofileid")->_errorString.contains("file id"));
QVERIFY(completeSpy.findItem("nopermissions/A")->_errorString.contains("permission"));
}
void testQuotaReportedAsDouble()
{
FakeFolder fakeFolder{ FileInfo() };
fakeFolder.remoteModifier().mkdir("doubleValue");
fakeFolder.remoteModifier().find("doubleValue")->folderQuota.setBytesAvailableString("2.345E+12");
fakeFolder.remoteModifier().mkdir("intValue");
fakeFolder.remoteModifier().find("intValue")->folderQuota.setBytesAvailableString("2345000000000");
fakeFolder.remoteModifier().mkdir("unlimited");
fakeFolder.remoteModifier().find("unlimited")->folderQuota.setBytesAvailableString("-3");
fakeFolder.remoteModifier().mkdir("invalidValue");
fakeFolder.remoteModifier().find("invalidValue")->folderQuota.setBytesAvailableString("maybe like, 3 GB");
ItemCompletedSpy completeSpy(fakeFolder);
QVERIFY(fakeFolder.syncOnce());
int64_t expectedValue = 2345000000000;
QCOMPARE(completeSpy.findItem("doubleValue")->_folderQuota.bytesAvailable, expectedValue);
QCOMPARE(completeSpy.findItem("intValue")->_folderQuota.bytesAvailable, expectedValue);
QCOMPARE(completeSpy.findItem("unlimited")->_folderQuota.bytesAvailable, -3);
QCOMPARE(completeSpy.findItem("invalidValue")->_folderQuota.bytesAvailable, -1);
}
};
QTEST_GUILESS_MAIN(TestRemoteDiscovery)
#include "testremotediscovery.moc"
|