testshareemodel.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*
  2. * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. * for more details.
  13. */
  14. #include "gui/filedetails/shareemodel.h"
  15. #include <QTest>
  16. #include <QSignalSpy>
  17. #include "accountmanager.h"
  18. #include "syncenginetestutils.h"
  19. #include "testhelper.h"
  20. using namespace OCC;
  21. static QByteArray fake400Response = R"(
  22. {"ocs":{"meta":{"status":"failure","statuscode":400,"message":"Parameter is incorrect.\n"},"data":[]}}
  23. )";
  24. constexpr auto searchResultsReplyDelay = 100;
  25. class TestShareeModel : public QObject
  26. {
  27. Q_OBJECT
  28. public:
  29. ~TestShareeModel() override
  30. {
  31. AccountManager::instance()->deleteAccount(_accountState.data());
  32. };
  33. struct FakeShareeDefinition
  34. {
  35. QString label;
  36. QString shareWith;
  37. Sharee::Type type;
  38. QString shareWithAdditionalInfo;
  39. };
  40. void appendShareeToReply(const FakeShareeDefinition &definition)
  41. {
  42. QJsonObject newShareeJson;
  43. newShareeJson.insert("label", definition.label);
  44. QJsonObject newShareeValueJson;
  45. newShareeValueJson.insert("shareWith", definition.shareWith);
  46. newShareeValueJson.insert("shareType", definition.type);
  47. newShareeValueJson.insert("shareWithAdditionalInfo", definition.shareWithAdditionalInfo);
  48. newShareeJson.insert("value", newShareeValueJson);
  49. QString category;
  50. switch(definition.type) {
  51. case Sharee::Circle:
  52. category = QStringLiteral("circles");
  53. break;
  54. case Sharee::Email:
  55. category = QStringLiteral("emails");
  56. break;
  57. case Sharee::Federated:
  58. category = QStringLiteral("remotes");
  59. break;
  60. case Sharee::Group:
  61. category = QStringLiteral("groups");
  62. break;
  63. case Sharee::Room:
  64. category = QStringLiteral("rooms");
  65. break;
  66. case Sharee::User:
  67. category = QStringLiteral("users");
  68. break;
  69. }
  70. auto shareesInCategory = _shareesMap.value(category).toJsonArray();
  71. shareesInCategory.append(newShareeJson);
  72. _shareesMap.insert(category, shareesInCategory);
  73. }
  74. void standardReplyPopulate()
  75. {
  76. appendShareeToReply(_michaelUserDefinition);
  77. appendShareeToReply(_liamUserDefinition);
  78. appendShareeToReply(_iqbalUserDefinition);
  79. appendShareeToReply(_universityGroupDefinition);
  80. appendShareeToReply(_testEmailDefinition);
  81. }
  82. QVariantMap filteredSharees(const QString &searchString)
  83. {
  84. if (searchString.isEmpty()) {
  85. return _shareesMap;
  86. }
  87. QVariantMap returnSharees;
  88. QJsonArray exactMatches;
  89. for (auto it = _shareesMap.constKeyValueBegin(); it != _shareesMap.constKeyValueEnd(); ++it) {
  90. const auto shareesCategory = it->first;
  91. const auto shareesArray = it->second.toJsonArray();
  92. QJsonArray filteredShareesArray;
  93. std::copy_if(shareesArray.cbegin(), shareesArray.cend(), std::back_inserter(filteredShareesArray), [&searchString](const QJsonValue &shareeValue) {
  94. const auto shareeObject = shareeValue.toObject().value("value").toObject();
  95. const auto shareeShareWith = shareeObject.value("shareWith").toString();
  96. return shareeShareWith.contains(searchString, Qt::CaseInsensitive);
  97. });
  98. std::copy_if(filteredShareesArray.cbegin(), filteredShareesArray.cend(), std::back_inserter(exactMatches), [&searchString](const QJsonValue &shareeValue) {
  99. const auto shareeObject = shareeValue.toObject().value("value").toObject();
  100. const auto shareeShareWith = shareeObject.value("shareWith").toString();
  101. return shareeShareWith == searchString;
  102. });
  103. returnSharees.insert(shareesCategory, filteredShareesArray);
  104. }
  105. returnSharees.insert(QStringLiteral("exact"), exactMatches);
  106. return returnSharees;
  107. }
  108. QByteArray testShareesReply(const QString &searchString)
  109. {
  110. QJsonObject root;
  111. QJsonObject ocs;
  112. QJsonObject meta;
  113. meta.insert("statuscode", 200);
  114. const auto resultSharees = filteredSharees(searchString);
  115. const auto shareesJsonObject = QJsonObject::fromVariantMap(resultSharees);
  116. ocs.insert(QStringLiteral("data"), shareesJsonObject);
  117. ocs.insert(QStringLiteral("meta"), meta);
  118. root.insert(QStringLiteral("ocs"), ocs);
  119. return QJsonDocument(root).toJson();
  120. }
  121. int shareesCount(const QString &searchString)
  122. {
  123. const auto sharees = filteredSharees(searchString);
  124. auto count = 0;
  125. const auto shareesCategories = sharees.values();
  126. for (const auto &shareesArrayValue : shareesCategories) {
  127. const auto shareesArray = shareesArrayValue.toJsonArray();
  128. count += shareesArray.count();
  129. }
  130. return count;
  131. }
  132. void resetTestData()
  133. {
  134. _alwaysReturnErrors = false;
  135. _shareesMap.clear();
  136. }
  137. private:
  138. AccountPtr _account;
  139. AccountStatePtr _accountState;
  140. QScopedPointer<FakeQNAM> _fakeQnam;
  141. QVariantMap _shareesMap;
  142. // Some fake sharees of different categories
  143. // ALL OF THEM CONTAIN AN 'I' !! Important for testing
  144. FakeShareeDefinition _michaelUserDefinition {
  145. QStringLiteral("Michael"),
  146. QStringLiteral("michael"),
  147. Sharee::User,
  148. {},
  149. };
  150. FakeShareeDefinition _liamUserDefinition {
  151. QStringLiteral("Liam"),
  152. QStringLiteral("liam"),
  153. Sharee::User,
  154. {},
  155. };
  156. FakeShareeDefinition _iqbalUserDefinition {
  157. QStringLiteral("Iqbal"),
  158. QStringLiteral("iqbal"),
  159. Sharee::User,
  160. {},
  161. };
  162. FakeShareeDefinition _universityGroupDefinition {
  163. QStringLiteral("University"),
  164. QStringLiteral("university"),
  165. Sharee::Group,
  166. {},
  167. };
  168. FakeShareeDefinition _testEmailDefinition {
  169. QStringLiteral("test.email@nextcloud.com"),
  170. QStringLiteral("test.email@nextcloud.com"),
  171. Sharee::Email,
  172. {},
  173. };
  174. bool _alwaysReturnErrors = false;
  175. private slots:
  176. void initTestCase()
  177. {
  178. _fakeQnam.reset(new FakeQNAM({}));
  179. _fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  180. Q_UNUSED(device);
  181. QNetworkReply *reply = nullptr;
  182. if (_alwaysReturnErrors) {
  183. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  184. return reply;
  185. }
  186. const auto reqUrl = req.url();
  187. const auto reqRawPath = reqUrl.path();
  188. const auto reqPath = reqRawPath.startsWith("/owncloud/") ? reqRawPath.mid(10) : reqRawPath;
  189. qDebug() << req.url() << reqPath << op;
  190. if(req.url().toString().startsWith(_accountState->account()->url().toString()) &&
  191. reqPath == QStringLiteral("ocs/v2.php/apps/files_sharing/api/v1/sharees") &&
  192. req.attribute(QNetworkRequest::CustomVerbAttribute) == "GET") {
  193. const auto urlQuery = QUrlQuery(req.url());
  194. const auto searchParam = urlQuery.queryItemValue(QStringLiteral("search"));
  195. const auto itemTypeParam = urlQuery.queryItemValue(QStringLiteral("itemType"));
  196. const auto pageParam = urlQuery.queryItemValue(QStringLiteral("page"));
  197. const auto perPageParam = urlQuery.queryItemValue(QStringLiteral("perPage"));
  198. const auto lookupParam = urlQuery.queryItemValue(QStringLiteral("lookup"));
  199. const auto formatParam = urlQuery.queryItemValue(QStringLiteral("format"));
  200. if (formatParam != QStringLiteral("json")) {
  201. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  202. } else {
  203. reply = new FakePayloadReply(op, req, testShareesReply(searchParam), searchResultsReplyDelay, _fakeQnam.data());
  204. }
  205. }
  206. return reply;
  207. });
  208. _account = Account::create();
  209. _account->setCredentials(new FakeCredentials{_fakeQnam.data()});
  210. _account->setUrl(QUrl(("owncloud://somehost/owncloud")));
  211. _accountState = new AccountState(_account);
  212. AccountManager::instance()->addAccount(_account);
  213. // Let's verify our test is working -- all sharees have an I in their "shareWith"
  214. standardReplyPopulate();
  215. const auto searchString = QStringLiteral("i");
  216. QCOMPARE(shareesCount(searchString), 5);
  217. const auto emailSearchString = QStringLiteral("email");
  218. QCOMPARE(shareesCount(emailSearchString), 1);
  219. }
  220. void testSetAccountAndPath()
  221. {
  222. resetTestData();
  223. ShareeModel model;
  224. QAbstractItemModelTester modelTester(&model);
  225. QCOMPARE(model.rowCount(), 0);
  226. QSignalSpy accountStateChanged(&model, &ShareeModel::accountStateChanged);
  227. QSignalSpy shareItemIsFolderChanged(&model, &ShareeModel::shareItemIsFolderChanged);
  228. QSignalSpy searchStringChanged(&model, &ShareeModel::searchStringChanged);
  229. QSignalSpy lookupModeChanged(&model, &ShareeModel::lookupModeChanged);
  230. QSignalSpy shareeBlocklistChanged(&model, &ShareeModel::shareeBlocklistChanged);
  231. model.setAccountState(_accountState.data());
  232. QCOMPARE(accountStateChanged.count(), 1);
  233. QCOMPARE(model.accountState(), _accountState.data());
  234. const auto shareItemIsFolder = !model.shareItemIsFolder();
  235. model.setShareItemIsFolder(shareItemIsFolder);
  236. QCOMPARE(shareItemIsFolderChanged.count(), 1);
  237. QCOMPARE(model.shareItemIsFolder(), shareItemIsFolder);
  238. const auto searchString = QStringLiteral("search string");
  239. model.setSearchString(searchString);
  240. QCOMPARE(searchStringChanged.count(), 1);
  241. QCOMPARE(model.searchString(), searchString);
  242. const auto lookupMode = ShareeModel::LookupMode::GlobalSearch;
  243. model.setLookupMode(lookupMode);
  244. QCOMPARE(lookupModeChanged.count(), 1);
  245. QCOMPARE(model.lookupMode(), lookupMode);
  246. const ShareePtr sharee(new Sharee(_testEmailDefinition.shareWith, _testEmailDefinition.label, _testEmailDefinition.type));
  247. const QVariantList shareeBlocklist {QVariant::fromValue(sharee)};
  248. model.setShareeBlocklist(shareeBlocklist);
  249. QCOMPARE(shareeBlocklistChanged.count(), 1);
  250. QCOMPARE(model.shareeBlocklist(), shareeBlocklist);
  251. }
  252. void testShareesFetch()
  253. {
  254. resetTestData();
  255. standardReplyPopulate();
  256. ShareeModel model;
  257. QAbstractItemModelTester modelTester(&model);
  258. QCOMPARE(model.rowCount(), 0);
  259. model.setAccountState(_accountState.data());
  260. QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
  261. const auto searchString = QStringLiteral("i");
  262. model.setSearchString(searchString);
  263. QVERIFY(shareesReady.wait(3000));
  264. QCOMPARE(model.rowCount(), shareesCount(searchString));
  265. const auto emailSearchString = QStringLiteral("email");
  266. model.setSearchString(emailSearchString);
  267. QVERIFY(shareesReady.wait(3000));
  268. QCOMPARE(model.rowCount(), shareesCount(emailSearchString));
  269. }
  270. void testFetchSignalling()
  271. {
  272. resetTestData();
  273. standardReplyPopulate();
  274. ShareeModel model;
  275. QAbstractItemModelTester modelTester(&model);
  276. QCOMPARE(model.rowCount(), 0);
  277. model.setAccountState(_accountState.data());
  278. QSignalSpy fetchOngoingChanged(&model, &ShareeModel::fetchOngoingChanged);
  279. const auto searchString = QStringLiteral("i");
  280. model.setSearchString(searchString);
  281. QVERIFY(fetchOngoingChanged.wait(1000));
  282. QCOMPARE(model.fetchOngoing(), true);
  283. QVERIFY(fetchOngoingChanged.wait(3000));
  284. QCOMPARE(model.fetchOngoing(), false);
  285. }
  286. void testData()
  287. {
  288. resetTestData();
  289. appendShareeToReply(_testEmailDefinition);
  290. ShareeModel model;
  291. QAbstractItemModelTester modelTester(&model);
  292. QCOMPARE(model.rowCount(), 0);
  293. model.setAccountState(_accountState.data());
  294. const auto searchString = QStringLiteral("i");
  295. model.setSearchString(searchString);
  296. QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
  297. QVERIFY(shareesReady.wait(3000));
  298. QCOMPARE(model.rowCount(), shareesCount(searchString));
  299. const auto shareeIndex = model.index(0, 0, {});
  300. const ShareePtr expectedSharee(new Sharee(_testEmailDefinition.shareWith, _testEmailDefinition.label, _testEmailDefinition.type));
  301. const auto sharee = shareeIndex.data(ShareeModel::ShareeRole).value<ShareePtr>();
  302. QCOMPARE(sharee->format(), expectedSharee->format());
  303. QCOMPARE(sharee->shareWith(), expectedSharee->shareWith());
  304. QCOMPARE(sharee->displayName(), expectedSharee->displayName());
  305. QCOMPARE(sharee->type(), expectedSharee->type());
  306. const auto expectedShareeDisplay = QString(_testEmailDefinition.label + QStringLiteral(" (email)"));
  307. const auto shareeDisplay = shareeIndex.data(Qt::DisplayRole).toString();
  308. QCOMPARE(shareeDisplay, expectedShareeDisplay);
  309. const auto expectedAutoCompleterStringMatch = QString(_testEmailDefinition.label +
  310. QStringLiteral(" (") +
  311. _testEmailDefinition.shareWith +
  312. QStringLiteral(")"));
  313. const auto autoCompleterStringMatch = shareeIndex.data(ShareeModel::AutoCompleterStringMatchRole).toString();
  314. QCOMPARE(autoCompleterStringMatch, expectedAutoCompleterStringMatch);
  315. }
  316. void testBlocklist()
  317. {
  318. resetTestData();
  319. standardReplyPopulate();
  320. ShareeModel model;
  321. QAbstractItemModelTester modelTester(&model);
  322. QCOMPARE(model.rowCount(), 0);
  323. model.setAccountState(_accountState.data());
  324. const ShareePtr sharee(new Sharee(_testEmailDefinition.shareWith, _testEmailDefinition.label, _testEmailDefinition.type));
  325. const QVariantList shareeBlocklist {QVariant::fromValue(sharee)};
  326. model.setShareeBlocklist(shareeBlocklist);
  327. QSignalSpy shareesReady(&model, &ShareeModel::shareesReady);
  328. const auto searchString = QStringLiteral("i");
  329. model.setSearchString(searchString);
  330. QVERIFY(shareesReady.wait(3000));
  331. QCOMPARE(model.rowCount(), shareesCount(searchString) - 1);
  332. const ShareePtr shareeTwo(new Sharee(_michaelUserDefinition.shareWith, _michaelUserDefinition.label, _michaelUserDefinition.type));
  333. const QVariantList largerShareeBlocklist {QVariant::fromValue(sharee), QVariant::fromValue(shareeTwo)};
  334. model.setShareeBlocklist(largerShareeBlocklist);
  335. QCOMPARE(model.rowCount(), shareesCount(searchString) - 2);
  336. }
  337. void testServerError()
  338. {
  339. resetTestData();
  340. _alwaysReturnErrors = true;
  341. ShareeModel model;
  342. QAbstractItemModelTester modelTester(&model);
  343. QCOMPARE(model.rowCount(), 0);
  344. model.setAccountState(_accountState.data());
  345. QSignalSpy displayErrorMessage(&model, &ShareeModel::displayErrorMessage);
  346. QSignalSpy fetchOngoingChanged(&model, &ShareeModel::fetchOngoingChanged);
  347. model.setSearchString(QStringLiteral("i"));
  348. QVERIFY(displayErrorMessage.wait(3000));
  349. QCOMPARE(fetchOngoingChanged.count(), 2);
  350. QCOMPARE(model.fetchOngoing(), false);
  351. }
  352. };
  353. QTEST_MAIN(TestShareeModel)
  354. #include "testshareemodel.moc"