/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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/logvaluetool.h"
#include "servers/mocktcpserver.h"

#include <qglobal.h>

using namespace nymeaserver;

class TestLogging : public NymeaTestBase
{
    Q_OBJECT

private:

    inline void verifyLoggingError(const QVariant &response, Logging::LoggingError error = Logging::LoggingErrorNoError) {
        verifyError(response, "loggingError", enumValueName(error));
    }
    inline void verifyThingError(const QVariant &response, Thing::ThingError error = Thing::ThingErrorNoError) {
        verifyError(response, "thingError", enumValueName(error));
    }
    // DEPRECTATED
    inline void verifyDeviceError(const QVariant &response, Thing::ThingError error = Thing::ThingErrorNoError) {
        verifyError(response, "deviceError", enumValueName(error).replace("Thing", "Device"));
    }

private slots:
    void initTestCase();
    void init();

    void initLogs();

    void databaseSerializationTest_data();
    void databaseSerializationTest();

    void coverageCalls();

    void systemLogs();

    void invalidFilter_data();
    void invalidFilter();

    void eventLogs_data();
    void eventLogs();

    void actionLog();

    void thingLogs();

    void testDoubleValues();

    void testHouseKeeping();

    void testLimits();

    // this has to be the last test
    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()
{
    QVariant response = injectAndWait("Logging.GetLogEntries");
    verifyLoggingError(response);

    QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QVERIFY2(logEntries.count() > 0,
             QString("Expected at least one log entry.")
             .toUtf8());

    clearLoggingDatabase();
    waitForDBSync();

    response = injectAndWait("Logging.GetLogEntries");
    verifyLoggingError(response);
    logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QVERIFY(logEntries.count() == 0);

    restartServer();
    NymeaCore::instance()->logEngine()->setMaxLogEntries(1000, 10);
}

void TestLogging::databaseSerializationTest_data()
{
    QUuid uuid = QUuid("3782732b-61b4-48e8-8d6d-b5205159d7cd");

    QVariantMap variantMap;
    variantMap.insert("string", "value");
    variantMap.insert("int", 5);
    variantMap.insert("double", 3.14);
    variantMap.insert("uuid", uuid);

    QVariantList variantList;
    variantList.append(variantMap);
    variantList.append("String");
    variantList.append(3.14);
    variantList.append(uuid);

    QTest::addColumn<QVariant>("value");

    QTest::newRow("QString") << QVariant(QString("Hello"));
    QTest::newRow("Integer") << QVariant((int)2);
    QTest::newRow("Double") << QVariant((double)2.34);
    QTest::newRow("Float") << QVariant((float)2.34);
    QTest::newRow("QColor") << QVariant(QColor(0,255,128));
    QTest::newRow("QByteArray") << QVariant(QByteArray("\nthisisatestarray\n"));
    QTest::newRow("QUuid") << QVariant(uuid);
    QTest::newRow("QVariantMap") << QVariant(variantMap);
    QTest::newRow("QVariantList") << QVariant(variantList);
}

void TestLogging::databaseSerializationTest()
{
    QFETCH(QVariant, value);

    QString serializedValue = LogValueTool::serializeValue(value);
    QVariant deserializedValue = LogValueTool::deserializeValue(serializedValue);

    qDebug() << "Stored:" << value;
    qDebug() << "Loaded:" << deserializedValue;
    QCOMPARE(deserializedValue, value);
}

void TestLogging::coverageCalls()
{
    LogEntry entry(QDateTime::currentDateTime(), Logging::LoggingLevelInfo, Logging::LoggingSourceSystem);
    qDebug() << entry;

    LogFilter filter;
    qDebug() << filter.queryString() << filter.timeFilters();
}

void TestLogging::systemLogs()
{
    qWarning() << "Clearing logging DB";
    clearLoggingDatabase();

    waitForDBSync();

    QVariantMap params;
    params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceSystem));
    params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeActiveChange));

    // 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
    qWarning() << "Restarting server";
    restartServer();
    qWarning() << "Restart done";
    waitForDBSync();

    // there should be 2 log entries, one for shutdown, one for startup (from server restart)
    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);
    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();
    // We cannot rely on the order those events
    if (!logEntryStartup.value("active").toBool()) {
        logEntryStartup = logEntries.last().toMap();
        logEntryShutdown = logEntries.first().toMap();
    }

    QCOMPARE(logEntryShutdown.value("active").toBool(), false);
    QCOMPARE(logEntryShutdown.value("eventType").toString(), enumValueName(Logging::LoggingEventTypeActiveChange));
    QCOMPARE(logEntryShutdown.value("source").toString(), enumValueName(Logging::LoggingSourceSystem));
    QCOMPARE(logEntryShutdown.value("loggingLevel").toString(), enumValueName(Logging::LoggingLevelInfo));


    QCOMPARE(logEntryStartup.value("active").toBool(), true);
    QCOMPARE(logEntryStartup.value("eventType").toString(), enumValueName(Logging::LoggingEventTypeActiveChange));
    QCOMPARE(logEntryStartup.value("source").toString(), enumValueName(Logging::LoggingSourceSystem));
    QCOMPARE(logEntryStartup.value("loggingLevel").toString(), enumValueName(Logging::LoggingLevelInfo));
}

void TestLogging::invalidFilter_data()
{
    QVariantMap invalidSourcesFilter;
    invalidSourcesFilter.insert("loggingSources", QVariantList() << "bla");

    QVariantMap invalidFilterValue;
    invalidFilterValue.insert("loggingSource", QVariantList() << "bla");

    QVariantMap invalidTypeIds;
    invalidTypeIds.insert("typeId", QVariantList() << "bla" << "blub");

    QVariantMap invalidEventTypes;
    invalidEventTypes.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger) << "blub");

    QTest::addColumn<QVariantMap>("filter");

    QTest::newRow("Invalid source") << invalidSourcesFilter;
    QTest::newRow("Invalid filter value") << invalidFilterValue;
    QTest::newRow("Invalid typeIds") << invalidTypeIds;
    QTest::newRow("Invalid eventTypes") << invalidEventTypes;
}

void TestLogging::invalidFilter()
{
    QFETCH(QVariantMap, filter);
    QVariant response = injectAndWait("Logging.GetLogEntries", filter);
    QVERIFY(!response.isNull());

    // verify json error
    QVERIFY(response.toMap().value("status").toString() == "error");
    qDebug() << response.toMap().value("error").toString();
}

void TestLogging::eventLogs_data()
{
    QTest::addColumn<StateTypeId>("stateTypeId");
    QTest::addColumn<QVariant>("initValue");
    QTest::addColumn<QVariant>("newValue");
    QTest::addColumn<bool>("expectLogEntry");

    QTest::newRow("logged event") << mockConnectedStateTypeId << QVariant(false) << QVariant(true) << true;
    QTest::newRow("not logged event") << mockSignalStrengthStateTypeId << QVariant(10) << QVariant(20) << false;
}

void TestLogging::eventLogs()
{
    QFETCH(StateTypeId, stateTypeId);
    QFETCH(QVariant, initValue);
    QFETCH(QVariant, newValue);
    QFETCH(bool, expectLogEntry);

    QList<Thing*> devices = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
    QVERIFY2(devices.count() > 0, "There needs to be at least one configured Mock Device for this test");
    Thing *device = devices.first();

    // Setup connection to mock client
    QNetworkAccessManager nam;

    int port = device->paramValue(mockThingHttpportParamTypeId).toInt();

    // 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();
    }

    // Now snoop in for the events
    clearLoggingDatabase();
    enableNotifications({"Events", "Logging"});
    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();

    // Make sure the logg notification contains all the stuff we expect
    QVariantList logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded");
    qDebug() << "got" << logEntryAddedVariants.count() << "Logging.LogEntryAdded notifications";

    bool found = false;
    qDebug() << "got" << logEntryAddedVariants.count() << "Logging.LogEntryAdded";
    foreach (const QVariant &logEntryAddedVariant, logEntryAddedVariants) {
        QVariantMap logEntry = logEntryAddedVariant.toMap().value("params").toMap().value("logEntry").toMap();
        if (logEntry.value("thingId").toUuid() == device->id()) {
            found = true;
            // Make sure the notification contains all the stuff we expect
            QCOMPARE(logEntry.value("typeId").toUuid().toString(), stateTypeId.toString());
            QCOMPARE(logEntry.value("eventType").toString(), enumValueName(Logging::LoggingEventTypeTrigger));
            QCOMPARE(logEntry.value("source").toString(), enumValueName(Logging::LoggingSourceStates));
            QCOMPARE(logEntry.value("loggingLevel").toString(), enumValueName(Logging::LoggingLevelInfo));
            break;
        }
    }

    QVERIFY2(found == expectLogEntry, "Could not find the corresponding Logging.LogEntryAdded notification");

    if (expectLogEntry) {
        // get this logentry with filter
        QVariantMap params;
        params.insert("thingIds", QVariantList() << device->id());
        params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceStates));
        params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger));
        params.insert("typeIds", QVariantList() << stateTypeId);

        QVariant response = injectAndWait("Logging.GetLogEntries", params);
        verifyLoggingError(response);

        QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
        qCDebug(dcTests()) << qUtf8Printable(QJsonDocument::fromVariant(logEntries).toJson());
        QCOMPARE(logEntries.count(), 1);
    }


    // disable notifications
    QCOMPARE(disableNotifications(), true);
}

void TestLogging::actionLog()
{
    clearLoggingDatabase();

    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);
    verifyThingError(response);

    // wait for the outgoing data
    // 3 packets: ExecuteAction reply,  LogDatabaseUpdated signal and LogEntryAdded signal
    clientSpy.wait(500);
    if (clientSpy.count() < 3) {
        clientSpy.wait(500);
    }

    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("thingId").toUuid() == m_mockThingId) {
            found = true;
            // Make sure the notification contains all the stuff we expect
            QCOMPARE(logEntry.value("typeId").toUuid().toString(), mockWithParamsActionTypeId.toString());
            QCOMPARE(logEntry.value("eventType").toString(), enumValueName(Logging::LoggingEventTypeTrigger));
            QCOMPARE(logEntry.value("source").toString(), enumValueName(Logging::LoggingSourceActions));
            QCOMPARE(logEntry.value("loggingLevel").toString(), enumValueName(Logging::LoggingLevelInfo));
            break;
        }
    }

    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);
    verifyThingError(response);

    clientSpy.wait(200);

    logEntryAddedVariants = checkNotifications(clientSpy, "Logging.LogEntryAdded");
    QVERIFY(!logEntryAddedVariants.isEmpty());

    // get this logentry with filter
    params.clear();
    params.insert("thingIds", QVariantList() << m_mockThingId);
    params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceActions));
    params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger));

    // FIXME: currently is filtering for values not supported
    //params.insert("values", QVariantList() << "7, true");

    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);

    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);
    verifyThingError(response, Thing::ThingErrorSetupFailed);

    clientSpy.wait(200);

    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("thingId").toUuid() == m_mockThingId) {
            found = true;
            // Make sure the notification contains all the stuff we expect
            QCOMPARE(logEntry.value("typeId").toUuid().toString(), mockFailingActionTypeId.toString());
            QCOMPARE(logEntry.value("eventType").toString(), enumValueName(Logging::LoggingEventTypeTrigger));
            QCOMPARE(logEntry.value("source").toString(), enumValueName(Logging::LoggingSourceActions));
            QCOMPARE(logEntry.value("loggingLevel").toString(), enumValueName(Logging::LoggingLevelAlert));
            QCOMPARE(logEntry.value("errorCode").toString(), enumValueName(Thing::ThingErrorSetupFailed));
            break;
        }
    }

    QVERIFY2(found, "Could not find the corresponding Logging.LogEntryAdded notification");

    // get this logentry with filter
    params.clear();
    params.insert("thingIds", QVariantList() << m_mockThingId);
    params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceActions));
    params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger));

    // FIXME: filter for values currently not working
    //params.insert("values", QVariantList() << "7, true");

    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);

    logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QVERIFY2(!logEntries.isEmpty(), "No logs received");

    // check different filters
    params.clear();
    params.insert("thingIds", QVariantList() << m_mockThingId);
    params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceActions));
    params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger));
    params.insert("typeIds", QVariantList() << mockWithoutParamsActionTypeId);

    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);

    logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QVERIFY2(!logEntries.isEmpty(), "No logs received");

    params.clear();
    params.insert("thingIds", QVariantList() << m_mockThingId);
    params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceActions));
    params.insert("eventTypes", QVariantList() << enumValueName(Logging::LoggingEventTypeTrigger));
    params.insert("typeIds", QVariantList() << mockWithoutParamsActionTypeId << mockWithParamsActionTypeId << mockFailingActionTypeId);

    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);

    logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QVERIFY2(!logEntries.isEmpty(), "No logs received");

    // disable notifications
    QCOMPARE(disableNotifications(), true);
}

void TestLogging::thingLogs()
{
    QVariantMap params;
    params.insert("thingClassId", parentMockThingClassId);
    params.insert("name", "Parent thing");

    QVariant response = injectAndWait("Integrations.AddThing", params);
    verifyThingError(response);

    ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
    QVERIFY(!thingId.isNull());

    // get this logentry with filter
    params.clear();
    params.insert("thingIds", QVariantList() << m_mockThingId << thingId);
    params.insert("loggingSources", QVariantList() << enumValueName(Logging::LoggingSourceActions)
                  << enumValueName(Logging::LoggingSourceEvents)
                  << enumValueName(Logging::LoggingSourceStates));
    params.insert("loggingLevels", QVariantList() << enumValueName(Logging::LoggingLevelInfo)
                  << enumValueName(Logging::LoggingLevelAlert));
    params.insert("values", QVariantList() << "7, true" << "9, false");

    QVariantMap timeFilter;
    timeFilter.insert("startDate", QDateTime::currentDateTime().toTime_t() - 5);
    timeFilter.insert("endDate", QDateTime::currentDateTime().toTime_t());

    QVariantMap timeFilter2;
    timeFilter2.insert("endDate", QDateTime::currentDateTime().toTime_t() - 20);

    params.insert("timeFilters", QVariantList() << timeFilter << timeFilter2);

    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);

}

void TestLogging::testDoubleValues()
{
    enableNotifications({"Logging"});

    // Add display pin device which contains a double value

    // Discover device
    QVariantList discoveryParams;
    QVariantMap resultCountParam;
    resultCountParam.insert("paramTypeId", displayPinMockDiscoveryResultCountParamTypeId);
    resultCountParam.insert("value", 1);
    discoveryParams.append(resultCountParam);

    QVariantMap params;
    params.insert("thingClassId", displayPinMockThingClassId);
    params.insert("discoveryParams", discoveryParams);
    QVariant response = injectAndWait("Integrations.DiscoverThings", params);

    verifyThingError(response, Thing::ThingErrorNoError);

    // Pair device
    ThingDescriptorId descriptorId = ThingDescriptorId(response.toMap().value("params").toMap().value("thingDescriptors").toList().first().toMap().value("id").toString());
    params.clear();
    params.insert("thingClassId", displayPinMockThingClassId);
    params.insert("name", "Display pin mock device");
    params.insert("thingDescriptorId", descriptorId.toString());
    response = injectAndWait("Integrations.PairThing", params);

    verifyThingError(response);

    PairingTransactionId pairingTransactionId(response.toMap().value("params").toMap().value("pairingTransactionId").toString());
    QString displayMessage = response.toMap().value("params").toMap().value("displayMessage").toString();

    qCDebug(dcTests) << "displayMessage" << displayMessage;

    params.clear();
    params.insert("pairingTransactionId", pairingTransactionId.toString());
    params.insert("secret", "243681");
    response = injectAndWait("Integrations.ConfirmPairing", params);

    verifyThingError(response);

    ThingId thingId(response.toMap().value("params").toMap().value("thingId").toString());

    QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));

    // Set the double state value and sniff for LogEntryAdded notification
    double value = 23.80;
    QVariantMap actionParam;
    actionParam.insert("paramTypeId", displayPinMockDoubleActionDoubleParamTypeId.toString());
    actionParam.insert("value", value);

    params.clear(); response.clear();
    params.insert("thingId", thingId);
    params.insert("actionTypeId", displayPinMockDoubleActionTypeId.toString());
    params.insert("params", QVariantList() << actionParam);

    response = injectAndWait("Integrations.ExecuteAction", params);
    verifyThingError(response);

    notificationSpy.wait();
    QVariantList logNotificationsList = checkNotifications(notificationSpy, "Logging.LogEntryAdded");
    QVERIFY2(!logNotificationsList.isEmpty(), "Did not get Logging.LogEntryAdded notification.");

    foreach (const QVariant &logNotificationVariant, logNotificationsList) {
        QVariantMap logNotification = logNotificationVariant.toMap().value("params").toMap().value("logEntry").toMap();

        if (logNotification.value("typeId").toString() == displayPinMockDoubleActionDoubleParamTypeId.toString()) {
            if (logNotification.value("typeId").toString() == displayPinMockDoubleActionDoubleParamTypeId.toString()) {

                // If state source
                if (logNotification.value("source").toString() == enumValueName(Logging::LoggingSourceStates)) {
                    QString logValue = logNotification.value("value").toString();
                    qDebug() << QString::number(value) << logValue;
                    QCOMPARE(logValue, QString::number(value));
                }

                // If action source notification
                if (logNotification.value("source").toString() == enumValueName(Logging::LoggingSourceActions)) {
                    QString logValue = logNotification.value("value").toString();
                    qDebug() << QString::number(value) << logValue;
                    QCOMPARE(logValue, QString::number(value));
                }
            }
        }
    }


    // Remove device
    params.clear();
    params.insert("thingId", thingId.toString());
    response = injectAndWait("Integrations.RemoveThing", params);
    verifyThingError(response);
}

void TestLogging::testHouseKeeping()
{
    QVariantMap params;
    params.insert("thingClassId", mockThingClassId);
    params.insert("name", "TestDeviceToBeRemoved");
    QVariantList thingParams;
    QVariantMap httpParam;
    httpParam.insert("paramTypeId", mockThingHttpportParamTypeId);
    httpParam.insert("value", 6667);
    thingParams.append(httpParam);
    params.insert("thingParams", thingParams);
    QVariant response = injectAndWait("Integrations.AddThing", params);
    ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toUuid());
    QVERIFY2(!thingId.isNull(), "Something went wrong creating the thing for testing.");

    // Trigger something that creates a logging entry
    QNetworkAccessManager nam;
    QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
    QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(6667).arg(mockConnectedStateTypeId.toString()).arg(false)));
    QNetworkReply *reply = nam.get(request);
    connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater()));
    spy.wait();

    waitForDBSync();

    params.clear();
    params.insert("thingIds", QVariantList() << thingId);
    response = injectAndWait("Logging.GetLogEntries", params);
    QVERIFY2(response.toMap().value("params").toMap().value("logEntries").toList().count() > 0, "Couldn't find state change event in log...");

    // Manually delete this device from config
    NymeaSettings settings(NymeaSettings::SettingsRoleThings);
    settings.beginGroup("ThingConfig");
    settings.remove(thingId.toString());
    settings.endGroup();

    restartServer();

    waitForDBSync();

    params.clear();
    params.insert("thingIds", QVariantList() << thingId);
    response = injectAndWait("Logging.GetLogEntries", params);
    qCDebug(dcTests()) << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
    QVERIFY2(response.toMap().value("status").toString() == QString("success"), "GetLogEntries failed");
    QVERIFY2(response.toMap().value("params").toMap().value("logEntries").toList().count() == 0, "Device state change event still in log. Should've been cleaned by housekeeping.");
}

void TestLogging::testLimits()
{
    clearLoggingDatabase();

    for (int i = 0; i < 50; i++) {
        QVariantList actionParams;
        QVariantMap param1;
        param1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
        param1.insert("value", i);
        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);

        // EXECUTE with params
        QVariant response = injectAndWait("Integrations.ExecuteAction", params);
        verifyThingError(response);
    }

    waitForDBSync();

    QVariantMap params;
    QVariantMap response;

    // No limits, should be all 50 entries
    params.clear();
    response = injectAndWait("Logging.GetLogEntries", params).toMap();
    QCOMPARE(response.value("params").toMap().value("count").toInt(), 50);
    QCOMPARE(response.value("params").toMap().value("logEntries").toList().count(), 50);

    // Add a limit of 20
    params.clear();
    params.insert("limit", 20);
    response = injectAndWait("Logging.GetLogEntries", params).toMap();
    QCOMPARE(response.value("params").toMap().value("count").toInt(), 20);
    QCOMPARE(response.value("params").toMap().value("logEntries").toList().count(), 20);

    // Add a offset of 40, keeping a limit of 20. should return 10 entries
    params.clear();
    params.insert("limit", 20);
    params.insert("offset", 40);
    response = injectAndWait("Logging.GetLogEntries", params).toMap();
    QCOMPARE(response.value("params").toMap().value("count").toInt(), 10);
    QCOMPARE(response.value("params").toMap().value("logEntries").toList().count(), 10);
}

void TestLogging::removeThing()
{
    // enable notifications
    enableNotifications({"Logging"});

    // get this logentry with filter
    QVariantMap params;
    params.insert("thingIds", QVariantList() << m_mockThingId);
    QVariant response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);
    QVariantList logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QVERIFY(logEntries.count() > 0);

    QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));

    // Remove the device
    params.clear();
    params.insert("thingId", m_mockThingId);
    response = injectAndWait("Integrations.RemoveThing", params);
    verifyThingError(response);

    clientSpy.wait(200);
    QVariant notification = checkNotification(clientSpy, "Logging.LogDatabaseUpdated");
    QVERIFY(!notification.isNull());

    // verify that the logs from this device where removed from the db
    params.clear();
    params.insert("thingIds", QVariantList() << m_mockThingId);
    response = injectAndWait("Logging.GetLogEntries", params);
    verifyLoggingError(response);
    logEntries = response.toMap().value("params").toMap().value("logEntries").toList();
    QCOMPARE(logEntries.count(), 0);

    // disable notifications
    QCOMPARE(disableNotifications(), true);
}

#include "testlogging.moc"
QTEST_MAIN(TestLogging)

