testactivitylistmodel.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. /*
  2. * Copyright (C) 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/tray/activitylistmodel.h"
  15. #include "account.h"
  16. #include "accountstate.h"
  17. #include "accountmanager.h"
  18. #include "syncenginetestutils.h"
  19. #include "syncresult.h"
  20. #include <QAbstractItemModelTester>
  21. #include <QDesktopServices>
  22. #include <QSignalSpy>
  23. #include <QTest>
  24. namespace {
  25. constexpr auto startingId = 90000;
  26. }
  27. static QByteArray fake404Response = R"(
  28. {"ocs":{"meta":{"status":"failure","statuscode":404,"message":"Invalid query, please check the syntax. API specifications are here: http:\/\/www.freedesktop.org\/wiki\/Specifications\/open-collaboration-services.\n"},"data":[]}}
  29. )";
  30. static QByteArray fake400Response = R"(
  31. {"ocs":{"meta":{"status":"failure","statuscode":400,"message":"Parameter is incorrect.\n"},"data":[]}}
  32. )";
  33. static QByteArray fake500Response = R"(
  34. {"ocs":{"meta":{"status":"failure","statuscode":500,"message":"Internal Server Error.\n"},"data":[]}}
  35. )";
  36. class FakeRemoteActivityStorage
  37. {
  38. FakeRemoteActivityStorage() = default;
  39. public:
  40. static FakeRemoteActivityStorage *instance()
  41. {
  42. if (!_instance) {
  43. _instance = new FakeRemoteActivityStorage();
  44. _instance->init();
  45. }
  46. return _instance;
  47. }
  48. static void destroy()
  49. {
  50. if (_instance) {
  51. delete _instance;
  52. }
  53. _instance = nullptr;
  54. }
  55. void init()
  56. {
  57. if (!_activityData.isEmpty()) {
  58. return;
  59. }
  60. _metaSuccess = {{QStringLiteral("status"), QStringLiteral("ok")}, {QStringLiteral("statuscode"), 200},
  61. {QStringLiteral("message"), QStringLiteral("OK")}};
  62. initActivityData();
  63. }
  64. void initActivityData()
  65. {
  66. // Insert activity data
  67. for (quint32 i = 0; i <= _numItemsToInsert; i++) {
  68. QJsonObject activity;
  69. activity.insert(QStringLiteral("object_type"), "files");
  70. activity.insert(QStringLiteral("activity_id"), _startingId);
  71. activity.insert(QStringLiteral("type"), QStringLiteral("file"));
  72. activity.insert(QStringLiteral("subject"), QStringLiteral("You created %1.txt").arg(i));
  73. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  74. activity.insert(QStringLiteral("object_name"), QStringLiteral("%1.txt").arg(i));
  75. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  76. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/apps/files/img/add-color.svg"));
  77. _activityData.push_back(activity);
  78. _startingId++;
  79. }
  80. // Insert notification data
  81. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  82. QJsonObject activity;
  83. activity.insert(QStringLiteral("activity_id"), _startingId);
  84. activity.insert(QStringLiteral("object_type"), "calendar");
  85. activity.insert(QStringLiteral("type"), QStringLiteral("calendar-event"));
  86. activity.insert(
  87. QStringLiteral("subject"), QStringLiteral("You created event %1 in calendar Events").arg(i));
  88. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  89. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  90. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  91. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/calendar.svg"));
  92. _activityData.push_back(activity);
  93. _startingId++;
  94. }
  95. // Insert notification data
  96. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  97. QJsonObject activity;
  98. activity.insert(QStringLiteral("activity_id"), _startingId);
  99. activity.insert(QStringLiteral("object_type"), "chat");
  100. activity.insert(QStringLiteral("type"), QStringLiteral("chat"));
  101. activity.insert(QStringLiteral("subject"), QStringLiteral("You have received %1's message").arg(i));
  102. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  103. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  104. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  105. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  106. QJsonArray actionsArray;
  107. QJsonObject replyAction;
  108. replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
  109. replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
  110. replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
  111. replyAction.insert(QStringLiteral("primary"), true);
  112. actionsArray.push_back(replyAction);
  113. QJsonObject primaryAction;
  114. primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
  115. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  116. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  117. primaryAction.insert(QStringLiteral("primary"), false);
  118. actionsArray.push_back(primaryAction);
  119. QJsonObject additionalAction;
  120. additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 1"));
  121. additionalAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  122. additionalAction.insert(QStringLiteral("type"), QStringLiteral("POST"));
  123. additionalAction.insert(QStringLiteral("primary"), false);
  124. actionsArray.push_back(additionalAction);
  125. additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 2"));
  126. actionsArray.push_back(additionalAction);
  127. activity.insert(QStringLiteral("actions"), actionsArray);
  128. _activityData.push_back(activity);
  129. _startingId++;
  130. }
  131. // Insert notification data
  132. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  133. QJsonObject activity;
  134. activity.insert(QStringLiteral("activity_id"), _startingId);
  135. activity.insert(QStringLiteral("object_type"), "room");
  136. activity.insert(QStringLiteral("type"), QStringLiteral("room"));
  137. activity.insert(QStringLiteral("subject"), QStringLiteral("You have been invited into room%1").arg(i));
  138. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  139. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  140. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  141. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  142. QJsonArray actionsArray;
  143. QJsonObject replyAction;
  144. replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
  145. replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
  146. replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
  147. replyAction.insert(QStringLiteral("primary"), true);
  148. actionsArray.push_back(replyAction);
  149. QJsonObject primaryAction;
  150. primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
  151. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  152. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  153. primaryAction.insert(QStringLiteral("primary"), false);
  154. actionsArray.push_back(primaryAction);
  155. activity.insert(QStringLiteral("actions"), actionsArray);
  156. _activityData.push_back(activity);
  157. _startingId++;
  158. }
  159. // Insert notification data
  160. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  161. QJsonObject activity;
  162. activity.insert(QStringLiteral("activity_id"), _startingId);
  163. activity.insert(QStringLiteral("object_type"), "call");
  164. activity.insert(QStringLiteral("type"), QStringLiteral("call"));
  165. activity.insert(QStringLiteral("subject"), QStringLiteral("You have missed a %1's call").arg(i));
  166. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  167. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  168. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  169. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  170. QJsonArray actionsArray;
  171. QJsonObject primaryAction;
  172. primaryAction.insert(QStringLiteral("label"), QStringLiteral("Call back"));
  173. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  174. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  175. primaryAction.insert(QStringLiteral("primary"), true);
  176. actionsArray.push_back(primaryAction);
  177. QJsonObject replyAction;
  178. replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
  179. replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
  180. replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
  181. replyAction.insert(QStringLiteral("primary"), false);
  182. actionsArray.push_back(replyAction);
  183. activity.insert(QStringLiteral("actions"), actionsArray);
  184. _activityData.push_back(activity);
  185. _startingId++;
  186. }
  187. // Insert notification data
  188. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  189. QJsonObject activity;
  190. activity.insert(QStringLiteral("activity_id"), _startingId);
  191. activity.insert(QStringLiteral("object_type"), "2fa_id");
  192. activity.insert(QStringLiteral("subject"), QStringLiteral("Login attempt from 127.0.0.1"));
  193. activity.insert(QStringLiteral("message"), QStringLiteral("Please apporve or deny the login attempt."));
  194. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  195. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  196. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/password.svg"));
  197. QJsonArray actionsArray;
  198. QJsonObject primaryAction;
  199. primaryAction.insert(QStringLiteral("label"), QStringLiteral("Approve"));
  200. primaryAction.insert(QStringLiteral("link"), QStringLiteral("/ocs/v2.php/apps/twofactor_nextcloud_notification/api/v1/attempt/39"));
  201. primaryAction.insert(QStringLiteral("type"), QStringLiteral("POST"));
  202. primaryAction.insert(QStringLiteral("primary"), true);
  203. actionsArray.push_back(primaryAction);
  204. QJsonObject secondaryAction;
  205. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Cancel"));
  206. secondaryAction.insert(QStringLiteral("link"),
  207. QString(QStringLiteral("/ocs/v2.php/apps/twofactor_nextcloud_notification/api/v1/attempt/39")));
  208. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  209. secondaryAction.insert(QStringLiteral("primary"), false);
  210. actionsArray.push_back(secondaryAction);
  211. activity.insert(QStringLiteral("actions"), actionsArray);
  212. _activityData.push_back(activity);
  213. _startingId++;
  214. }
  215. _startingId--;
  216. }
  217. const QByteArray activityJsonData(int sinceId, int limit)
  218. {
  219. QJsonArray data;
  220. const auto itFound = std::find_if(
  221. std::cbegin(_activityData), std::cend(_activityData), [&sinceId](const QJsonValue &currentActivityValue) {
  222. const auto currentActivityId =
  223. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  224. return currentActivityId == sinceId;
  225. });
  226. const int startIndex = itFound != std::cend(_activityData)
  227. ? static_cast<int>(std::distance(std::cbegin(_activityData), itFound))
  228. : -1;
  229. if (startIndex > 0) {
  230. for (int dataIndex = startIndex, iteration = 0; dataIndex >= 0 && iteration < limit;
  231. --dataIndex, ++iteration) {
  232. if (_activityData[dataIndex].toObject().value(QStringLiteral("activity_id")).toInt()
  233. > sinceId - limit) {
  234. data.append(_activityData[dataIndex]);
  235. }
  236. }
  237. }
  238. QJsonObject root;
  239. QJsonObject ocs;
  240. ocs.insert(QStringLiteral("data"), data);
  241. root.insert(QStringLiteral("ocs"), ocs);
  242. return QJsonDocument(root).toJson();
  243. }
  244. QJsonValue activityById(int id)
  245. {
  246. const auto itFound = std::find_if(
  247. std::cbegin(_activityData), std::cend(_activityData), [&id](const QJsonValue &currentActivityValue) {
  248. const auto currentActivityId =
  249. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  250. return currentActivityId == id;
  251. });
  252. if (itFound != std::cend(_activityData)) {
  253. return (*itFound);
  254. }
  255. return {};
  256. }
  257. [[nodiscard]] int startingIdLast() const { return _startingId; }
  258. private:
  259. static FakeRemoteActivityStorage *_instance;
  260. QJsonArray _activityData;
  261. QVariantMap _metaSuccess;
  262. quint32 _numItemsToInsert = 30;
  263. int _startingId = startingId;
  264. };
  265. FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr;
  266. class TestingALM : public OCC::ActivityListModel
  267. {
  268. Q_OBJECT
  269. public:
  270. TestingALM() = default;
  271. void startFetchJob() override
  272. {
  273. auto *job = new OCC::JsonApiJob(
  274. accountState()->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
  275. QObject::connect(this, &TestingALM::activityJobStatusCode, this, &TestingALM::slotProcessReceivedActivities);
  276. QObject::connect(job, &OCC::JsonApiJob::jsonReceived, this, &TestingALM::activitiesReceived);
  277. QUrlQuery params;
  278. params.addQueryItem(QLatin1String("since"), QString::number(currentItem()));
  279. params.addQueryItem(QLatin1String("limit"), QString::number(50));
  280. job->addQueryParams(params);
  281. setAndRefreshCurrentlyFetching(true);
  282. job->start();
  283. }
  284. public slots:
  285. void slotProcessReceivedActivities()
  286. {
  287. if (rowCount() > _numRowsPrev) {
  288. auto finalListCopy = finalList();
  289. for (int i = _numRowsPrev; i < rowCount(); ++i) {
  290. const auto modelIndex = index(i, 0);
  291. auto activity = finalListCopy.at(modelIndex.row());
  292. if (activity._links.isEmpty()) {
  293. const auto activityJsonObject = FakeRemoteActivityStorage::instance()->activityById(activity._id);
  294. if (!activityJsonObject.isNull()) {
  295. // because "_links" are normally populated within the notificationhandler.cpp, which we don't run as part of this unit test, we have to fill them here
  296. // TODO: move the logic to populate "_links" to "activitylistmodel.cpp"
  297. auto actions = activityJsonObject.toObject().value("actions").toArray();
  298. foreach (auto action, actions) {
  299. activity._links.append(OCC::ActivityLink::createFomJsonObject(action.toObject()));
  300. }
  301. finalListCopy[modelIndex.row()] = activity;
  302. }
  303. }
  304. }
  305. setFinalList(finalListCopy);
  306. }
  307. _numRowsPrev = rowCount();
  308. setAndRefreshCurrentlyFetching(false);
  309. emit activitiesProcessed();
  310. }
  311. signals:
  312. void activitiesProcessed();
  313. private:
  314. int _numRowsPrev = 0;
  315. };
  316. class TestActivityListModel : public QObject
  317. {
  318. Q_OBJECT
  319. public:
  320. TestActivityListModel() = default;
  321. ~TestActivityListModel() override
  322. {
  323. OCC::AccountManager::instance()->deleteAccount(accountState.data());
  324. }
  325. QScopedPointer<FakeQNAM> fakeQnam;
  326. OCC::AccountPtr account;
  327. QScopedPointer<OCC::AccountState> accountState;
  328. OCC::Activity testNotificationActivity;
  329. OCC::Activity testSyncResultErrorActivity;
  330. OCC::Activity testSyncFileItemActivity;
  331. OCC::Activity testFileIgnoredActivity;
  332. static constexpr int searchResultsReplyDelay = 100;
  333. QSharedPointer<TestingALM> testingALM() {
  334. QSharedPointer<TestingALM> model(new TestingALM);
  335. model->setAccountState(accountState.data());
  336. QAbstractItemModelTester modelTester(model.data());
  337. return model;
  338. }
  339. void testActivityAdd(void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&), OCC::Activity &activity) {
  340. const auto model = testingALM();
  341. QCOMPARE(model->rowCount(), 0);
  342. (model.data()->*addingMethod)(activity);
  343. QCOMPARE(model->rowCount(), 1);
  344. const auto index = model->index(0, 0);
  345. QVERIFY(index.isValid());
  346. }
  347. private slots:
  348. void initTestCase()
  349. {
  350. fakeQnam.reset(new FakeQNAM({}));
  351. account = OCC::Account::create();
  352. account->setCredentials(new FakeCredentials{fakeQnam.data()});
  353. account->setUrl(QUrl(("http://example.de")));
  354. accountState.reset(new OCC::AccountState(account));
  355. fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  356. Q_UNUSED(device);
  357. QNetworkReply *reply = nullptr;
  358. const auto urlQuery = QUrlQuery(req.url());
  359. const auto format = urlQuery.queryItemValue(QStringLiteral("format"));
  360. const auto since = urlQuery.queryItemValue(QStringLiteral("since")).toInt();
  361. const auto limit = urlQuery.queryItemValue(QStringLiteral("limit")).toInt();
  362. const auto path = req.url().path();
  363. if (!req.url().toString().startsWith(accountState->account()->url().toString())) {
  364. reply = new FakeErrorReply(op, req, this, 404, fake404Response);
  365. }
  366. if (format != QStringLiteral("json")) {
  367. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  368. }
  369. if (path.startsWith(QStringLiteral("/ocs/v2.php/apps/activity/api/v2/activity"))) {
  370. reply = new FakePayloadReply(op, req, FakeRemoteActivityStorage::instance()->activityJsonData(since, limit), searchResultsReplyDelay, fakeQnam.data());
  371. }
  372. if (!reply) {
  373. return qobject_cast<QNetworkReply*>(new FakeErrorReply(op, req, this, 404, QByteArrayLiteral("{error: \"Not found!\"}")));
  374. }
  375. return reply;
  376. });
  377. OCC::AccountManager::instance()->addAccount(account);
  378. // Activity comparison is done by checking type, id, and accName
  379. // We need an activity with these details, at least
  380. testNotificationActivity._accName = accountState->account()->displayName();
  381. testNotificationActivity._id = 1;
  382. testNotificationActivity._type = OCC::Activity::NotificationType;
  383. testNotificationActivity._dateTime = QDateTime::currentDateTime();
  384. testNotificationActivity._subject = QStringLiteral("Sample notification text");
  385. testSyncResultErrorActivity._id = 2;
  386. testSyncResultErrorActivity._type = OCC::Activity::SyncResultType;
  387. testSyncResultErrorActivity._syncResultStatus = OCC::SyncResult::Error;
  388. testSyncResultErrorActivity._dateTime = QDateTime::currentDateTime();
  389. testSyncResultErrorActivity._subject = QStringLiteral("Sample failed sync text");
  390. testSyncResultErrorActivity._message = QStringLiteral("/path/to/thingy");
  391. testSyncResultErrorActivity._link = QStringLiteral("/path/to/thingy");
  392. testSyncResultErrorActivity._accName = accountState->account()->displayName();
  393. testSyncFileItemActivity._id = 3;
  394. testSyncFileItemActivity._type = OCC::Activity::SyncFileItemType; //client activity
  395. testSyncFileItemActivity._syncFileItemStatus = OCC::SyncFileItem::Success;
  396. testSyncFileItemActivity._dateTime = QDateTime::currentDateTime();
  397. testSyncFileItemActivity._message = QStringLiteral("Sample file successfully synced text");
  398. testSyncFileItemActivity._link = accountState->account()->url();
  399. testSyncFileItemActivity._accName = accountState->account()->displayName();
  400. testSyncFileItemActivity._file = QStringLiteral("xyz.pdf");
  401. testFileIgnoredActivity._id = 4;
  402. testFileIgnoredActivity._type = OCC::Activity::SyncFileItemType;
  403. testFileIgnoredActivity._syncFileItemStatus = OCC::SyncFileItem::FileIgnored;
  404. testFileIgnoredActivity._dateTime = QDateTime::currentDateTime();
  405. testFileIgnoredActivity._subject = QStringLiteral("Sample ignored file sync text");
  406. testFileIgnoredActivity._link = accountState->account()->url();
  407. testFileIgnoredActivity._accName = accountState->account()->displayName();
  408. testFileIgnoredActivity._folder = QStringLiteral("thingy");
  409. testFileIgnoredActivity._file = QStringLiteral("test.txt");
  410. };
  411. // Test receiving activity from server
  412. void testFetchingRemoteActivity() {
  413. const auto model = testingALM();
  414. QCOMPARE(model->rowCount(), 0);
  415. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  416. model->startFetchJob();
  417. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  418. QVERIFY(activitiesJob.wait(3000));
  419. QCOMPARE(model->rowCount(), 50);
  420. };
  421. // Test receiving activity from local user action
  422. void testLocalSyncFileAction() {
  423. testActivityAdd(&TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity);
  424. };
  425. void testAddNotification() {
  426. testActivityAdd(&TestingALM::addNotificationToActivityList, testNotificationActivity);
  427. };
  428. void testAddError() {
  429. testActivityAdd(&TestingALM::addErrorToActivityList, testSyncResultErrorActivity);
  430. };
  431. void testAddIgnoredFile() {
  432. testActivityAdd(&TestingALM::addIgnoredFileToList, testFileIgnoredActivity);
  433. };
  434. // Test removing activity from list
  435. void testRemoveActivityWithRow() {
  436. const auto model = testingALM();
  437. QCOMPARE(model->rowCount(), 0);
  438. model->addNotificationToActivityList(testNotificationActivity);
  439. QCOMPARE(model->rowCount(), 1);
  440. model->removeActivityFromActivityList(0);
  441. QCOMPARE(model->rowCount(), 0);
  442. }
  443. void testRemoveActivityWithActivity() {
  444. const auto model = testingALM();
  445. QCOMPARE(model->rowCount(), 0);
  446. model->addNotificationToActivityList(testNotificationActivity);
  447. QCOMPARE(model->rowCount(), 1);
  448. model->removeActivityFromActivityList(testNotificationActivity);
  449. QCOMPARE(model->rowCount(), 0);
  450. }
  451. void testDummyFetchingActivitiesActivity() {
  452. const auto model = testingALM();
  453. QCOMPARE(model->rowCount(), 0);
  454. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  455. model->startFetchJob();
  456. // Check for the dummy before activities have arrived
  457. QCOMPARE(model->rowCount(), 1);
  458. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  459. QVERIFY(activitiesJob.wait(3000));
  460. // Test the dummy was removed
  461. QCOMPARE(model->rowCount(), 50);
  462. }
  463. // Test getting the data from the model
  464. void testData() {
  465. const auto model = testingALM();
  466. QCOMPARE(model->rowCount(), 0);
  467. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  468. model->startFetchJob();
  469. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  470. QVERIFY(activitiesJob.wait(3000));
  471. QCOMPARE(model->rowCount(), 50);
  472. model->addSyncFileItemToActivityList(testSyncFileItemActivity);
  473. QCOMPARE(model->rowCount(), 51);
  474. model->addErrorToActivityList(testSyncResultErrorActivity);
  475. QCOMPARE(model->rowCount(), 52);
  476. model->addIgnoredFileToList(testFileIgnoredActivity);
  477. QCOMPARE(model->rowCount(), 53);
  478. model->addNotificationToActivityList(testNotificationActivity);
  479. QCOMPARE(model->rowCount(), 54);
  480. // Test all rows for things in common
  481. for (int i = 0; i < model->rowCount(); i++) {
  482. const auto index = model->index(i, 0);
  483. auto text = index.data(OCC::ActivityListModel::ActionTextRole).toString();
  484. QVERIFY(index.data(OCC::ActivityListModel::ActionRole).canConvert<int>());
  485. const auto type = index.data(OCC::ActivityListModel::ActionRole).toInt();
  486. QVERIFY(type >= OCC::Activity::DummyFetchingActivityType);
  487. QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
  488. QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
  489. QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty());
  490. QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty());
  491. QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
  492. QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
  493. QVERIFY(index.data(OCC::ActivityListModel::ObjectNameRole).canConvert<QString>());
  494. QVERIFY(index.data(OCC::ActivityListModel::ObjectIdRole).canConvert<int>());
  495. QVERIFY(index.data(OCC::ActivityListModel::ActionsLinksRole).canConvert<QList<QVariant>>());
  496. QVERIFY(index.data(OCC::ActivityListModel::ActionTextRole).canConvert<QString>());
  497. QVERIFY(index.data(OCC::ActivityListModel::MessageRole).canConvert<QString>());
  498. QVERIFY(index.data(OCC::ActivityListModel::LinkRole).canConvert<QUrl>());
  499. QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
  500. QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
  501. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationConversationTokenRole).canConvert<QString>());
  502. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageIdRole).canConvert<QString>());
  503. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageSentRole).canConvert<QString>());
  504. QVERIFY(index.data(OCC::ActivityListModel::ActivityRole).canConvert<OCC::Activity>());
  505. // Unfortunately, trying to check anything relating to filepaths causes a crash
  506. // when the folder manager is invoked by the model to look for the relevant file
  507. }
  508. };
  509. void testActivityActionsData()
  510. {
  511. const auto model = testingALM();
  512. QCOMPARE(model->rowCount(), 0);
  513. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  514. int prevModelRowCount = model->rowCount();
  515. do {
  516. prevModelRowCount = model->rowCount();
  517. model->startFetchJob();
  518. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  519. QVERIFY(activitiesJob.wait(3000));
  520. for (int i = prevModelRowCount; i < model->rowCount(); i++) {
  521. const auto index = model->index(i, 0);
  522. const auto actionsLinks = index.data(OCC::ActivityListModel::ActionsLinksRole).toList();
  523. if (!actionsLinks.isEmpty()) {
  524. const auto actionsLinksContextMenu =
  525. index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
  526. // context menu must be shorter than total action links
  527. QVERIFY(actionsLinksContextMenu.isEmpty() || actionsLinksContextMenu.size() < actionsLinks.size());
  528. // context menu must not contain the primary action
  529. QVERIFY(std::find_if(std::begin(actionsLinksContextMenu), std::end(actionsLinksContextMenu),
  530. [](const QVariant &entry) { return entry.value<OCC::ActivityLink>()._primary; })
  531. == std::end(actionsLinksContextMenu));
  532. const auto objectType = index.data(OCC::ActivityListModel::ObjectTypeRole).toString();
  533. const auto actionButtonsLinks =
  534. index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
  535. // Login attempt notification
  536. if (objectType == QStringLiteral("2fa_id")) {
  537. QVERIFY(actionsLinks.size() == 2);
  538. QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._primary);
  539. QVERIFY(!actionsLinks[1].value<OCC::ActivityLink>()._primary);
  540. QVERIFY(actionsLinksContextMenu.isEmpty());
  541. }
  542. if ((objectType == QStringLiteral("chat") || objectType == QStringLiteral("call")
  543. || objectType == QStringLiteral("room"))) {
  544. auto replyActionPos = 0;
  545. if (objectType == QStringLiteral("call")) {
  546. replyActionPos = 1;
  547. }
  548. // both action links and buttons must contain a "REPLY" verb element as secondary action
  549. QVERIFY(actionsLinks[replyActionPos].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
  550. QVERIFY(actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
  551. // the first action button for chat must have image set
  552. QVERIFY(!actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._imageSource.isEmpty());
  553. QVERIFY(!actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._imageSourceHovered.isEmpty());
  554. // logic for "chat" and other types of activities with multiple actions
  555. if ((objectType == QStringLiteral("chat")
  556. || (objectType != QStringLiteral("room") && objectType != QStringLiteral("call")))) {
  557. // button's label for "chat" must be renamed to "Reply"
  558. QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QObject::tr("Reply"));
  559. if (static_cast<quint32>(actionsLinks.size()) > OCC::ActivityListModel::maxActionButtons()) {
  560. // in case total actions is longer than ActivityListModel::maxActionButtons, only one button must be present in a list of action buttons
  561. QVERIFY(actionButtonsLinks.size() == 1);
  562. const auto actionButtonsAndContextMenuEntries = actionButtonsLinks + actionsLinksContextMenu;
  563. // in case total actions is longer than ActivityListModel::maxActionButtons, then a sum of action buttons and action menu entries must be equal to a total of action links
  564. QVERIFY(actionButtonsLinks.size() + actionsLinksContextMenu.size() == actionsLinks.size());
  565. }
  566. } else if ((objectType == QStringLiteral("call"))) {
  567. QVERIFY(
  568. actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
  569. }
  570. }
  571. }
  572. }
  573. } while (prevModelRowCount < model->rowCount());
  574. };
  575. };
  576. QTEST_MAIN(TestActivityListModel)
  577. #include "testactivitylistmodel.moc"