mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-25 06:25:56 +03:00
098f4ef164
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
507 lines
19 KiB
C++
507 lines
19 KiB
C++
/*
|
|
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include "gui/filedetails/shareemodel.h"
|
|
|
|
#include <QTest>
|
|
#include <QSignalSpy>
|
|
|
|
#include "accountmanager.h"
|
|
#include "syncenginetestutils.h"
|
|
#include "testhelper.h"
|
|
|
|
using namespace OCC;
|
|
|
|
static QByteArray fake400Response = R"(
|
|
{"ocs":{"meta":{"status":"failure","statuscode":400,"message":"Parameter is incorrect.\n"},"data":[]}}
|
|
)";
|
|
|
|
constexpr auto searchResultsReplyDelay = 100;
|
|
|
|
class TestShareeModel : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
int _numLookupSearchParamSet = 0;
|
|
|
|
public:
|
|
~TestShareeModel() override
|
|
{
|
|
AccountManager::instance()->deleteAccount(_accountState.data());
|
|
};
|
|
|
|
struct FakeShareeDefinition
|
|
{
|
|
QString label;
|
|
|
|
QString shareWith;
|
|
Sharee::Type type;
|
|
QString shareWithAdditionalInfo;
|
|
};
|
|
|
|
void appendShareeToReply(const FakeShareeDefinition &definition)
|
|
{
|
|
QJsonObject newShareeJson;
|
|
newShareeJson.insert("label", definition.label);
|
|
|
|
QJsonObject newShareeValueJson;
|
|
newShareeValueJson.insert("shareWith", definition.shareWith);
|
|
newShareeValueJson.insert("shareType", definition.type);
|
|
newShareeValueJson.insert("shareWithAdditionalInfo", definition.shareWithAdditionalInfo);
|
|
|
|
newShareeJson.insert("value", newShareeValueJson);
|
|
|
|
QString category;
|
|
switch(definition.type) {
|
|
case Sharee::Invalid:
|
|
category = QStringLiteral("invalid");
|
|
break;
|
|
case Sharee::Circle:
|
|
category = QStringLiteral("circles");
|
|
break;
|
|
case Sharee::Email:
|
|
category = QStringLiteral("emails");
|
|
break;
|
|
case Sharee::Federated:
|
|
category = QStringLiteral("remotes");
|
|
break;
|
|
case Sharee::Group:
|
|
category = QStringLiteral("groups");
|
|
break;
|
|
case Sharee::Room:
|
|
category = QStringLiteral("rooms");
|
|
break;
|
|
case Sharee::User:
|
|
category = QStringLiteral("users");
|
|
break;
|
|
case Sharee::LookupServerSearch:
|
|
category = QStringLiteral("placeholder_lookupserversearch");
|
|
break;
|
|
case Sharee::LookupServerSearchResults:
|
|
category = QStringLiteral("placeholder_lookupserversearchresults");
|
|
break;
|
|
}
|
|
|
|
auto shareesInCategory = _shareesMap.value(category).toJsonArray();
|
|
shareesInCategory.append(newShareeJson);
|
|
_shareesMap.insert(category, shareesInCategory);
|
|
}
|
|
|
|
void standardReplyPopulate()
|
|
{
|
|
appendShareeToReply(_michaelUserDefinition);
|
|
appendShareeToReply(_liamUserDefinition);
|
|
appendShareeToReply(_iqbalUserDefinition);
|
|
appendShareeToReply(_universityGroupDefinition);
|
|
appendShareeToReply(_testEmailDefinition);
|
|
}
|
|
|
|
QVariantMap filteredSharees(const QString &searchString)
|
|
{
|
|
if (searchString.isEmpty()) {
|
|
return _shareesMap;
|
|
}
|
|
|
|
QVariantMap returnSharees;
|
|
QJsonArray exactMatches;
|
|
|
|
for (auto it = _shareesMap.constKeyValueBegin(); it != _shareesMap.constKeyValueEnd(); ++it) {
|
|
const auto shareesCategory = it->first;
|
|
const auto shareesArray = it->second.toJsonArray();
|
|
QJsonArray filteredShareesArray;
|
|
|
|
std::copy_if(shareesArray.cbegin(), shareesArray.cend(), std::back_inserter(filteredShareesArray), [&searchString](const QJsonValue &shareeValue) {
|
|
const auto shareeObject = shareeValue.toObject().value("value").toObject();
|
|
const auto shareeShareWith = shareeObject.value("shareWith").toString();
|
|
return shareeShareWith.contains(searchString, Qt::CaseInsensitive);
|
|
});
|
|
|
|
std::copy_if(filteredShareesArray.cbegin(), filteredShareesArray.cend(), std::back_inserter(exactMatches), [&searchString](const QJsonValue &shareeValue) {
|
|
const auto shareeObject = shareeValue.toObject().value("value").toObject();
|
|
const auto shareeShareWith = shareeObject.value("shareWith").toString();
|
|
return shareeShareWith == searchString;
|
|
});
|
|
|
|
returnSharees.insert(shareesCategory, filteredShareesArray);
|
|
}
|
|
|
|
returnSharees.insert(QStringLiteral("exact"), exactMatches);
|
|
|
|
return returnSharees;
|
|
}
|
|
|
|
QByteArray testShareesReply(const QString &searchString)
|
|
{
|
|
QJsonObject root;
|
|
QJsonObject ocs;
|
|
QJsonObject meta;
|
|
|
|
meta.insert("statuscode", 200);
|
|
|
|
const auto resultSharees = filteredSharees(searchString);
|
|
const auto shareesJsonObject = QJsonObject::fromVariantMap(resultSharees);
|
|
|
|
ocs.insert(QStringLiteral("data"), shareesJsonObject);
|
|
ocs.insert(QStringLiteral("meta"), meta);
|
|
root.insert(QStringLiteral("ocs"), ocs);
|
|
|
|
return QJsonDocument(root).toJson();
|
|
}
|
|
|
|
int shareesCount(const QString &searchString)
|
|
{
|
|
const auto sharees = filteredSharees(searchString);
|
|
|
|
auto count = 0;
|
|
const auto shareesCategories = sharees.values();
|
|
for (const auto &shareesArrayValue : shareesCategories) {
|
|
const auto shareesArray = shareesArrayValue.toJsonArray();
|
|
count += shareesArray.count();
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void resetTestData()
|
|
{
|
|
_alwaysReturnErrors = false;
|
|
_shareesMap.clear();
|
|
}
|
|
|
|
|
|
private:
|
|
AccountPtr _account;
|
|
AccountStatePtr _accountState;
|
|
QScopedPointer<FakeQNAM> _fakeQnam;
|
|
|
|
QVariantMap _shareesMap;
|
|
|
|
// Some fake sharees of different categories
|
|
// ALL OF THEM CONTAIN AN 'I' !! Important for testing
|
|
FakeShareeDefinition _michaelUserDefinition {
|
|
QStringLiteral("Michael"),
|
|
QStringLiteral("michael"),
|
|
Sharee::User,
|
|
{},
|
|
};
|
|
FakeShareeDefinition _liamUserDefinition {
|
|
QStringLiteral("Liam"),
|
|
QStringLiteral("liam"),
|
|
Sharee::User,
|
|
{},
|
|
};
|
|
FakeShareeDefinition _iqbalUserDefinition {
|
|
QStringLiteral("Iqbal"),
|
|
QStringLiteral("iqbal"),
|
|
Sharee::User,
|
|
{},
|
|
};
|
|
|
|
FakeShareeDefinition _universityGroupDefinition {
|
|
QStringLiteral("University"),
|
|
QStringLiteral("university"),
|
|
Sharee::Group,
|
|
{},
|
|
};
|
|
|
|
FakeShareeDefinition _testEmailDefinition {
|
|
QStringLiteral("test.email@nextcloud.com"),
|
|
QStringLiteral("test.email@nextcloud.com"),
|
|
Sharee::Email,
|
|
{},
|
|
};
|
|
|
|
bool _alwaysReturnErrors = false;
|
|
|
|
private slots:
|
|
void initTestCase()
|
|
{
|
|
OCC::Logger::instance()->setLogFlush(true);
|
|
OCC::Logger::instance()->setLogDebug(true);
|
|
|
|
QStandardPaths::setTestModeEnabled(true);
|
|
|
|
_fakeQnam.reset(new FakeQNAM({}));
|
|
_fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
|
|
Q_UNUSED(device);
|
|
|
|
QNetworkReply *reply = nullptr;
|
|
|
|
if (_alwaysReturnErrors) {
|
|
reply = new FakeErrorReply(op, req, this, 400, fake400Response);
|
|
return reply;
|
|
}
|
|
|
|
const auto reqUrl = req.url();
|
|
const auto reqRawPath = reqUrl.path();
|
|
const auto reqPath = reqRawPath.startsWith("/owncloud/") ? reqRawPath.mid(10) : reqRawPath;
|
|
qDebug() << req.url() << reqPath << op;
|
|
|
|
if(req.url().toString().startsWith(_accountState->account()->url().toString()) &&
|
|
reqPath == QStringLiteral("ocs/v2.php/apps/files_sharing/api/v1/sharees") &&
|
|
req.attribute(QNetworkRequest::CustomVerbAttribute).toString() == "GET") {
|
|
|
|
const auto urlQuery = QUrlQuery(req.url());
|
|
const auto searchParam = urlQuery.queryItemValue(QStringLiteral("search"));
|
|
const auto itemTypeParam = urlQuery.queryItemValue(QStringLiteral("itemType"));
|
|
const auto pageParam = urlQuery.queryItemValue(QStringLiteral("page"));
|
|
const auto perPageParam = urlQuery.queryItemValue(QStringLiteral("perPage"));
|
|
const auto lookupParam = urlQuery.queryItemValue(QStringLiteral("lookup"));
|
|
const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format"));
|
|
|
|
if (!lookupParam.isEmpty() && lookupParam == QStringLiteral("true")) {
|
|
++_numLookupSearchParamSet;
|
|
}
|
|
|
|
if (formatParam != QStringLiteral("json")) {
|
|
reply = new FakeErrorReply(op, req, this, 400, fake400Response);
|
|
} else {
|
|
reply = new FakePayloadReply(op, req, testShareesReply(searchParam), searchResultsReplyDelay, _fakeQnam.data());
|
|
}
|
|
}
|
|
|
|
return reply;
|
|
});
|
|
|
|
_account = Account::create();
|
|
_account->setCredentials(new FakeCredentials{_fakeQnam.data()});
|
|
_account->setUrl(QUrl(("owncloud://somehost/owncloud")));
|
|
_accountState = new AccountState(_account);
|
|
AccountManager::instance()->addAccount(_account);
|
|
|
|
// Let's verify our test is working -- all sharees have an I in their "shareWith"
|
|
standardReplyPopulate();
|
|
const auto searchString = QStringLiteral("i");
|
|
QCOMPARE(shareesCount(searchString), 5);
|
|
|
|
const auto emailSearchString = QStringLiteral("email");
|
|
QCOMPARE(shareesCount(emailSearchString), 1);
|
|
}
|
|
|
|
void testSetAccountAndPath()
|
|
{
|
|
resetTestData();
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
QSignalSpy accountStateChanged(&model, &ShareeModel::accountStateChanged);
|
|
QSignalSpy shareItemIsFolderChanged(&model, &ShareeModel::shareItemIsFolderChanged);
|
|
QSignalSpy searchStringChanged(&model, &ShareeModel::searchStringChanged);
|
|
QSignalSpy lookupModeChanged(&model, &ShareeModel::lookupModeChanged);
|
|
QSignalSpy shareeBlocklistChanged(&model, &ShareeModel::shareeBlocklistChanged);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
QCOMPARE(accountStateChanged.count(), 1);
|
|
QCOMPARE(model.accountState(), _accountState.data());
|
|
|
|
const auto shareItemIsFolder = !model.shareItemIsFolder();
|
|
model.setShareItemIsFolder(shareItemIsFolder);
|
|
QCOMPARE(shareItemIsFolderChanged.count(), 1);
|
|
QCOMPARE(model.shareItemIsFolder(), shareItemIsFolder);
|
|
|
|
const auto searchString = QStringLiteral("search string");
|
|
model.setSearchString(searchString);
|
|
QCOMPARE(searchStringChanged.count(), 1);
|
|
QCOMPARE(model.searchString(), searchString);
|
|
|
|
const auto lookupMode = ShareeModel::LookupMode::GlobalSearch;
|
|
model.setLookupMode(lookupMode);
|
|
QCOMPARE(lookupModeChanged.count(), 1);
|
|
QCOMPARE(model.lookupMode(), lookupMode);
|
|
|
|
const ShareePtr sharee(new Sharee(_testEmailDefinition.shareWith, _testEmailDefinition.label, _testEmailDefinition.type));
|
|
const QVariantList shareeBlocklist {QVariant::fromValue(sharee)};
|
|
model.setShareeBlocklist(shareeBlocklist);
|
|
QCOMPARE(shareeBlocklistChanged.count(), 1);
|
|
QCOMPARE(model.shareeBlocklist(), shareeBlocklist);
|
|
}
|
|
|
|
void testShareesFetch()
|
|
{
|
|
resetTestData();
|
|
standardReplyPopulate();
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
|
|
QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
|
|
const auto searchString = QStringLiteral("i");
|
|
model.setSearchString(searchString);
|
|
QVERIFY(shareesReady.wait(3000));
|
|
QCOMPARE(model.rowCount(), shareesCount(searchString) + 1);
|
|
QVERIFY(model.rowCount() > 0);
|
|
auto lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearch);
|
|
|
|
const auto emailSearchString = QStringLiteral("email");
|
|
model.setSearchString(emailSearchString);
|
|
QVERIFY(shareesReady.wait(3000));
|
|
QCOMPARE(model.rowCount(), shareesCount(emailSearchString) + 1);
|
|
QVERIFY(model.rowCount() > 0);
|
|
lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearch);
|
|
}
|
|
|
|
void testShareesFetchGlobally()
|
|
{
|
|
resetTestData();
|
|
standardReplyPopulate();
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
|
|
QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
|
|
const auto emailSearchString = QStringLiteral("email");
|
|
model.setSearchString(emailSearchString);
|
|
QVERIFY(shareesReady.wait(3000));
|
|
QCOMPARE(model.rowCount(), shareesCount(emailSearchString) + 1);
|
|
QVERIFY(model.rowCount() > 0);
|
|
auto lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearch);
|
|
QCOMPARE(_numLookupSearchParamSet, 0);
|
|
|
|
QSignalSpy lookupModeChanged(&model, &ShareeModel::lookupModeChanged);
|
|
model.searchGlobally();
|
|
QVERIFY(shareesReady.wait(3000));
|
|
QCOMPARE(lookupModeChanged.count(), 2);
|
|
QVERIFY(model.lookupMode() == ShareeModel::LookupMode::LocalSearch);
|
|
QCOMPARE(model.rowCount(), shareesCount(emailSearchString) + 1);
|
|
QVERIFY(model.rowCount() > 0);
|
|
lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearchResults);
|
|
QCOMPARE(_numLookupSearchParamSet, 1);
|
|
}
|
|
|
|
void testFetchSignalling()
|
|
{
|
|
resetTestData();
|
|
standardReplyPopulate();
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
QSignalSpy fetchOngoingChanged(&model, &ShareeModel::fetchOngoingChanged);
|
|
const auto searchString = QStringLiteral("i");
|
|
model.setSearchString(searchString);
|
|
|
|
QVERIFY(fetchOngoingChanged.wait(1000));
|
|
QCOMPARE(model.fetchOngoing(), true);
|
|
QVERIFY(fetchOngoingChanged.wait(3000));
|
|
QCOMPARE(model.fetchOngoing(), false);
|
|
}
|
|
|
|
void testData()
|
|
{
|
|
resetTestData();
|
|
appendShareeToReply(_testEmailDefinition);
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
const auto searchString = QStringLiteral("i");
|
|
model.setSearchString(searchString);
|
|
|
|
QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
|
|
QVERIFY(shareesReady.wait(3000));
|
|
QCOMPARE(model.rowCount(), shareesCount(searchString) + 1);
|
|
auto lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearch);
|
|
|
|
const auto shareeIndex = model.index(0, 0, {});
|
|
|
|
const ShareePtr expectedSharee(new Sharee(_testEmailDefinition.shareWith, _testEmailDefinition.label, _testEmailDefinition.type));
|
|
const auto sharee = shareeIndex.data(ShareeModel::ShareeRole).value<ShareePtr>();
|
|
QCOMPARE(sharee->format(), expectedSharee->format());
|
|
QCOMPARE(sharee->shareWith(), expectedSharee->shareWith());
|
|
QCOMPARE(sharee->displayName(), expectedSharee->displayName());
|
|
QCOMPARE(sharee->type(), expectedSharee->type());
|
|
|
|
const auto expectedShareeDisplay = QString(_testEmailDefinition.label + QStringLiteral(" (email)"));
|
|
const auto shareeDisplay = shareeIndex.data(Qt::DisplayRole).toString();
|
|
QCOMPARE(shareeDisplay, expectedShareeDisplay);
|
|
|
|
const auto expectedAutoCompleterStringMatch = QString(_testEmailDefinition.label +
|
|
QStringLiteral(" (") +
|
|
_testEmailDefinition.shareWith +
|
|
QStringLiteral(")"));
|
|
const auto autoCompleterStringMatch = shareeIndex.data(ShareeModel::AutoCompleterStringMatchRole).toString();
|
|
QCOMPARE(autoCompleterStringMatch, expectedAutoCompleterStringMatch);
|
|
}
|
|
|
|
void testBlocklist()
|
|
{
|
|
resetTestData();
|
|
standardReplyPopulate();
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
|
|
const ShareePtr sharee(new Sharee(_testEmailDefinition.shareWith, _testEmailDefinition.label, _testEmailDefinition.type));
|
|
const QVariantList shareeBlocklist {QVariant::fromValue(sharee)};
|
|
model.setShareeBlocklist(shareeBlocklist);
|
|
|
|
QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
|
|
const auto searchString = QStringLiteral("i");
|
|
model.setSearchString(searchString);
|
|
QVERIFY(shareesReady.wait(3000));
|
|
QCOMPARE(model.rowCount(), shareesCount(searchString) - 1 + 1);
|
|
auto lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearch);
|
|
|
|
const ShareePtr shareeTwo(new Sharee(_michaelUserDefinition.shareWith, _michaelUserDefinition.label, _michaelUserDefinition.type));
|
|
const QVariantList largerShareeBlocklist {QVariant::fromValue(sharee), QVariant::fromValue(shareeTwo)};
|
|
model.setShareeBlocklist(largerShareeBlocklist);
|
|
QCOMPARE(model.rowCount(), shareesCount(searchString) - 2 + 1);
|
|
lastElementType = model.data(model.index(model.rowCount() - 1), ShareeModel::Roles::TypeRole).toInt();
|
|
QVERIFY(lastElementType == Sharee::Type::LookupServerSearch);
|
|
}
|
|
|
|
void testServerError()
|
|
{
|
|
resetTestData();
|
|
_alwaysReturnErrors = true;
|
|
|
|
ShareeModel model;
|
|
QAbstractItemModelTester modelTester(&model);
|
|
QCOMPARE(model.rowCount(), 0);
|
|
|
|
model.setAccountState(_accountState.data());
|
|
|
|
QSignalSpy displayErrorMessage(&model, &ShareeModel::displayErrorMessage);
|
|
QSignalSpy fetchOngoingChanged(&model, &ShareeModel::fetchOngoingChanged);
|
|
model.setSearchString(QStringLiteral("i"));
|
|
QVERIFY(displayErrorMessage.wait(3000));
|
|
|
|
QCOMPARE(fetchOngoingChanged.count(), 2);
|
|
QCOMPARE(model.fetchOngoing(), false);
|
|
}
|
|
};
|
|
|
|
QTEST_MAIN(TestShareeModel)
|
|
#include "testshareemodel.moc"
|