/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 . * * 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" 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 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 triggerStateChangeEvent(); void params(); 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"); QTest::addColumn("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"); QTest::addColumn("value"); QTest::addColumn("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"); QTest::addColumn>("thingClassIds"); QTest::addColumn("resultCount"); QTest::newRow("vendor nymea") << nymeaVendorId << QList() << 17; QTest::newRow("no filter") << VendorId() << QList() << 17; QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << QList() << 0; QTest::newRow("mockThingClassId") << VendorId() << (QList() << mockThingClassId) << 1; QTest::newRow("invalid thingClassId") << VendorId() << (QList() << ThingClassId("6c78ec28-09b6-476d-ac27-1d6966a45c57")) << 0; } void TestIntegrations::getThingClasses() { QFETCH(VendorId, vendorId); QFETCH(QList, 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"}; qCDebug(dcTests()) << interfaces; qCDebug(dcTests()) << expectedInterfaces; QCOMPARE(interfaces, expectedInterfaces); } void TestIntegrations::addThing_data() { QTest::addColumn("thingClassId"); QTest::addColumn("thingParams"); QTest::addColumn("jsonValidation"); QTest::addColumn("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 ¶m, 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 ¶m, 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 ¶m, 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"); QTest::addColumn("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()); // 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) { qDebug() << "found added thing" << thing.toMap().value("params"); qDebug() << "expected thingParams:" << thingParams; verifyParams(thingParams, thing.toMap().value("params").toList()); found = true; break; } } QVERIFY2(found, "thing missing in config!"); params.clear(); params.insert("thingId", addedThingId); response = injectAndWait("Integrations.RemoveThing", params); verifyThingError(response); } void TestIntegrations::discoverThings_data() { QTest::addColumn("thingClassId"); QTest::addColumn("resultCount"); QTest::addColumn("error"); QTest::addColumn("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"); QTest::addColumn("error"); QTest::addColumn("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"); QTest::addColumn("error"); QTest::addColumn("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"); QTest::addColumn >("actionTypeTestData"); QTest::newRow("valid thingClass") << mockThingClassId << (QList() << mockAsyncActionTypeId << mockAsyncFailingActionTypeId << mockFailingActionTypeId << mockWithoutParamsActionTypeId << mockPowerActionTypeId << mockWithoutParamsActionTypeId << mockBatteryLevelActionTypeId << mockSignalStrengthActionTypeId << mockUpdateStatusActionTypeId << mockPerformUpdateActionTypeId); QTest::newRow("invalid thingClass") << ThingClassId("094f8024-5caa-48c1-ab6a-de486a92088f") << QList(); } void TestIntegrations::getActionTypes() { QFETCH(ThingClassId, thingClassId); QFETCH(QList, 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"); QTest::addColumn("resultCount"); QTest::newRow("valid thingClass") << mockThingClassId << 13; 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"); QTest::addColumn("resultCount"); QTest::newRow("valid thingClass") << mockThingClassId << 11; 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"); QTest::addColumn("stateTypeId"); QTest::addColumn("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() { 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"); QTest::addColumn("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(), 11); // Mock has 11 states... } } void TestIntegrations::editThings_data() { QTest::addColumn("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(), 1); 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(), 1); 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(), 1); 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("broken"); QTest::addColumn("newThingParams"); QTest::addColumn("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"); QTest::addColumn("resultCount"); QTest::addColumn("error"); QTest::addColumn("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 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"); QTest::addColumn("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 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"); 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(); 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(); 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(), &NymeaCore::thingRemoved); QPair > ret = NymeaCore::instance()->removeConfiguredThing(parentThing->id(), QHash()); QCOMPARE(ret.first, Thing::ThingErrorNoError); QCOMPARE(removeSpy.count(), 3); // The parent, the auto-mock and the discovered mock } void TestIntegrations::testExecuteBrowserItem_data() { QTest::addColumn("thingId"); QTest::addColumn("itemId"); QTest::addColumn("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"); 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"); QTest::addColumn("actionTypeId"); QTest::addColumn("actionParams"); QTest::addColumn("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 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(), SIGNAL(eventTriggered(const Event&))); QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); // 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(); 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::triggerStateChangeEvent() { enableNotifications({"Integrations"}); QList 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(), SIGNAL(eventTriggered(const Event&))); QSignalSpy notificationSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); // 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(11))); 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(); if (event.thingId() == thing->id()) { // Make sure the event contains all the stuff we expect QCOMPARE(event.eventTypeId().toString(), mockIntStateTypeId.toString()); QCOMPARE(event.param(ParamTypeId(mockIntStateTypeId.toString())).value().toInt(), 11); } } // 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("deviceId").toUuid().toString(), thing->id().toString()); QCOMPARE(notificationContent.value("event").toMap().value("eventTypeId").toUuid().toString(), mockIntEventTypeId.toString()); } 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::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 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> 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() == autoMockIntEventTypeId) { etFound = true; QCOMPARE(etMap.value("displayName").toString(), QString("Simulierter Integer Zustand geƤndert")); } } 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)