mirror of https://github.com/nymea/nymea.git
483 lines
18 KiB
C++
483 lines
18 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright 2013 - 2020, nymea GmbH
|
|
* Contact: contact@nymea.io
|
|
*
|
|
* This file is part of nymea.
|
|
* This project including source code and documentation is protected by
|
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
|
* reproduction, publication, editing and translation, are reserved. The use of
|
|
* this project is subject to the terms of a license agreement to be concluded
|
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
|
* under https://nymea.io/license
|
|
*
|
|
* GNU General Public License Usage
|
|
* Alternatively, this project may be redistributed and/or modified under the
|
|
* terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, GNU version 3. This project is distributed in the hope that it
|
|
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
* Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this project. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* For any further details and any questions please contact us under
|
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
|
* https://nymea.io/license/faq
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "nymeatestbase.h"
|
|
#include "nymeacore.h"
|
|
#include "nymeasettings.h"
|
|
#include "logging/logengine.h"
|
|
#include "servers/mocktcpserver.h"
|
|
|
|
#include "../plugins/mock/extern-plugininfo.h"
|
|
|
|
#include <qglobal.h>
|
|
|
|
#include "version.h"
|
|
|
|
using namespace nymeaserver;
|
|
|
|
class TestLogging : public NymeaTestBase
|
|
{
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
void initTestCase();
|
|
void init();
|
|
|
|
void initLogs();
|
|
|
|
void systemLogs();
|
|
|
|
void invalidFilter_data();
|
|
void invalidFilter();
|
|
|
|
void stateChangeLogs_data();
|
|
void stateChangeLogs();
|
|
|
|
void eventLog();
|
|
|
|
void actionLog();
|
|
|
|
void removeThing();
|
|
};
|
|
|
|
void TestLogging::initTestCase()
|
|
{
|
|
NymeaTestBase::initTestCase();
|
|
QLoggingCategory::setFilterRules("*.debug=false\n"
|
|
"LogEngine.debug=true\n"
|
|
"Tests.debug=true\n"
|
|
"MockDevice.debug=true\n"
|
|
"DeviceManager.debug=true\n"
|
|
"Application.debug=true\n");
|
|
}
|
|
|
|
void TestLogging::init()
|
|
{
|
|
waitForDBSync();
|
|
}
|
|
|
|
void TestLogging::initLogs()
|
|
{
|
|
QVariantMap params;
|
|
params.insert("sources", QStringList{"core"});
|
|
QVariant response = injectAndWait("Logging.GetLogEntries", params);
|
|
|
|
QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(logEntries.count() > 0,
|
|
QString("Expected at least one log entry.")
|
|
.toUtf8());
|
|
|
|
clearLoggingDatabase("core");
|
|
waitForDBSync();
|
|
|
|
response = injectAndWait("Logging.GetLogEntries");
|
|
logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY(logEntries.count() == 0);
|
|
|
|
restartServer();
|
|
}
|
|
|
|
void TestLogging::systemLogs()
|
|
{
|
|
qCDebug(dcTests()) << "Clearing logging DB";
|
|
|
|
clearLoggingDatabase("core");
|
|
|
|
QVariantMap params;
|
|
params.insert("sources", QStringList{"core"});
|
|
params.insert("sortOrder", enumValueName(Qt::DescendingOrder));
|
|
|
|
// there should be 0 log entries
|
|
QVariant response = injectAndWait("Logging.GetLogEntries", params);
|
|
QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(logEntries.count() == 0,
|
|
QString("Expected 0 log entries but got:\n%1")
|
|
.arg(QString(QJsonDocument::fromVariant(logEntries).toJson()))
|
|
.toUtf8());
|
|
|
|
// check the active system log at boot
|
|
qCDebug(dcTests) << "Restarting server";
|
|
restartServer();
|
|
qCDebug(dcTests) << "Restart done";
|
|
waitForDBSync();
|
|
|
|
// there should be 2 log entries, one for shutdown, one for startup (from server restart)
|
|
response = injectAndWait("Logging.GetLogEntries", params);
|
|
logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(logEntries.count() == 2,
|
|
QString("Expected 2 log entries but got:\n%1")
|
|
.arg(QString(QJsonDocument::fromVariant(logEntries).toJson()))
|
|
.toUtf8());
|
|
|
|
QVariantMap logEntryStartup = logEntries.first().toMap();
|
|
QVariantMap logEntryShutdown = logEntries.last().toMap();
|
|
|
|
QCOMPARE(logEntryShutdown.value("values").toMap().value("event").toString(), QString("stopped"));
|
|
QCOMPARE(logEntryShutdown.value("values").toMap().value("shutdownReason").toString(), enumValueName(NymeaCore::ShutdownReasonRestart));
|
|
QCOMPARE(logEntryShutdown.value("values").toMap().value("version").toString(), QString(NYMEA_VERSION_STRING));
|
|
|
|
|
|
QCOMPARE(logEntryStartup.value("values").toMap().value("event").toString(), QString("started"));
|
|
QCOMPARE(logEntryStartup.value("values").toMap().value("version").toString(), QString(NYMEA_VERSION_STRING));
|
|
}
|
|
|
|
void TestLogging::invalidFilter_data()
|
|
{
|
|
QVariantMap invalidSourcesFilter;
|
|
invalidSourcesFilter.insert("sources", QStringList{"bla"});
|
|
|
|
QTest::addColumn<QVariantMap>("filter");
|
|
|
|
QTest::newRow("Invalid source") << invalidSourcesFilter;
|
|
}
|
|
|
|
void TestLogging::invalidFilter()
|
|
{
|
|
QFETCH(QVariantMap, filter);
|
|
QVariant response = injectAndWait("Logging.GetLogEntries", filter);
|
|
QVERIFY(!response.isNull());
|
|
|
|
// verify json error
|
|
QVERIFY(response.toMap().value("status").toString() == "success");
|
|
QVERIFY(response.toMap().value("params").toMap().contains("logEntries"));
|
|
QVERIFY(response.toMap().value("params").toMap().value("logEntries").toList().isEmpty());
|
|
qDebug() << response.toMap().value("error").toString();
|
|
}
|
|
|
|
void TestLogging::stateChangeLogs_data()
|
|
{
|
|
QTest::addColumn<StateTypeId>("stateTypeId");
|
|
QTest::addColumn<QString>("stateName");
|
|
QTest::addColumn<QVariant>("initValue");
|
|
QTest::addColumn<QVariant>("newValue");
|
|
QTest::addColumn<bool>("expectLogEntry");
|
|
|
|
QTest::newRow("logged state") << mockConnectedStateTypeId << "connected" << QVariant(false) << QVariant(true) << true;
|
|
QTest::newRow("not logged state") << mockDoubleStateTypeId << "double" << QVariant(10) << QVariant(20) << false;
|
|
}
|
|
|
|
void TestLogging::stateChangeLogs()
|
|
{
|
|
QFETCH(StateTypeId, stateTypeId);
|
|
QFETCH(QString, stateName);
|
|
QFETCH(QVariant, initValue);
|
|
QFETCH(QVariant, newValue);
|
|
QFETCH(bool, expectLogEntry);
|
|
|
|
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
|
|
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock Device for this test");
|
|
Thing *thing = things.first();
|
|
|
|
// Setup connection to mock client
|
|
QNetworkAccessManager nam;
|
|
|
|
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
|
|
|
|
QString logSourceName = "state-" + thing->id().toString() + "-" + stateName;
|
|
|
|
// init state in mock device
|
|
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(stateTypeId.toString()).arg(initValue.toString())));
|
|
QNetworkReply *reply = nam.get(request);
|
|
{
|
|
QSignalSpy finishedSpy(reply, &QNetworkReply::finished);
|
|
finishedSpy.wait();
|
|
}
|
|
|
|
waitForDBSync();
|
|
|
|
// Now snoop in for the events
|
|
clearLoggingDatabase(logSourceName);
|
|
|
|
enableNotifications({"Integrations"});
|
|
QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
|
|
|
// trigger state change in mock device
|
|
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(stateTypeId.toString()).arg(newValue.toString())));
|
|
reply = nam.get(request);
|
|
{
|
|
QSignalSpy finishedSpy(reply, &QNetworkReply::finished);
|
|
finishedSpy.wait();
|
|
}
|
|
|
|
// Lets wait for the notification
|
|
QTest::qWait(200);
|
|
clientSpy.wait(1000);
|
|
reply->deleteLater();
|
|
|
|
QVariantList stateChangeNotification = checkNotifications(clientSpy, "Integrations.StateChanged");
|
|
|
|
waitForDBSync();
|
|
|
|
QVariant response = injectAndWait("Logging.GetLogEntries", {{"sources", QStringList{logSourceName}}});
|
|
QVERIFY(!response.isNull());
|
|
|
|
QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(logEntries.count() == (expectLogEntry ? 1 : 0), "Unexpected amount of log entries in DB");
|
|
|
|
if (expectLogEntry) {
|
|
QVariantMap entry = logEntries.first().toMap();
|
|
QVERIFY2(entry.value("values").toMap().value(stateName) == QVariant(newValue), "Log entry value not matching");
|
|
}
|
|
|
|
// disable notifications
|
|
QCOMPARE(disableNotifications(), true);
|
|
}
|
|
|
|
void TestLogging::eventLog()
|
|
{
|
|
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
|
|
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock Device for this test");
|
|
Thing *thing = things.first();
|
|
|
|
EventTypeId eventTypeId = mockEvent2EventTypeId;
|
|
QString eventName = "event2";
|
|
|
|
QString logSourceName = "event-" + thing->id().toString() + "-" + eventName;
|
|
clearLoggingDatabase(logSourceName);
|
|
|
|
enableNotifications({"Integrations"});
|
|
QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
|
|
|
// trigger state change in mock device
|
|
QNetworkAccessManager nam;
|
|
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
|
|
QNetworkRequest request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2&%3=%4").arg(port).arg(eventTypeId.toString()).arg(mockEvent2EventIntParamParamTypeId.toString()).arg(42)));
|
|
QNetworkReply *reply = nam.get(request);
|
|
{
|
|
QSignalSpy finishedSpy(reply, &QNetworkReply::finished);
|
|
finishedSpy.wait();
|
|
}
|
|
|
|
clientSpy.wait();
|
|
reply->deleteLater();
|
|
|
|
QVariantList stateChangeNotification = checkNotifications(clientSpy, "Integrations.EventTriggered");
|
|
|
|
waitForDBSync();
|
|
|
|
QVariant response = injectAndWait("Logging.GetLogEntries", {{"sources", QStringList{logSourceName}}});
|
|
QVERIFY(!response.isNull());
|
|
|
|
QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(logEntries.count() == 1, "Unexpected amount of log entries in DB");
|
|
|
|
QVariantMap entry = logEntries.first().toMap();
|
|
QCOMPARE(entry.value("source").toString(), logSourceName);
|
|
QByteArray paramsJson = entry.value("values").toMap().value("params").toByteArray();
|
|
QJsonParseError error;
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(paramsJson, &error);
|
|
QCOMPARE(error.error, QJsonParseError::NoError);
|
|
QCOMPARE(jsonDoc.toVariant().toMap().value("intParam").toInt(), 42);
|
|
|
|
// disable notifications
|
|
QCOMPARE(disableNotifications(), true);
|
|
}
|
|
|
|
void TestLogging::actionLog()
|
|
{
|
|
QVariantList actionParams;
|
|
QVariantMap param1;
|
|
param1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
|
|
param1.insert("value", 7);
|
|
actionParams.append(param1);
|
|
QVariantMap param2;
|
|
param2.insert("paramTypeId", mockWithParamsActionParam2ParamTypeId);
|
|
param2.insert("value", true);
|
|
actionParams.append(param2);
|
|
|
|
QVariantMap params;
|
|
params.insert("actionTypeId", mockWithParamsActionTypeId);
|
|
params.insert("thingId", m_mockThingId);
|
|
params.insert("params", actionParams);
|
|
|
|
enableNotifications({"Logging"});
|
|
|
|
QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
|
|
|
// EXECUTE with params
|
|
QVariant response = injectAndWait("Integrations.ExecuteAction", params);
|
|
|
|
// wait for the outgoing data
|
|
// 3 packets: ExecuteAction reply, LogDatabaseUpdated signal and LogEntryAdded signal
|
|
while (clientSpy.count() < 3) {
|
|
bool success = clientSpy.wait();
|
|
if (!success) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
QVariantList logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded");
|
|
qWarning() << QJsonDocument::fromVariant(logEntryAddedVariants).toJson();
|
|
QVERIFY2(!logEntryAddedVariants.isEmpty(), "Did not get Logging.LogEntryAdded notification.");
|
|
|
|
bool found = false;
|
|
foreach (const QVariant &loggEntryAddedVariant, logEntryAddedVariants) {
|
|
QVariantMap logEntry = loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap();
|
|
if (logEntry.value("source").toString() == "action-" + m_mockThingId.toString() + "-withParams" ) {
|
|
found = true;
|
|
QCOMPARE(logEntry.value("values").toMap().value("status").toString(), enumValueName(Thing::ThingErrorNoError));
|
|
QCOMPARE(logEntry.value("values").toMap().value("triggeredBy").toString(), enumValueName(Action::TriggeredByUser));
|
|
QJsonParseError error;
|
|
QVariantMap actionParams = QJsonDocument::fromJson(logEntry.value("values").toMap().value("params").toByteArray(), &error).toVariant().toMap();
|
|
QCOMPARE(error.error, QJsonParseError::NoError);
|
|
QCOMPARE(actionParams.value("param1").toInt(), 7);
|
|
QCOMPARE(actionParams.value("param2").toBool(), true);
|
|
}
|
|
}
|
|
|
|
QVERIFY2(found, "Could not find the corresponding Logging.LogEntryAdded notification");
|
|
|
|
// EXECUTE without params
|
|
params.clear(); clientSpy.clear();
|
|
params.insert("actionTypeId", mockWithoutParamsActionTypeId);
|
|
params.insert("thingId", m_mockThingId);
|
|
response = injectAndWait("Integrations.ExecuteAction", params);
|
|
|
|
clientSpy.wait();
|
|
|
|
logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded");
|
|
QVERIFY(!logEntryAddedVariants.isEmpty());
|
|
|
|
// get this logentry with filter
|
|
params.clear();
|
|
params.insert("sources", QStringList{"action-" + m_mockThingId.toString() + "-withoutParams"});
|
|
response = injectAndWait("Logging.GetLogEntries", params);
|
|
|
|
QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(!logEntries.isEmpty(), "No logs received");
|
|
|
|
// EXECUTE broken action
|
|
params.clear(); clientSpy.clear();
|
|
params.insert("actionTypeId", mockFailingActionTypeId);
|
|
params.insert("thingId", m_mockThingId);
|
|
response = injectAndWait("Integrations.ExecuteAction", params);
|
|
|
|
clientSpy.wait();
|
|
|
|
logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded");
|
|
QVERIFY2(!logEntryAddedVariants.isEmpty(), "Did not get Logging.LogEntryAdded notification.");
|
|
|
|
found = false;
|
|
foreach (const QVariant &loggEntryAddedVariant, logEntryAddedVariants) {
|
|
QVariantMap logEntry = loggEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap();
|
|
if (logEntry.value("source").toString() == "action-" + m_mockThingId.toString() + "-failing") {
|
|
found = true;
|
|
QCOMPARE(logEntry.value("values").toMap().value("status").toString(), enumValueName(Thing::ThingErrorSetupFailed));
|
|
QCOMPARE(logEntry.value("values").toMap().value("triggeredBy").toString(), enumValueName(Action::TriggeredByUser));
|
|
QJsonParseError error;
|
|
QVariantMap actionParams = QJsonDocument::fromJson(logEntry.value("values").toMap().value("params").toByteArray(), &error).toVariant().toMap();
|
|
QCOMPARE(error.error, QJsonParseError::NoError);
|
|
QVERIFY(actionParams.isEmpty());
|
|
break;
|
|
}
|
|
}
|
|
|
|
QVERIFY2(found, "Could not find the corresponding Logging.LogEntryAdded notification");
|
|
|
|
// get this logentry with filter
|
|
params.clear();
|
|
params.insert("sources", QStringList{"action-" + m_mockThingId.toString() + "-failing"});
|
|
response = injectAndWait("Logging.GetLogEntries", params);
|
|
|
|
logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY2(!logEntries.isEmpty(), "No logs received");
|
|
|
|
// Get all action logs in one go
|
|
params.clear();
|
|
params.insert("sources", QStringList{
|
|
"action-" + m_mockThingId.toString() + "-withParams",
|
|
"action-" + m_mockThingId.toString() + "-withoutParams",
|
|
"action-" + m_mockThingId.toString() + "-failing"
|
|
});
|
|
|
|
response = injectAndWait("Logging.GetLogEntries", params);
|
|
|
|
logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QCOMPARE(logEntries.count(), 3);
|
|
|
|
// disable notifications
|
|
QCOMPARE(disableNotifications(), true);
|
|
}
|
|
|
|
void TestLogging::removeThing()
|
|
{
|
|
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
|
|
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock Device for this test");
|
|
Thing *thing = things.first();
|
|
|
|
QString stateName = "int";
|
|
StateTypeId stateTypeId = mockIntStateTypeId;
|
|
|
|
qCDebug(dcTests) << "Using mock:" << things.first()->id();
|
|
|
|
|
|
// Setup connection to mock client
|
|
QNetworkAccessManager nam;
|
|
|
|
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
|
|
|
|
QString logSourceName = "state-" + thing->id().toString() + "-" + stateName;
|
|
|
|
// init state in mock device
|
|
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(stateTypeId.toString()).arg("12")));
|
|
QNetworkReply *reply = nam.get(request);
|
|
{
|
|
QSignalSpy finishedSpy(reply, &QNetworkReply::finished);
|
|
finishedSpy.wait();
|
|
}
|
|
|
|
waitForDBSync();
|
|
|
|
// enable notifications
|
|
enableNotifications({"Logging"});
|
|
|
|
// get this logentry with filter
|
|
QVariant response = injectAndWait("Logging.GetLogEntries", {{"sources", QStringList{logSourceName}}});
|
|
QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QVERIFY(logEntries.count() > 0);
|
|
|
|
// Remove the device
|
|
response = injectAndWait("Integrations.RemoveThing", {{"thingId", m_mockThingId}});
|
|
|
|
waitForDBSync();
|
|
|
|
// verify that the logs from this device where removed from the db
|
|
response = injectAndWait("Logging.GetLogEntries", {{"sources", QStringList{logSourceName}}});
|
|
logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
|
|
QCOMPARE(logEntries.count(), 0);
|
|
|
|
// disable notifications
|
|
QCOMPARE(disableNotifications(), true);
|
|
}
|
|
|
|
#include "testlogging.moc"
|
|
QTEST_MAIN(TestLogging)
|
|
|