testpushnotifications.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /*
  2. * Copyright (C) by Felix Weilbach <felix.weilbach@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 <QTest>
  15. #include <QVector>
  16. #include <QWebSocketServer>
  17. #include <QSignalSpy>
  18. #include "accountfwd.h"
  19. #include "pushnotifications.h"
  20. #include "pushnotificationstestutils.h"
  21. #define RETURN_FALSE_ON_FAIL(expr) \
  22. if (!(expr)) { \
  23. return false; \
  24. }
  25. bool verifyCalledOnceWithAccount(QSignalSpy &spy, OCC::AccountPtr account)
  26. {
  27. RETURN_FALSE_ON_FAIL(spy.count() == 1);
  28. auto accountFromSpy = spy.at(0).at(0).value<OCC::Account *>();
  29. RETURN_FALSE_ON_FAIL(accountFromSpy == account.data());
  30. return true;
  31. }
  32. bool failThreeAuthenticationAttempts(FakeWebSocketServer &fakeServer, OCC::AccountPtr account)
  33. {
  34. RETURN_FALSE_ON_FAIL(account);
  35. RETURN_FALSE_ON_FAIL(account->pushNotifications());
  36. account->pushNotifications()->setReconnectTimerInterval(0);
  37. QSignalSpy authenticationFailedSpy(account->pushNotifications(), &OCC::PushNotifications::authenticationFailed);
  38. // Let three authentication attempts fail
  39. for (uint8_t i = 0; i < 3; ++i) {
  40. RETURN_FALSE_ON_FAIL(fakeServer.waitForTextMessages());
  41. RETURN_FALSE_ON_FAIL(fakeServer.textMessagesCount() == 2);
  42. auto socket = fakeServer.socketForTextMessage(0);
  43. fakeServer.clearTextMessages();
  44. socket->sendTextMessage("err: Invalid credentials");
  45. }
  46. // Now the authenticationFailed Signal should be emitted
  47. RETURN_FALSE_ON_FAIL(authenticationFailedSpy.wait());
  48. RETURN_FALSE_ON_FAIL(authenticationFailedSpy.count() == 1);
  49. return true;
  50. }
  51. class TestPushNotifications : public QObject
  52. {
  53. Q_OBJECT
  54. private slots:
  55. void testSetup_correctCredentials_authenticateAndEmitReady()
  56. {
  57. FakeWebSocketServer fakeServer;
  58. std::unique_ptr<QSignalSpy> filesChangedSpy;
  59. std::unique_ptr<QSignalSpy> notificationsChangedSpy;
  60. std::unique_ptr<QSignalSpy> activitiesChangedSpy;
  61. auto account = FakeWebSocketServer::createAccount();
  62. QVERIFY(fakeServer.authenticateAccount(
  63. account, [&](OCC::PushNotifications *pushNotifications) {
  64. filesChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::filesChanged));
  65. notificationsChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::notificationsChanged));
  66. activitiesChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::activitiesChanged));
  67. },
  68. [&] {
  69. QVERIFY(verifyCalledOnceWithAccount(*filesChangedSpy, account));
  70. QVERIFY(verifyCalledOnceWithAccount(*notificationsChangedSpy, account));
  71. QVERIFY(verifyCalledOnceWithAccount(*activitiesChangedSpy, account));
  72. }));
  73. }
  74. void testOnWebSocketTextMessageReceived_notifyFileMessage_emitFilesChanged()
  75. {
  76. FakeWebSocketServer fakeServer;
  77. auto account = FakeWebSocketServer::createAccount();
  78. const auto socket = fakeServer.authenticateAccount(account);
  79. QVERIFY(socket);
  80. QSignalSpy filesChangedSpy(account->pushNotifications(), &OCC::PushNotifications::filesChanged);
  81. socket->sendTextMessage("notify_file");
  82. // filesChanged signal should be emitted
  83. QVERIFY(filesChangedSpy.wait());
  84. QVERIFY(verifyCalledOnceWithAccount(filesChangedSpy, account));
  85. }
  86. void testOnWebSocketTextMessageReceived_notifyActivityMessage_emitNotification()
  87. {
  88. FakeWebSocketServer fakeServer;
  89. auto account = FakeWebSocketServer::createAccount();
  90. const auto socket = fakeServer.authenticateAccount(account);
  91. QVERIFY(socket);
  92. QSignalSpy activitySpy(account->pushNotifications(), &OCC::PushNotifications::activitiesChanged);
  93. QVERIFY(activitySpy.isValid());
  94. // Send notify_file push notification
  95. socket->sendTextMessage("notify_activity");
  96. // notification signal should be emitted
  97. QVERIFY(activitySpy.wait());
  98. QVERIFY(verifyCalledOnceWithAccount(activitySpy, account));
  99. }
  100. void testOnWebSocketTextMessageReceived_notifyNotificationMessage_emitNotification()
  101. {
  102. FakeWebSocketServer fakeServer;
  103. auto account = FakeWebSocketServer::createAccount();
  104. const auto socket = fakeServer.authenticateAccount(account);
  105. QVERIFY(socket);
  106. QSignalSpy notificationSpy(account->pushNotifications(), &OCC::PushNotifications::notificationsChanged);
  107. QVERIFY(notificationSpy.isValid());
  108. // Send notify_file push notification
  109. socket->sendTextMessage("notify_notification");
  110. // notification signal should be emitted
  111. QVERIFY(notificationSpy.wait());
  112. QVERIFY(verifyCalledOnceWithAccount(notificationSpy, account));
  113. }
  114. void testOnWebSocketTextMessageReceived_invalidCredentialsMessage_reconnectWebSocket()
  115. {
  116. FakeWebSocketServer fakeServer;
  117. auto account = FakeWebSocketServer::createAccount();
  118. // Need to set reconnect timer interval to zero for tests
  119. account->pushNotifications()->setReconnectTimerInterval(0);
  120. // Wait for authentication attempt and then sent invalid credentials
  121. QVERIFY(fakeServer.waitForTextMessages());
  122. QCOMPARE(fakeServer.textMessagesCount(), 2);
  123. const auto socket = fakeServer.socketForTextMessage(0);
  124. const auto firstPasswordSent = fakeServer.textMessage(1);
  125. QCOMPARE(firstPasswordSent, account->credentials()->password());
  126. fakeServer.clearTextMessages();
  127. socket->sendTextMessage("err: Invalid credentials");
  128. // Wait for a new authentication attempt
  129. QVERIFY(fakeServer.waitForTextMessages());
  130. QCOMPARE(fakeServer.textMessagesCount(), 2);
  131. const auto secondPasswordSent = fakeServer.textMessage(1);
  132. QCOMPARE(secondPasswordSent, account->credentials()->password());
  133. }
  134. void testOnWebSocketError_connectionLost_emitConnectionLost()
  135. {
  136. FakeWebSocketServer fakeServer;
  137. auto account = FakeWebSocketServer::createAccount();
  138. QSignalSpy connectionLostSpy(account->pushNotifications(), &OCC::PushNotifications::connectionLost);
  139. QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
  140. QVERIFY(connectionLostSpy.isValid());
  141. // Wait for authentication and then sent a network error
  142. QVERIFY(fakeServer.waitForTextMessages());
  143. QCOMPARE(fakeServer.textMessagesCount(), 2);
  144. auto socket = fakeServer.socketForTextMessage(0);
  145. socket->abort();
  146. QVERIFY(connectionLostSpy.wait());
  147. // Account handled connectionLost signal and disabled push notifications
  148. QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
  149. }
  150. void testSetup_maxConnectionAttemptsReached_disablePushNotifications()
  151. {
  152. FakeWebSocketServer fakeServer;
  153. auto account = FakeWebSocketServer::createAccount();
  154. QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
  155. QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
  156. // Account disabled the push notifications
  157. QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
  158. }
  159. void testOnWebSocketSslError_sslError_disablePushNotifications()
  160. {
  161. FakeWebSocketServer fakeServer;
  162. auto account = FakeWebSocketServer::createAccount();
  163. QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
  164. QVERIFY(fakeServer.waitForTextMessages());
  165. // FIXME: This a little bit ugly but I had no better idea how to trigger a error on the websocket client.
  166. // The websocket that is retrived through the server is not connected to the ssl error signal.
  167. auto pushNotificationsWebSocketChildren = account->pushNotifications()->findChildren<QWebSocket *>();
  168. QVERIFY(pushNotificationsWebSocketChildren.size() == 1);
  169. emit pushNotificationsWebSocketChildren[0]->sslErrors(QList<QSslError>());
  170. // Account handled connectionLost signal and the authenticationFailed Signal should be emitted
  171. QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
  172. }
  173. void testAccount_web_socket_connectionLost_emitNotificationsDisabled()
  174. {
  175. FakeWebSocketServer fakeServer;
  176. auto account = FakeWebSocketServer::createAccount();
  177. // Need to set reconnect timer interval to zero for tests
  178. account->pushNotifications()->setReconnectTimerInterval(0);
  179. const auto socket = fakeServer.authenticateAccount(account);
  180. QVERIFY(socket);
  181. QSignalSpy connectionLostSpy(account->pushNotifications(), &OCC::PushNotifications::connectionLost);
  182. QVERIFY(connectionLostSpy.isValid());
  183. QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
  184. QVERIFY(pushNotificationsDisabledSpy.isValid());
  185. // Wait for authentication and then sent a network error
  186. socket->abort();
  187. QVERIFY(pushNotificationsDisabledSpy.wait());
  188. QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
  189. QCOMPARE(connectionLostSpy.count(), 1);
  190. auto accountSent = pushNotificationsDisabledSpy.at(0).at(0).value<OCC::Account *>();
  191. QCOMPARE(accountSent, account.data());
  192. }
  193. void testAccount_web_socket_authenticationFailed_emitNotificationsDisabled()
  194. {
  195. FakeWebSocketServer fakeServer;
  196. auto account = FakeWebSocketServer::createAccount();
  197. account->setPushNotificationsReconnectInterval(0);
  198. QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled);
  199. QVERIFY(pushNotificationsDisabledSpy.isValid());
  200. QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
  201. // Now the pushNotificationsDisabled Signal should be emitted
  202. QCOMPARE(pushNotificationsDisabledSpy.count(), 1);
  203. auto accountSent = pushNotificationsDisabledSpy.at(0).at(0).value<OCC::Account *>();
  204. QCOMPARE(accountSent, account.data());
  205. }
  206. void testPingTimeout_pingTimedOut_reconnect()
  207. {
  208. FakeWebSocketServer fakeServer;
  209. std::unique_ptr<QSignalSpy> filesChangedSpy;
  210. std::unique_ptr<QSignalSpy> notificationsChangedSpy;
  211. std::unique_ptr<QSignalSpy> activitiesChangedSpy;
  212. auto account = FakeWebSocketServer::createAccount();
  213. QVERIFY(fakeServer.authenticateAccount(account));
  214. // Set the ping timeout interval to zero and check if the server attemps to authenticate again
  215. fakeServer.clearTextMessages();
  216. account->pushNotifications()->setPingInterval(0);
  217. QVERIFY(fakeServer.authenticateAccount(
  218. account, [&](OCC::PushNotifications *pushNotifications) {
  219. filesChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::filesChanged));
  220. notificationsChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::notificationsChanged));
  221. activitiesChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::activitiesChanged));
  222. },
  223. [&] {
  224. QVERIFY(verifyCalledOnceWithAccount(*filesChangedSpy, account));
  225. QVERIFY(verifyCalledOnceWithAccount(*notificationsChangedSpy, account));
  226. QVERIFY(verifyCalledOnceWithAccount(*activitiesChangedSpy, account));
  227. }));
  228. }
  229. void testTryReconnect_capabilitesReportPushNotificationsAvailable_reconnectForEver()
  230. {
  231. FakeWebSocketServer fakeServer;
  232. auto account = FakeWebSocketServer::createAccount();
  233. account->setPushNotificationsReconnectInterval(0);
  234. // Let if fail a few times
  235. QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
  236. QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
  237. // Push notifications should try to reconnect
  238. QVERIFY(fakeServer.authenticateAccount(account));
  239. }
  240. };
  241. QTEST_GUILESS_MAIN(TestPushNotifications)
  242. #include "testpushnotifications.moc"