testactivitylistmodel.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  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 approve 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. // Insert notification data
  216. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  217. QJsonObject activity;
  218. activity.insert(QStringLiteral("activity_id"), _startingId);
  219. activity.insert(QStringLiteral("object_type"), "create");
  220. activity.insert(QStringLiteral("subject"), QStringLiteral("Generate backup codes"));
  221. activity.insert(QStringLiteral("message"), QStringLiteral("You enabled two-factor authentication but did not generate backup codes yet. They are needed to restore access to your account in case you lose your second factor."));
  222. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  223. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  224. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/password.svg"));
  225. QJsonArray actionsArray;
  226. QJsonObject secondaryAction;
  227. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  228. secondaryAction.insert(QStringLiteral("link"),
  229. QString(QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications/19867")));
  230. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  231. secondaryAction.insert(QStringLiteral("primary"), false);
  232. actionsArray.push_back(secondaryAction);
  233. activity.insert(QStringLiteral("actions"), actionsArray);
  234. _activityData.push_back(activity);
  235. _startingId++;
  236. }
  237. _startingId--;
  238. }
  239. const QByteArray activityJsonData(int sinceId, int limit)
  240. {
  241. QJsonArray data;
  242. const auto itFound = std::find_if(
  243. std::cbegin(_activityData), std::cend(_activityData), [&sinceId](const QJsonValue &currentActivityValue) {
  244. const auto currentActivityId =
  245. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  246. return currentActivityId == sinceId;
  247. });
  248. const int startIndex = itFound != std::cend(_activityData)
  249. ? static_cast<int>(std::distance(std::cbegin(_activityData), itFound))
  250. : -1;
  251. if (startIndex > 0) {
  252. for (int dataIndex = startIndex, iteration = 0; dataIndex >= 0 && iteration < limit;
  253. --dataIndex, ++iteration) {
  254. if (_activityData[dataIndex].toObject().value(QStringLiteral("activity_id")).toInt()
  255. > sinceId - limit) {
  256. data.append(_activityData[dataIndex]);
  257. }
  258. }
  259. }
  260. QJsonObject root;
  261. QJsonObject ocs;
  262. ocs.insert(QStringLiteral("data"), data);
  263. root.insert(QStringLiteral("ocs"), ocs);
  264. return QJsonDocument(root).toJson();
  265. }
  266. QJsonValue activityById(int id)
  267. {
  268. const auto itFound = std::find_if(
  269. std::cbegin(_activityData), std::cend(_activityData), [&id](const QJsonValue &currentActivityValue) {
  270. const auto currentActivityId =
  271. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  272. return currentActivityId == id;
  273. });
  274. if (itFound != std::cend(_activityData)) {
  275. return (*itFound);
  276. }
  277. return {};
  278. }
  279. [[nodiscard]] int startingIdLast() const { return _startingId; }
  280. private:
  281. static FakeRemoteActivityStorage *_instance;
  282. QJsonArray _activityData;
  283. QVariantMap _metaSuccess;
  284. quint32 _numItemsToInsert = 30;
  285. int _startingId = startingId;
  286. };
  287. FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr;
  288. class TestingALM : public OCC::ActivityListModel
  289. {
  290. Q_OBJECT
  291. public:
  292. TestingALM() = default;
  293. void startFetchJob() override
  294. {
  295. auto *job = new OCC::JsonApiJob(
  296. accountState()->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
  297. QObject::connect(this, &TestingALM::activityJobStatusCode, this, &TestingALM::slotProcessReceivedActivities);
  298. QObject::connect(job, &OCC::JsonApiJob::jsonReceived, this, &TestingALM::activitiesReceived);
  299. QUrlQuery params;
  300. params.addQueryItem(QLatin1String("since"), QString::number(currentItem()));
  301. params.addQueryItem(QLatin1String("limit"), QString::number(50));
  302. job->addQueryParams(params);
  303. setAndRefreshCurrentlyFetching(true);
  304. job->start();
  305. }
  306. public slots:
  307. void slotProcessReceivedActivities()
  308. {
  309. if (rowCount() > _numRowsPrev) {
  310. auto finalListCopy = finalList();
  311. for (int i = _numRowsPrev; i < rowCount(); ++i) {
  312. const auto modelIndex = index(i, 0);
  313. auto activity = finalListCopy.at(modelIndex.row());
  314. if (activity._links.isEmpty()) {
  315. const auto activityJsonObject = FakeRemoteActivityStorage::instance()->activityById(activity._id);
  316. if (!activityJsonObject.isNull()) {
  317. // 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
  318. // TODO: move the logic to populate "_links" to "activitylistmodel.cpp"
  319. auto actions = activityJsonObject.toObject().value("actions").toArray();
  320. foreach (auto action, actions) {
  321. activity._links.append(OCC::ActivityLink::createFomJsonObject(action.toObject()));
  322. }
  323. finalListCopy[modelIndex.row()] = activity;
  324. }
  325. }
  326. }
  327. setFinalList(finalListCopy);
  328. }
  329. _numRowsPrev = rowCount();
  330. setAndRefreshCurrentlyFetching(false);
  331. emit activitiesProcessed();
  332. }
  333. signals:
  334. void activitiesProcessed();
  335. private:
  336. int _numRowsPrev = 0;
  337. };
  338. class TestActivityListModel : public QObject
  339. {
  340. Q_OBJECT
  341. public:
  342. TestActivityListModel() = default;
  343. ~TestActivityListModel() override
  344. {
  345. OCC::AccountManager::instance()->deleteAccount(accountState.data());
  346. }
  347. QScopedPointer<FakeQNAM> fakeQnam;
  348. OCC::AccountPtr account;
  349. QScopedPointer<OCC::AccountState> accountState;
  350. OCC::Activity testNotificationActivity;
  351. OCC::Activity testSyncResultErrorActivity;
  352. OCC::Activity testSyncFileItemActivity;
  353. OCC::Activity testFileIgnoredActivity;
  354. static constexpr int searchResultsReplyDelay = 100;
  355. QSharedPointer<TestingALM> testingALM() {
  356. QSharedPointer<TestingALM> model(new TestingALM);
  357. model->setAccountState(accountState.data());
  358. QAbstractItemModelTester modelTester(model.data());
  359. return model;
  360. }
  361. void testActivityAdd(void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&), OCC::Activity &activity) {
  362. const auto model = testingALM();
  363. QCOMPARE(model->rowCount(), 0);
  364. (model.data()->*addingMethod)(activity);
  365. QCOMPARE(model->rowCount(), 1);
  366. const auto index = model->index(0, 0);
  367. QVERIFY(index.isValid());
  368. }
  369. private slots:
  370. void initTestCase()
  371. {
  372. fakeQnam.reset(new FakeQNAM({}));
  373. account = OCC::Account::create();
  374. account->setCredentials(new FakeCredentials{fakeQnam.data()});
  375. account->setUrl(QUrl(("http://example.de")));
  376. accountState.reset(new OCC::AccountState(account));
  377. fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  378. Q_UNUSED(device);
  379. QNetworkReply *reply = nullptr;
  380. const auto urlQuery = QUrlQuery(req.url());
  381. const auto format = urlQuery.queryItemValue(QStringLiteral("format"));
  382. const auto since = urlQuery.queryItemValue(QStringLiteral("since")).toInt();
  383. const auto limit = urlQuery.queryItemValue(QStringLiteral("limit")).toInt();
  384. const auto path = req.url().path();
  385. if (!req.url().toString().startsWith(accountState->account()->url().toString())) {
  386. reply = new FakeErrorReply(op, req, this, 404, fake404Response);
  387. }
  388. if (format != QStringLiteral("json")) {
  389. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  390. }
  391. if (path.startsWith(QStringLiteral("/ocs/v2.php/apps/activity/api/v2/activity"))) {
  392. reply = new FakePayloadReply(op, req, FakeRemoteActivityStorage::instance()->activityJsonData(since, limit), searchResultsReplyDelay, fakeQnam.data());
  393. }
  394. if (!reply) {
  395. return qobject_cast<QNetworkReply*>(new FakeErrorReply(op, req, this, 404, QByteArrayLiteral("{error: \"Not found!\"}")));
  396. }
  397. return reply;
  398. });
  399. OCC::AccountManager::instance()->addAccount(account);
  400. // Activity comparison is done by checking type, id, and accName
  401. // We need an activity with these details, at least
  402. testNotificationActivity._accName = accountState->account()->displayName();
  403. testNotificationActivity._id = 1;
  404. testNotificationActivity._type = OCC::Activity::NotificationType;
  405. testNotificationActivity._dateTime = QDateTime::currentDateTime();
  406. testNotificationActivity._subject = QStringLiteral("Sample notification text");
  407. testSyncResultErrorActivity._id = 2;
  408. testSyncResultErrorActivity._type = OCC::Activity::SyncResultType;
  409. testSyncResultErrorActivity._syncResultStatus = OCC::SyncResult::Error;
  410. testSyncResultErrorActivity._dateTime = QDateTime::currentDateTime();
  411. testSyncResultErrorActivity._subject = QStringLiteral("Sample failed sync text");
  412. testSyncResultErrorActivity._message = QStringLiteral("/path/to/thingy");
  413. testSyncResultErrorActivity._link = QStringLiteral("/path/to/thingy");
  414. testSyncResultErrorActivity._accName = accountState->account()->displayName();
  415. testSyncFileItemActivity._id = 3;
  416. testSyncFileItemActivity._type = OCC::Activity::SyncFileItemType; //client activity
  417. testSyncFileItemActivity._syncFileItemStatus = OCC::SyncFileItem::Success;
  418. testSyncFileItemActivity._dateTime = QDateTime::currentDateTime();
  419. testSyncFileItemActivity._message = QStringLiteral("Sample file successfully synced text");
  420. testSyncFileItemActivity._link = accountState->account()->url();
  421. testSyncFileItemActivity._accName = accountState->account()->displayName();
  422. testSyncFileItemActivity._file = QStringLiteral("xyz.pdf");
  423. testFileIgnoredActivity._id = 4;
  424. testFileIgnoredActivity._type = OCC::Activity::SyncFileItemType;
  425. testFileIgnoredActivity._syncFileItemStatus = OCC::SyncFileItem::FileIgnored;
  426. testFileIgnoredActivity._dateTime = QDateTime::currentDateTime();
  427. testFileIgnoredActivity._subject = QStringLiteral("Sample ignored file sync text");
  428. testFileIgnoredActivity._link = accountState->account()->url();
  429. testFileIgnoredActivity._accName = accountState->account()->displayName();
  430. testFileIgnoredActivity._folder = QStringLiteral("thingy");
  431. testFileIgnoredActivity._file = QStringLiteral("test.txt");
  432. };
  433. // Test receiving activity from server
  434. void testFetchingRemoteActivity() {
  435. const auto model = testingALM();
  436. QCOMPARE(model->rowCount(), 0);
  437. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  438. model->startFetchJob();
  439. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  440. QVERIFY(activitiesJob.wait(3000));
  441. QCOMPARE(model->rowCount(), 50);
  442. };
  443. // Test receiving activity from local user action
  444. void testLocalSyncFileAction() {
  445. testActivityAdd(&TestingALM::addSyncFileItemToActivityList, testSyncFileItemActivity);
  446. };
  447. void testAddNotification() {
  448. testActivityAdd(&TestingALM::addNotificationToActivityList, testNotificationActivity);
  449. };
  450. void testAddError() {
  451. testActivityAdd(&TestingALM::addErrorToActivityList, testSyncResultErrorActivity);
  452. };
  453. void testAddIgnoredFile() {
  454. testActivityAdd(&TestingALM::addIgnoredFileToList, testFileIgnoredActivity);
  455. };
  456. // Test removing activity from list
  457. void testRemoveActivityWithRow() {
  458. const auto model = testingALM();
  459. QCOMPARE(model->rowCount(), 0);
  460. model->addNotificationToActivityList(testNotificationActivity);
  461. QCOMPARE(model->rowCount(), 1);
  462. model->removeActivityFromActivityList(0);
  463. QCOMPARE(model->rowCount(), 0);
  464. }
  465. void testRemoveActivityWithActivity() {
  466. const auto model = testingALM();
  467. QCOMPARE(model->rowCount(), 0);
  468. model->addNotificationToActivityList(testNotificationActivity);
  469. QCOMPARE(model->rowCount(), 1);
  470. model->removeActivityFromActivityList(testNotificationActivity);
  471. QCOMPARE(model->rowCount(), 0);
  472. }
  473. void testDummyFetchingActivitiesActivity() {
  474. const auto model = testingALM();
  475. QCOMPARE(model->rowCount(), 0);
  476. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  477. model->startFetchJob();
  478. // Check for the dummy before activities have arrived
  479. QCOMPARE(model->rowCount(), 1);
  480. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  481. QVERIFY(activitiesJob.wait(3000));
  482. // Test the dummy was removed
  483. QCOMPARE(model->rowCount(), 50);
  484. }
  485. // Test getting the data from the model
  486. void testData() {
  487. const auto model = testingALM();
  488. QCOMPARE(model->rowCount(), 0);
  489. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  490. model->startFetchJob();
  491. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  492. QVERIFY(activitiesJob.wait(3000));
  493. QCOMPARE(model->rowCount(), 50);
  494. model->addSyncFileItemToActivityList(testSyncFileItemActivity);
  495. QCOMPARE(model->rowCount(), 51);
  496. model->addErrorToActivityList(testSyncResultErrorActivity);
  497. QCOMPARE(model->rowCount(), 52);
  498. model->addIgnoredFileToList(testFileIgnoredActivity);
  499. QCOMPARE(model->rowCount(), 53);
  500. model->addNotificationToActivityList(testNotificationActivity);
  501. QCOMPARE(model->rowCount(), 54);
  502. // Test all rows for things in common
  503. for (int i = 0; i < model->rowCount(); i++) {
  504. const auto index = model->index(i, 0);
  505. auto text = index.data(OCC::ActivityListModel::ActionTextRole).toString();
  506. QVERIFY(index.data(OCC::ActivityListModel::ActionRole).canConvert<int>());
  507. const auto type = index.data(OCC::ActivityListModel::ActionRole).toInt();
  508. QVERIFY(type >= OCC::Activity::DummyFetchingActivityType);
  509. QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
  510. QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
  511. QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty());
  512. QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty());
  513. QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
  514. QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
  515. QVERIFY(index.data(OCC::ActivityListModel::ObjectNameRole).canConvert<QString>());
  516. QVERIFY(index.data(OCC::ActivityListModel::ObjectIdRole).canConvert<int>());
  517. QVERIFY(index.data(OCC::ActivityListModel::ActionsLinksRole).canConvert<QList<QVariant>>());
  518. QVERIFY(index.data(OCC::ActivityListModel::ActionTextRole).canConvert<QString>());
  519. QVERIFY(index.data(OCC::ActivityListModel::MessageRole).canConvert<QString>());
  520. QVERIFY(index.data(OCC::ActivityListModel::LinkRole).canConvert<QUrl>());
  521. QVERIFY(index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).canConvert<QList<QVariant>>());
  522. QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
  523. QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
  524. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationConversationTokenRole).canConvert<QString>());
  525. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageIdRole).canConvert<QString>());
  526. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageSentRole).canConvert<QString>());
  527. QVERIFY(index.data(OCC::ActivityListModel::ActivityRole).canConvert<OCC::Activity>());
  528. // Unfortunately, trying to check anything relating to filepaths causes a crash
  529. // when the folder manager is invoked by the model to look for the relevant file
  530. }
  531. };
  532. void testActivityActionsData()
  533. {
  534. const auto model = testingALM();
  535. QCOMPARE(model->rowCount(), 0);
  536. model->setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  537. int prevModelRowCount = model->rowCount();
  538. do {
  539. prevModelRowCount = model->rowCount();
  540. model->startFetchJob();
  541. QSignalSpy activitiesJob(model.data(), &TestingALM::activitiesProcessed);
  542. QVERIFY(activitiesJob.wait(3000));
  543. for (int i = prevModelRowCount; i < model->rowCount(); i++) {
  544. const auto index = model->index(i, 0);
  545. const auto actionsLinks = index.data(OCC::ActivityListModel::ActionsLinksRole).toList();
  546. if (!actionsLinks.isEmpty()) {
  547. const auto actionsLinksContextMenu =
  548. index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
  549. // context menu must be shorter than total action links
  550. QVERIFY(actionsLinksContextMenu.isEmpty() || actionsLinksContextMenu.size() < actionsLinks.size());
  551. // context menu must not contain the primary action
  552. QVERIFY(std::find_if(std::begin(actionsLinksContextMenu), std::end(actionsLinksContextMenu),
  553. [](const QVariant &entry) { return entry.value<OCC::ActivityLink>()._primary; })
  554. == std::end(actionsLinksContextMenu));
  555. const auto objectType = index.data(OCC::ActivityListModel::ObjectTypeRole).toString();
  556. const auto actionButtonsLinks =
  557. index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
  558. // Login attempt notification
  559. if (objectType == QStringLiteral("2fa_id")) {
  560. QVERIFY(actionsLinks.size() == 2);
  561. QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._primary);
  562. QVERIFY(!actionsLinks[1].value<OCC::ActivityLink>()._primary);
  563. QVERIFY(actionsLinksContextMenu.isEmpty());
  564. }
  565. // Generate 2FA backup codes notification
  566. if (objectType == QStringLiteral("create")) {
  567. QVERIFY(actionsLinks.size() == 1);
  568. QVERIFY(!actionsLinks[0].value<OCC::ActivityLink>()._primary);
  569. QVERIFY(actionsLinksContextMenu.isEmpty());
  570. }
  571. if ((objectType == QStringLiteral("chat") || objectType == QStringLiteral("call")
  572. || objectType == QStringLiteral("room"))) {
  573. auto replyActionPos = 0;
  574. if (objectType == QStringLiteral("call")) {
  575. replyActionPos = 1;
  576. }
  577. // both action links and buttons must contain a "REPLY" verb element as secondary action
  578. QVERIFY(actionsLinks[replyActionPos].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
  579. QVERIFY(actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
  580. // the first action button for chat must have image set
  581. QVERIFY(!actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._imageSource.isEmpty());
  582. QVERIFY(!actionButtonsLinks[replyActionPos].value<OCC::ActivityLink>()._imageSourceHovered.isEmpty());
  583. // logic for "chat" and other types of activities with multiple actions
  584. if ((objectType == QStringLiteral("chat")
  585. || (objectType != QStringLiteral("room") && objectType != QStringLiteral("call")))) {
  586. // button's label for "chat" must be renamed to "Reply"
  587. QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QObject::tr("Reply"));
  588. if (static_cast<quint32>(actionsLinks.size()) > OCC::ActivityListModel::maxActionButtons()) {
  589. // in case total actions is longer than ActivityListModel::maxActionButtons, only one button must be present in a list of action buttons
  590. QVERIFY(actionButtonsLinks.size() == 1);
  591. const auto actionButtonsAndContextMenuEntries = actionButtonsLinks + actionsLinksContextMenu;
  592. // 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
  593. QVERIFY(actionButtonsLinks.size() + actionsLinksContextMenu.size() == actionsLinks.size());
  594. }
  595. } else if ((objectType == QStringLiteral("call"))) {
  596. QVERIFY(
  597. actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
  598. }
  599. }
  600. }
  601. }
  602. } while (prevModelRowCount < model->rowCount());
  603. };
  604. };
  605. QTEST_MAIN(TestActivityListModel)
  606. #include "testactivitylistmodel.moc"