This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
powersync-core/tests/auto/integrations/testintegrations.cpp
Michael Zanetti 92197cb97c Refactor NymeaCore class
This moves all the things and rules logic away from NymeaCore
into their respective modules where it belongs.

One major change is the removal of the removePolicy functionality.
This was somewhat broken as it was only working for rules but not
for all the other modules like scripts, experiences etc. After
an attempt to create something that works with all modules it
really seemed that this does not make a lot of sence after all,
given that updating rules would in most cases leave something
very broken behind and removing them was the only sane thing to do.

On the other hand, experience plugins may not work well with such
a policy eithre as they may require to do their own special thing.

So in the end the removePolicy was dropped altogether. Apps should
instead figure out themselves what removal of a thing may imply and
inform the user about that beforehand.
2023-01-24 17:56:04 +01:00

2403 lines
98 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 "integrations/thingdiscoveryinfo.h"
#include "integrations/thingsetupinfo.h"
#include "servers/mocktcpserver.h"
#include "jsonrpc/integrationshandler.h"
#include "../plugins/mock/extern-plugininfo.h"
using namespace nymeaserver;
class TestIntegrations : public NymeaTestBase
{
Q_OBJECT
private:
ThingId m_mockThingAsyncId;
inline void verifyThingError(const QVariant &response, Thing::ThingError error = Thing::ThingErrorNoError) {
verifyError(response, "thingError", enumValueName(error));
}
private slots:
void initTestCase();
void getPlugins();
void getPluginConfig_data();
void getPluginConfig();
void setPluginConfig_data();
void setPluginConfig();
void getSupportedVendors();
void getThingClasses_data();
void getThingClasses();
void verifyInterfaces();
void addThing_data();
void addThing();
void thingAddedRemovedNotifications();
void thingChangedNotifications();
void getThings();
void getThing_data();
void getThing();
void storedThings();
void stateCache();
void discoverThings_data();
void discoverThings();
void addPushButtonThings_data();
void addPushButtonThings();
void addDisplayPinThings_data();
void addDisplayPinThings();
void parentChildThings();
void getActionTypes_data();
void getActionTypes();
void getEventTypes_data();
void getEventTypes();
void getStateTypes_data();
void getStateTypes();
void getStateValue_data();
void getStateValue();
void getStateValues_data();
void getStateValues();
void editThings_data();
void editThings();
void testThingSettings();
void reconfigureThings_data();
void reconfigureThings();
void reconfigureByDiscovery_data();
void reconfigureByDiscovery();
void reconfigureByDiscoveryAndPair();
void reconfigureAutoThing();
void testBrowsing_data();
void testBrowsing();
void testExecuteBrowserItem_data();
void testExecuteBrowserItem();
void testExecuteBrowserItemAction_data();
void testExecuteBrowserItemAction();
void executeAction_data();
void executeAction();
void triggerEvent();
void triggerStateChangeSignal();
void params();
void dynamicMinMax();
void asyncSetupEmitsSetupStatusUpdate();
void testTranslations();
// Keep those at last as they will remove things
void removeThing_data();
void removeThing();
void removeAutoThing();
void discoverThingsParenting();
};
void TestIntegrations::initTestCase()
{
NymeaTestBase::initTestCase();
QLoggingCategory::setFilterRules("*.debug=false\n"
"Tests.debug=true\n"
"Mock.debug=true\n"
"Translations.debug=true\n"
"PythonIntegrations.debug=true\n"
);
// Adding an async mock to be used in tests below
QVariantMap params;
params.insert("thingClassId", mockThingClassId);
params.insert("name", "Mocked Thing (Async)");
QVariantList thingParams;
QVariantMap asyncParam;
asyncParam.insert("paramTypeId", mockThingAsyncParamTypeId);
asyncParam.insert("value", true);
thingParams.append(asyncParam);
QVariantMap httpParam;
httpParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpParam.insert("value", 8765);
thingParams.append(httpParam);
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
m_mockThingAsyncId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY2(!m_mockThingAsyncId.isNull(), "Creating an async mock failed");
qCDebug(dcTests()) << "Created Async mock with ID" << m_mockThingAsyncId;
}
void TestIntegrations::getPlugins()
{
QVariant response = injectAndWait("Integrations.GetPlugins");
QVariantList plugins = response.toMap().value("params").toMap().value("plugins").toList();
QCOMPARE(plugins.count() > 0, true);
bool found = false;
foreach (const QVariant &listEntry, plugins) {
if (PluginId(listEntry.toMap().value("id").toString()) == mockPluginId) {
found = true;
}
}
QCOMPARE(found, true);
}
void TestIntegrations::getPluginConfig_data()
{
QTest::addColumn<PluginId>("pluginId");
QTest::addColumn<Thing::ThingError>("error");
QTest::newRow("valid plugin") << mockPluginId << Thing::ThingErrorNoError;
QTest::newRow("invalid plugin") << PluginId::createPluginId() << Thing::ThingErrorPluginNotFound;
}
void TestIntegrations::getPluginConfig()
{
QFETCH(PluginId, pluginId);
QFETCH(Thing::ThingError, error);
QVariantMap params;
params.insert("pluginId", pluginId);
QVariant response = injectAndWait("Integrations.GetPluginConfiguration", params);
verifyThingError(response, error);
}
void TestIntegrations::setPluginConfig_data()
{
QTest::addColumn<PluginId>("pluginId");
QTest::addColumn<QVariant>("value");
QTest::addColumn<Thing::ThingError>("error");
QTest::newRow("valid") << mockPluginId << QVariant(13) << Thing::ThingErrorNoError;
QTest::newRow("invalid plugin") << PluginId::createPluginId() << QVariant(13) << Thing::ThingErrorPluginNotFound;
QTest::newRow("too big") << mockPluginId << QVariant(130) << Thing::ThingErrorInvalidParameter;
QTest::newRow("too small") << mockPluginId << QVariant(-13) << Thing::ThingErrorInvalidParameter;
QTest::newRow("wrong type") << mockPluginId << QVariant("wrontType") << Thing::ThingErrorInvalidParameter;
}
void TestIntegrations::setPluginConfig()
{
QFETCH(PluginId, pluginId);
QFETCH(QVariant, value);
QFETCH(Thing::ThingError, error);
QVariantMap params;
params.insert("pluginId", pluginId);
QVariantList configuration;
QVariantMap configParam;
configParam.insert("paramTypeId", mockPluginConfigParamIntParamTypeId);
configParam.insert("value", value);
configuration.append(configParam);
params.insert("configuration", configuration);
QVariant response = injectAndWait("Integrations.SetPluginConfiguration", params);
verifyThingError(response, error);
if (error == Thing::ThingErrorNoError) {
params.clear();
params.insert("pluginId", pluginId);
response = injectAndWait("Integrations.GetPluginConfiguration", params);
verifyThingError(response);
qDebug() << value << response.toMap().value("params").toMap().value("configuration").toList().first();
QVERIFY2(ParamTypeId(response.toMap().value("params").toMap().value("configuration").toList().first().toMap().value("paramTypeId").toString()) == mockPluginConfigParamIntParamTypeId, "Value not set correctly");
QVERIFY2(response.toMap().value("params").toMap().value("configuration").toList().first().toMap().value("value") == value, "Value not set correctly");
}
}
void TestIntegrations::getSupportedVendors()
{
QVariant supportedVendors = injectAndWait("Integrations.GetVendors");
qDebug() << "response" << supportedVendors;
// Make sure there is exactly 1 Vendor with nymea's id
QVariantList vendorList = supportedVendors.toMap().value("params").toMap().value("vendors").toList();
QCOMPARE(vendorList.count() > 0, true);
bool found = false;
foreach (const QVariant &listEntry, vendorList) {
if (VendorId(listEntry.toMap().value("id").toString()) == nymeaVendorId) {
found = true;
}
}
QCOMPARE(found, true);
}
void TestIntegrations::getThingClasses_data()
{
QTest::addColumn<VendorId>("vendorId");
QTest::addColumn<QList<ThingClassId>>("thingClassIds");
QTest::addColumn<int>("resultCount");
QTest::newRow("vendor nymea") << nymeaVendorId << QList<ThingClassId>() << 16;
QTest::newRow("no filter") << VendorId() << QList<ThingClassId>() << 16;
QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << QList<ThingClassId>() << 0;
QTest::newRow("mockThingClassId") << VendorId() << (QList<ThingClassId>() << mockThingClassId) << 1;
QTest::newRow("invalid thingClassId") << VendorId() << (QList<ThingClassId>() << ThingClassId("6c78ec28-09b6-476d-ac27-1d6966a45c57")) << 0;
}
void TestIntegrations::getThingClasses()
{
QFETCH(VendorId, vendorId);
QFETCH(QList<ThingClassId>, thingClassIds);
QFETCH(int, resultCount);
QVariantMap params;
if (!vendorId.isNull()) {
params.insert("vendorId", vendorId);
}
if (!thingClassIds.isEmpty()) {
QStringList thingClassIdStrings;
foreach (const ThingClassId &id, thingClassIds) {
thingClassIdStrings.append(id.toString());
}
params.insert("thingClassIds", thingClassIdStrings);
}
QVariant result = injectAndWait("Integrations.GetThingClasses", params);
QVariantList thingClasses = result.toMap().value("params").toMap().value("thingClasses").toList();
// Make sure there are the right amount of thing classes
QCOMPARE(thingClasses.count(), resultCount);
}
void TestIntegrations::verifyInterfaces()
{
QVariantMap params;
params.insert("vendorId", nymeaVendorId);
QVariant result = injectAndWait("Integrations.GetThingClasses", params);
QVariantList supportedThings = result.toMap().value("params").toMap().value("thingClasses").toList();
QVariantMap mock;
foreach (const QVariant &thingClass, supportedThings) {
if (thingClass.toMap().value("id").toUuid() == mockThingClassId) {
mock = thingClass.toMap();
}
}
QVERIFY(!mock.isEmpty());
QVariantList interfaces = mock.value("interfaces").toList();
QVariantList expectedInterfaces = {"system", "light", "power", "battery", "wirelessconnectable", "connectable", "update", "multibutton", "button"};
qCDebug(dcTests()) << interfaces;
qCDebug(dcTests()) << expectedInterfaces;
QCOMPARE(interfaces, expectedInterfaces);
}
void TestIntegrations::addThing_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<QVariantList>("thingParams");
QTest::addColumn<bool>("jsonValidation");
QTest::addColumn<Thing::ThingError>("thingError");
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId.toString());
httpportParam.insert("value", m_mockThing1Port - 1);
QVariantMap asyncParam;
asyncParam.insert("paramTypeId", mockThingAsyncParamTypeId);
asyncParam.insert("value", true);
QVariantMap brokenParam;
brokenParam.insert("paramTypeId", mockThingBrokenParamTypeId);
brokenParam.insert("value", true);
QVariantList thingParams;
thingParams.clear(); thingParams << httpportParam;
QTest::newRow("User, JustAdd") << mockThingClassId << thingParams << true << Thing::ThingErrorNoError;
thingParams.clear(); thingParams << httpportParam << asyncParam;
QTest::newRow("User, JustAdd, Async") << mockThingClassId << thingParams << true << Thing::ThingErrorNoError;
QTest::newRow("Invalid ThingClassId") << ThingClassId::createThingClassId() << thingParams << true << Thing::ThingErrorThingClassNotFound;
thingParams.clear(); thingParams << httpportParam << brokenParam;
QTest::newRow("Setup failure") << mockThingClassId << thingParams << true << Thing::ThingErrorSetupFailed;
thingParams.clear(); thingParams << httpportParam << asyncParam << brokenParam;
QTest::newRow("Setup failure, Async") << mockThingClassId << thingParams << true << Thing::ThingErrorSetupFailed;
QVariantList invalidThingParams;
QTest::newRow("User, JustAdd, missing params") << mockThingClassId << invalidThingParams << true << Thing::ThingErrorMissingParameter;
QVariantMap fakeparam;
fakeparam.insert("paramTypeId", ParamTypeId::createParamTypeId());
invalidThingParams.append(fakeparam);
QTest::newRow("User, JustAdd, invalid param") << mockThingClassId << invalidThingParams << false << Thing::ThingErrorMissingParameter;
QVariantMap fakeparam2;
fakeparam2.insert("paramTypeId", mockThingHttpportParamTypeId.toString());
fakeparam2.insert("value", "blabla");
invalidThingParams.clear();
invalidThingParams.append(fakeparam2);
QTest::newRow("User, JustAdd, wrong param") << mockThingClassId << invalidThingParams << true << Thing::ThingErrorInvalidParameter;
thingParams.clear(); thingParams << httpportParam << fakeparam;
QTest::newRow("USer, JustAdd, additional invalid param") << mockThingClassId << thingParams << false << Thing::ThingErrorInvalidParameter;
thingParams.clear(); thingParams << httpportParam << fakeparam2;
QTest::newRow("USer, JustAdd, duplicate param") << mockThingClassId << thingParams << true << Thing::ThingErrorInvalidParameter;
}
void TestIntegrations::addThing()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(QVariantList, thingParams);
QFETCH(bool, jsonValidation);
QFETCH(Thing::ThingError, thingError);
QVariantMap params;
params.insert("thingClassId", thingClassId);
params.insert("name", "Test Add Thing");
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
if (!jsonValidation) {
QCOMPARE(response.toMap().value("status").toString(), QString("error"));
return;
}
verifyThingError(response, thingError);
if (thingError == Thing::ThingErrorNoError) {
QUuid thingId(response.toMap().value("params").toMap().value("thingId").toString());
params.clear();
params.insert("thingId", thingId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
}
void TestIntegrations::thingAddedRemovedNotifications()
{
enableNotifications({"Integrations"});
// Setup connection to mock client
QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
// add thing and wait for notification
QVariantList thingParams;
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParam.insert("value", 5678);
thingParams.append(httpportParam);
QVariantMap params; clientSpy.clear();
params.insert("thingClassId", mockThingClassId);
params.insert("name", "Mocked thing");
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
if (clientSpy.count() == 0) clientSpy.wait();
verifyThingError(response);
QVariantMap notificationThingMap = checkNotification(clientSpy, "Integrations.ThingAdded").toMap().value("params").toMap().value("thing").toMap();
ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!thingId.isNull());
// check the ThingAdded notification
QCOMPARE(notificationThingMap.value("thingClassId").toUuid(), QUuid(mockThingClassId));
QCOMPARE(notificationThingMap.value("id").toUuid(), QUuid(thingId));
foreach (const QVariant &param, notificationThingMap.value("params").toList()) {
if (param.toMap().value("name").toString() == "httpport") {
QCOMPARE(param.toMap().value("value").toInt(), httpportParam.value("value").toInt());
}
}
// now remove the thong and check the thing removed notification
params.clear(); response.clear(); clientSpy.clear();
params.insert("thingId", thingId);
response = injectAndWait("Integrations.RemoveThing", params);
if (clientSpy.count() == 0) clientSpy.wait();
verifyThingError(response);
checkNotification(clientSpy, "Integrations.ThingRemoved");
QCOMPARE(disableNotifications(), true);
}
void TestIntegrations::thingChangedNotifications()
{
enableNotifications({"Integrations"});
// Setup connection to mock client
QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
// ADD
// add thing and wait for notification
QVariantList thingParams;
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParam.insert("value", 23234);
thingParams.append(httpportParam);
clientSpy.clear();
QVariantMap params;
params.insert("thingClassId", mockThingClassId);
params.insert("name", "Mock");
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!thingId.isNull());
if (clientSpy.count() == 0) clientSpy.wait();
verifyThingError(response);
QVariantMap notificationThingMap = checkNotification(clientSpy, "Integrations.ThingAdded").toMap().value("params").toMap().value("thing").toMap();
QCOMPARE(notificationThingMap.value("thingClassId").toUuid(), QUuid(mockThingClassId));
QCOMPARE(notificationThingMap.value("id").toUuid(), QUuid(thingId));
foreach (const QVariant &param, notificationThingMap.value("params").toList()) {
if (param.toMap().value("name").toString() == "httpport") {
QCOMPARE(param.toMap().value("value").toInt(), httpportParam.value("value").toInt());
}
}
// RECONFIGURE
// now reconfigure the thing and check the thing changed notification
QVariantList newThingParams;
QVariantMap newHttpportParam;
newHttpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
newHttpportParam.insert("value", 45473);
newThingParams.append(newHttpportParam);
params.clear(); response.clear(); clientSpy.clear();
params.insert("thingId", thingId);
params.insert("thingParams", newThingParams);
response = injectAndWait("Integrations.ReconfigureThing", params);
if (clientSpy.count() == 0) clientSpy.wait();
verifyThingError(response);
QVariantMap reconfigureThingNotificationMap = checkNotification(clientSpy, "Integrations.ThingChanged").toMap().value("params").toMap().value("thing").toMap();
QCOMPARE(reconfigureThingNotificationMap.value("thingClassId").toUuid(), QUuid(mockThingClassId));
QCOMPARE(reconfigureThingNotificationMap.value("id").toUuid(), QUuid(thingId));
foreach (const QVariant &param, reconfigureThingNotificationMap.value("params").toList()) {
if (param.toMap().value("name").toString() == "httpport") {
QCOMPARE(param.toMap().value("value").toInt(), newHttpportParam.value("value").toInt());
}
}
// EDIT thing name
QString thingName = "Test thing 1234";
params.clear(); response.clear(); clientSpy.clear();
params.insert("thingId", thingId);
params.insert("name", thingName);
response = injectAndWait("Integrations.EditThing", params);
if (clientSpy.count() == 0) clientSpy.wait();
verifyThingError(response);
QVariantMap editThingNotificationMap = checkNotification(clientSpy, "Integrations.ThingChanged").toMap().value("params").toMap().value("thing").toMap();
QCOMPARE(editThingNotificationMap.value("thingClassId").toUuid(), QUuid(mockThingClassId));
QCOMPARE(editThingNotificationMap.value("id").toUuid(), QUuid(thingId));
QCOMPARE(editThingNotificationMap.value("name").toString(), thingName);
// REMOVE
// now remove the thing and check the thing removed notification
params.clear(); response.clear(); clientSpy.clear();
params.insert("thingId", thingId);
response = injectAndWait("Integrations.RemoveThing", params);
if (clientSpy.count() == 0) clientSpy.wait();
verifyThingError(response);
checkNotification(clientSpy, "Integrations.ThingRemoved");
checkNotification(clientSpy, "Logging.LogDatabaseUpdated");
}
void TestIntegrations::getThings()
{
QVariant response = injectAndWait("Integrations.GetThings");
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
QCOMPARE(things.count(), 3); // There should be: one auto created mock, one created in NymeaTestBase::initTestcase() and one created in TestIntegrations::initTestCase()
}
void TestIntegrations::getThing_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::addColumn<Thing::ThingError>("expectedError");
QTest::newRow("valid thingId") << ThingId(m_mockThingId) << Thing::ThingErrorNoError;
QTest::newRow("invalid thingId") << ThingId::createThingId() << Thing::ThingErrorThingNotFound;
}
void TestIntegrations::getThing()
{
QFETCH(ThingId, thingId);
QFETCH(Thing::ThingError, expectedError);
QVariantMap params;
params.insert("thingId", thingId);
QVariant response = injectAndWait("Integrations.GetThings", params);
// qCDebug(dcTests()) << qUtf8Printable(QJsonDocument::fromVariant(response).toJson());
if (expectedError == Thing::ThingErrorNoError) {
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
QCOMPARE(things.count(), 1);
}
}
void TestIntegrations::storedThings()
{
QVariantMap params;
params.insert("thingClassId", mockThingClassId);
params.insert("name", "Test stored thing");
QVariantList thingParams;
QVariantMap asyncParam;
asyncParam.insert("paramTypeId", mockThingAsyncParamTypeId);
asyncParam.insert("value", false);
thingParams.append(asyncParam);
QVariantMap brokenParam;
brokenParam.insert("paramTypeId", mockThingBrokenParamTypeId);
brokenParam.insert("value", false);
thingParams.append(brokenParam);
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParam.insert("value", 8889);
thingParams.append(httpportParam);
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId addedThingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!addedThingId.isNull());
clearLoggingDatabase();
// Restart the core instance to check if settings are loaded at startup
restartServer();
response = injectAndWait("Integrations.GetThings", QVariantMap());
bool found = false;
foreach (const QVariant &thing, response.toMap().value("params").toMap().value("things").toList()) {
if (ThingId(thing.toMap().value("id").toString()) == addedThingId) {
qCDebug(dcTests()) << "found added thing" << thing.toMap().value("params");
qCDebug(dcTests()) << "expected thingParams:" << thingParams;
verifyParams(thingParams, thing.toMap().value("params").toList());
found = true;
break;
}
}
QVERIFY2(found, "thing missing in config!");
// Wait for the DB to sync up and then verify that just restarting did not add state changes for this thing
waitForDBSync();
params.clear();
params.insert("thingIds", QVariantList() << addedThingId);
params.insert("loggingSources", QVariantList() << "LoggingSourceStates");
response = injectAndWait("Logging.GetLogEntries", params);
QVERIFY2(response.toMap().value("params").toMap().contains("logEntries"), "Huh? GetLogEntries failed!");
qCDebug(dcTests()) << "log response:" << response.toMap().value("params").toMap().value("logEntries");
QVERIFY2(response.toMap().value("params").toMap().value("logEntries").toList().isEmpty(), "There are state changed events after a core restart even though states did not change!");
params.clear();
params.insert("thingId", addedThingId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
void TestIntegrations::stateCache()
{
ThingClass mockThingClass = NymeaCore::instance()->thingManager()->findThingClass(mockThingClassId);
QVERIFY2(mockThingClass.getStateType(mockIntStateTypeId).cached(), "Mock int state is not cached (required to be true for this test)");
QVERIFY2(!mockThingClass.getStateType(mockBoolStateTypeId).cached(), "Mock bool state is cached (required to be false for this test)");
Thing* thing = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId).first();
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
QNetworkAccessManager nam;
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
// First set the state values to something that is *not* the default
int oldIntValue = mockThingClass.getStateType(mockIntStateTypeId).defaultValue().toInt();
int newIntValue = oldIntValue + 1;
bool oldBoolValue = mockThingClass.getStateType(mockBoolStateTypeId).defaultValue().toBool();
bool newBoolValue = !oldBoolValue;
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(newIntValue)));
QNetworkReply *reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
spy.wait();
spy.clear();
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(newBoolValue)));
reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
spy.wait();
// For completeness, verify through JSONRPC that they were actually yet.
QVariantMap params;
params.insert("thingId", thing->id());
params["stateTypeId"] = mockIntStateTypeId;
QVariant response = injectAndWait("Integrations.GetStateValue", params);
QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue);
params["stateTypeId"] = mockBoolStateTypeId;
response = injectAndWait("Integrations.GetStateValue", params);
QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), newBoolValue);
// Restart the server
restartServer();
// And check if the cached int state has successfully been restored
params["stateTypeId"] = mockIntStateTypeId;
response = injectAndWait("Integrations.GetStateValue", params);
QCOMPARE(response.toMap().value("params").toMap().value("value").toInt(), newIntValue);
// and that the non-cached bool state is back to its default
params["stateTypeId"] = mockBoolStateTypeId;
response = injectAndWait("Integrations.GetStateValue", params);
QCOMPARE(response.toMap().value("params").toMap().value("value").toBool(), mockThingClass.getStateType(mockBoolStateTypeId).defaultValue().toBool());
// Reset back to default values
spy.clear();
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(oldIntValue)));
reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
spy.wait();
spy.clear();
request = QNetworkRequest(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockBoolStateTypeId.toString()).arg(oldBoolValue)));
reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
spy.wait();
}
void TestIntegrations::discoverThings_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::addColumn<Thing::ThingError>("error");
QTest::addColumn<QVariantList>("discoveryParams");
QVariantList discoveryParams;
QVariantMap resultCountParam;
resultCountParam.insert("paramTypeId", mockDiscoveryResultCountParamTypeId);
resultCountParam.insert("value", 1);
discoveryParams.append(resultCountParam);
QTest::newRow("valid thingClassId") << mockThingClassId << 2 << Thing::ThingErrorNoError << QVariantList();
QTest::newRow("valid thingClassId with params") << mockThingClassId << 1 << Thing::ThingErrorNoError << discoveryParams;
QTest::newRow("invalid thingClassId") << ThingClassId::createThingClassId() << 0 << Thing::ThingErrorThingClassNotFound << QVariantList();
}
void TestIntegrations::discoverThings()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(int, resultCount);
QFETCH(Thing::ThingError, error);
QFETCH(QVariantList, discoveryParams);
QVariantMap params;
params.insert("thingClassId", thingClassId);
params.insert("discoveryParams", discoveryParams);
QVariant response = injectAndWait("Integrations.DiscoverThings", params);
verifyThingError(response, error);
if (error == Thing::ThingErrorNoError) {
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), resultCount);
}
// If we found something, lets try to add it
if (error == Thing::ThingErrorNoError) {
ThingDescriptorId descriptorId = ThingDescriptorId(response.toMap().value("params").toMap().value("thingDescriptors").toList().first().toMap().value("id").toString());
params.clear();
params.insert("thingClassId", thingClassId);
params.insert("name", "Discoverd mock");
params.insert("thingDescriptorId", descriptorId.toString());
response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId thingId(response.toMap().value("params").toMap().value("thingId").toString());
params.clear();
params.insert("thingId", thingId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
}
void TestIntegrations::addPushButtonThings_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<Thing::ThingError>("error");
QTest::addColumn<bool>("waitForButtonPressed");
QTest::newRow("Valid: Add PushButton thing") << pushButtonMockThingClassId << Thing::ThingErrorNoError << true;
QTest::newRow("Invalid: Add PushButton thing (press to early)") << pushButtonMockThingClassId << Thing::ThingErrorAuthenticationFailure << false;
}
void TestIntegrations::addPushButtonThings()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(Thing::ThingError, error);
QFETCH(bool, waitForButtonPressed);
// Discover things
QVariantList discoveryParams;
QVariantMap resultCountParam;
resultCountParam.insert("paramTypeId", pushButtonMockDiscoveryResultCountParamTypeId);
resultCountParam.insert("value", 1);
discoveryParams.append(resultCountParam);
QVariantMap params;
params.insert("thingClassId", thingClassId);
params.insert("discoveryParams", discoveryParams);
QVariant response = injectAndWait("Integrations.DiscoverThings", params);
verifyThingError(response, Thing::ThingErrorNoError);
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), 1);
// Pair thing
ThingDescriptorId descriptorId = ThingDescriptorId(response.toMap().value("params").toMap().value("thingDescriptors").toList().first().toMap().value("id").toString());
params.clear();
params.insert("thingClassId", thingClassId);
params.insert("name", "Pushbutton mock");
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();
qDebug() << "displayMessage" << displayMessage;
if (waitForButtonPressed)
QTest::qWait(3500);
// Confirm pairing
params.clear();
params.insert("pairingTransactionId", pairingTransactionId.toString());
response = injectAndWait("Integrations.ConfirmPairing", params);
verifyThingError(response, error);
if (error == Thing::ThingErrorNoError) {
ThingId thingId(response.toMap().value("params").toMap().value("thingId").toString());
params.clear();
params.insert("thingId", thingId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
}
void TestIntegrations::addDisplayPinThings_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<Thing::ThingError>("error");
QTest::addColumn<QString>("secret");
QTest::newRow("Valid: Add DisplayPin mock") << displayPinMockThingClassId << Thing::ThingErrorNoError << "243681";
QTest::newRow("Invalid: Add DisplayPin mock (wrong pin)") << displayPinMockThingClassId << Thing::ThingErrorAuthenticationFailure << "243682";
}
void TestIntegrations::addDisplayPinThings()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(Thing::ThingError, error);
QFETCH(QString, secret);
// Discover things
QVariantList discoveryParams;
QVariantMap resultCountParam;
resultCountParam.insert("paramTypeId", displayPinMockDiscoveryResultCountParamTypeId);
resultCountParam.insert("value", 1);
discoveryParams.append(resultCountParam);
QVariantMap params;
params.insert("thingClassId", thingClassId);
params.insert("discoveryParams", discoveryParams);
QVariant response = injectAndWait("Integrations.DiscoverThings", params);
verifyThingError(response, Thing::ThingErrorNoError);
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), 1);
// Pair thing
ThingDescriptorId descriptorId = ThingDescriptorId(response.toMap().value("params").toMap().value("thingDescriptors").toList().first().toMap().value("id").toString());
params.clear();
params.insert("thingClassId", thingClassId);
params.insert("name", "Display pin mock");
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", secret);
response = injectAndWait("Integrations.ConfirmPairing", params);
verifyThingError(response, error);
if (error == Thing::ThingErrorNoError) {
ThingId thingId(response.toMap().value("params").toMap().value("thingId").toString());
params.clear();
params.insert("thingId", thingId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
}
void TestIntegrations::parentChildThings()
{
// add parent
QVariantMap params;
params.insert("thingClassId", parentMockThingClassId);
params.insert("name", "Parent");
QSignalSpy thingAddedSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingAdded);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId parentId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!parentId.isNull());
thingAddedSpy.wait();
QCOMPARE(thingAddedSpy.count(), 2);
// find child
response = injectAndWait("Integrations.GetThings");
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
ThingId childId;
foreach (const QVariant thingVariant, things) {
QVariantMap thingMap = thingVariant.toMap();
if (thingMap.value("thingClassId").toUuid() == childMockThingClassId) {
if (thingMap.value("parentId").toUuid() == parentId) {
childId = ThingId(thingMap.value("id").toString());
break;
}
}
}
QVERIFY2(!childId.isNull(), QString("Could not find child:\nParent ID:%1\nResponse:%2")
.arg(parentId.toString())
.arg(qUtf8Printable(QJsonDocument::fromVariant(response).toJson()))
.toUtf8());
// Try to remove the child
params.clear();
params.insert("thingId", childId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, Thing::ThingErrorThingIsChild);
// check if the child is still there
response = injectAndWait("Integrations.GetThings");
things = response.toMap().value("params").toMap().value("things").toList();
bool found = false;
foreach (const QVariant thingVariant, things) {
QVariantMap thingMap = thingVariant.toMap();
if (thingMap.value("thingClassId").toUuid() == childMockThingClassId) {
if (thingMap.value("id").toUuid() == childId) {
found = true;
break;
}
}
}
QVERIFY2(found, "Could not find child.");
// remove the parent
params.clear();
params.insert("thingId", parentId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
// check if the child is still there
response = injectAndWait("Integrations.GetThings");
things = response.toMap().value("params").toMap().value("things").toList();
found = false;
foreach (const QVariant thingVariant, things) {
QVariantMap thingMap = thingVariant.toMap();
if (thingMap.value("thingClassId").toString() == childMockThingClassId.toString()) {
if (thingMap.value("id") == childId.toString()) {
found = true;
break;
}
}
}
QVERIFY2(!found, "Could not find child.");
}
void TestIntegrations::getActionTypes_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<QList<ActionTypeId> >("actionTypeTestData");
QTest::newRow("valid thingClass") << mockThingClassId
<< (QList<ActionTypeId>() << mockIntWithLimitsActionTypeId << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId << mockPressButtonActionTypeId);
QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList<ActionTypeId>();
}
void TestIntegrations::getActionTypes()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(QList<ActionTypeId>, actionTypeTestData);
QVariantMap params;
params.insert("thingClassId", thingClassId);
QVariant response = injectAndWait("Integrations.GetActionTypes", params);
QVariantList actionTypes = response.toMap().value("params").toMap().value("actionTypes").toList();
QCOMPARE(actionTypes.count(), actionTypeTestData.count());
foreach (const ActionTypeId &testDataId, actionTypeTestData) {
bool found = false;
foreach (const QVariant &at, actionTypes) {
if (testDataId == at.toMap().value("id").toUuid()) {
found = true;
break;
}
}
QVERIFY(found);
}
}
void TestIntegrations::getEventTypes_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::newRow("valid thingClass") << mockThingClassId << 3;
QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0;
}
void TestIntegrations::getEventTypes()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(int, resultCount);
QVariantMap params;
params.insert("thingClassId", thingClassId);
QVariant response = injectAndWait("Integrations.GetEventTypes", params);
qDebug() << response;
QVariantList eventTypes = response.toMap().value("params").toMap().value("eventTypes").toList();
QCOMPARE(eventTypes.count(), resultCount);
}
void TestIntegrations::getStateTypes_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::newRow("valid thingClass") << mockThingClassId << 12;
QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << 0;
}
void TestIntegrations::getStateTypes()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(int, resultCount);
QVariantMap params;
params.insert("thingClassId", thingClassId);
QVariant response = injectAndWait("Integrations.GetStateTypes", params);
QVariantList stateTypes = response.toMap().value("params").toMap().value("stateTypes").toList();
QCOMPARE(stateTypes.count(), resultCount);
if (resultCount > 0) {
QCOMPARE(stateTypes.first().toMap().value("id").toUuid().toString(), mockIntStateTypeId.toString());
}
}
void TestIntegrations::getStateValue_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::addColumn<StateTypeId>("stateTypeId");
QTest::addColumn<Thing::ThingError>("statusCode");
QTest::newRow("valid thingId") << ThingId(m_mockThingId) << mockIntStateTypeId << Thing::ThingErrorNoError;
QTest::newRow("invalid thingId") << ThingId("094f8024-5caa-48c1-ab6a-de486a92088f") << mockIntStateTypeId << Thing::ThingErrorThingNotFound;
QTest::newRow("invalid statetypeId") << ThingId(m_mockThingId) << StateTypeId("120514f1-343e-4621-9bff-dac616169df9") << Thing::ThingErrorStateTypeNotFound;
}
void TestIntegrations::getStateValue()
{
NymeaCore::instance()->thingManager()->findConfiguredThing(m_mockThingId)->setStateValue(mockIntStateTypeId, 10);
QFETCH(ThingId, thingId);
QFETCH(StateTypeId, stateTypeId);
QFETCH(Thing::ThingError, statusCode);
QVariantMap params;
params.insert("thingId", thingId);
params.insert("stateTypeId", stateTypeId);
QVariant response = injectAndWait("Integrations.GetStateValue", params);
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), enumValueName(statusCode));
if (statusCode == Thing::ThingErrorNoError) {
QVariant value = response.toMap().value("params").toMap().value("value");
QCOMPARE(value.toInt(), 10); // Mock has value 10 by default...
}
}
void TestIntegrations::getStateValues_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::addColumn<Thing::ThingError>("statusCode");
QTest::newRow("valid thingId") << ThingId(m_mockThingId) << Thing::ThingErrorNoError;
QTest::newRow("invalid thingId") << ThingId("094f8024-5caa-48c1-ab6a-de486a92088f") << Thing::ThingErrorThingNotFound;
}
void TestIntegrations::getStateValues()
{
QFETCH(ThingId, thingId);
QFETCH(Thing::ThingError, statusCode);
QVariantMap params;
params.insert("thingId", thingId);
QVariant response = injectAndWait("Integrations.GetStateValues", params);
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), enumValueName(statusCode));
if (statusCode == Thing::ThingErrorNoError) {
QVariantList values = response.toMap().value("params").toMap().value("values").toList();
QCOMPARE(values.count(), 12); // Mock has 12 states...
}
}
void TestIntegrations::editThings_data()
{
QTest::addColumn<QString>("name");
QTest::newRow("change name") << "New name";
QTest::newRow("change name") << "Foo";
QTest::newRow("change name") << "Bar";
}
void TestIntegrations::editThings()
{
QFETCH(QString, name);
QString originalName = "Test thing";
// add thing
QVariantList thingParams;
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParam.insert("value", 8889);
thingParams.append(httpportParam);
QVariantMap params;
params.insert("thingClassId", mockThingClassId);
params.insert("name", originalName);
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
// edit thing
params.clear();
params.insert("thingId", thingId);
params.insert("name", name);
response = injectAndWait("Integrations.EditThing", params);
verifyThingError(response);
// verify changed
QString newName;
response = injectAndWait("Integrations.GetThings");
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
foreach (const QVariant &thingVariant, things) {
QVariantMap thing = thingVariant.toMap();
if (ThingId(thing.value("id").toString()) == thingId) {
newName = thing.value("name").toString();
}
}
QCOMPARE(newName, name);
restartServer();
// check if the changed name is still there after loading
response = injectAndWait("Integrations.GetThings");
things = response.toMap().value("params").toMap().value("things").toList();
foreach (const QVariant &thingVariant, things) {
QVariantMap thing = thingVariant.toMap();
if (ThingId(thing.value("id").toString()) == thingId) {
newName = thing.value("name").toString();
break;
}
}
QCOMPARE(newName, name);
params.clear();
params.insert("thingId", thingId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
void TestIntegrations::testThingSettings()
{
// add thing
QVariantList thingParams;
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParam.insert("value", 8889);
thingParams.append(httpportParam);
QVariantMap params;
params.insert("thingClassId", mockThingClassId);
params.insert("name", "Mock");
params.insert("thingParams", thingParams);
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
// check if default settings are loaded
params.clear();
params.insert("thingId", thingId);
response = injectAndWait("Integrations.GetThings", params);
QVariantList things = response.toMap().value("params").toMap().value("things").toList();
QVERIFY2(things.count() == 1, "Error creating thing");
QVariantMap thing = things.first().toMap();
QVERIFY2(ThingId(thing.value("id").toString()) == thingId, "thingId not matching");
QVariantList settings = thing.value("settings").toList();
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 5, "Setting 1 default value not matching");
// change a setting
params.clear();
params.insert("thingId", thingId);
settings.clear();
QVariantMap setting;
setting.insert("paramTypeId", mockSettingsSetting1ParamTypeId);
setting.insert("value", 7);
settings.append(setting);
params.insert("settings", settings);
response = injectAndWait("Integrations.SetThingSettings", params);
// Check if the change happened
params.clear();
params.insert("thingId", thingId);
response = injectAndWait("Integrations.GetThings", params);
things = response.toMap().value("params").toMap().value("things").toList();
QVERIFY2(things.count() == 1, "Error creating thing");
thing = things.first().toMap();
QVERIFY2(ThingId(thing.value("id").toString()) == thingId, "thingId not matching");
settings = thing.value("settings").toList();
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not matching");
restartServer();
// Check if the change persisted
params.clear();
params.insert("thingId", thingId);
response = injectAndWait("Integrations.GetThings", params);
things = response.toMap().value("params").toMap().value("things").toList();
QVERIFY2(things.count() == 1, "Error creating thing");
thing = things.first().toMap();
QVERIFY2(ThingId(thing.value("id").toString()) == thingId, "thingId not matching");
settings = thing.value("settings").toList();
QCOMPARE(settings.count(), 3);
QCOMPARE(settings.first().toMap().value("paramTypeId").toUuid(), QUuid(mockSettingsSetting1ParamTypeId));
QVERIFY2(settings.first().toMap().value("value").toInt() == 7, "Setting 1 changed value not persisting restart");
}
void TestIntegrations::reconfigureThings_data()
{
QVariantList asyncChangeThingParams;
QVariantMap asyncParamDifferent;
asyncParamDifferent.insert("paramTypeId", mockThingAsyncParamTypeId);
asyncParamDifferent.insert("value", true);
asyncChangeThingParams.append(asyncParamDifferent);
QVariantList httpportChangeThingParams;
QVariantMap httpportParamDifferent;
httpportParamDifferent.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParamDifferent.insert("value", 8893); // if changing this, change also newPort in reconfigureThings()
httpportChangeThingParams.append(httpportParamDifferent);
QVariantList brokenChangedThingParams;
QVariantMap brokenParamDifferent;
brokenParamDifferent.insert("paramTypeId", mockThingBrokenParamTypeId);
brokenParamDifferent.insert("value", true);
brokenChangedThingParams.append(brokenParamDifferent);
QVariantList asyncAndPortChangeThingParams;
asyncAndPortChangeThingParams.append(asyncParamDifferent);
asyncAndPortChangeThingParams.append(httpportParamDifferent);
QVariantList changeAllWritableThingParams;
changeAllWritableThingParams.append(asyncParamDifferent);
changeAllWritableThingParams.append(httpportParamDifferent);
QTest::addColumn<bool>("broken");
QTest::addColumn<QVariantList>("newThingParams");
QTest::addColumn<Thing::ThingError>("thingError");
QTest::newRow("valid - change async param") << false << asyncChangeThingParams << Thing::ThingErrorParameterNotWritable;
QTest::newRow("valid - change httpport param") << false << httpportChangeThingParams << Thing::ThingErrorNoError;
QTest::newRow("invalid - change httpport and async param") << false << asyncAndPortChangeThingParams << Thing::ThingErrorParameterNotWritable;
QTest::newRow("invalid - change all params (except broken)") << false << changeAllWritableThingParams << Thing::ThingErrorParameterNotWritable;
}
void TestIntegrations::reconfigureThings()
{
QFETCH(bool, broken);
QFETCH(QVariantList, newThingParams);
QFETCH(Thing::ThingError, thingError);
// add thing
QVariantMap params;
params.insert("thingClassId", mockThingClassId);
params.insert("name", "Thing to edit");
QVariantList thingParams;
QVariantMap asyncParam;
asyncParam.insert("paramTypeId", mockThingAsyncParamTypeId);
asyncParam.insert("value", false);
thingParams.append(asyncParam);
QVariantMap brokenParam;
brokenParam.insert("paramTypeId", mockThingBrokenParamTypeId);
brokenParam.insert("value", broken);
thingParams.append(brokenParam);
QVariantMap httpportParam;
httpportParam.insert("paramTypeId", mockThingHttpportParamTypeId);
httpportParam.insert("value", 8892);
thingParams.append(httpportParam);
params.insert("thingParams", thingParams);
// add a mock
QVariant response = injectAndWait("Integrations.AddThing", params);
verifyThingError(response);
ThingId thingId = ThingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!thingId.isNull());
// now EDIT the added mock
response.clear();
QVariantMap editParams;
editParams.insert("thingId", thingId);
editParams.insert("thingParams", newThingParams);
response = injectAndWait("Integrations.ReconfigureThing", editParams);
verifyThingError(response, thingError);
// if the edit should have been successful
if (thingError == Thing::ThingErrorNoError) {
response = injectAndWait("Integrations.GetThings", QVariantMap());
bool found = false;
foreach (const QVariant &thing, response.toMap().value("params").toMap().value("things").toList()) {
if (ThingId(thing.toMap().value("id").toString()) == thingId) {
qDebug() << "found added thing" << thing.toMap().value("params");
qDebug() << "expected thingParams:" << newThingParams;
// check if the edit was ok
verifyParams(newThingParams, thing.toMap().value("params").toList(), false);
found = true;
break;
}
}
QVERIFY2(found, "Thing missing in config!");
// Restart the core instance to check if settings are loaded at startup
restartServer();
response = injectAndWait("Integrations.GetThings", QVariantMap());
found = false;
foreach (const QVariant &thing, response.toMap().value("params").toMap().value("things").toList()) {
if (ThingId(thing.toMap().value("id").toString()) == thingId) {
qDebug() << "found added thing" << thing.toMap().value("params");
qDebug() << "expected params:" << newThingParams;
// check if the edit was ok
verifyParams(newThingParams, thing.toMap().value("params").toList(), false);
found = true;
break;
}
}
QVERIFY2(found, "Thing missing in config!");
// delete it
params.clear();
params.insert("thingId", thingId);
response.clear();
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
return;
} else {
// The edit was not ok, check if the old params are still there
response = injectAndWait("Integrations.GetThings", QVariantMap());
bool found = false;
foreach (const QVariant &thing, response.toMap().value("params").toMap().value("things").toList()) {
if (ThingId(thing.toMap().value("id").toString()) == thingId) {
qDebug() << "found added thing" << thing.toMap().value("params");
qDebug() << "expected thingParams:" << newThingParams;
// check if the params are unchanged
verifyParams(thingParams, thing.toMap().value("params").toList());
found = true;
break;
}
}
QVERIFY2(found, "Thing missing in config!");
// Restart the core instance to check if settings are loaded at startup
restartServer();
response = injectAndWait("Integrations.GetThings", QVariantMap());
found = false;
foreach (const QVariant &thing, response.toMap().value("params").toMap().value("things").toList()) {
if (ThingId(thing.toMap().value("id").toString()) == thingId) {
qDebug() << "found added thing" << thing.toMap().value("params");
qDebug() << "expected thingParams:" << newThingParams;
// check if after the reboot the settings are unchanged
verifyParams(thingParams, thing.toMap().value("params").toList());
found = true;
break;
}
}
QVERIFY2(found, "Thing missing in config!");
}
// delete it
params.clear();
params.insert("thingId", thingId);
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
void TestIntegrations::reconfigureByDiscovery_data()
{
QTest::addColumn<ThingClassId>("thingClassId");
QTest::addColumn<int>("resultCount");
QTest::addColumn<Thing::ThingError>("error");
QTest::addColumn<QVariantList>("discoveryParams");
QVariantList discoveryParams;
QVariantMap resultCountParam;
resultCountParam.insert("paramTypeId", mockDiscoveryResultCountParamTypeId);
resultCountParam.insert("value", 2);
discoveryParams.append(resultCountParam);
QTest::newRow("discover 2 things with params") << mockThingClassId << 2 << Thing::ThingErrorNoError << discoveryParams;
}
void TestIntegrations::reconfigureByDiscovery()
{
QFETCH(ThingClassId, thingClassId);
QFETCH(int, resultCount);
QFETCH(Thing::ThingError, error);
QFETCH(QVariantList, discoveryParams);
qCDebug(dcTests()) << "Discovering...";
QVariantMap params;
params.insert("thingClassId", thingClassId);
params.insert("discoveryParams", discoveryParams);
QVariant response = injectAndWait("Integrations.DiscoverThings", params);
verifyThingError(response);
if (error == Thing::ThingErrorNoError) {
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), resultCount);
}
// add Discovered Thing 1 port 55555
QVariantList thingDescriptors = response.toMap().value("params").toMap().value("thingDescriptors").toList();
ThingDescriptorId descriptorId;
foreach (const QVariant &descriptor, thingDescriptors) {
// find the thing with port 55555
if (descriptor.toMap().value("description").toString() == "55555") {
descriptorId = ThingDescriptorId(descriptor.toMap().value("id").toString());
qDebug() << descriptorId.toString();
break;
}
}
QVERIFY(!descriptorId.isNull());
qCDebug(dcTests()) << "Adding...";
params.clear();
response.clear();
params.insert("thingClassId", thingClassId);
params.insert("name", "Discoverd mock");
params.insert("thingDescriptorId", descriptorId);
response = injectAndWait("Integrations.AddThing", params);
ThingId thingId(response.toMap().value("params").toMap().value("thingId").toString());
QVERIFY(!thingId.isNull());
// and now rediscover and find the existing thing in the discovery results
qCDebug(dcTests()) << "Re-Discovering...";
params.clear();
response.clear();
params.insert("thingClassId", thingClassId);
params.insert("discoveryParams", discoveryParams);
response = injectAndWait("Integrations.DiscoverThings", params);
verifyThingError(response, error);
if (error == Thing::ThingErrorNoError) {
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), resultCount);
}
thingDescriptors = response.toMap().value("params").toMap().value("thingDescriptors").toList();
// find the already added thing
descriptorId = ThingDescriptorId(); // reset it first
foreach (const QVariant &descriptor, thingDescriptors) {
if (descriptor.toMap().value("thingId").toUuid().toString() == thingId.toString()) {
descriptorId = ThingDescriptorId(descriptor.toMap().value("id").toString());
break;
}
}
QVERIFY2(!descriptorId.isNull(), QString("Tjhing %1 not found in discovery results: %2").arg(thingId.toString()).arg(qUtf8Printable(QJsonDocument::fromVariant(response).toJson())).toUtf8());
qCDebug(dcTests()) << "Reconfiguring...";
response.clear();
params.clear();
params.insert("thingDescriptorId", descriptorId);
// override port param
QVariantMap portParam;
portParam.insert("paramTypeId", mockThingHttpportParamTypeId);
portParam.insert("value", "55556");
params.insert("thingParams", QVariantList() << portParam);
response = injectAndWait("Integrations.ReconfigureThing", params);
verifyThingError(response, error);
response.clear();
response = injectAndWait("Integrations.GetThings", QVariantMap());
QVariantMap thingMap;
bool found = false;
foreach (const QVariant &thing, response.toMap().value("params").toMap().value("things").toList()) {
if (ThingId(thing.toMap().value("id").toString()) == thingId) {
qDebug() << "found added thing" << thing.toMap().value("params");
found = true;
thingMap = thing.toMap();
break;
}
}
QVERIFY2(found, "Thing missing in config!");
QCOMPARE(thingMap.value("id").toUuid(), QUuid(thingId));
if (thingMap.contains("setupComplete"))
QVERIFY2(thingMap.value("setupComplete").toBool(), "Setup not completed after edit");
// Note: this shows that by discovery a not editable param (name) can be changed!
foreach (QVariant param, thingMap.value("params").toList()) {
if (param.toMap().value("paramTypeId") == mockThingHttpportParamTypeId) {
QCOMPARE(param.toMap().value("value").toInt(), 55556);
}
}
// check if the daemons are running
QNetworkAccessManager nam;
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
// check if old daemon is still running (should not)
QNetworkRequest request(QUrl(QString("http://localhost:%1").arg(55555)));
QNetworkReply *reply = nam.get(request);
spy.wait();
QVERIFY2(reply->error(), "The old daemon is still running");
reply->deleteLater();
// check if the daemon is really running on the new port
request = QNetworkRequest(QUrl(QString("http://localhost:%1").arg(55556)));
reply = nam.get(request);
spy.wait();
QVERIFY2(reply->error() == QNetworkReply::NoError, "The new daemon is not running");
reply->deleteLater();
params.clear();
params.insert("thingId", thingId.toString());
response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response);
}
void TestIntegrations::reconfigureByDiscoveryAndPair()
{
QVariantList discoveryParams;
QVariantMap resultCountParam;
resultCountParam.insert("paramTypeId", displayPinMockDiscoveryResultCountParamTypeId);
resultCountParam.insert("value", 1);
discoveryParams.append(resultCountParam);
qCDebug(dcTests()) << "Discovering things...";
QVariantMap params;
params.insert("thingClassId", displayPinMockThingClassId);
params.insert("discoveryParams", discoveryParams);
QVariant response = injectAndWait("Integrations.DiscoverThings", params);
verifyThingError(response);
QVariantList thingDescriptors = response.toMap().value("params").toMap().value("thingDescriptors").toList();
qCDebug(dcTests()) << "Discovery result:" << qUtf8Printable(QJsonDocument::fromVariant(thingDescriptors).toJson(QJsonDocument::Indented));
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), 1);
// add Discovered thing 1 port 55555
QVariant descriptor = thingDescriptors.first();
ThingDescriptorId descriptorId = ThingDescriptorId(descriptor.toMap().value("id").toString());
QVERIFY2(!descriptorId.isNull(), "ThingDescriptorId is Null");
qCDebug(dcTests()) << "Pairing descriptorId:" << descriptorId;
params.clear();
response.clear();
params.insert("thingClassId", displayPinMockThingClassId);
params.insert("name", "Discoverd mock");
params.insert("thingDescriptorId", descriptorId);
response = injectAndWait("Integrations.PairThing", params);
verifyThingError(response);
PairingTransactionId pairingTransactionId = PairingTransactionId(response.toMap().value("params").toMap().value("pairingTransactionId").toString());
qCDebug(dcTests()) << "PairThing result:" << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
qCDebug(dcTests()) << "Confirming pairing for transaction ID" << pairingTransactionId;
params.clear();
response.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());
QVERIFY(!thingId.isNull());
qCDebug(dcTests()) << "Discovering again...";
// and now rediscover, and edit the first thing with the second
params.clear();
response.clear();
params.insert("thingClassId", displayPinMockThingClassId);
params.insert("discoveryParams", discoveryParams);
response = injectAndWait("Integrations.DiscoverThings", params);
thingDescriptors = response.toMap().value("params").toMap().value("thingDescriptors").toList();
qCDebug(dcTests()) << "Discovery result:" << qUtf8Printable(QJsonDocument::fromVariant(thingDescriptors).toJson(QJsonDocument::Indented));
verifyThingError(response, Thing::ThingErrorNoError);
QCOMPARE(thingDescriptors.count(), 1);
descriptor = thingDescriptors.first();
QVERIFY2(ThingId(descriptor.toMap().value("thingId").toString()) == thingId, "thingId not set in descriptor");
// get the descriptor again
descriptorId = ThingDescriptorId(descriptor.toMap().value("id").toString());
QVERIFY(!descriptorId.isNull());
qDebug() << "Reconfiguring thing by pairing again" << descriptorId;
params.clear();
response.clear();
params.insert("thingClassId", displayPinMockThingClassId);
params.insert("name", "Discoverd mock");
params.insert("thingDescriptorId", descriptorId);
response = injectAndWait("Integrations.PairThing", params);
verifyThingError(response);
pairingTransactionId = PairingTransactionId(response.toMap().value("params").toMap().value("pairingTransactionId").toString());
qCDebug(dcTests()) << "PairThing result:" << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
qCDebug(dcTests()) << "Confirming pairing for transaction ID" << pairingTransactionId;
params.clear();
response.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());
QVERIFY(!thingId.isNull());
}
void TestIntegrations::reconfigureAutoThing()
{
qCDebug(dcTests()) << "Reconfigure auto thing";
// Get the auto mock
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(autoMockThingClassId);
QVERIFY2(things.count() > 0, "There needs to be at least one auto-created Mock for this test");
// Get current auto mock infos
Thing *currentThing = things.first();
ThingId thingId = currentThing->id();
int currentPort = currentThing->paramValue(autoMockThingHttpportParamTypeId).toInt();
// Trigger reconfigure signal in mock
QNetworkAccessManager *nam = new QNetworkAccessManager(this);
QSignalSpy spy(nam, &QNetworkAccessManager::finished);
QNetworkReply *reply = nam->get(QNetworkRequest(QUrl(QString("http://localhost:%1/reconfigureautodevice").arg(currentPort))));
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
Thing *thing = NymeaCore::instance()->thingManager()->findConfiguredThing(thingId);
QVERIFY(thing);
int newPort = thing->paramValue(autoMockThingHttpportParamTypeId).toInt();
// Note: reconfigure auto mock increases the http port by 1
QCOMPARE(newPort, currentPort + 1);
}
void TestIntegrations::removeThing_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::addColumn<Thing::ThingError>("thingError");
QTest::newRow("Existing thing") << ThingId(m_mockThingId) << Thing::ThingErrorNoError;
QTest::newRow("Not existing thing") << ThingId::createThingId() << Thing::ThingErrorThingNotFound;
// QTest::newRow("Auto device") << m_mockThingAutoId << Thing::ThingErrorCreationMethodNotSupported;
}
void TestIntegrations::removeThing()
{
QFETCH(ThingId, thingId);
QFETCH(Thing::ThingError, thingError);
NymeaSettings settings(NymeaSettings::SettingsRoleThings);
settings.beginGroup("ThingConfig");
if (thingError == Thing::ThingErrorNoError) {
settings.beginGroup(m_mockThingId.toString());
// Make sure we have some config values for this device
QVERIFY(settings.allKeys().count() > 0);
}
QVariantMap params;
params.insert("thingId", thingId);
QVariant response = injectAndWait("Integrations.RemoveThing", params);
verifyThingError(response, thingError);
if (Thing::ThingErrorNoError) {
// Make sure the device is gone from settings too
QCOMPARE(settings.allKeys().count(), 0);
}
}
void TestIntegrations::removeAutoThing()
{
// Setup connection to mock client
QNetworkAccessManager *nam = new QNetworkAccessManager(this);
QSignalSpy spy(nam, SIGNAL(finished(QNetworkReply*)));
// First try to make a manually created device disappear. It must not go away
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
int oldCount = things.count();
QVERIFY2(oldCount > 0, "There needs to be at least one configured Mock Device for this test");
Thing *thing = things.first();
// trigger disappear signal in mock device
int port = thing->paramValue(autoMockThingHttpportParamTypeId).toInt();
QNetworkRequest request(QUrl(QString("http://localhost:%1/disappear").arg(port)));
QNetworkReply *reply = nam->get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
QVERIFY2(NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId).count() == oldCount, "Mocked thing has disappeared even though it shouldn't");
// Ok, now do the same with an autocreated one. It should go away
things = NymeaCore::instance()->thingManager()->findConfiguredThings(autoMockThingClassId);
oldCount = things.count();
QVERIFY2(oldCount > 0, "There needs to be at least one auto-created Mock Device for this test");
thing = things.first();
// trigger disappear signal in mock device
spy.clear();
port = thing->paramValue(autoMockThingHttpportParamTypeId).toInt();
request.setUrl(QUrl(QString("http://localhost:%1/disappear").arg(port)));
reply = nam->get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
// Make sure one mock device has disappeared
QCOMPARE(NymeaCore::instance()->thingManager()->findConfiguredThings(autoMockThingClassId).count(), oldCount - 1);
}
void TestIntegrations::testBrowsing_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::newRow("regular mock") << ThingId(m_mockThingId);
QTest::newRow("async mock") << ThingId(m_mockThingAsyncId);
}
void TestIntegrations::testBrowsing()
{
QFETCH(ThingId, thingId);
// Check if mockdevice is browsable
QVariant response = injectAndWait("Integrations.GetThingClasses");
QVariantMap mockThingClass;
foreach (const QVariant &thingClassVariant, response.toMap().value("params").toMap().value("thingClasses").toList()) {
if (ThingClassId(thingClassVariant.toMap().value("id").toString()) == mockThingClassId) {
mockThingClass = thingClassVariant.toMap();
}
}
QVERIFY2(ThingClassId(mockThingClass.value("id").toString()) == mockThingClassId, "Could not find mock device");
QCOMPARE(mockThingClass.value("browsable").toBool(), true);
// Browse it
QVariantMap params;
params.insert("thingId", thingId);
response = injectAndWait("Integrations.BrowseThing", params);
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), QString("ThingErrorNoError"));
QVariantList browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY2(browserEntries.count() > 0, "BrowseThing did not return any items.");
// Browse item 001, it should be a folder with 2 items
params.insert("itemId", "001");
response = injectAndWait("Integrations.BrowseThing", params);
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), QString("ThingErrorNoError"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY2(browserEntries.count() == 2, "BrowseThing did not return 2 items as childs in folder with id 001.");
// Browse a non-existent item
params["itemId"] = "this-does-not-exist";
response = injectAndWait("Integrations.BrowseThing", params);
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), QString("ThingErrorItemNotFound"));
QCOMPARE(browserEntries.count(), 0);
}
void TestIntegrations::discoverThingsParenting()
{
// Try to discover a mock child device. We don't have a mockParent yet, so it should fail
ThingDiscoveryInfo *discoveryInfo = NymeaCore::instance()->thingManager()->discoverThings(childMockThingClassId, ParamList());
{
QSignalSpy spy(discoveryInfo, &ThingDiscoveryInfo::finished);
spy.wait();
}
QVERIFY(discoveryInfo->thingDescriptors().count() == 0);
// Now create a mock parent by discovering...
discoveryInfo = NymeaCore::instance()->thingManager()->discoverThings(parentMockThingClassId, ParamList());
{
QSignalSpy spy(discoveryInfo, &ThingDiscoveryInfo::finished);
spy.wait();
}
QVERIFY(discoveryInfo->thingDescriptors().count() == 1);
ThingDescriptorId descriptorId = discoveryInfo->thingDescriptors().first().id();
QSignalSpy addSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingAdded);
ThingSetupInfo *setupInfo = NymeaCore::instance()->thingManager()->addConfiguredThing(descriptorId, ParamList(), "Mock Parent (Discovered)");
{
QSignalSpy spy(setupInfo, &ThingSetupInfo::finished);
spy.wait();
}
QCOMPARE(setupInfo->status(), Thing::ThingErrorNoError);
addSpy.wait();
QCOMPARE(addSpy.count(), 2); // Mock parent will also auto-create a child instantly
Thing *parentThing = addSpy.at(0).first().value<Thing*>();
qCDebug(dcTests()) << "Added parent:" << parentThing->name();
QVERIFY(parentThing->thingClassId() == parentMockThingClassId);
// Ok we have our parent device, let's discover for childs again
discoveryInfo = NymeaCore::instance()->thingManager()->discoverThings(childMockThingClassId, ParamList());
{
QSignalSpy spy(discoveryInfo, &ThingDiscoveryInfo::finished);
spy.wait();
}
QVERIFY(discoveryInfo->thingDescriptors().count() == 1);
descriptorId = discoveryInfo->thingDescriptors().first().id();
// Found one! Adding it...
addSpy.clear();
setupInfo = NymeaCore::instance()->thingManager()->addConfiguredThing(descriptorId, ParamList(), "Mock Child (Discovered)");
{
QSignalSpy spy(setupInfo, &ThingSetupInfo::finished);
spy.wait();
}
QCOMPARE(setupInfo->status(), Thing::ThingErrorNoError);
QCOMPARE(addSpy.count(), 1);
Thing *childThing = addSpy.at(0).first().value<Thing*>();
qCDebug(dcTests()) << "Added child:" << childThing->name();
QVERIFY(childThing->thingClassId() == childMockThingClassId);
// Now delete the parent and make sure the child will be deleted too
QSignalSpy removeSpy(NymeaCore::instance()->thingManager(), &ThingManager::thingRemoved);
Thing::ThingError ret = NymeaCore::instance()->thingManager()->removeConfiguredThing(parentThing->id());
QCOMPARE(ret, Thing::ThingErrorNoError);
QCOMPARE(removeSpy.count(), 3); // The parent, the auto-mock and the discovered mock
}
void TestIntegrations::testExecuteBrowserItem_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::addColumn<QString>("itemId");
QTest::addColumn<QString>("thingError");
QTest::newRow("regular mock - good item") << ThingId(m_mockThingId) << "002" << "ThingErrorNoError";
QTest::newRow("regular mock - bad item") << ThingId(m_mockThingId) << "001" << "ThingErrorItemNotExecutable";
QTest::newRow("async mock - good item") << ThingId(m_mockThingAsyncId) << "002" << "ThingErrorNoError";
}
void TestIntegrations::testExecuteBrowserItem()
{
QFETCH(ThingId, thingId);
QFETCH(QString, itemId);
QFETCH(QString, thingError);
QVariantMap params;
params.insert("thingId", thingId);
params.insert("itemId", itemId);
QVariant response = injectAndWait("Integrations.ExecuteBrowserItem", params);
qCDebug(dcTests()) << "resp" << response;
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), thingError);
}
void TestIntegrations::testExecuteBrowserItemAction_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::newRow("regular mock") << ThingId(m_mockThingId);
QTest::newRow("async mock") << ThingId(m_mockThingAsyncId);
}
void TestIntegrations::testExecuteBrowserItemAction()
{
QFETCH(ThingId, thingId);
QVariantMap getItemsParams;
getItemsParams.insert("thingId", thingId);
QVariant response = injectAndWait("Integrations.BrowseThing", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
QVariantList browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY(browserEntries.count() > 2);
QVariantMap item002; // Find the item we need for this test
foreach (const QVariant &item, browserEntries) {
if (item.toMap().value("id").toString() == "002") {
item002 = item.toMap();
break;
}
}
QVERIFY2(item002.value("id").toString() == QString("002"), "Item with context actions not found");
QVERIFY2(item002.value("actionTypeIds").toList().count() > 0, "Item doesn't have actionTypeIds");
QVERIFY2(ActionTypeId(item002.value("actionTypeIds").toList().first().toString()) == mockAddToFavoritesBrowserItemActionTypeId, "AddToFavorites action type id not found in item");
// Browse favorites
// ID is "favorites" in mock
// It should be ampty at this point
getItemsParams.insert("itemId", "favorites");
response = injectAndWait("Integrations.BrowseThing", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QVERIFY2(browserEntries.count() == 0, "Favorites should be empty at this point");
// Now add an item to the favorites
QVariantMap actionParams;
actionParams.insert("thingId", thingId);
actionParams.insert("itemId", "002");
actionParams.insert("actionTypeId", mockAddToFavoritesBrowserItemActionTypeId);
response = injectAndWait("Integrations.ExecuteBrowserItemAction", actionParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), QString("ThingErrorNoError"));
// Fetch the list again
response = injectAndWait("Integrations.BrowseThing", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QCOMPARE(browserEntries.count(), 1);
QString favoriteItemId = browserEntries.first().toMap().value("id").toString();
QVERIFY2(!favoriteItemId.isEmpty(), "ItemId is empty in favorites list");
// Now remove the again from favorites
actionParams.clear();
actionParams.insert("thingId", thingId);
actionParams.insert("itemId", favoriteItemId);
actionParams.insert("actionTypeId", mockRemoveFromFavoritesBrowserItemActionTypeId);
response = injectAndWait("Integrations.ExecuteBrowserItemAction", actionParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
QCOMPARE(response.toMap().value("params").toMap().value("thingError").toString(), QString("ThingErrorNoError"));
// Fetch the list again
response = injectAndWait("Integrations.BrowseThing", getItemsParams);
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
browserEntries = response.toMap().value("params").toMap().value("items").toList();
QCOMPARE(browserEntries.count(), 0);
}
void TestIntegrations::executeAction_data()
{
QTest::addColumn<ThingId>("thingId");
QTest::addColumn<ActionTypeId>("actionTypeId");
QTest::addColumn<QVariantList>("actionParams");
QTest::addColumn<Thing::ThingError>("error");
QVariantList params;
QVariantMap param1;
param1.insert("paramTypeId", mockWithParamsActionParam1ParamTypeId);
param1.insert("value", 5);
params.append(param1);
QVariantMap param2;
param2.insert("paramTypeId", mockWithParamsActionParam2ParamTypeId);
param2.insert("value", true);
params.append(param2);
QTest::newRow("valid action") << ThingId(m_mockThingId) << mockWithParamsActionTypeId << params << Thing::ThingErrorNoError;
QTest::newRow("invalid thingId") << ThingId::createThingId() << mockWithParamsActionTypeId << params << Thing::ThingErrorThingNotFound;
QTest::newRow("invalid actionTypeId") << ThingId(m_mockThingId) << ActionTypeId::createActionTypeId() << params << Thing::ThingErrorActionTypeNotFound;
QTest::newRow("missing params") << ThingId(m_mockThingId) << mockWithParamsActionTypeId << QVariantList() << Thing::ThingErrorMissingParameter;
QTest::newRow("async action") << ThingId(m_mockThingId) << mockAsyncActionTypeId << QVariantList() << Thing::ThingErrorNoError;
QTest::newRow("broken action") << ThingId(m_mockThingId) << mockFailingActionTypeId << QVariantList() << Thing::ThingErrorSetupFailed;
QTest::newRow("async broken action") << ThingId(m_mockThingId) << mockAsyncFailingActionTypeId << QVariantList() << Thing::ThingErrorSetupFailed;
}
void TestIntegrations::executeAction()
{
QFETCH(ThingId, thingId);
QFETCH(ActionTypeId, actionTypeId);
QFETCH(QVariantList, actionParams);
QFETCH(Thing::ThingError, error);
QVariantMap params;
params.insert("actionTypeId", actionTypeId);
params.insert("thingId", thingId);
params.insert("params", actionParams);
QVariant response = injectAndWait("Integrations.ExecuteAction", params);
qDebug() << "executeActionresponse" << response;
verifyError(response, "thingError", enumValueName(error));
// Fetch action execution history from mock device
QNetworkAccessManager nam;
QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*)));
QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockThing1Port)));
QNetworkReply *reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
QByteArray data = reply->readAll();
if (error == Thing::ThingErrorNoError) {
QVERIFY2(actionTypeId == ActionTypeId(data), QString("ActionTypeId mismatch. Got %1, Expected: %2")
.arg(ActionTypeId(data).toString()).arg(actionTypeId.toString()).toLatin1().data());
} else {
QVERIFY2(data.length() == 0, QString("Data is %1, should be empty.").arg(QString(data)).toLatin1().data());
}
// cleanup for the next run
spy.clear();
request.setUrl(QUrl(QString("http://localhost:%1/clearactionhistory").arg(m_mockThing1Port)));
reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
spy.clear();
request.setUrl(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockThing1Port)));
reply = nam.get(request);
spy.wait();
QCOMPARE(spy.count(), 1);
reply->deleteLater();
data = reply->readAll();
qDebug() << "cleared data:" << data;
}
void TestIntegrations::triggerEvent()
{
enableNotifications({"Integrations"});
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();
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::eventTriggered);
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);
// Setup connection to mock client
QNetworkAccessManager nam;
// trigger event in mock device
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
QNetworkRequest request(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(port).arg(mockEvent1EventTypeId.toString())));
QNetworkReply *reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
// Lets wait for the notification
spy.wait();
QVERIFY(spy.count() > 0);
for (int i = 0; i < spy.count(); i++ ){
Event event = spy.at(i).at(0).value<Event>();
if (event.thingId() == thing->id()) {
// Make sure the event contains all the stuff we expect
QCOMPARE(event.eventTypeId(), mockEvent1EventTypeId);
}
}
// Check for the notification on JSON API
QVariantList notifications;
notifications = checkNotifications(notificationSpy, "Integrations.EventTriggered");
QVERIFY2(notifications.count() == 1, "Should get Integrations.EventTriggered notification");
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
QCOMPARE(notificationContent.value("event").toMap().value("thingId").toUuid().toString(), thing->id().toString());
QCOMPARE(notificationContent.value("event").toMap().value("eventTypeId").toUuid().toString(), mockEvent1EventTypeId.toString());
}
void TestIntegrations::triggerStateChangeSignal()
{
enableNotifications({"Integrations"});
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock for this test");
Thing *thing = things.first();
QSignalSpy spy(NymeaCore::instance()->thingManager(), &ThingManager::thingStateChanged);
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);
// Setup connection to mock client
QNetworkAccessManager nam;
// trigger state changed event in mock device
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntStateTypeId.toString()).arg(37)));
QNetworkReply *reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
// Lets wait for the notification
spy.wait();
QVERIFY(spy.count() == 1);
Thing *t = spy.at(0).at(0).value<Thing*>();
QCOMPARE(t->id(), thing->id());
StateTypeId stId = spy.at(0).at(1).value<StateTypeId>();
QCOMPARE(stId, mockIntStateTypeId);
QVariant value = spy.at(0).at(2);
QCOMPARE(value.toInt(), 37);
// Check for the notification on JSON API
QVariantList notifications;
notifications = checkNotifications(notificationSpy, "Integrations.StateChanged");
QVERIFY2(notifications.count() == 1, "Should get Integrations.StateChanged notification");
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
QCOMPARE(notificationContent.value("thingId").toUuid().toString(), thing->id().toString());
QCOMPARE(notificationContent.value("stateTypeId").toUuid().toString(), mockIntStateTypeId.toString());
QCOMPARE(notificationContent.value("value").toInt(), 37);
}
void TestIntegrations::params()
{
Event event;
ParamList params;
ParamTypeId id = ParamTypeId::createParamTypeId();
Param p(id, "foo bar");
params.append(p);
event.setParams(params);
QVERIFY(event.param(id).value().toString() == "foo bar");
QVERIFY(!event.param(ParamTypeId::createParamTypeId()).value().isValid());
}
void TestIntegrations::dynamicMinMax()
{
enableNotifications({"Integrations"});
QList<Thing*> things = NymeaCore::instance()->thingManager()->findConfiguredThings(mockThingClassId);
QVERIFY2(things.count() > 0, "There needs to be at least one configured Mock for this test");
Thing *thing = things.first();
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
// Setup connection to mock client
QNetworkAccessManager nam;
// trigger state changed event in mock device
qCDebug(dcTests()) << "Changing state in mock thing to 11";
int port = thing->paramValue(mockThingHttpportParamTypeId).toInt();
QNetworkRequest request(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(port).arg(mockIntWithLimitsStateTypeId.toString()).arg(11)));
QNetworkReply *reply = nam.get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
// Check for the notification on JSON API
notificationSpy.wait();
QVariantList notifications;
notifications = checkNotifications(notificationSpy, "Integrations.StateChanged");
QVERIFY2(notifications.count() == 1, QString("Expected 1 Integrations.StateChanged notification. Received: %1").arg(notifications.count()).toUtf8());
QVariantMap notificationContent = notifications.first().toMap().value("params").toMap();
QCOMPARE(notificationContent.value("thingId").toUuid().toString(), thing->id().toString());
QCOMPARE(notificationContent.value("stateTypeId").toUuid().toString(), mockIntWithLimitsStateTypeId.toString());
QCOMPARE(notificationContent.value("value").toInt(), 11);
QCOMPARE(notificationContent.value("minValue").toInt(), 0);
QCOMPARE(notificationContent.value("maxValue").toInt(), 50);
// set the max to 8
qCDebug(dcTests()) << "Changing state max value to 8";
notificationSpy.clear();
thing->setStateMaxValue(mockIntWithLimitsStateTypeId, 8);
// Check for the notification on JSON API, state chould adapt to new max
notificationSpy.wait();
notifications = checkNotifications(notificationSpy, "Integrations.StateChanged");
QVERIFY2(notifications.count() == 1, "Should get Integrations.StateChanged notification");
notificationContent = notifications.first().toMap().value("params").toMap();
QCOMPARE(notificationContent.value("thingId").toUuid().toString(), thing->id().toString());
QCOMPARE(notificationContent.value("stateTypeId").toUuid().toString(), mockIntWithLimitsStateTypeId.toString());
QCOMPARE(notificationContent.value("value").toInt(), 8);
// Try to execute an action on the api that exceeds the max value
qCDebug(dcTests()) << "Executing action with invalid max value (40)";
QVariantMap actionParams;
actionParams.insert("thingId", thing->id());
actionParams.insert("actionTypeId", mockIntWithLimitsActionTypeId);
QVariantMap valueParam;
valueParam.insert("paramTypeId", mockIntWithLimitsActionIntWithLimitsParamTypeId);
// intentionally between thingClass max and dynamic max
valueParam.insert("value", 40);
actionParams.insert("params", QVariantList() << valueParam);
QVariant response = injectAndWait("Integrations.ExecuteAction", actionParams);
verifyThingError(response, Thing::ThingErrorInvalidParameter);
// Set the max to 100
qCDebug(dcTests()) << "Changing max state value to 100";
thing->setStateMaxValue(mockIntWithLimitsStateTypeId, 100);
// And try to execute the action again
// intentionally greater than thingClass max
qCDebug(dcTests()) << "Executing action with valid max (52)";
valueParam.insert("value", 52);
actionParams.insert("params", QVariantList() << valueParam);
response = injectAndWait("Integrations.ExecuteAction", actionParams);
verifyThingError(response, Thing::ThingErrorNoError);
}
void TestIntegrations::asyncSetupEmitsSetupStatusUpdate()
{
QVariantMap configuredDevices = injectAndWait("Integrations.GetThings").toMap();
foreach (const QVariant &deviceVariant, configuredDevices.value("params").toMap().value("things").toList()) {
QVariantMap device = deviceVariant.toMap();
qCDebug(dcTests()) << "configured thing" << device.value("setupStatus");
}
// Restart the core instance to check if settings are loaded at startup
restartServer();
enableNotifications({"Integrations"});
QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
configuredDevices = injectAndWait("Integrations.GetThings").toMap();
QList<QUuid> thingsWithSetupInProgress;
foreach (const QVariant &deviceVariant, configuredDevices.value("params").toMap().value("things").toList()) {
QVariantMap thing = deviceVariant.toMap();
qCDebug(dcTests()) << "Configured thing" << thing.value("name").toString() << "with setup status" << thing.value("setupStatus").toString();
if (thing.value("setupStatus").toString() == "ThingSetupStatusInProgress") {
thingsWithSetupInProgress << thing.value("id").toUuid();
}
}
QVERIFY2(thingsWithSetupInProgress.count() > 0, "This test requires at least one device that is still being set up at this point.");
QDateTime maxTime = QDateTime::currentDateTime().addSecs(10);
while (QDateTime::currentDateTime() < maxTime && thingsWithSetupInProgress.count() > 0) {
QList<QList<QVariant>> notifications = notificationSpy;
while (notifications.count() > 0) {
QByteArray notificationData = notifications.takeFirst().at(1).toByteArray();
QVariantMap notification = QJsonDocument::fromJson(notificationData).toVariant().toMap();
if (notification.value("notification").toString() == "Integrations.ThingChanged") {
QString setupStatus = notification.value("params").toMap().value("thing").toMap().value("setupStatus").toString();
if (setupStatus == "ThingSetupStatusComplete") {
qCDebug(dcTests()) << "Device setup completed for" << notification.value("params").toMap().value("thing").toMap().value("name").toString();
ThingId thingId = notification.value("params").toMap().value("thing").toMap().value("id").toUuid();
thingsWithSetupInProgress.removeAll(thingId);
}
}
}
notificationSpy.clear();
if (thingsWithSetupInProgress.count() > 0) {
notificationSpy.wait();
}
}
QVERIFY2(thingsWithSetupInProgress.isEmpty(), "Some things did not finish the setup!");
}
void TestIntegrations::testTranslations()
{
// switch language to de_AT
QVariantMap params;
params.insert("locale", "de_AT");
QVariantMap handShake = injectAndWait("JSONRPC.Hello", params).toMap();
QCOMPARE(handShake.value("params").toMap().value("locale").toString(), QString("de_AT"));
QVariantMap thingClasses = injectAndWait("Integrations.GetThingClasses").toMap();
bool found = false;
foreach (const QVariant &tcVariant, thingClasses.value("params").toMap().value("thingClasses").toList()) {
QVariantMap tcMap = tcVariant.toMap();
if (tcMap.value("id").toUuid() == autoMockThingClassId) {
found = true;
// Verify thingClass' displayName is translated
QCOMPARE(tcMap.value("displayName").toString(), QString("Mock \"Thing\" (automatisch erstellt)"));
// Verify paramTypes are translated
bool ptFound = false;
foreach (const QVariant &ptVariant, tcMap.value("paramTypes").toList()) {
QVariantMap ptMap = ptVariant.toMap();
if (ptMap.value("id").toUuid() == autoMockThingAsyncParamTypeId) {
ptFound = true;
QCOMPARE(ptMap.value("displayName").toString(), QString("asynchron"));
}
}
QVERIFY2(ptFound, "ParamType not found in mock thing class.");
// Verify settings are translated
bool sFound = false;
foreach (const QVariant &sVariant, tcMap.value("settingsTypes").toList()) {
QVariantMap sMap = sVariant.toMap();
if (sMap.value("id").toUuid() == autoMockSettingsMockSettingParamTypeId) {
sFound = true;
QCOMPARE(sMap.value("displayName").toString(), QString("Mock-Einstellung"));
}
}
QVERIFY2(sFound, "SettingsType not found in mock thing class.");
// Verify stateTypes are translated
bool stFound = false;
foreach (const QVariant &stVariant, tcMap.value("stateTypes").toList()) {
QVariantMap stMap = stVariant.toMap();
if (stMap.value("id").toUuid() == autoMockIntStateTypeId) {
stFound = true;
QCOMPARE(stMap.value("displayName").toString(), QString("Simulierter Integer Zustand"));
}
}
QVERIFY2(stFound, "StateType not found in mock thing class.");
// Verify eventTypes are translated
bool etFound = false;
foreach (const QVariant &etVariant, tcMap.value("eventTypes").toList()) {
QVariantMap etMap = etVariant.toMap();
if (etMap.value("id").toUuid() == autoMockEvent1EventTypeId) {
etFound = true;
QCOMPARE(etMap.value("displayName").toString(), QString("Mock Ereignis 1"));
}
}
QVERIFY2(etFound, "EventType not found in mock thing class.");
// Verify actionTypes are translated
bool atFound = false;
foreach (const QVariant &atVariant, tcMap.value("actionTypes").toList()) {
QVariantMap atMap = atVariant.toMap();
if (atMap.value("id").toUuid() == autoMockWithParamsActionTypeId) {
atFound = true;
QCOMPARE(atMap.value("displayName").toString(), QString("Mock Aktion 1 (mit Parameter)"));
}
}
QVERIFY2(atFound, "ActionType not found in mock thing class.");
}
}
QVERIFY2(found, "Mock thing class not found.");
}
#include "testintegrations.moc"
QTEST_MAIN(TestIntegrations)