#include "nymea-remoteproxy-tests.h" #include "engine.h" #include "loggingcategories.h" #include "remoteproxyconnection.h" #include #include #include #include #include RemoteProxyTests::RemoteProxyTests(QObject *parent) : QObject(parent) { QFile certificateFile(":/test-certificate.crt"); if (!certificateFile.open(QIODevice::ReadOnly)) { qWarning() << "Could not open resource file" << certificateFile.fileName(); exit(1); } QByteArray certificateData = certificateFile.readAll(); //qDebug() << "Certificate:" << endl << qUtf8Printable(certificateData); QFile keyFile(":/test-certificate.key"); if (!keyFile.open(QIODevice::ReadOnly)) { qWarning() << "Could not open resource file" << keyFile.fileName(); exit(1); } QByteArray keyData = keyFile.readAll(); //qDebug() << "Certificate key:" << endl << qUtf8Printable(keyData); m_authenticator = new MockAuthenticator(this); m_sslConfiguration.setPrivateKey(QSslKey(keyData, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey)); m_sslConfiguration.setLocalCertificate(QSslCertificate(certificateData, QSsl::Pem)); m_sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); m_sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater); } void RemoteProxyTests::cleanUpEngine() { if (Engine::exists()) { Engine::instance()->stop(); Engine::instance()->destroy(); QVERIFY(!Engine::exists()); } } void RemoteProxyTests::restartEngine() { cleanUpEngine(); startEngine(); } void RemoteProxyTests::startEngine() { if (!Engine::exists()) { QString serverName = "nymea-remoteproxy-testserver"; Engine::instance()->setAuthenticator(m_authenticator); Engine::instance()->setAuthenticationServerUrl(QUrl("https://localhost")); Engine::instance()->setServerName(serverName); Engine::instance()->setWebSocketServerPort(m_port); Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); Engine::instance()->setSslConfiguration(m_sslConfiguration); QVERIFY(Engine::exists()); QVERIFY(Engine::instance()->serverName() == serverName); } } void RemoteProxyTests::startServer() { startEngine(); if (!Engine::instance()->running()) { QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->start(); runningSpy.wait(); QVERIFY(runningSpy.count() == 1); } QVERIFY(Engine::instance()->running()); QVERIFY(Engine::instance()->webSocketServer()->running()); } void RemoteProxyTests::stopServer() { if (!Engine::instance()->running()) return; Engine::instance()->stop(); QVERIFY(!Engine::instance()->running()); } QVariant RemoteProxyTests::invokeApiCall(const QString &method, const QVariantMap params, bool remainsConnected) { Q_UNUSED(remainsConnected) QVariantMap request; request.insert("id", m_commandCounter); request.insert("method", method); request.insert("params", params); QJsonDocument jsonDoc = QJsonDocument::fromVariant(request); QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &RemoteProxyTests::sslErrors); QSignalSpy spyConnection(socket, SIGNAL(connected())); socket->open(Engine::instance()->webSocketServer()->serverUrl()); spyConnection.wait(); if (spyConnection.count() == 0) { return QVariant(); } QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString))); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); dataSpy.wait(); socket->close(); socket->deleteLater(); for (int i = 0; i < dataSpy.count(); i++) { // Make sure the response it a valid JSON string QJsonParseError error; jsonDoc = QJsonDocument::fromJson(dataSpy.at(i).last().toByteArray(), &error); if (error.error != QJsonParseError::NoError) { qWarning() << "JSON parser error" << error.errorString(); return QVariant(); } QVariantMap response = jsonDoc.toVariant().toMap(); // skip notifications if (response.contains("notification")) continue; if (response.value("id").toInt() == m_commandCounter) { m_commandCounter++; return jsonDoc.toVariant(); } } m_commandCounter++; return QVariant(); } QVariant RemoteProxyTests::injectSocketData(const QByteArray &data) { QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &RemoteProxyTests::sslErrors); QSignalSpy spyConnection(socket, SIGNAL(connected())); socket->open(Engine::instance()->webSocketServer()->serverUrl()); spyConnection.wait(); if (spyConnection.count() == 0) { return QVariant(); } QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString))); socket->sendTextMessage(QString(data)); spy.wait(); socket->close(); socket->deleteLater(); for (int i = 0; i < spy.count(); i++) { // Make sure the response it a valid JSON string QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.at(i).last().toByteArray(), &error); if (error.error != QJsonParseError::NoError) { qWarning() << "JSON parser error" << error.errorString(); return QVariant(); } return jsonDoc.toVariant(); } m_commandCounter++; return QVariant(); } void RemoteProxyTests::initTestCase() { qRegisterMetaType(); qCDebug(dcApplication()) << "Init test case."; restartEngine(); } void RemoteProxyTests::cleanupTestCase() { qCDebug(dcApplication()) << "Clean up test case."; cleanUpEngine(); } void RemoteProxyTests::startStopServer() { startServer(); stopServer(); } void RemoteProxyTests::webserverConnectionBlocked() { // Create a dummy server which blocks the port QWebSocketServer dummyServer("dummy-server", QWebSocketServer::NonSecureMode); dummyServer.listen(QHostAddress::LocalHost, m_port); // Start proxy webserver Engine::instance()->setWebSocketServerPort(m_port); Engine::instance()->setAuthenticator(m_authenticator); Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost); Engine::instance()->setSslConfiguration(m_sslConfiguration); QSignalSpy runningSpy(Engine::instance(), &Engine::runningChanged); Engine::instance()->start(); runningSpy.wait(); QVERIFY(runningSpy.count() == 1); // Make sure the server is not running QVERIFY(Engine::instance()->running()); // Make sure the websocket server is not running QVERIFY(!Engine::instance()->webSocketServer()->running()); QSignalSpy closedSpy(&dummyServer, &QWebSocketServer::closed); dummyServer.close(); closedSpy.wait(); QVERIFY(closedSpy.count() == 1); // Try again cleanUpEngine(); startServer(); // Clean up stopServer(); } void RemoteProxyTests::webserverConnection() { } void RemoteProxyTests::getIntrospect() { // Start the server startServer(); QVariant response = invokeApiCall("RemoteProxy.Introspect"); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Clean up stopServer(); } void RemoteProxyTests::getHello() { // Start the server startServer(); QVariant response = invokeApiCall("RemoteProxy.Hello"); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Clean up stopServer(); } void RemoteProxyTests::apiBasicCalls_data() { QTest::addColumn("data"); QTest::addColumn("valid"); QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << true; QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}")<< false; QTest::newRow("missing method") << QByteArray("{\"id\":42}") << false; QTest::newRow("borked") << QByteArray("{\"id\":42, \"method\":\"RemoteProx")<< false; QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << false; QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << false; QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << false; QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << false; } void RemoteProxyTests::apiBasicCalls() { QFETCH(QByteArray, data); QFETCH(bool, valid); // Start the server startServer(); QVariant response = injectSocketData(data); if (valid) { QVERIFY2(response.toMap().value("status").toString() == "success", "Call wasn't parsed correctly by nymea."); } // Clean up stopServer(); } void RemoteProxyTests::authenticate_data() { QTest::addColumn("uuid"); QTest::addColumn("name"); QTest::addColumn("token"); QTest::addColumn("timeout"); QTest::addColumn("expectedError"); QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "Hh5JrkdFVstVVyCnfE3vVT3zG_JTacXEhPLZhCei" << 100 << Authenticator::AuthenticationErrorNoError; QTest::newRow("failed") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" << 100 << Authenticator::AuthenticationErrorAuthenticationFailed; QTest::newRow("not responding") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" << 200 << Authenticator::AuthenticationErrorAuthenticationServerNotResponding; QTest::newRow("aborted") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" << 100 << Authenticator::AuthenticationErrorAborted; QTest::newRow("unknown") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42" << 100 << Authenticator::AuthenticationErrorUnknown; } void RemoteProxyTests::authenticate() { QFETCH(QString, uuid); QFETCH(QString, name); QFETCH(QString, token); QFETCH(int, timeout); QFETCH(Authenticator::AuthenticationError, expectedError); // Start the server startServer(); // Configure result m_authenticator->setExpectedAuthenticationError(expectedError); m_authenticator->setTimeoutDuration(timeout); // Create request QVariantMap params; params.insert("uuid", uuid); params.insert("name", name); params.insert("token", token); QVariant response = invokeApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); verifyAuthenticationError(response, expectedError); // Clean up stopServer(); } void RemoteProxyTests::clientConnection() { // Start the server startServer(); // Connect to the server (insecure disabled) RemoteProxyConnection *connectorOne = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", RemoteProxyConnection::ConnectionTypeWebSocket, this); connectorOne->setInsecureConnection(true); // Connect to server (insecue enabled for testing) QSignalSpy readySpy(connectorOne, &RemoteProxyConnection::ready); QVERIFY(connectorOne->connectServer(QHostAddress::LocalHost, m_port)); readySpy.wait(); QVERIFY(readySpy.count() == 1); QVERIFY(connectorOne->isConnected()); QVERIFY(connectorOne->state() == RemoteProxyConnection::StateReady); // Authenticate m_authenticator->setTimeoutDuration(100); m_authenticator->setExpectedAuthenticationError(); connectorOne->authenticate("foobar"); QTest::qWait(1000); // Disconnect and clean up QSignalSpy spyDisconnected(connectorOne, &RemoteProxyConnection::disconnected); connectorOne->disconnectServer(); spyDisconnected.wait(); QVERIFY(!connectorOne->isConnected()); connectorOne->deleteLater(); stopServer(); } void RemoteProxyTests::sslConfigurations() { // // Start the server // startServer(); // // Connect to the server (insecure disabled) // RemoteProxyConnection *connector = new RemoteProxyConnection(RemoteProxyConnection::ConnectionTypeWebSocket, this); // connector->setInsecureConnection(false); // QSignalSpy spyError(connector, &RemoteProxyConnection::errorOccured); // connector->connectServer(QHostAddress::LocalHost, m_port); // spyError.wait(); // QCOMPARE(connector->error(), RemoteProxyConnection::ErrorSslError); // QCOMPARE(connector->state(), RemoteProxyConnection::StateDisconnected); // // Connect to server (insecue enabled) // QSignalSpy spyConnected(connector, &RemoteProxyConnection::connected); // connector->setInsecureConnection(true); // connector->connectServer(QHostAddress::LocalHost, m_port); // spyConnected.wait(); // QVERIFY(connector->isConnected()); // // Disconnect and clean up // connector->disconnectServer(); // QVERIFY(!connector->isConnected()); // connector->deleteLater(); // stopServer(); } void RemoteProxyTests::timeout() { // Start the server startServer(); // Configure result // Start the server startServer(); // Configure result m_authenticator->setExpectedAuthenticationError(); m_authenticator->setTimeoutDuration(6000); // Create request QVariantMap params; params.insert("uuid", "uuid"); params.insert("name", "name"); params.insert("token", "token"); QVariant response = invokeApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); // Clean up stopServer(); } QTEST_MAIN(RemoteProxyTests)