From d7499b0746b599e48925bc6afce51b44923b2025 Mon Sep 17 00:00:00 2001 From: Felix Weilbach Date: Thu, 8 Apr 2021 09:29:29 +0200 Subject: [PATCH] Refactor push notification test utils Signed-off-by: Felix Weilbach --- test/pushnotificationstestutils.cpp | 86 ++++++++- test/pushnotificationstestutils.h | 18 +- test/testpushnotifications.cpp | 262 ++++++++-------------------- 3 files changed, 166 insertions(+), 200 deletions(-) diff --git a/test/pushnotificationstestutils.cpp b/test/pushnotificationstestutils.cpp index 8dde9f930..60727592f 100644 --- a/test/pushnotificationstestutils.cpp +++ b/test/pushnotificationstestutils.cpp @@ -1,8 +1,10 @@ #include #include #include +#include #include "pushnotificationstestutils.h" +#include "pushnotifications.h" Q_LOGGING_CATEGORY(lcFakeWebSocketServer, "nextcloud.test.fakewebserver", QtInfoMsg) @@ -10,13 +12,13 @@ FakeWebSocketServer::FakeWebSocketServer(quint16 port, QObject *parent) : QObject(parent) , _webSocketServer(new QWebSocketServer(QStringLiteral("Fake Server"), QWebSocketServer::NonSecureMode, this)) { - if (_webSocketServer->listen(QHostAddress::Any, port)) { - connect(_webSocketServer, &QWebSocketServer::newConnection, this, &FakeWebSocketServer::onNewConnection); - connect(_webSocketServer, &QWebSocketServer::closed, this, &FakeWebSocketServer::closed); - qCInfo(lcFakeWebSocketServer) << "Open fake websocket server on port:" << port; - return; + if (!_webSocketServer->listen(QHostAddress::Any, port)) { + Q_UNREACHABLE(); } - Q_UNREACHABLE(); + connect(_webSocketServer, &QWebSocketServer::newConnection, this, &FakeWebSocketServer::onNewConnection); + connect(_webSocketServer, &QWebSocketServer::closed, this, &FakeWebSocketServer::closed); + qCInfo(lcFakeWebSocketServer) << "Open fake websocket server on port:" << port; + _processTextMessageSpy = std::make_unique(this, &FakeWebSocketServer::processTextMessage); } FakeWebSocketServer::~FakeWebSocketServer() @@ -24,6 +26,46 @@ FakeWebSocketServer::~FakeWebSocketServer() close(); } +QWebSocket *FakeWebSocketServer::authenticateAccount(const OCC::AccountPtr account, std::function beforeAuthentication, std::function afterAuthentication) +{ + const auto pushNotifications = account->pushNotifications(); + Q_ASSERT(pushNotifications); + QSignalSpy readySpy(pushNotifications, &OCC::PushNotifications::ready); + + beforeAuthentication(pushNotifications); + + // Wait for authentication + if (!waitForTextMessages()) { + return nullptr; + } + + // Right authentication data should be sent + if (textMessagesCount() != 2) { + return nullptr; + } + + const auto socket = socketForTextMessage(0); + const auto userSent = textMessage(0); + const auto passwordSent = textMessage(1); + + if (userSent != account->credentials()->user() || passwordSent != account->credentials()->password()) { + return nullptr; + } + + // Sent authenticated + socket->sendTextMessage("authenticated"); + + // Wait for ready signal + readySpy.wait(); + if (readySpy.count() != 1 || !account->pushNotifications()->isReady()) { + return nullptr; + } + + afterAuthentication(); + + return socket; +} + void FakeWebSocketServer::close() { if (_webSocketServer->isListening()) { @@ -64,7 +106,34 @@ void FakeWebSocketServer::socketDisconnected() } } -OCC::AccountPtr FakeWebSocketServer::createAccount() +bool FakeWebSocketServer::waitForTextMessages() const +{ + return _processTextMessageSpy->wait(); +} + +uint32_t FakeWebSocketServer::textMessagesCount() const +{ + return _processTextMessageSpy->count(); +} + +QString FakeWebSocketServer::textMessage(uint32_t messageNumber) const +{ + Q_ASSERT(messageNumber < _processTextMessageSpy->count()); + return _processTextMessageSpy->at(messageNumber).at(1).toString(); +} + +QWebSocket *FakeWebSocketServer::socketForTextMessage(uint32_t messageNumber) const +{ + Q_ASSERT(messageNumber < _processTextMessageSpy->count()); + return _processTextMessageSpy->at(messageNumber).at(0).value(); +} + +void FakeWebSocketServer::clearTextMessages() +{ + _processTextMessageSpy->clear(); +} + +OCC::AccountPtr FakeWebSocketServer::createAccount(const QString &username, const QString &password) { auto account = OCC::Account::create(); @@ -87,6 +156,9 @@ OCC::AccountPtr FakeWebSocketServer::createAccount() account->setCapabilities(capabilitiesMap); + auto credentials = new CredentialsStub(username, password); + account->setCredentials(credentials); + return account; } diff --git a/test/pushnotificationstestutils.h b/test/pushnotificationstestutils.h index c6b5b7d3b..12c14d1e5 100644 --- a/test/pushnotificationstestutils.h +++ b/test/pushnotificationstestutils.h @@ -18,6 +18,7 @@ #include #include +#include #include "creds/abstractcredentials.h" #include "account.h" @@ -30,9 +31,22 @@ public: ~FakeWebSocketServer(); + QWebSocket *authenticateAccount( + const OCC::AccountPtr account, std::function beforeAuthentication = [](OCC::PushNotifications *) {}, std::function afterAuthentication = [] {}); + void close(); - static OCC::AccountPtr createAccount(); + bool waitForTextMessages() const; + + uint32_t textMessagesCount() const; + + QString textMessage(uint32_t messageNumber) const; + + QWebSocket *socketForTextMessage(uint32_t messageNumber) const; + + void clearTextMessages(); + + static OCC::AccountPtr createAccount(const QString &username = "user", const QString &password = "password"); signals: void closed(); @@ -46,6 +60,8 @@ private slots: private: QWebSocketServer *_webSocketServer; QList _clients; + + std::unique_ptr _processTextMessageSpy; }; class CredentialsStub : public OCC::AbstractCredentials diff --git a/test/testpushnotifications.cpp b/test/testpushnotifications.cpp index e5e1010df..161ae94b8 100644 --- a/test/testpushnotifications.cpp +++ b/test/testpushnotifications.cpp @@ -3,9 +3,24 @@ #include #include +#include "accountfwd.h" #include "pushnotifications.h" #include "pushnotificationstestutils.h" +bool verifyCalledOnceWithAccount(QSignalSpy &spy, OCC::AccountPtr account) +{ + if (spy.count() != 1) { + return false; + } + + auto accountFromSpy = spy.at(0).at(0).value(); + if (accountFromSpy != account.data()) { + return false; + } + + return true; +} + class TestPushNotifications : public QObject { Q_OBJECT @@ -14,170 +29,107 @@ private slots: void testSetup_correctCredentials_authenticateAndEmitReady() { FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - const QString user = "user"; - const QString password = "password"; + std::unique_ptr filesChangedSpy; + std::unique_ptr notificationsChangedSpy; + std::unique_ptr activitiesChangedSpy; auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); - QSignalSpy readySpy(account->pushNotifications(), &OCC::PushNotifications::ready); - QVERIFY(readySpy.isValid()); - // Wait for authentication - QVERIFY(processTextMessageSpy.wait()); - - // Right authentication data should be sent - QCOMPARE(processTextMessageSpy.count(), 2); - - const auto socket = processTextMessageSpy.at(0).at(0).value(); - const auto userSent = processTextMessageSpy.at(0).at(1).toString(); - const auto passwordSent = processTextMessageSpy.at(1).at(1).toString(); - - QCOMPARE(userSent, user); - QCOMPARE(passwordSent, password); - - // Sent authenticated - socket->sendTextMessage("authenticated"); - - // Wait for ready signal - readySpy.wait(); - QCOMPARE(readySpy.count(), 1); - QCOMPARE(account->pushNotifications()->isReady(), true); + QVERIFY(fakeServer.authenticateAccount( + account, [&](OCC::PushNotifications *pushNotifications) { + filesChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::filesChanged)); + notificationsChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::notificationsChanged)); + activitiesChangedSpy.reset(new QSignalSpy(pushNotifications, &OCC::PushNotifications::activitiesChanged)); + }, + [&] { + QVERIFY(verifyCalledOnceWithAccount(*filesChangedSpy, account)); + QVERIFY(verifyCalledOnceWithAccount(*notificationsChangedSpy, account)); + QVERIFY(verifyCalledOnceWithAccount(*activitiesChangedSpy, account)); + })); } void testOnWebSocketTextMessageReceived_notifyFileMessage_emitFilesChanged() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); + const auto socket = fakeServer.authenticateAccount(account); + QVERIFY(socket); QSignalSpy filesChangedSpy(account->pushNotifications(), &OCC::PushNotifications::filesChanged); - QVERIFY(filesChangedSpy.isValid()); - // Wait for authentication and then send notify_file push notification - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - const auto socket = processTextMessageSpy.at(0).at(0).value(); socket->sendTextMessage("notify_file"); // filesChanged signal should be emitted QVERIFY(filesChangedSpy.wait()); - QCOMPARE(filesChangedSpy.count(), 1); - auto accountFilesChanged = filesChangedSpy.at(0).at(0).value(); - QCOMPARE(accountFilesChanged, account.data()); + QVERIFY(verifyCalledOnceWithAccount(filesChangedSpy, account)); } void testOnWebSocketTextMessageReceived_notifyActivityMessage_emitNotification() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); + const auto socket = fakeServer.authenticateAccount(account); + QVERIFY(socket); QSignalSpy activitySpy(account->pushNotifications(), &OCC::PushNotifications::activitiesChanged); QVERIFY(activitySpy.isValid()); - // Wait for authentication and then send notify_file push notification - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - const auto socket = processTextMessageSpy.at(0).at(0).value(); + // Send notify_file push notification socket->sendTextMessage("notify_activity"); // notification signal should be emitted QVERIFY(activitySpy.wait()); - QCOMPARE(activitySpy.count(), 1); - auto accountFilesChanged = activitySpy.at(0).at(0).value(); - QCOMPARE(accountFilesChanged, account.data()); + QVERIFY(verifyCalledOnceWithAccount(activitySpy, account)); } void testOnWebSocketTextMessageReceived_notifyNotificationMessage_emitNotification() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); + const auto socket = fakeServer.authenticateAccount(account); + QVERIFY(socket); QSignalSpy notificationSpy(account->pushNotifications(), &OCC::PushNotifications::notificationsChanged); QVERIFY(notificationSpy.isValid()); - // Wait for authentication and then send notify_file push notification - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - const auto socket = processTextMessageSpy.at(0).at(0).value(); + // Send notify_file push notification socket->sendTextMessage("notify_notification"); // notification signal should be emitted QVERIFY(notificationSpy.wait()); - QCOMPARE(notificationSpy.count(), 1); - auto accountFilesChanged = notificationSpy.at(0).at(0).value(); - QCOMPARE(accountFilesChanged, account.data()); + QVERIFY(verifyCalledOnceWithAccount(notificationSpy, account)); } void testOnWebSocketTextMessageReceived_invalidCredentialsMessage_reconnectWebSocket() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); // Need to set reconnect timer interval to zero for tests account->pushNotifications()->setReconnectTimerInterval(0); // Wait for authentication attempt and then sent invalid credentials - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - const auto socket = processTextMessageSpy.at(0).at(0).value(); - const auto firstPasswordSent = processTextMessageSpy.at(1).at(1).toString(); - QCOMPARE(firstPasswordSent, password); - processTextMessageSpy.clear(); + QVERIFY(fakeServer.waitForTextMessages()); + QCOMPARE(fakeServer.textMessagesCount(), 2); + const auto socket = fakeServer.socketForTextMessage(0); + const auto firstPasswordSent = fakeServer.textMessage(1); + QCOMPARE(firstPasswordSent, account->credentials()->password()); + fakeServer.clearTextMessages(); socket->sendTextMessage("err: Invalid credentials"); // Wait for a new authentication attempt - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - const auto secondPasswordSent = processTextMessageSpy.at(1).at(1).toString(); - QCOMPARE(secondPasswordSent, password); + QVERIFY(fakeServer.waitForTextMessages()); + QCOMPARE(fakeServer.textMessagesCount(), 2); + const auto secondPasswordSent = fakeServer.textMessage(1); + QCOMPARE(secondPasswordSent, account->credentials()->password()); } void testOnWebSocketError_connectionLost_emitConnectionLost() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); - // Need to set reconnect timer interval to zero for tests - account->pushNotifications()->setReconnectTimerInterval(0); - QSignalSpy connectionLostSpy(account->pushNotifications(), &OCC::PushNotifications::connectionLost); QVERIFY(connectionLostSpy.isValid()); // Wait for authentication and then sent a network error - processTextMessageSpy.wait(); - QCOMPARE(processTextMessageSpy.count(), 2); - auto socket = processTextMessageSpy.at(0).at(0).value(); + QVERIFY(fakeServer.waitForTextMessages()); + QCOMPARE(fakeServer.textMessagesCount(), 2); + auto socket = fakeServer.socketForTextMessage(0); socket->abort(); QVERIFY(connectionLostSpy.wait()); @@ -187,57 +139,34 @@ private slots: void testSetup_maxConnectionAttemptsReached_deletePushNotifications() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); account->pushNotifications()->setReconnectTimerInterval(0); QSignalSpy authenticationFailedSpy(account->pushNotifications(), &OCC::PushNotifications::authenticationFailed); QVERIFY(authenticationFailedSpy.isValid()); // Let three authentication attempts fail - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - auto socket = processTextMessageSpy.at(0).at(0).value(); - socket->sendTextMessage("err: Invalid credentials"); - - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 4); - socket = processTextMessageSpy.at(2).at(0).value(); - socket->sendTextMessage("err: Invalid credentials"); - - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 6); - socket = processTextMessageSpy.at(4).at(0).value(); - socket->sendTextMessage("err: Invalid credentials"); + for (uint8_t i = 0; i < 3; ++i) { + QVERIFY(fakeServer.waitForTextMessages()); + QCOMPARE(fakeServer.textMessagesCount(), 2); + auto socket = fakeServer.socketForTextMessage(0); + fakeServer.clearTextMessages(); + socket->sendTextMessage("err: Invalid credentials"); + } // Now the authenticationFailed Signal should be emitted QVERIFY(authenticationFailedSpy.wait()); QCOMPARE(authenticationFailedSpy.count(), 1); - // Account deleted the push notifications QCOMPARE(account->pushNotifications(), nullptr); } void testOnWebSocketSslError_sslError_deletePushNotifications() { - const QString user = "user"; - const QString password = "password"; FakeWebSocketServer fakeServer; - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - auto account = FakeWebSocketServer::createAccount(); - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); - - processTextMessageSpy.wait(); + QVERIFY(fakeServer.waitForTextMessages()); // FIXME: This a little bit ugly but I had no better idea how to trigger a error on the websocket client. // The websocket that is retrived through the server is not connected to the ssl error signal. auto pushNotificationsWebSocketChildren = account->pushNotifications()->findChildren(); @@ -248,45 +177,14 @@ private slots: QCOMPARE(account->pushNotifications(), nullptr); } - void testAccountSetCredentials_correctCredentials_emitPushNotificationsReady() - { - FakeWebSocketServer fakeServer; - auto account = FakeWebSocketServer::createAccount(); - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - const QString user = "user"; - const QString password = "password"; - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); - - QSignalSpy pushNotificationsReady(account.data(), &OCC::Account::pushNotificationsReady); - QVERIFY(pushNotificationsReady.isValid()); - - // Wait for authentication - QVERIFY(processTextMessageSpy.wait()); - auto socket = processTextMessageSpy.at(0).at(0).value(); - // Don't care about which message was sent - socket->sendTextMessage("authenticated"); - - // Wait for push notifactions ready signal - QVERIFY(pushNotificationsReady.wait()); - auto accountSent = pushNotificationsReady.at(0).at(0).value(); - QCOMPARE(accountSent, account.data()); - } - void testAccount_web_socket_connectionLost_emitNotificationsDisabled() { FakeWebSocketServer fakeServer; auto account = FakeWebSocketServer::createAccount(); - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - const QString user = "user"; - const QString password = "password"; - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); - // Need to set reconnect timer interval to zero for tests account->pushNotifications()->setReconnectTimerInterval(0); + const auto socket = fakeServer.authenticateAccount(account); + QVERIFY(socket); QSignalSpy connectionLostSpy(account->pushNotifications(), &OCC::PushNotifications::connectionLost); QVERIFY(connectionLostSpy.isValid()); @@ -295,9 +193,6 @@ private slots: QVERIFY(pushNotificationsDisabledSpy.isValid()); // Wait for authentication and then sent a network error - processTextMessageSpy.wait(); - QCOMPARE(processTextMessageSpy.count(), 2); - auto socket = processTextMessageSpy.at(0).at(0).value(); socket->abort(); QVERIFY(pushNotificationsDisabledSpy.wait()); @@ -313,42 +208,25 @@ private slots: { FakeWebSocketServer fakeServer; auto account = FakeWebSocketServer::createAccount(); - QSignalSpy processTextMessageSpy(&fakeServer, &FakeWebSocketServer::processTextMessage); - QVERIFY(processTextMessageSpy.isValid()); - const QString user = "user"; - const QString password = "password"; - auto credentials = new CredentialsStub(user, password); - account->setCredentials(credentials); - account->pushNotifications()->setReconnectTimerInterval(0); QSignalSpy authenticationFailedSpy(account->pushNotifications(), &OCC::PushNotifications::authenticationFailed); QVERIFY(authenticationFailedSpy.isValid()); - QSignalSpy pushNotificationsDisabledSpy(account.data(), &OCC::Account::pushNotificationsDisabled); QVERIFY(pushNotificationsDisabledSpy.isValid()); // Let three authentication attempts fail - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 2); - auto socket = processTextMessageSpy.at(0).at(0).value(); - socket->sendTextMessage("err: Invalid credentials"); - - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 4); - socket = processTextMessageSpy.at(2).at(0).value(); - socket->sendTextMessage("err: Invalid credentials"); - - QVERIFY(processTextMessageSpy.wait()); - QCOMPARE(processTextMessageSpy.count(), 6); - socket = processTextMessageSpy.at(4).at(0).value(); - socket->sendTextMessage("err: Invalid credentials"); + for (uint8_t i = 0; i < 3; ++i) { + QVERIFY(fakeServer.waitForTextMessages()); + QCOMPARE(fakeServer.textMessagesCount(), 2); + auto socket = fakeServer.socketForTextMessage(0); + fakeServer.clearTextMessages(); + socket->sendTextMessage("err: Invalid credentials"); + } // Now the authenticationFailed and pushNotificationsDisabled Signals should be emitted QVERIFY(pushNotificationsDisabledSpy.wait()); QCOMPARE(pushNotificationsDisabledSpy.count(), 1); - QCOMPARE(authenticationFailedSpy.count(), 1); - auto accountSent = pushNotificationsDisabledSpy.at(0).at(0).value(); QCOMPARE(accountSent, account.data()); }