diff --git a/create-coverage-html.sh b/create-coverage-html.sh index cbbc6d1..a6908b8 100755 --- a/create-coverage-html.sh +++ b/create-coverage-html.sh @@ -6,7 +6,7 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/libnymea-remoteproxy:$(pwd)/libny # Build qmake CONFIG+=coverage CONFIG+=ccache make -j$(nproc) -#make check +#make test make coverage-html # Clean build diff --git a/libnymea-remoteproxy/authentication/authenticationreply.cpp b/libnymea-remoteproxy/authentication/authenticationreply.cpp index c7fa483..79bf9f4 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.cpp +++ b/libnymea-remoteproxy/authentication/authenticationreply.cpp @@ -26,23 +26,30 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "engine.h" +#include "loggingcategories.h" #include "authenticationreply.h" #include "authentication/authenticator.h" namespace remoteproxy { AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *parent) : - QObject(parent), - m_proxyClient(proxyClient) + QObject(parent) { + m_proxyClient = proxyClient; + m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, &QTimer::timeout, this, &AuthenticationReply::onTimeout); - + qCDebug(dcAuthentication) << "Created authentication reply for" << proxyClient << "Timeout:" << Engine::instance()->configuration()->authenticationTimeout() << "[ms]"; m_timer->start(Engine::instance()->configuration()->authenticationTimeout()); } -ProxyClient *AuthenticationReply::proxyClient() const +AuthenticationReply::~AuthenticationReply() +{ + qCCritical(dcAuthentication()) << "Destroy authentication reply"; +} + +QPointer AuthenticationReply::proxyClient() const { return m_proxyClient; } diff --git a/libnymea-remoteproxy/authentication/authenticationreply.h b/libnymea-remoteproxy/authentication/authenticationreply.h index e57bf69..cf4d82f 100644 --- a/libnymea-remoteproxy/authentication/authenticationreply.h +++ b/libnymea-remoteproxy/authentication/authenticationreply.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -44,7 +45,7 @@ class AuthenticationReply : public QObject public: friend class Authenticator; - ProxyClient *proxyClient() const; + QPointer proxyClient() const; bool isTimedOut() const; bool isFinished() const; @@ -53,7 +54,9 @@ public: private: explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr); - ProxyClient *m_proxyClient = nullptr; + ~AuthenticationReply(); + + QPointer m_proxyClient; QTimer *m_timer = nullptr; bool m_timedOut = false; diff --git a/libnymea-remoteproxy/authentication/authenticator.cpp b/libnymea-remoteproxy/authentication/authenticator.cpp index d6c1b7f..329d76e 100644 --- a/libnymea-remoteproxy/authentication/authenticator.cpp +++ b/libnymea-remoteproxy/authentication/authenticator.cpp @@ -37,6 +37,11 @@ Authenticator::Authenticator(QObject *parent) : } +Authenticator::~Authenticator() +{ + +} + void Authenticator::setReplyError(AuthenticationReply *reply, Authenticator::AuthenticationError error) { reply->setError(error); @@ -52,9 +57,4 @@ AuthenticationReply *Authenticator::createAuthenticationReply(ProxyClient *proxy return new AuthenticationReply(proxyClient, parent); } -Authenticator::~Authenticator() -{ - -} - } diff --git a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp index 127058f..57ab1fb 100644 --- a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp @@ -89,7 +89,7 @@ void AuthenticationHandler::onAuthenticationFinished() AuthenticationReply *authenticationReply = static_cast(sender()); authenticationReply->deleteLater(); - qCDebug(dcJsonRpc()) << "Authentication response ready for" << authenticationReply->proxyClient() << authenticationReply->error(); + qCDebug(dcJsonRpc()) << "Authentication reply finished"; JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply); if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) { @@ -100,10 +100,12 @@ void AuthenticationHandler::onAuthenticationFinished() jsonReply->setSuccess(true); } - // Set client authenticated - authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError); + // Set client authenticated if still there + if (!authenticationReply->proxyClient().isNull()) { + authenticationReply->proxyClient()->setAuthenticated(authenticationReply->error() == Authenticator::AuthenticationErrorNoError); + jsonReply->setData(errorToReply(authenticationReply->error())); + } - jsonReply->setData(errorToReply(authenticationReply->error())); jsonReply->finished(); } diff --git a/libnymea-remoteproxy/jsonrpcserver.cpp b/libnymea-remoteproxy/jsonrpcserver.cpp index 8b64e0f..b74d3a8 100644 --- a/libnymea-remoteproxy/jsonrpcserver.cpp +++ b/libnymea-remoteproxy/jsonrpcserver.cpp @@ -188,7 +188,7 @@ void JsonRpcServer::asyncReplyFinished() qCDebug(dcJsonRpc()) << "Async reply finished" << reply->handler()->name() << reply->method() << reply->clientId().toString(); if (!proxyClient) { - qCWarning(dcJsonRpc()) << "Got an async reply but the client does not exist any more"; + qCWarning(dcJsonRpc()) << "Got an async reply but the client does not exist any more."; return; } @@ -210,6 +210,7 @@ void JsonRpcServer::asyncReplyFinished() } } else { + qCWarning(dcJsonRpc()) << "The reply timeouted."; sendErrorResponse(proxyClient, reply->commandId(), "Command timed out"); // Disconnect this client since he requested something that created a timeout proxyClient->killConnection("API call timeouted."); @@ -233,8 +234,14 @@ void JsonRpcServer::unregisterClient(ProxyClient *proxyClient) qCWarning(dcJsonRpc()) << "Client was not registered" << proxyClient; return; } - m_clients.removeAll(proxyClient); + + if (m_asyncReplies.values().contains(proxyClient)) { + qCWarning(dcJsonRpc()) << "Client was still waiting for a reply. Clean up reply"; + JsonReply *reply = m_asyncReplies.key(proxyClient); + m_asyncReplies.remove(reply); + // Note: the reply will be deleted in the finished slot + } } void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data) @@ -246,7 +253,6 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); - if(error.error != QJsonParseError::NoError) { qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString(); sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString())); diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index 7a95e90..10784f3 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -40,9 +40,10 @@ ProxyClient::ProxyClient(TransportInterface *interface, const QUuid &clientId, c { m_creationTimeStamp = QDateTime::currentDateTime().toTime_t(); - connect(&m_timer, &QTimer::timeout, this, &ProxyClient::timeoutOccured); - m_timer.setSingleShot(true); - m_timer.start(Engine::instance()->configuration()->inactiveTimeout()); + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &ProxyClient::timeoutOccured); + m_timer->setSingleShot(true); + resetTimer(); } QUuid ProxyClient::clientId() const @@ -74,8 +75,8 @@ void ProxyClient::setAuthenticated(bool isAuthenticated) { m_authenticated = isAuthenticated; if (m_authenticated) { - m_timer.stop(); - m_timer.start(Engine::instance()->configuration()->aloneTimeout()); + m_timerWaitState = TimerWaitStateAlone; + resetTimer(); emit authenticated(); } } @@ -89,7 +90,7 @@ void ProxyClient::setTunnelConnected(bool isTunnelConnected) { m_tunnelConnected = isTunnelConnected; if (m_tunnelConnected) { - m_timer.stop(); + m_timer->stop(); emit tunnelConnected(); } } @@ -174,6 +175,25 @@ void ProxyClient::addTxDataCount(int dataCount) m_txDataCount += static_cast(dataCount); } +ProxyClient::TimerWaitState ProxyClient::timerWaitState() const +{ + return m_timerWaitState; +} + +void ProxyClient::resetTimer() +{ + switch (m_timerWaitState) { + case TimerWaitStateInactive: + m_timer->stop(); + m_timer->start(Engine::instance()->configuration()->inactiveTimeout()); + break; + case TimerWaitStateAlone: + m_timer->stop(); + m_timer->start(Engine::instance()->configuration()->aloneTimeout()); + break; + } +} + void ProxyClient::sendData(const QByteArray &data) { if (!m_interface) @@ -200,8 +220,8 @@ QDebug operator<<(QDebug debug, ProxyClient *proxyClient) debug.nospace() << ", " << proxyClient->clientId().toString(); debug.nospace() << ", " << proxyClient->userName(); debug.nospace() << ", " << proxyClient->peerAddress().toString(); - debug.nospace() << ", " << proxyClient->creationTimeString() << ") "; - return debug; + debug.nospace() << ", " << proxyClient->creationTimeString() << ")"; + return debug.space(); } } diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index 58b83ac..792714f 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -43,6 +43,12 @@ class ProxyClient : public QObject Q_OBJECT public: + enum TimerWaitState { + TimerWaitStateInactive, + TimerWaitStateAlone + }; + Q_ENUM(TimerWaitState) + explicit ProxyClient(TransportInterface *interface, const QUuid &clientId, const QHostAddress &address, QObject *parent = nullptr); QUuid clientId() const; @@ -84,12 +90,15 @@ public: void addTxDataCount(int dataCount); // Actions for this client + TimerWaitState timerWaitState() const; + void resetTimer(); void sendData(const QByteArray &data); void killConnection(const QString &reason); private: TransportInterface *m_interface = nullptr; - QTimer m_timer; + QTimer *m_timer = nullptr; + TimerWaitState m_timerWaitState = TimerWaitStateInactive; QUuid m_clientId; QHostAddress m_peerAddress; diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index c7f20c1..6048114 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -274,6 +274,8 @@ void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray if (!proxyClient->isAuthenticated() && !proxyClient->isTunnelConnected()) { qCDebug(dcProxyServerTraffic()) << "Client data available" << proxyClient << qUtf8Printable(data); m_jsonRpcServer->processData(proxyClient, data); + // Reset the inactive timer + proxyClient->resetTimer(); return; } @@ -373,8 +375,15 @@ void ProxyServer::onProxyClientAuthenticated() void ProxyServer::onProxyClientTimeoutOccured() { ProxyClient *proxyClient = static_cast(sender()); - qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient; - proxyClient->killConnection("Proxy timeout occuret"); + qCDebug(dcProxyServer()) << "Timeout occured for" << proxyClient; + switch (proxyClient->timerWaitState()) { + case ProxyClient::TimerWaitStateInactive: + proxyClient->killConnection("Proxy timeout occuret. The socket was inactive."); + break; + case ProxyClient::TimerWaitStateAlone: + proxyClient->killConnection("Proxy timeout occuret. The tunnel partner did not show up."); + break; + } } void ProxyServer::startServer() diff --git a/libnymea-remoteproxy/tcpsocketserver.cpp b/libnymea-remoteproxy/tcpsocketserver.cpp index 40725f3..7fd358d 100644 --- a/libnymea-remoteproxy/tcpsocketserver.cpp +++ b/libnymea-remoteproxy/tcpsocketserver.cpp @@ -46,7 +46,10 @@ void TcpSocketServer::sendData(const QUuid &clientId, const QByteArray &data) return; } - client->write(data + '\n'); + qCDebug(dcTcpSocketServerTraffic()) << "Send data to" << clientId.toString() << data + '\n'; + if (client->write(data + '\n') < 0) { + qCWarning(dcTcpSocketServer()) << "Could not write data to client socket" << clientId.toString(); + } } void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason) @@ -56,7 +59,7 @@ void TcpSocketServer::killClientConnection(const QUuid &clientId, const QString return; qCWarning(dcTcpSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; - client->abort(); + client->close(); } bool TcpSocketServer::running() const @@ -69,7 +72,7 @@ bool TcpSocketServer::running() const void TcpSocketServer::onDataAvailable(QSslSocket *client, const QByteArray &data) { - qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal."; + //qCDebug(dcTcpSocketServerTraffic()) << "Emitting data available internal."; QUuid clientId = m_clientList.key(qobject_cast(client)); emit dataAvailable(clientId, data); } @@ -85,7 +88,7 @@ void TcpSocketServer::onClientConnected(QSslSocket *client) void TcpSocketServer::onClientDisconnected(QSslSocket *client) { QUuid clientId = m_clientList.key(client); - qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); + qCDebug(dcTcpSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString(); m_clientList.take(clientId); // Note: the SslServer is deleting the socket object emit clientDisconnected(clientId); @@ -138,12 +141,13 @@ SslServer::SslServer(bool sslEnabled, const QSslConfiguration &config, QObject * void SslServer::incomingConnection(qintptr socketDescriptor) { QSslSocket *sslSocket = new QSslSocket(this); - - qCDebug(dcTcpSocketServer()) << "New client connected:" << sslSocket << sslSocket->peerAddress().toString(); - - connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){ emit clientConnected(sslSocket); }); + qCDebug(dcTcpSocketServer()) << "Incomming connection" << sslSocket; connect(sslSocket, &QSslSocket::readyRead, this, &SslServer::onSocketReadyRead); connect(sslSocket, &QSslSocket::disconnected, this, &SslServer::onClientDisconnected); + connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket](){ + qCDebug(dcTcpSocketServer()) << "SSL encryption established for" << sslSocket; + emit clientConnected(sslSocket); + }); if (!sslSocket->setSocketDescriptor(socketDescriptor)) { qCWarning(dcTcpSocketServer()) << "Failed to set SSL socket descriptor."; @@ -153,6 +157,7 @@ void SslServer::incomingConnection(qintptr socketDescriptor) if (m_sslEnabled) { sslSocket->setSslConfiguration(m_config); + qCDebug(dcTcpSocketServer()) << "Start SSL encryption for" << sslSocket; sslSocket->startServerEncryption(); } else { emit clientConnected(sslSocket); diff --git a/libnymea-remoteproxy/websocketserver.cpp b/libnymea-remoteproxy/websocketserver.cpp index 205fd17..d7ffa66 100644 --- a/libnymea-remoteproxy/websocketserver.cpp +++ b/libnymea-remoteproxy/websocketserver.cpp @@ -78,8 +78,6 @@ void WebSocketServer::killClientConnection(const QUuid &clientId, const QString qCWarning(dcWebSocketServer()) << "Killing client connection" << clientId.toString() << "Reason:" << killReason; client->close(QWebSocketProtocol::CloseCodeBadOperation, killReason); - client->flush(); - client->abort(); } void WebSocketServer::onClientConnected() @@ -95,9 +93,7 @@ void WebSocketServer::onClientConnected() if (client->version() != QWebSocketProtocol::Version13) { qCWarning(dcWebSocketServer()) << "Client with invalid protocol version" << client->version() << ". Rejecting."; client->close(QWebSocketProtocol::CloseCodeProtocolError, QString("invalid protocol version: %1 != Supported Version 13").arg(client->version())); - client->flush(); - client->abort(); - delete client; + client->deleteLater(); return; } @@ -125,8 +121,6 @@ void WebSocketServer::onClientDisconnected() // Manually close it in any case client->close(); - client->flush(); - client->abort(); m_clientList.take(clientId)->deleteLater(); emit clientDisconnected(clientId); @@ -145,8 +139,6 @@ void WebSocketServer::onBinaryMessageReceived(const QByteArray &data) qCWarning(dcWebSocketServerTraffic()) << "<-- Binary message from" << client->peerAddress().toString() << ":" << data; // Note: this is not expected, so close this client connection. client->close(QWebSocketProtocol::CloseCodeBadOperation, "Binary message not expected."); - client->flush(); - client->abort(); } void WebSocketServer::onClientError(QAbstractSocket::SocketError error) @@ -156,8 +148,6 @@ void WebSocketServer::onClientError(QAbstractSocket::SocketError error) // Note: on any error which can occure, make sure the socket will be closed in any case client->close(); - client->flush(); - client->abort(); } void WebSocketServer::onAcceptError(QAbstractSocket::SocketError error) diff --git a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri index 6ae9be7..d1d89c8 100644 --- a/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri +++ b/libnymea-remoteproxyclient/libnymea-remoteproxyclient.pri @@ -1,7 +1,7 @@ INCLUDEPATH += $${PWD} HEADERS += \ - $$PWD/tcpsocketconnection.h \ + $${PWD}/tcpsocketconnection.h \ $${PWD}/proxyjsonrpcclient.h \ $${PWD}/jsonreply.h \ $${PWD}/remoteproxyconnection.h \ @@ -9,7 +9,7 @@ HEADERS += \ $${PWD}/websocketconnection.h SOURCES += \ - $$PWD/tcpsocketconnection.cpp \ + $${PWD}/tcpsocketconnection.cpp \ $${PWD}/proxyjsonrpcclient.cpp \ $${PWD}/jsonreply.cpp \ $${PWD}/remoteproxyconnection.cpp \ diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index 73e0a10..dde5028 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -319,23 +319,18 @@ void RemoteProxyConnection::onTunnelEstablished(const QString &clientName, const bool RemoteProxyConnection::connectServer(const QUrl &url) { - if (url.scheme() != "wss") { - // FIXME: support also tcp - qCWarning(dcRemoteProxyClientConnection()) << "Unsupported connection type" << url.scheme() << "Default to wss"; - m_serverUrl.setScheme("wss"); - } - m_serverUrl = url; - m_connectionType = ConnectionTypeWebSocket; m_error = QAbstractSocket::UnknownSocketError; cleanUp(); switch (m_connectionType) { case ConnectionTypeWebSocket: + qCDebug(dcRemoteProxyClientConnection()) << "Creating a web socket connection to" << url.toString(); m_connection = qobject_cast(new WebSocketConnection(this)); break; case ConnectionTypeTcpSocket: + qCDebug(dcRemoteProxyClientConnection()) << "Creating a TCP socket connection to" << url.toString(); m_connection = qobject_cast(new TcpSocketConnection(this)); break; } diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.cpp b/libnymea-remoteproxyclient/tcpsocketconnection.cpp index 13bbf13..4ad69a3 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.cpp +++ b/libnymea-remoteproxyclient/tcpsocketconnection.cpp @@ -31,6 +31,7 @@ TcpSocketConnection::TcpSocketConnection(QObject *parent) : connect(m_tcpSocket, &QSslSocket::disconnected, this, &TcpSocketConnection::onDisconnected); connect(m_tcpSocket, &QSslSocket::encrypted, this, &TcpSocketConnection::onEncrypted); + connect(m_tcpSocket, &QSslSocket::readyRead, this, &TcpSocketConnection::onReadyRead); connect(m_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); connect(m_tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState))); connect(m_tcpSocket, SIGNAL(sslErrors(QList)), this, SIGNAL(sslErrors(QList))); @@ -65,6 +66,7 @@ void TcpSocketConnection::onDisconnected() void TcpSocketConnection::onEncrypted() { qCDebug(dcRemoteProxyClientTcpSocket()) << "Connection encrypted"; + setConnected(true); } void TcpSocketConnection::onError(QAbstractSocket::SocketError error) @@ -80,7 +82,9 @@ void TcpSocketConnection::onStateChanged(QAbstractSocket::SocketState state) switch (state) { case QAbstractSocket::ConnectedState: qCDebug(dcRemoteProxyClientTcpSocket()) << "Connected with" << serverUrl().toString(); - setConnected(true); + if (!m_ssl) { + setConnected(true); + } break; default: setConnected(false); @@ -97,12 +101,13 @@ void TcpSocketConnection::onReadyRead() void TcpSocketConnection::connectServer(const QUrl &serverUrl) { - qCDebug(dcRemoteProxyClientTcpSocket()) << "Connecting to" << this->serverUrl().toString(); setServerUrl(serverUrl); + qCDebug(dcRemoteProxyClientTcpSocket()) << "Connecting to" << this->serverUrl().toString(); if (serverUrl.scheme() == "tcp") { m_tcpSocket->connectToHost(QHostAddress(this->serverUrl().host()), static_cast(this->serverUrl().port())); } else { + m_ssl = true; m_tcpSocket->connectToHostEncrypted(this->serverUrl().host(), static_cast(this->serverUrl().port())); } } diff --git a/libnymea-remoteproxyclient/tcpsocketconnection.h b/libnymea-remoteproxyclient/tcpsocketconnection.h index f4fc187..934536e 100644 --- a/libnymea-remoteproxyclient/tcpsocketconnection.h +++ b/libnymea-remoteproxyclient/tcpsocketconnection.h @@ -48,6 +48,7 @@ public: private: QSslSocket *m_tcpSocket = nullptr; + bool m_ssl = false; private slots: void onDisconnected(); diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 314a20b..38806df 100644 --- a/nymea-remoteproxy.pri +++ b/nymea-remoteproxy.pri @@ -23,7 +23,7 @@ ccache { QMAKE_CXX = ccache g++ } -coverage {< +coverage { # Note: this works only if you build in the source dir OBJECTS_DIR = MOC_DIR = diff --git a/nymea-remoteproxy.pro b/nymea-remoteproxy.pro index 3d44545..2e38692 100644 --- a/nymea-remoteproxy.pro +++ b/nymea-remoteproxy.pro @@ -15,7 +15,9 @@ server.depends = libnymea-remoteproxy client.depends = libnymea-remoteproxyclient tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient -test.commands = LD_LIBRARY_PATH=$$top_builddir/libnymea-remoteproxy:$$top_builddir/libnymea-remoteproxyclient make check +test.commands = LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$$top_builddir/libnymea-remoteproxy:$$top_builddir/libnymea-remoteproxyclient \ + LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$$top_srcdir/libnymea-remoteproxy:$$top_srcdir/libnymea-remoteproxyclient \ + make check QMAKE_EXTRA_TARGETS += test message("----------------------------------------------------------") diff --git a/tests/resources/test-configuration.conf b/tests/resources/test-configuration.conf index e0fb21e..54bec2c 100644 --- a/tests/resources/test-configuration.conf +++ b/tests/resources/test-configuration.conf @@ -3,10 +3,10 @@ name=test-nymea-remoteproxy writeLogs=false logFile=/var/log/nymea-remoteproxy.log monitorSocket=/tmp/nymea-remoteproxy-test.sock -jsonRpcTimeout=1000 -authenticationTimeout=1500 +jsonRpcTimeout=2000 +authenticationTimeout=1000 inactiveTimeout=1500 -aloneTimeout=1000 +aloneTimeout=1500 [SSL] certificate=:/test-certificate.crt diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index 4a4ee39..b845662 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -53,9 +53,14 @@ void RemoteProxyOfflineTests::dummyAuthenticator() { cleanUpEngine(); + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_dummyAuthenticator = new DummyAuthenticator(this); + m_authenticator = qobject_cast(m_dummyAuthenticator); + // Start proxy webserver Engine::instance()->setAuthenticator(m_dummyAuthenticator); - Engine::instance()->setAuthenticator(m_dummyAuthenticator); QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->start(m_configuration); runningSpy.wait(); @@ -209,16 +214,21 @@ void RemoteProxyOfflineTests::serverPortBlocked() { cleanUpEngine(); + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_mockAuthenticator = new MockAuthenticator(this); + m_authenticator = qobject_cast(m_mockAuthenticator); + // Create a dummy server which blocks the port QWebSocketServer dummyServer("dummy-server", QWebSocketServer::NonSecureMode); - dummyServer.listen(QHostAddress::LocalHost, 1212); + QVERIFY(dummyServer.listen(QHostAddress::LocalHost, m_configuration->webSocketServerPort())); // Start proxy webserver QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->setAuthenticator(m_authenticator); Engine::instance()->start(m_configuration); runningSpy.wait(); - qDebug() << runningSpy.count(); QVERIFY(runningSpy.count() == 1); // Make sure the server is not running @@ -233,11 +243,49 @@ void RemoteProxyOfflineTests::serverPortBlocked() QVERIFY(closedSpy.count() == 1); // Try again - cleanUpEngine(); startServer(); // Clean up stopServer(); + + // Do the same with the tcp server + cleanUpEngine(); + + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_mockAuthenticator = new MockAuthenticator(this); + m_authenticator = qobject_cast(m_mockAuthenticator); + + // Create a dummy server which blocks the port + QTcpServer *tcpDummyServer = new QTcpServer(this); + QVERIFY(tcpDummyServer->listen(QHostAddress::LocalHost, m_configuration->tcpServerPort())); + + // Start proxy webserver + QSignalSpy runningSpy2(Engine::instance(), &Engine::runningChanged); + Engine::instance()->setAuthenticator(m_authenticator); + Engine::instance()->start(m_configuration); + runningSpy2.wait(); + QVERIFY(runningSpy2.count() == 1); + + // Make sure the engine is running + QVERIFY(Engine::instance()->running()); + + // Make sure the TCP server is not running + QVERIFY(!Engine::instance()->tcpSocketServer()->running()); + + tcpDummyServer->close(); + delete tcpDummyServer; + + // Try again + startServer(); + + // Make sure the TCP server is not running + QVERIFY(Engine::instance()->webSocketServer()->running()); + QVERIFY(Engine::instance()->tcpSocketServer()->running()); + + // Clean up + stopServer(); } void RemoteProxyOfflineTests::websocketBinaryData() @@ -286,13 +334,70 @@ void RemoteProxyOfflineTests::websocketPing() stopServer(); } +//void RemoteProxyOfflineTests::apiBasicCallsTcp_data() +//{ +// QTest::addColumn("data"); +// QTest::addColumn("responseId"); +// QTest::addColumn("responseStatus"); + +// QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << 42 << "success"; +// QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}") << -1 << "error"; +// QTest::newRow("missing method") << QByteArray("{\"id\":42}") << 42 << "error"; +// QTest::newRow("invalid json") << QByteArray("{\"id\":42, \"method\":\"RemoteProx") << -1 << "error"; +// QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << 42 << "error"; +// QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << 42 << "error"; +// QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << 42 << "error"; +// QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << 42 << "error"; +// QTest::newRow("invalid authentication params") << QByteArray("{\"id\":42, \"method\":\"Authentication.Authenticate\", \"params\":{\"your\":\"mamma\"}}") << 42 << "error"; +//} + +//void RemoteProxyOfflineTests::apiBasicCallsTcp() +//{ +// QFETCH(QByteArray, data); +// QFETCH(int, responseId); +// QFETCH(QString, responseStatus); + +// // Start the server +// startServer(); + +// QVariant response = injectTcpSocketData(data); +// QVERIFY(!response.isNull()); + +// qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + +// QCOMPARE(response.toMap().value("id").toInt(), responseId); +// QCOMPARE(response.toMap().value("status").toString(), responseStatus); + +// // Clean up +// stopServer(); +//} + void RemoteProxyOfflineTests::getIntrospect() { // Start the server startServer(); - QVariant response = invokeWebSocketApiCall("RemoteProxy.Introspect"); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVariantMap response; + + // WebSocket + response = invokeWebSocketApiCall("RemoteProxy.Introspect").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(!response.isEmpty()); + QVERIFY(response.value("status").toString() == "success"); + QVERIFY(response.value("params").toMap().contains("methods")); + QVERIFY(response.value("params").toMap().contains("notifications")); + QVERIFY(response.value("params").toMap().contains("types")); + + // Tcp + response.clear(); + response = invokeTcpSocketApiCall("RemoteProxy.Introspect").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + + QVERIFY(!response.isEmpty()); + QVERIFY(response.value("status").toString() == "success"); + QVERIFY(response.value("params").toMap().contains("methods")); + QVERIFY(response.value("params").toMap().contains("notifications")); + QVERIFY(response.value("params").toMap().contains("types")); // Clean up stopServer(); @@ -302,16 +407,32 @@ void RemoteProxyOfflineTests::getHello() { // Start the server startServer(); + QVariantMap response; - QVariantMap response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap(); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + // WebSocket + response = invokeWebSocketApiCall("RemoteProxy.Hello").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Verify data + QVERIFY(!response.isEmpty()); QCOMPARE(response.value("params").toMap().value("name").toString(), Engine::instance()->configuration()->serverName()); QCOMPARE(response.value("params").toMap().value("server").toString(), QString(SERVER_NAME_STRING)); QCOMPARE(response.value("params").toMap().value("version").toString(), QString(SERVER_VERSION_STRING)); QCOMPARE(response.value("params").toMap().value("apiVersion").toString(), QString(API_VERSION_STRING)); + // TCP + response.clear(); + response = invokeTcpSocketApiCall("RemoteProxy.Hello").toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + + // Verify data + QVERIFY(!response.isEmpty()); + QCOMPARE(response.value("params").toMap().value("name").toString(), Engine::instance()->configuration()->serverName()); + QCOMPARE(response.value("params").toMap().value("server").toString(), QString(SERVER_NAME_STRING)); + QCOMPARE(response.value("params").toMap().value("version").toString(), QString(SERVER_VERSION_STRING)); + QCOMPARE(response.value("params").toMap().value("apiVersion").toString(), QString(API_VERSION_STRING)); + + // Clean up stopServer(); } @@ -342,11 +463,22 @@ void RemoteProxyOfflineTests::apiBasicCalls() // Start the server startServer(); - QVariant response = injectWebSocketData(data); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVariantMap response; - QCOMPARE(response.toMap().value("id").toInt(), responseId); - QCOMPARE(response.toMap().value("status").toString(), responseStatus); + // Websocket + response = injectWebSocketData(data).toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(!response.isEmpty()); + QCOMPARE(response.value("id").toInt(), responseId); + QCOMPARE(response.value("status").toString(), responseStatus); + + // TCP + response.clear(); + response = injectTcpSocketData(data).toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + QVERIFY(!response.isEmpty()); + QCOMPARE(response.value("id").toInt(), responseId); + QCOMPARE(response.value("status").toString(), responseStatus); // Clean up stopServer(); @@ -409,8 +541,14 @@ void RemoteProxyOfflineTests::authenticate() params.insert("token", token); if (!nonce.isEmpty()) params.insert("nonce", nonce); - QVariant response = invokeWebSocketApiCall("Authentication.Authenticate", params); - qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + // WebSocket + QVariantMap response; + response = invokeWebSocketApiCall("Authentication.Authenticate", params).toMap(); + //qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); + verifyAuthenticationError(response, expectedError); + + // TCP + response = invokeTcpSocketApiCall("Authentication.Authenticate", params).toMap(); verifyAuthenticationError(response, expectedError); // Clean up @@ -518,6 +656,9 @@ void RemoteProxyOfflineTests::authenticateSendData() // Start the server startServer(); + m_mockAuthenticator->setTimeoutDuration(100); + m_mockAuthenticator->setExpectedAuthenticationError(); + QVariantMap params; params.insert("uuid", "uuid"); params.insert("name", "name"); @@ -533,18 +674,18 @@ void RemoteProxyOfflineTests::authenticateSendData() // Connect socket QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors); - QSignalSpy spyConnection(socket, SIGNAL(connected())); + QSignalSpy spyConnection(socket, &QWebSocket::connected); socket->open(Engine::instance()->webSocketServer()->serverUrl()); spyConnection.wait(); QVERIFY(spyConnection.count() == 1); // Authenticate - QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString))); + QSignalSpy dataSpy(socket, &QWebSocket::textMessageReceived); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); dataSpy.wait(); QVERIFY(dataSpy.count() == 1); - // Send data and make sure we get disconnected + // Send data again and make sure we get disconnected since sending data while waiting for the partner is forbidden QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected())); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); disconnectedSpy.wait(); @@ -556,7 +697,7 @@ void RemoteProxyOfflineTests::authenticateSendData() stopServer(); } -void RemoteProxyOfflineTests::clientConnection() +void RemoteProxyOfflineTests::clientConnectionWebSocket() { // Start the server startServer(); @@ -565,7 +706,7 @@ void RemoteProxyOfflineTests::clientConnection() m_mockAuthenticator->setTimeoutDuration(100); m_mockAuthenticator->setExpectedAuthenticationError(); - // Connect to the server (insecure disabled) + // Connect to the server using WebSocket (insecure disabled) RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", this); connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); @@ -606,6 +747,56 @@ void RemoteProxyOfflineTests::clientConnection() stopServer(); } +void RemoteProxyOfflineTests::clientConnectionTcpSocket() +{ + // Start the server + startServer(); + + // Configure mock authenticator + m_mockAuthenticator->setTimeoutDuration(100); + m_mockAuthenticator->setExpectedAuthenticationError(); + + // Connect to the server using TCP (insecure disabled) + RemoteProxyConnection *connection = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", RemoteProxyConnection::ConnectionTypeTcpSocket, this); + connect(connection, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + + // Connect to server (insecue enabled for testing) + QSignalSpy readySpy(connection, &RemoteProxyConnection::ready); + QVERIFY(connection->connectServer(m_serverUrlTcp)); + readySpy.wait(); + QVERIFY(readySpy.count() == 1); + QVERIFY(connection->isConnected()); + QVERIFY(!connection->isRemoteConnected()); + QVERIFY(connection->state() == RemoteProxyConnection::StateReady); + QVERIFY(connection->error() == QAbstractSocket::UnknownSocketError); + QVERIFY(connection->serverUrl() == m_serverUrlTcp); + QVERIFY(connection->connectionType() == RemoteProxyConnection::ConnectionTypeTcpSocket); + QVERIFY(connection->serverName() == SERVER_NAME_STRING); + QVERIFY(connection->proxyServerName() == Engine::instance()->serverName()); + QVERIFY(connection->proxyServerVersion() == SERVER_VERSION_STRING); + QVERIFY(connection->proxyServerApiVersion() == API_VERSION_STRING); + + QSignalSpy authenticatedSpy(connection, &RemoteProxyConnection::authenticated); + QVERIFY(connection->authenticate("foobar")); + authenticatedSpy.wait(); + QVERIFY(authenticatedSpy.count() == 1); + QVERIFY(connection->isConnected()); + QVERIFY(connection->isAuthenticated()); + QVERIFY(connection->state() == RemoteProxyConnection::StateAuthenticated); + + // Disconnect and clean up + QSignalSpy spyDisconnected(connection, &RemoteProxyConnection::disconnected); + connection->disconnectServer(); + // FIXME: check why it waits the full time here + spyDisconnected.wait(500); + + QVERIFY(spyDisconnected.count() >= 1); + QVERIFY(!connection->isConnected()); + + connection->deleteLater(); + stopServer(); +} + void RemoteProxyOfflineTests::remoteConnection() { // Start the server @@ -912,6 +1103,10 @@ void RemoteProxyOfflineTests::jsonRpcTimeout() // Start the server startServer(); + m_configuration->setAuthenticationTimeout(3000); + m_configuration->setJsonRpcTimeout(1000); + m_configuration->setInactiveTimeout(2000); + // Configure result (authentication takes longer than json rpc timeout m_mockAuthenticator->setExpectedAuthenticationError(); m_mockAuthenticator->setTimeoutDuration(4000); @@ -964,7 +1159,7 @@ void RemoteProxyOfflineTests::authenticationReplyTimeout() // Configure result (authentication takes longer than json rpc timeout m_mockAuthenticator->setExpectedAuthenticationError(); - m_mockAuthenticator->setTimeoutDuration(1000); + m_mockAuthenticator->setTimeoutDuration(2000); m_configuration->setAuthenticationTimeout(500); m_configuration->setJsonRpcTimeout(1000); @@ -1017,4 +1212,98 @@ void RemoteProxyOfflineTests::authenticationReplyConnection() stopServer(); } +//void RemoteProxyOfflineTests::tcpRemoteConnection() +//{ +// // Start the server +// startServer(); + +// // Configure mock authenticator +// m_mockAuthenticator->setTimeoutDuration(100); +// m_mockAuthenticator->setExpectedAuthenticationError(); + +// QString nameConnectionOne = "Test client one"; +// QUuid uuidConnectionOne = QUuid::createUuid(); + +// QString nameConnectionTwo = "Test client two"; +// QUuid uuidConnectionTwo = QUuid::createUuid(); + +// QByteArray dataOne = "Hello from client one :-)"; +// QByteArray dataTwo = "Hello from client two :-)"; + +// // Create two connection +// RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, RemoteProxyConnection::ConnectionTypeTcpSocket, this); +// connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + +// RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, RemoteProxyConnection::ConnectionTypeTcpSocket, this); +// connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError); + +// // Connect one +// QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready); +// QVERIFY(connectionOne->connectServer(m_serverUrlTcp)); +// connectionOneReadySpy.wait(); +// QVERIFY(connectionOneReadySpy.count() == 1); +// QVERIFY(connectionOne->isConnected()); + +// // Connect two +// QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready); +// QVERIFY(connectionTwo->connectServer(m_serverUrlTcp)); +// connectionTwoReadySpy.wait(); +// QVERIFY(connectionTwoReadySpy.count() == 1); +// QVERIFY(connectionTwo->isConnected()); + +// // Authenticate one +// QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished); +// QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated); +// QVERIFY(connectionOne->authenticate(m_testToken)); +// connectionOneAuthenticatedSpy.wait(); +// QVERIFY(connectionOneAuthenticatedSpy.count() == 1); +// QVERIFY(connectionOne->isConnected()); +// QVERIFY(connectionOne->isAuthenticated()); +// QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated); + +// // Authenticate two +// QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished); +// QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated); +// QVERIFY(connectionTwo->authenticate(m_testToken)); +// connectionTwoAuthenticatedSpy.wait(); +// qDebug() << connectionTwoAuthenticatedSpy.count(); +// QVERIFY(connectionTwoAuthenticatedSpy.count() == 1); +// QVERIFY(connectionTwo->isConnected()); +// QVERIFY(connectionTwo->isAuthenticated()); + +// // Wait for both to be connected +// remoteConnectionEstablishedOne.wait(500); +// remoteConnectionEstablishedTwo.wait(500); + +// QVERIFY(remoteConnectionEstablishedOne.count() == 1); +// QVERIFY(remoteConnectionEstablishedTwo.count() == 1); +// QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected); +// QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected); + +// QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo); +// QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString()); +// QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne); +// QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString()); + +// // Pipe data trought the tunnel +// QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady); +// QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady); + +// connectionOne->sendData(dataOne); +// remoteConnectionDataTwo.wait(500); +// QVERIFY(remoteConnectionDataTwo.count() == 1); +// QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne); + +// connectionTwo->sendData(dataTwo); +// remoteConnectionDataOne.wait(500); +// QVERIFY(remoteConnectionDataOne.count() == 1); +// QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo); + +// connectionOne->deleteLater(); +// connectionTwo->deleteLater(); + +// // Clean up +// stopServer(); +//} + QTEST_MAIN(RemoteProxyOfflineTests) diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index af62b3c..e7fe61e 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -54,10 +54,7 @@ private slots: void websocketBinaryData(); void websocketPing(); - // TCP socket connection - - - // Api + // WebSocket connection API void getIntrospect(); void getHello(); @@ -71,20 +68,21 @@ private slots: void authenticateSendData(); // Client lib - void clientConnection(); + void clientConnectionTcpSocket(); + void clientConnectionWebSocket(); void remoteConnection(); void multipleRemoteConnection(); void trippleConnection(); void duplicateUuid(); void sslConfigurations(); - void jsonRpcTimeout(); void inactiveTimeout(); + void jsonRpcTimeout(); void authenticationReplyTimeout(); void authenticationReplyConnection(); // TCP Websocket combinations - + //void tcpRemoteConnection(); }; diff --git a/tests/testbase/basetest.cpp b/tests/testbase/basetest.cpp index 6b398cd..62d54eb 100644 --- a/tests/testbase/basetest.cpp +++ b/tests/testbase/basetest.cpp @@ -31,8 +31,10 @@ #include "loggingcategories.h" #include "remoteproxyconnection.h" +#include #include #include +#include #include #include #include @@ -47,16 +49,34 @@ void BaseTest::loadConfiguration(const QString &fileName) { qDebug() << "Load test configurations" << fileName; m_configuration->loadConfiguration(fileName); - restartEngine(); + //restartEngine(); } void BaseTest::cleanUpEngine() { + qDebug() << "Clean up engine"; if (Engine::exists()) { Engine::instance()->stop(); Engine::instance()->destroy(); QVERIFY(!Engine::exists()); } + + if (m_mockAuthenticator) { + delete m_mockAuthenticator; + m_mockAuthenticator = nullptr; + } + + if (m_dummyAuthenticator) { + delete m_dummyAuthenticator; + m_dummyAuthenticator = nullptr; + } + + m_authenticator = nullptr; + + if (m_configuration) { + delete m_configuration; + m_configuration = nullptr; + } } void BaseTest::restartEngine() @@ -67,22 +87,29 @@ void BaseTest::restartEngine() void BaseTest::startEngine() { + m_configuration = new ProxyConfiguration(this); + loadConfiguration(":/test-configuration.conf"); + + m_dummyAuthenticator = new DummyAuthenticator(this); + m_mockAuthenticator = new MockAuthenticator(this); + m_authenticator = qobject_cast(m_mockAuthenticator); if (!Engine::exists()) { Engine::instance()->setAuthenticator(m_authenticator); Engine::instance()->setDeveloperModeEnabled(true); QVERIFY(Engine::exists()); + QVERIFY(!Engine::instance()->running()); } } void BaseTest::startServer() { - startEngine(); + restartEngine(); if (!Engine::instance()->running()) { QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->setDeveloperModeEnabled(true); Engine::instance()->start(m_configuration); - runningSpy.wait(100); + runningSpy.wait(200); QVERIFY(runningSpy.count() == 1); } @@ -100,6 +127,8 @@ void BaseTest::stopServer() Engine::instance()->stop(); QVERIFY(!Engine::instance()->running()); + + cleanUpEngine(); } QVariant BaseTest::invokeWebSocketApiCall(const QString &method, const QVariantMap params, bool remainsConnected) @@ -310,20 +339,117 @@ bool BaseTest::createRemoteConnection(const QString &token, const QString &nonce return true; } +void BaseTest::initTestCase() +{ + Q_UNUSED(remainsConnected) + + QVariantMap request; + request.insert("id", m_commandCounter); + request.insert("method", method); + request.insert("params", params); + QJsonDocument jsonDoc = QJsonDocument::fromVariant(request); + + QSslSocket *socket = new QSslSocket(this); + typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); + QObject::connect(socket, static_cast(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors); + + QSignalSpy spyConnection(socket, &QSslSocket::connected); + socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(), + static_cast(Engine::instance()->tcpSocketServer()->serverUrl().port())); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + + QSignalSpy dataSpy(socket, &QSslSocket::readyRead); + socket->write(jsonDoc.toJson(QJsonDocument::Compact) + '\n'); + // FIXME: check why it waits the full time here + dataSpy.wait(500); + if (dataSpy.count() != 1) { + qWarning() << "No data received"; + return QVariant(); + } + + QByteArray data = socket->readAll(); + socket->close(); + socket->deleteLater(); + + // Make sure the response ends with '}\n' + if (!data.endsWith("}\n")) { + qWarning() << "JSON data does not end with \"}\n\""; + return QVariant(); + } + + // Make sure the response it a valid JSON string + QJsonParseError error; + jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + + QVariantMap response = jsonDoc.toVariant().toMap(); + + if (response.value("id").toInt() == m_commandCounter) { + m_commandCounter++; + return jsonDoc.toVariant(); + } + + m_commandCounter++; + return QVariant(); +} + +QVariant BaseTest::injectTcpSocketData(const QByteArray &data) +{ + QSslSocket *socket = new QSslSocket(this); + typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); + QObject::connect(socket, static_cast(&QSslSocket::sslErrors), this, &BaseTest::sslSocketSslErrors); + + QSignalSpy spyConnection(socket, &QSslSocket::connected); + socket->connectToHostEncrypted(Engine::instance()->tcpSocketServer()->serverUrl().host(), + static_cast(Engine::instance()->tcpSocketServer()->serverUrl().port())); + spyConnection.wait(); + if (spyConnection.count() == 0) { + return QVariant(); + } + + QSignalSpy dataSpy(socket, &QSslSocket::readyRead); + socket->write(data + '\n'); + dataSpy.wait(); + // FIXME: check why it waits the full time here + dataSpy.wait(500); + if (dataSpy.count() != 1) { + qWarning() << "No data received"; + return QVariant(); + } + + QByteArray socketData = socket->readAll(); + socket->close(); + socket->deleteLater(); + + // Make sure the response ends with '}\n' + if (!socketData.endsWith("}\n")) { + qWarning() << "JSON data does not end with \"}\n\""; + return QVariant(); + } + + // Make sure the response it a valid JSON string + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(socketData, &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << "JSON parser error" << error.errorString(); + return QVariant(); + } + + m_commandCounter++; + return jsonDoc.toVariant(); +} + void BaseTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); - m_configuration = new ProxyConfiguration(this); - m_configuration->loadConfiguration(":/test-configuration.conf"); - - m_mockAuthenticator = new MockAuthenticator(this); - m_dummyAuthenticator = new DummyAuthenticator(this); - //m_awsAuthenticator = new AwsAuthenticator(m_configuration->awsCredentialsUrl(), this); - - m_authenticator = qobject_cast(m_mockAuthenticator); - m_testToken = "eyJraWQiOiJXdnFFT3prVVh5VDlINzFyRUpoNWdxRnkxNFhnR2l3SFAzVEIzUFQ1V3ZrPSIsImFsZyI6IlJT" "MjU2In0.eyJzdWIiOiJmZTJmZDNlNC1hMGJhLTQ1OTUtOWRiZS00ZDkxYjRiMjFlMzUiLCJhdWQiOiI4cmpoZ" "mRsZjlqZjFzdW9rMmpjcmx0ZDZ2IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV2ZW50X2lkIjoiN2Y5NTRiNm" @@ -337,20 +463,13 @@ void BaseTest::initTestCase() "pQbj58v1vktaAEATdmKmlzmcix-HJK9wWHRSuv3TsNa8DGxvcPOoeTu8Vql7krZ-y7Zu-s2WsgeP4VxyT80VE" "T_xh6pMkOhE6g"; - qCDebug(dcApplication()) << "Init test case."; - restartEngine(); + qCDebug(dcApplication()) << "Init test case done."; + //restartEngine(); } void BaseTest::cleanupTestCase() { qCDebug(dcApplication()) << "Clean up test case."; - delete m_configuration; - delete m_mockAuthenticator; - delete m_dummyAuthenticator; - //delete m_awsAuthenticator; - - m_authenticator = nullptr; - cleanUpEngine(); } diff --git a/tests/testbase/basetest.h b/tests/testbase/basetest.h index 60c029c..a5f04ef 100644 --- a/tests/testbase/basetest.h +++ b/tests/testbase/basetest.h @@ -57,6 +57,7 @@ protected: ProxyConfiguration *m_configuration = nullptr; QUrl m_serverUrl = QUrl("wss://127.0.0.1:1212"); + QUrl m_serverUrlTcp = QUrl("ssl://127.0.0.1:1213"); QSslConfiguration m_sslConfiguration; @@ -88,8 +89,13 @@ protected slots: void cleanupTestCase(); public slots: + inline void sslSocketSslErrors(const QList &) { + QSslSocket *socket = static_cast(sender()); + socket->ignoreSslErrors(); + } + inline void sslErrors(const QList &) { - QWebSocket *socket = static_cast(sender()); + QWebSocket *socket = static_cast(sender()); socket->ignoreSslErrors(); } diff --git a/tests/testbase/mockauthenticator.cpp b/tests/testbase/mockauthenticator.cpp index d699253..06731cf 100644 --- a/tests/testbase/mockauthenticator.cpp +++ b/tests/testbase/mockauthenticator.cpp @@ -55,8 +55,7 @@ void MockAuthenticator::replyFinished() { MockAuthenticationReply *reply = static_cast(sender()); - qCDebug(dcAuthentication()) << name() << "Authentication finished."; - + qCDebug(dcAuthentication()) << name() << "Authentication finished" << reply << reply->authenticationReply(); setReplyError(reply->authenticationReply(), reply->error()); setReplyFinished(reply->authenticationReply()); reply->deleteLater(); @@ -64,7 +63,7 @@ void MockAuthenticator::replyFinished() AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient) { - qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token(); + qCDebug(dcAuthentication()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token() << "Auth duration" << m_timeoutDuration << "[ms]"; AuthenticationReply *authenticationReply = createAuthenticationReply(proxyClient, proxyClient); @@ -84,3 +83,8 @@ MockAuthenticationReply::MockAuthenticationReply(int timeout, Authenticator::Aut QTimer::singleShot(timeout, this, &MockAuthenticationReply::finished); } +MockAuthenticationReply::~MockAuthenticationReply() +{ + qCCritical(dcAuthentication()) << "Destroy mock authentication reply"; +} + diff --git a/tests/testbase/mockauthenticator.h b/tests/testbase/mockauthenticator.h index fe80b83..77bae35 100644 --- a/tests/testbase/mockauthenticator.h +++ b/tests/testbase/mockauthenticator.h @@ -40,6 +40,7 @@ class MockAuthenticationReply : public QObject Q_OBJECT public: explicit MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent = nullptr); + ~MockAuthenticationReply(); AuthenticationReply *authenticationReply() const { return m_authenticationReply; } Authenticator::AuthenticationError error() const { return m_error; } @@ -66,7 +67,7 @@ public: void setExpectedAuthenticationError(Authenticator::AuthenticationError error = AuthenticationErrorNoError); private: - int m_timeoutDuration = 1000; + int m_timeoutDuration = 500; Authenticator::AuthenticationError m_expectedError; private slots: