File: testremotediscovery.cpp

package info (click to toggle)
nextcloud-desktop 4.0.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 40,404 kB
  • sloc: cpp: 118,401; objc: 752; python: 606; sh: 395; ansic: 391; ruby: 174; makefile: 44; javascript: 32; xml: 6
file content (227 lines) | stat: -rw-r--r-- 14,064 bytes parent folder | download
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"