Refactor push notification test utils

Signed-off-by: Felix Weilbach <felix.weilbach@nextcloud.com>
This commit is contained in:
Felix Weilbach 2021-04-08 09:29:29 +02:00 коммит произвёл Felix Weilbach (Rebase PR Action)
Родитель c49be218ec
Коммит d7499b0746
3 изменённых файлов: 166 добавлений и 200 удалений

Просмотреть файл

@ -1,8 +1,10 @@
#include <QLoggingCategory>
#include <QSignalSpy>
#include <QTest>
#include <functional>
#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<QSignalSpy>(this, &FakeWebSocketServer::processTextMessage);
}
FakeWebSocketServer::~FakeWebSocketServer()
@ -24,6 +26,46 @@ FakeWebSocketServer::~FakeWebSocketServer()
close();
}
QWebSocket *FakeWebSocketServer::authenticateAccount(const OCC::AccountPtr account, std::function<void(OCC::PushNotifications *pushNotifications)> beforeAuthentication, std::function<void(void)> 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<QWebSocket *>();
}
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;
}

Просмотреть файл

@ -18,6 +18,7 @@
#include <QWebSocketServer>
#include <QWebSocket>
#include <QSignalSpy>
#include "creds/abstractcredentials.h"
#include "account.h"
@ -30,9 +31,22 @@ public:
~FakeWebSocketServer();
QWebSocket *authenticateAccount(
const OCC::AccountPtr account, std::function<void(OCC::PushNotifications *pushNotifications)> beforeAuthentication = [](OCC::PushNotifications *) {}, std::function<void(void)> 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<QWebSocket *> _clients;
std::unique_ptr<QSignalSpy> _processTextMessageSpy;
};
class CredentialsStub : public OCC::AbstractCredentials

Просмотреть файл

@ -3,9 +3,24 @@
#include <QWebSocketServer>
#include <QSignalSpy>
#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<OCC::Account *>();
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<QSignalSpy> filesChangedSpy;
std::unique_ptr<QSignalSpy> notificationsChangedSpy;
std::unique_ptr<QSignalSpy> 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<QWebSocket *>();
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<QWebSocket *>();
socket->sendTextMessage("notify_file");
// filesChanged signal should be emitted
QVERIFY(filesChangedSpy.wait());
QCOMPARE(filesChangedSpy.count(), 1);
auto accountFilesChanged = filesChangedSpy.at(0).at(0).value<OCC::Account *>();
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<QWebSocket *>();
// 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<OCC::Account *>();
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<QWebSocket *>();
// 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<OCC::Account *>();
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<QWebSocket *>();
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<QWebSocket *>();
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<QWebSocket *>();
socket->sendTextMessage("err: Invalid credentials");
QVERIFY(processTextMessageSpy.wait());
QCOMPARE(processTextMessageSpy.count(), 4);
socket = processTextMessageSpy.at(2).at(0).value<QWebSocket *>();
socket->sendTextMessage("err: Invalid credentials");
QVERIFY(processTextMessageSpy.wait());
QCOMPARE(processTextMessageSpy.count(), 6);
socket = processTextMessageSpy.at(4).at(0).value<QWebSocket *>();
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<QWebSocket *>();
@ -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<QWebSocket *>();
// 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<OCC::Account *>();
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<QWebSocket *>();
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<QWebSocket *>();
socket->sendTextMessage("err: Invalid credentials");
QVERIFY(processTextMessageSpy.wait());
QCOMPARE(processTextMessageSpy.count(), 4);
socket = processTextMessageSpy.at(2).at(0).value<QWebSocket *>();
socket->sendTextMessage("err: Invalid credentials");
QVERIFY(processTextMessageSpy.wait());
QCOMPARE(processTextMessageSpy.count(), 6);
socket = processTextMessageSpy.at(4).at(0).value<QWebSocket *>();
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<OCC::Account *>();
QCOMPARE(accountSent, account.data());
}