diff --git a/debian/changelog b/debian/changelog index 64bf374a..b92ee7de 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,7 @@ +nymea (0.12.0) UNRELEASED; urgency=medium + + -- Michael Zanetti Fri, 22 Mar 2019 00:49:04 +0100 + nymea (0.11.1) xenial; urgency=medium [ Simon Stürz ] diff --git a/doc/jsonrpc-api.qdoc b/doc/jsonrpc-api.qdoc index 4bb429ea..8a2e43ed 100644 --- a/doc/jsonrpc-api.qdoc +++ b/doc/jsonrpc-api.qdoc @@ -30,29 +30,6 @@ See also: \l{Param} } \endcode See also: \l{ParamType} -\section2 BasicTag -\code -[ - "BasicTagService", - "BasicTagDevice", - "BasicTagSensor", - "BasicTagActuator", - "BasicTagLighting", - "BasicTagEnergy", - "BasicTagMultimedia", - "BasicTagWeather", - "BasicTagGateway", - "BasicTagHeating", - "BasicTagCooling", - "BasicTagNotification", - "BasicTagSecurity", - "BasicTagTime", - "BasicTagShading", - "BasicTagAppliance", - "BasicTagCamera", - "BasicTagLock" -] -\endcode \section2 BasicType \code [ @@ -134,13 +111,9 @@ See also: \l{Param} "actionTypes": [ "$ref:ActionType" ], - "basicTags": [ - "$ref:BasicTag" - ], "createMethods": [ "$ref:CreateMethod" ], - "deviceIcon": "$ref:DeviceIcon", "discoveryParamTypes": [ "$ref:ParamType" ], @@ -153,9 +126,6 @@ See also: \l{Param} "String" ], "name": "String", - "o:criticalStateTypeId": "Uuid", - "o:primaryActionTypeId": "Uuid", - "o:primaryStateTypeId": "Uuid", "paramTypes": [ "$ref:ParamType" ], @@ -167,7 +137,7 @@ See also: \l{Param} "vendorId": "Uuid" } \endcode -See also: \l{ActionType}, \l{CreateMethod}, \l{DeviceIcon}, \l{BasicTag}, \l{ParamType}, \l{SetupMethod}, \l{StateType}, \l{EventType}, \l{ParamType} +See also: \l{ActionType}, \l{CreateMethod}, \l{ParamType}, \l{SetupMethod}, \l{StateType}, \l{EventType}, \l{ParamType} \section2 DeviceDescriptor \code { @@ -206,47 +176,6 @@ See also: \l{ActionType}, \l{CreateMethod}, \l{DeviceIcon}, \l{BasicTag}, \l{Par "DeviceErrorParameterNotWritable" ] \endcode -\section2 DeviceIcon -\code -[ - "DeviceIconNone", - "DeviceIconBed", - "DeviceIconBlinds", - "DeviceIconCeilingLamp", - "DeviceIconCouch", - "DeviceIconDeskLamp", - "DeviceIconDesk", - "DeviceIconHifi", - "DeviceIconPower", - "DeviceIconEnergy", - "DeviceIconRadio", - "DeviceIconSmartPhone", - "DeviceIconSocket", - "DeviceIconStandardLamp", - "DeviceIconSun", - "DeviceIconTablet", - "DeviceIconThermometer", - "DeviceIconTune", - "DeviceIconTv", - "DeviceIconBattery", - "DeviceIconDishwasher", - "DeviceIconWashingMachine", - "DeviceIconLaundryDryer", - "DeviceIconIrHeater", - "DeviceIconRadiator", - "DeviceIconSwitch", - "DeviceIconMotionDetectors", - "DeviceIconWeather", - "DeviceIconTime", - "DeviceIconLightBulb", - "DeviceIconGateway", - "DeviceIconMail", - "DeviceIconNetwork", - "DeviceIconCloud", - "DeviceIconGarage", - "DeviceIconRollerShutter" -] -\endcode \section2 Event \code { @@ -278,8 +207,6 @@ See also: \l{ParamDescriptor} "id": "Uuid", "index": "Int", "name": "String", - "o:graphRelevant": "Bool", - "o:ruleRelevant": "Bool", "paramTypes": [ "$ref:ParamType" ] @@ -628,13 +555,11 @@ See also: \l{StateEvaluator}, \l{StateDescriptor}, \l{StateOperator} "id": "Uuid", "index": "Int", "name": "String", - "o:graphRelevant": "Bool", "o:maxValue": "Variant", "o:minValue": "Variant", "o:possibleValues": [ "Variant" ], - "o:ruleRelevant": "Bool", "o:unit": "$ref:Unit", "type": "$ref:BasicType" } @@ -3403,26 +3328,6 @@ See also: \l{Tag} "$ref:ParamType" ] }, - "BasicTag": [ - "BasicTagService", - "BasicTagDevice", - "BasicTagSensor", - "BasicTagActuator", - "BasicTagLighting", - "BasicTagEnergy", - "BasicTagMultimedia", - "BasicTagWeather", - "BasicTagGateway", - "BasicTagHeating", - "BasicTagCooling", - "BasicTagNotification", - "BasicTagSecurity", - "BasicTagTime", - "BasicTagShading", - "BasicTagAppliance", - "BasicTagCamera", - "BasicTagLock" - ], "BasicType": [ "Uuid", "String", @@ -3482,13 +3387,9 @@ See also: \l{Tag} "actionTypes": [ "$ref:ActionType" ], - "basicTags": [ - "$ref:BasicTag" - ], "createMethods": [ "$ref:CreateMethod" ], - "deviceIcon": "$ref:DeviceIcon", "discoveryParamTypes": [ "$ref:ParamType" ], @@ -3501,9 +3402,6 @@ See also: \l{Tag} "String" ], "name": "String", - "o:criticalStateTypeId": "Uuid", - "o:primaryActionTypeId": "Uuid", - "o:primaryStateTypeId": "Uuid", "paramTypes": [ "$ref:ParamType" ], @@ -3545,44 +3443,6 @@ See also: \l{Tag} "DeviceErrorPairingTransactionIdNotFound", "DeviceErrorParameterNotWritable" ], - "DeviceIcon": [ - "DeviceIconNone", - "DeviceIconBed", - "DeviceIconBlinds", - "DeviceIconCeilingLamp", - "DeviceIconCouch", - "DeviceIconDeskLamp", - "DeviceIconDesk", - "DeviceIconHifi", - "DeviceIconPower", - "DeviceIconEnergy", - "DeviceIconRadio", - "DeviceIconSmartPhone", - "DeviceIconSocket", - "DeviceIconStandardLamp", - "DeviceIconSun", - "DeviceIconTablet", - "DeviceIconThermometer", - "DeviceIconTune", - "DeviceIconTv", - "DeviceIconBattery", - "DeviceIconDishwasher", - "DeviceIconWashingMachine", - "DeviceIconLaundryDryer", - "DeviceIconIrHeater", - "DeviceIconRadiator", - "DeviceIconSwitch", - "DeviceIconMotionDetectors", - "DeviceIconWeather", - "DeviceIconTime", - "DeviceIconLightBulb", - "DeviceIconGateway", - "DeviceIconMail", - "DeviceIconNetwork", - "DeviceIconCloud", - "DeviceIconGarage", - "DeviceIconRollerShutter" - ], "Event": { "deviceId": "Uuid", "eventTypeId": "Uuid", @@ -3604,8 +3464,6 @@ See also: \l{Tag} "id": "Uuid", "index": "Int", "name": "String", - "o:graphRelevant": "Bool", - "o:ruleRelevant": "Bool", "paramTypes": [ "$ref:ParamType" ] @@ -3855,13 +3713,11 @@ See also: \l{Tag} "id": "Uuid", "index": "Int", "name": "String", - "o:graphRelevant": "Bool", "o:maxValue": "Variant", "o:minValue": "Variant", "o:possibleValues": [ "Variant" ], - "o:ruleRelevant": "Bool", "o:unit": "$ref:Unit", "type": "$ref:BasicType" }, diff --git a/doc/plugin-json.qdoc b/doc/plugin-json.qdoc index f85ae1c3..4dc9eec2 100644 --- a/doc/plugin-json.qdoc +++ b/doc/plugin-json.qdoc @@ -192,13 +192,8 @@ "displayName": "The name of the device class (translatable)", "o:createMethods": [ ], "o:setupMethod": "SetupMethod", - "o:deviceIcon": "Icon", "o:interfaces": [ "interfacename" ], - "o:basicTags": [ ], "o:pairingInfo": "Information how to pair the device. (translatable)", - "o:criticalStateTypeId": "uuid", - "o:primaryStateTypeId": "uuid", - "o:primaryActionTypeId": "uuid", "o:discoveryParamTypes": [ ], "o:paramTypes": [ ], "o:stateTypes": [ ], @@ -233,34 +228,9 @@ \li \tt interfaces \li \b O \li array - \li A string list of \l{Interfaces for DeviceClasses}{interfaces} this plugin implements. Interfaces show you how types - of this DeviceClass \underline{must} look like. + \li A string list of \l{Interfaces for DeviceClasses}{interfaces} this plugin implements. Interfaces define states, events and actions to + provide more defined ways of creating device class. A plugin developer should always try to follow interface definitions if possible. \row - \li \tt basicTags - \li \b O - \li array - \li A string list of \l{DeviceClass::BasicTag}{BasicTags} for this device \unicode{0x2192} \l{DeviceClass::basicTags()}. A \l{DeviceClass} can have - multiple \l{DeviceClass::BasicTag}{BasicTags} which describe the basic category of the DeviceClass. - A \l{DeviceClass} should be eighter a Service or a Device, never both. See enum \l{DeviceClass::BasicTag} for more information. - The expected value for the \e basicTags parameters matches the enum name like this: - - \tt {DeviceClass::BasicTagService} \unicode{0x2192} \tt {"basicTags": [ "Service" ]} - - \tt {DeviceClass::BasicTagLighting} \unicode{0x2192} \tt {"basicTags": [ "Lighting" ]} - - \tt ... - \row - \li \tt deviceIcon - \li \b O - \li string - \li Defines the icon for this \l{DeviceClass}. See enum \l{DeviceClass::DeviceIcon} for more information. The expected value for the \tt deviceIcon - parameters matches the enum name like this: - - \tt {DeviceClass::DeviceIconBed} \unicode{0x2192} \tt {"deviceIcon": "Bed"} - - \tt {DeviceClass::DeviceIconPower} \unicode{0x2192} \tt {"deviceIcon": "Power"} - - \tt ... \row \li \tt createMethods \li \b O @@ -295,21 +265,6 @@ \li The \l{DeviceClass::pairingInfo()}{pairingInfo} will inform the user how to pair the device \unicode{0x2192} \l{DeviceClass::setupMethod()}. This parameter will only be used for \l{DeviceClass::SetupMethodDisplayPin}{DisplayPin} and \l{DeviceClass::SetupMethodEnterPin}{EnterPin} and \l{DeviceClass::SetupMethodPushButton}{PushButton}. Example: "Please press the button on the device before continue." - \row - \li \tt criticalStateTypeId - \li \b O - \li string - \li Deprecated: please use \l{Interfaces for DeviceClasses}{interfaces} instead. - \row - \li \tt primaryStateTypeId - \li \b O - \li string - \li Deprecated: please use \l{Interfaces for DeviceClasses}{interfaces} instead. - \row - \li \tt primaryActionTypeId - \li \b O - \li string - \li Deprecated: please use \l{Interfaces for DeviceClasses}{interfaces} instead. \row \li \tt discoveryParamTypes \li \b O @@ -476,9 +431,6 @@ A \l{StateType} has following parameters: "type": "DataType", "defaultValue": "The state will be initialized with this value." "o:cached": "bool", - "o:ruleRelevant": "bool", - "o:eventRuleRelevant": "bool", - "o:graphRelevant": "bool", "o:unit": "The unit of the state value.", "o:minValue": "Numeric minimum value for this state.", "o:maxValue": "Numeric maximum value for this state.", @@ -537,23 +489,6 @@ A \l{StateType} has following parameters: \li Indicates if a state value should be cached over reboot of the server. The value will be initialized with the last known value. By default all states get chached. If you want to disable that behaviour you can set this property to \tt{false}. In that case the value will be initialized with the default value of the State. - \row - \li \tt ruleRelevant - \li \b O - \li bool - \li Since not all \l{State}{States} make sense for the user in a rule, with this flag can be specified if this state should be visible - in the rule engine for the user or not. This flag has no effect to the ruleengine mechanism and is only ment to filter out not - interesting \l{State}{States}. By default, every state is rule relevant. - \row - \li \tt eventRuleRelevant - \li \b O - \li bool - \li Deprecated: please use \l{Interfaces for DeviceClasses}{interfaces} instead. - \row - \li \tt graphRelevant - \li \b O - \li bool - \li Deprecated: please use \l{Interfaces for DeviceClasses}{interfaces} instead. \row \li \tt unit \li \b O @@ -710,8 +645,6 @@ A \l{StateType} has following parameters: "id": "uuid", "name": "eventName", "displayName": "Name of the event (translatable)", - "o:ruleRelevant": "bool", - "o:graphRelevant": "bool", "o:paramTypes": [ ... ] @@ -745,18 +678,6 @@ A \l{StateType} has following parameters: \li A list of \l{ParamType}{ParamTypes} which define the parameters of this event \unicode{0x2192} \l{EventType::paramTypes()}. \b{See also:} \l{The ParamType definition}" - \row - \li \tt ruleRelevant - \li \b O - \li bool - \li Since not all \l{Event}{Events} make sense for the user in a rule, with this flag can be specidied if this event should be visible in the rule engine - for the user or not. This flag has no effect to the ruleengine mechanism and is only ment to filter out not interesting \l{Event}{Events}. By default, - every event is rule relevant. - \row - \li \tt graphRelevant - \li \b O - \li bool - \li Deprecated: please use \l{Interfaces for DeviceClasses}{interfaces} instead. \endtable */ diff --git a/libnymea-core/jsonrpc/devicehandler.cpp b/libnymea-core/jsonrpc/devicehandler.cpp index 9bdee4bd..21b8ad28 100644 --- a/libnymea-core/jsonrpc/devicehandler.cpp +++ b/libnymea-core/jsonrpc/devicehandler.cpp @@ -298,6 +298,12 @@ DeviceHandler::DeviceHandler(QObject *parent) : params.insert("device", JsonTypes::deviceRef()); setParams("DeviceChanged", params); + params.clear(); returns.clear(); + setDescription("PluginConfigurationChanged", "Emitted whenever a plugin's configuration is changed."); + params.insert("pluginId", JsonTypes::basicTypeToString(JsonTypes::Uuid)); + params.insert("configuration", QVariantList() << JsonTypes::paramRef()); + setParams("PluginConfigurationChanged", params); + connect(NymeaCore::instance(), &NymeaCore::pluginConfigChanged, this, &DeviceHandler::pluginConfigChanged); connect(NymeaCore::instance(), &NymeaCore::deviceStateChanged, this, &DeviceHandler::deviceStateChanged); connect(NymeaCore::instance(), &NymeaCore::deviceRemoved, this, &DeviceHandler::deviceRemovedNotification); @@ -637,11 +643,11 @@ void DeviceHandler::pluginConfigChanged(const PluginId &id, const ParamList &con { QVariantMap params; params.insert("pluginId", id); - QVariantMap configMap; + QVariantList configList; foreach (const Param ¶m, config) { - configMap.insert(param.paramTypeId().toString(), param.value()); + configList << JsonTypes::packParam(param); } - params.insert("configuration", configMap); + params.insert("configuration", configList); emit PluginConfigurationChanged(params); } diff --git a/libnymea-core/jsonrpc/jsonrpcserver.cpp b/libnymea-core/jsonrpc/jsonrpcserver.cpp index a670638f..9625153b 100644 --- a/libnymea-core/jsonrpc/jsonrpcserver.cpp +++ b/libnymea-core/jsonrpc/jsonrpcserver.cpp @@ -248,6 +248,11 @@ JsonReply *JsonRPCServer::Hello(const QVariantMap ¶ms) qCDebug(dcJsonRpc()) << "Client" << clientId << "initiated handshake." << m_clientLocales.value(clientId); + // If we waited for the handshake, here it is. Remove the timer... + if (m_newConnectionWaitTimers.contains(clientId)) { + delete m_newConnectionWaitTimers.take(clientId); + } + return createReply(createWelcomeMessage(interface, clientId)); } @@ -285,6 +290,7 @@ JsonReply* JsonRPCServer::Version(const QVariantMap ¶ms) const JsonReply* JsonRPCServer::SetNotificationStatus(const QVariantMap ¶ms) { QUuid clientId = this->property("clientId").toUuid(); + Q_ASSERT_X(m_clientNotifications.contains(clientId), "JsonRPCServer", "SetNotificationStatus for an unknown client called"); m_clientNotifications[clientId] = params.value("enabled").toBool(); QVariantMap returns; returns.insert("enabled", m_clientNotifications[clientId]); @@ -585,12 +591,16 @@ void JsonRPCServer::processJsonPacket(TransportInterface *interface, const QUuid if (NymeaCore::instance()->userManager()->initRequired()) { if (!(targetNamespace == "JSONRPC" && authExemptMethodsNoUser.contains(method)) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) { sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first."); + qCWarning(dcJsonRpc()) << "Initial setup required but client does not call the setup. Dropping connection."; + interface->terminateClientConnection(clientId); return; } } else { // ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect Hello call if (!(targetNamespace == "JSONRPC" && authExemptMethodsWithUser.contains(method)) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) { sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token."); + qCWarning(dcJsonRpc()) << "Client did not not present a valid token. Dropping connection."; + interface->terminateClientConnection(clientId); return; } } @@ -622,7 +632,16 @@ void JsonRPCServer::processJsonPacket(TransportInterface *interface, const QUuid qCDebug(dcJsonRpc()) << "Invoking method" << targetNamespace << method.toLatin1().data(); - if (targetNamespace != "JSONRPC" || method != "Hello") { + if (!(targetNamespace == "JSONRPC" && method == "Hello")) { + // This is not the handshake message. If we've waited for it, consider this a protocol violation and drop connection + if (m_newConnectionWaitTimers.contains(clientId)) { + sendErrorResponse(interface, clientId, commandId, "Handshake required. Call JSONRPC.Hello first."); + qCWarning(dcJsonRpc()) << "Connection requires a handshake but client did not initiate handshake. Dropping connection"; + interface->terminateClientConnection(clientId); + return; + } + + // Unless this is the Hello message, which allows setting the locale explicity, attach the locale // for this connection // If the client did request a locale in the Hello message, use that locale @@ -767,7 +786,17 @@ void JsonRPCServer::clientConnected(const QUuid &clientId) // Initialize the connection locale to the settings default m_clientLocales.insert(clientId, NymeaCore::instance()->configuration()->locale()); - interface->sendData(clientId, QJsonDocument::fromVariant(createWelcomeMessage(interface, clientId)).toJson(QJsonDocument::Compact)); + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this, timer, clientId, interface](){ + // Client did not initiate handshake within timeout. Drop connection... + m_clientTransports.value(clientId)->disconnect(); + timer->deleteLater(); + m_newConnectionWaitTimers.remove(clientId); + qCDebug(dcJsonRpc()) << "Client" << clientId << "did not initiate the handshake within the required timeout. Dropping connection."; + interface->terminateClientConnection(clientId); + }); + m_newConnectionWaitTimers.insert(clientId, timer); + timer->start(10000); } void JsonRPCServer::clientDisconnected(const QUuid &clientId) @@ -780,6 +809,9 @@ void JsonRPCServer::clientDisconnected(const QUuid &clientId) if (m_pushButtonTransactions.values().contains(clientId)) { NymeaCore::instance()->userManager()->cancelPushButtonAuth(m_pushButtonTransactions.key(clientId)); } + if (m_newConnectionWaitTimers.contains(clientId)) { + delete m_newConnectionWaitTimers.take(clientId); + } } } diff --git a/libnymea-core/jsonrpc/jsonrpcserver.h b/libnymea-core/jsonrpc/jsonrpcserver.h index e85a6da6..0730b3c0 100644 --- a/libnymea-core/jsonrpc/jsonrpcserver.h +++ b/libnymea-core/jsonrpc/jsonrpcserver.h @@ -107,6 +107,7 @@ private: QHash m_clientNotifications; QHash m_clientLocales; QHash m_pushButtonTransactions; + QHash m_newConnectionWaitTimers; QHash m_pairingRequests; diff --git a/libnymea-core/jsonrpc/jsontypes.cpp b/libnymea-core/jsonrpc/jsontypes.cpp index 4b6e66ad..8daf0a52 100644 --- a/libnymea-core/jsonrpc/jsontypes.cpp +++ b/libnymea-core/jsonrpc/jsontypes.cpp @@ -70,8 +70,6 @@ bool JsonTypes::s_initialized = false; QString JsonTypes::s_lastError; QVariantList JsonTypes::s_basicType; -QVariantList JsonTypes::s_basicTag; -QVariantList JsonTypes::s_deviceIcon; QVariantList JsonTypes::s_stateOperator; QVariantList JsonTypes::s_valueOperator; QVariantList JsonTypes::s_inputType; @@ -139,8 +137,6 @@ void JsonTypes::init() s_unit = enumToStrings(Types::staticMetaObject, "Unit"); s_createMethod = enumToStrings(DeviceClass::staticMetaObject, "CreateMethod"); s_setupMethod = enumToStrings(DeviceClass::staticMetaObject, "SetupMethod"); - s_basicTag = enumToStrings(DeviceClass::staticMetaObject, "BasicTag"); - s_deviceIcon = enumToStrings(DeviceClass::staticMetaObject, "DeviceIcon"); s_removePolicy = enumToStrings(RuleEngine::staticMetaObject, "RemovePolicy"); s_deviceError = enumToStrings(DeviceManager::staticMetaObject, "DeviceError"); s_ruleError = enumToStrings(RuleEngine::staticMetaObject, "RuleError"); @@ -203,8 +199,6 @@ void JsonTypes::init() s_stateType.insert("index", basicTypeToString(Int)); s_stateType.insert("defaultValue", basicTypeToString(Variant)); s_stateType.insert("o:unit", unitRef()); - s_stateType.insert("o:ruleRelevant", basicTypeToString(Bool)); - s_stateType.insert("o:graphRelevant", basicTypeToString(Bool)); s_stateType.insert("o:minValue", basicTypeToString(Variant)); s_stateType.insert("o:maxValue", basicTypeToString(Variant)); s_stateType.insert("o:possibleValues", QVariantList() << basicTypeToString(Variant)); @@ -233,8 +227,6 @@ void JsonTypes::init() s_eventType.insert("displayName", basicTypeToString(String)); s_eventType.insert("index", basicTypeToString(Int)); s_eventType.insert("paramTypes", QVariantList() << paramTypeRef()); - s_eventType.insert("o:ruleRelevant", basicTypeToString(Bool)); - s_eventType.insert("o:graphRelevant", basicTypeToString(Bool)); // Event s_event.insert("eventTypeId", basicTypeToString(Uuid)); @@ -277,14 +269,9 @@ void JsonTypes::init() s_deviceClass.insert("pluginId", basicTypeToString(Uuid)); s_deviceClass.insert("name", basicTypeToString(String)); s_deviceClass.insert("displayName", basicTypeToString(String)); - s_deviceClass.insert("deviceIcon", deviceIconRef()); s_deviceClass.insert("interfaces", QVariantList() << basicTypeToString(String)); - s_deviceClass.insert("basicTags", QVariantList() << basicTagRef()); s_deviceClass.insert("setupMethod", setupMethodRef()); s_deviceClass.insert("createMethods", QVariantList() << createMethodRef()); - s_deviceClass.insert("o:criticalStateTypeId", basicTypeToString(Uuid)); - s_deviceClass.insert("o:primaryStateTypeId", basicTypeToString(Uuid)); - s_deviceClass.insert("o:primaryActionTypeId", basicTypeToString(Uuid)); s_deviceClass.insert("stateTypes", QVariantList() << stateTypeRef()); s_deviceClass.insert("eventTypes", QVariantList() << eventTypeRef()); s_deviceClass.insert("actionTypes", QVariantList() << actionTypeRef()); @@ -437,13 +424,11 @@ QVariantMap JsonTypes::allTypes() { QVariantMap allTypes; allTypes.insert("BasicType", basicType()); - allTypes.insert("BasicTag", basicTag()); allTypes.insert("ParamType", paramTypeDescription()); allTypes.insert("InputType", inputType()); allTypes.insert("Unit", unit()); allTypes.insert("CreateMethod", createMethod()); allTypes.insert("SetupMethod", setupMethod()); - allTypes.insert("DeviceIcon", deviceIcon()); allTypes.insert("ValueOperator", valueOperator()); allTypes.insert("StateOperator", stateOperator()); allTypes.insert("RemovePolicy", removePolicy()); @@ -507,11 +492,6 @@ QVariantMap JsonTypes::packEventType(const EventType &eventType, const PluginId variant.insert("name", eventType.name()); variant.insert("displayName", NymeaCore::instance()->deviceManager()->translator()->translate(pluginId, eventType.displayName(), locale)); variant.insert("index", eventType.index()); - if (!eventType.ruleRelevant()) - variant.insert("ruleRelevant", false); - - if (eventType.graphRelevant()) - variant.insert("graphRelevant", true); QVariantList paramTypes; foreach (const ParamType ¶mType, eventType.paramTypes()) @@ -642,12 +622,6 @@ QVariantMap JsonTypes::packStateType(const StateType &stateType, const PluginId variantMap.insert("type", basicTypeToString(stateType.type())); variantMap.insert("defaultValue", stateType.defaultValue()); - if (!stateType.ruleRelevant()) - variantMap.insert("ruleRelevant", false); - - if (stateType.graphRelevant()) - variantMap.insert("graphRelevant", true); - if (stateType.maxValue().isValid()) variantMap.insert("maxValue", stateType.maxValue()); @@ -782,13 +756,8 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo variant.insert("displayName", NymeaCore::instance()->deviceManager()->translator()->translate(deviceClass.pluginId(), deviceClass.displayName(), locale)); variant.insert("vendorId", deviceClass.vendorId().toString()); variant.insert("pluginId", deviceClass.pluginId().toString()); - variant.insert("deviceIcon", s_deviceIcon.at(deviceClass.deviceIcon())); variant.insert("interfaces", deviceClass.interfaces()); - QVariantList basicTags; - foreach (const DeviceClass::BasicTag &basicTag, deviceClass.basicTags()) - basicTags.append(s_basicTag.at(basicTag)); - QVariantList stateTypes; foreach (const StateType &stateType, deviceClass.stateTypes()) stateTypes.append(packStateType(stateType, deviceClass.pluginId(), locale)); @@ -809,16 +778,6 @@ QVariantMap JsonTypes::packDeviceClass(const DeviceClass &deviceClass, const QLo foreach (const ParamType ¶mType, deviceClass.discoveryParamTypes()) discoveryParamTypes.append(packParamType(paramType, deviceClass.pluginId(), locale)); - if (!deviceClass.criticalStateTypeId().isNull()) - variant.insert("criticalStateTypeId", deviceClass.criticalStateTypeId().toString()); - - if (!deviceClass.primaryStateTypeId().isNull()) - variant.insert("primaryStateTypeId", deviceClass.primaryStateTypeId().toString()); - - if (!deviceClass.primaryActionTypeId().isNull()) - variant.insert("primaryActionTypeId", deviceClass.primaryActionTypeId().toString()); - - variant.insert("basicTags", basicTags); variant.insert("paramTypes", paramTypes); variant.insert("discoveryParamTypes", discoveryParamTypes); variant.insert("stateTypes", stateTypes); @@ -1372,7 +1331,6 @@ Rule JsonTypes::unpackRule(const QVariantMap &ruleMap) QList eventDescriptors; if (ruleMap.contains("eventDescriptors")) { QVariantList eventDescriptorVariantList = ruleMap.value("eventDescriptors").toList(); - qCDebug(dcJsonRpc) << "unpacking eventDescriptors:" << eventDescriptorVariantList; foreach (const QVariant &eventDescriptorVariant, eventDescriptorVariantList) { eventDescriptors.append(JsonTypes::unpackEventDescriptor(eventDescriptorVariant.toMap())); } @@ -2122,18 +2080,6 @@ QPair JsonTypes::validateVariant(const QVariant &templateVariant, qCWarning(dcJsonRpc) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(unitRef()); return result; } - } else if (refName == basicTagRef()) { - QPair result = validateEnum(s_basicTag, variant); - if (!result.first) { - qCWarning(dcJsonRpc) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(basicTagRef()); - return result; - } - } else if (refName == deviceIconRef()) { - QPair result = validateEnum(s_deviceIcon, variant); - if (!result.first) { - qCWarning(dcJsonRpc) << QString("Value %1 not allowed in %2").arg(variant.toString()).arg(deviceIconRef()); - return result; - } } else if (refName == repeatingModeRef()) { QPair result = validateEnum(s_repeatingMode, variant); if (!result.first) { diff --git a/libnymea-core/jsonrpc/jsontypes.h b/libnymea-core/jsonrpc/jsontypes.h index dda5c3cb..6fc417eb 100644 --- a/libnymea-core/jsonrpc/jsontypes.h +++ b/libnymea-core/jsonrpc/jsontypes.h @@ -120,14 +120,12 @@ public: static QVariantMap allTypes(); DECLARE_TYPE(basicType, "BasicType", JsonTypes, BasicType) - DECLARE_TYPE(basicTag, "BasicTag", DeviceClass, BasicTag) DECLARE_TYPE(stateOperator, "StateOperator", Types, StateOperator) DECLARE_TYPE(valueOperator, "ValueOperator", Types, ValueOperator) DECLARE_TYPE(inputType, "InputType", Types, InputType) DECLARE_TYPE(unit, "Unit", Types, Unit) DECLARE_TYPE(createMethod, "CreateMethod", DeviceClass, CreateMethod) DECLARE_TYPE(setupMethod, "SetupMethod", DeviceClass, SetupMethod) - DECLARE_TYPE(deviceIcon, "DeviceIcon", DeviceClass, DeviceIcon) DECLARE_TYPE(deviceError, "DeviceError", DeviceManager, DeviceError) DECLARE_TYPE(removePolicy, "RemovePolicy", RuleEngine, RemovePolicy) DECLARE_TYPE(ruleError, "RuleError", RuleEngine, RuleError) diff --git a/libnymea-core/servers/mocktcpserver.cpp b/libnymea-core/servers/mocktcpserver.cpp index 5650034f..68f48901 100644 --- a/libnymea-core/servers/mocktcpserver.cpp +++ b/libnymea-core/servers/mocktcpserver.cpp @@ -33,6 +33,14 @@ MockTcpServer::MockTcpServer(QObject *parent): TransportInterface(ServerConfiguration(), parent) { s_allServers.append(this); + + connect(this, &TransportInterface::clientConnected, this, [this](const QUuid &clientId){ + m_connectedClients.append(clientId); + }); + + connect(this, &TransportInterface::clientDisconnected, this, [this](const QUuid &clientId){ + m_connectedClients.removeAll(clientId); + }); } MockTcpServer::~MockTcpServer() @@ -65,6 +73,7 @@ QList MockTcpServer::servers() void MockTcpServer::injectData(const QUuid &clientId, const QByteArray &data) { + Q_ASSERT_X(m_connectedClients.contains(clientId), "MockTcpServer", "Cannot inject data. Client is not connected"); emit dataAvailable(clientId, data); } diff --git a/libnymea-core/servers/mocktcpserver.h b/libnymea-core/servers/mocktcpserver.h index 47040a87..9253d719 100644 --- a/libnymea-core/servers/mocktcpserver.h +++ b/libnymea-core/servers/mocktcpserver.h @@ -57,6 +57,8 @@ public slots: private: static QList s_allServers; + + QList m_connectedClients; }; #endif // TCPSERVER_H diff --git a/libnymea/plugin/deviceplugin.cpp b/libnymea/plugin/deviceplugin.cpp index ca7af802..8a767b4f 100644 --- a/libnymea/plugin/deviceplugin.cpp +++ b/libnymea/plugin/deviceplugin.cpp @@ -594,15 +594,6 @@ void DevicePlugin::loadMetaData() } deviceClass.setCreateMethods(createMethods); - // Read device icon - QPair deviceIconVerification = loadAndVerifyDeviceIcon(deviceClassObject.value("deviceIcon").toString()); - if (!deviceIconVerification.first) { - broken = true; - break; - } else { - deviceClass.setDeviceIcon(deviceIconVerification.second); - } - // Read params QPair > paramTypesVerification = parseParamTypes(deviceClassObject.value("paramTypes").toArray()); if (!paramTypesVerification.first) { @@ -644,19 +635,6 @@ void DevicePlugin::loadMetaData() // Read pairing info deviceClass.setPairingInfo(deviceClassObject.value("pairingInfo").toString()); - // Read basic tags - QList basicTags; - foreach (const QJsonValue &basicTagJson, deviceClassObject.value("basicTags").toArray()) { - QPair basicTagVerification = loadAndVerifyBasicTag(basicTagJson.toString()); - if (!basicTagVerification.first) { - broken = true; - break; - } else { - basicTags.append(basicTagVerification.second); - } - } - deviceClass.setBasicTags(basicTags); - QList actionTypes; QList stateTypes; QList eventTypes; @@ -720,12 +698,6 @@ void DevicePlugin::loadMetaData() if (st.contains("maxValue")) stateType.setMaxValue(st.value("maxValue").toVariant()); - if (st.contains("ruleRelevant")) - stateType.setRuleRelevant(st.value("ruleRelevant").toBool()); - - if (st.contains("graphRelevant")) - stateType.setGraphRelevant(st.value("graphRelevant").toBool()); - if (st.contains("possibleValues")) { QVariantList possibleValues; foreach (const QJsonValue &possibleValueJson, st.value("possibleValues").toArray()) { @@ -748,9 +720,6 @@ void DevicePlugin::loadMetaData() // Events for state changed EventType eventType(EventTypeId(stateType.id().toString())); - if (st.contains("eventRuleRelevant")) - eventType.setRuleRelevant(st.value("eventRuleRelevant").toBool()); - eventType.setName(st.value("name").toString()); eventType.setDisplayName(st.value("displayNameEvent").toString()); ParamType paramType(ParamTypeId(stateType.id().toString()), st.value("name").toString(), stateType.type()); @@ -838,11 +807,6 @@ void DevicePlugin::loadMetaData() eventType.setName(et.value("name").toString()); eventType.setDisplayName(et.value("displayName").toString()); eventType.setIndex(index++); - if (et.contains("ruleRelevant")) - eventType.setRuleRelevant(et.value("ruleRelevant").toBool()); - - if (et.contains("graphRelevant")) - eventType.setGraphRelevant(et.value("graphRelevant").toBool()); QPair > paramVerification = parseParamTypes(et.value("paramTypes").toArray()); if (!paramVerification.first) { @@ -855,41 +819,6 @@ void DevicePlugin::loadMetaData() } deviceClass.setEventTypes(eventTypes); - // Note: keep this after the actionType / stateType / eventType parsing - if (deviceClassObject.contains("criticalStateTypeId")) { - StateTypeId criticalStateTypeId = StateTypeId(deviceClassObject.value("criticalStateTypeId").toString()); - if (!deviceClass.hasStateType(criticalStateTypeId)) { - qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend critical stateTypeId" << criticalStateTypeId.toString() << "does not match any StateType of this DeviceClass."; - broken = true; - } else if (deviceClass.getStateType(criticalStateTypeId).type() != QVariant::Bool) { - // Make sure the critical stateType is a bool state - qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend critical stateTypeId" << criticalStateTypeId.toString() << "is not a bool StateType."; - broken = true; - } else { - deviceClass.setCriticalStateTypeId(criticalStateTypeId); - } - } - - if (deviceClassObject.contains("primaryStateTypeId")) { - StateTypeId primaryStateTypeId = StateTypeId(deviceClassObject.value("primaryStateTypeId").toString()); - if (!deviceClass.hasStateType(primaryStateTypeId)) { - qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend primary stateTypeId" << primaryStateTypeId.toString() << "does not match any StateType of this DeviceClass."; - broken = true; - } else { - deviceClass.setPrimaryStateTypeId(primaryStateTypeId); - } - } - - if (deviceClassObject.contains("primaryActionTypeId")) { - ActionTypeId primaryActionTypeId = ActionTypeId(deviceClassObject.value("primaryActionTypeId").toString()); - if (!deviceClass.hasActionType(primaryActionTypeId)) { - qCWarning(dcDeviceManager()) << "Skipping device class" << deviceClass.name() << ": the definend primary actionTypeId" << primaryActionTypeId.toString() << "does not match any ActionType of this DeviceClass."; - broken = true; - } else { - deviceClass.setPrimaryActionTypeId(primaryActionTypeId); - } - } - // Read interfaces QStringList interfaces; foreach (const QJsonValue &value, deviceClassObject.value("interfaces").toArray()) { @@ -1053,58 +982,6 @@ QPair DevicePlugin::loadAndVerifyInputType(const QString return QPair(true, (Types::InputType)enumValue); } -QPair DevicePlugin::loadAndVerifyBasicTag(const QString &basicTag) const -{ - if (basicTag.isEmpty()) - return QPair(true, DeviceClass::BasicTagDevice); - - QMetaObject metaObject = DeviceClass::staticMetaObject; - int enumIndex = metaObject.indexOfEnumerator(QString("BasicTag").toLatin1().data()); - QMetaEnum metaEnum = metaObject.enumerator(enumIndex); - - int enumValue = -1; - for (int i = 0; i < metaEnum.keyCount(); i++) { - if (QString(metaEnum.valueToKey(metaEnum.value(i))) == QString("BasicTag" + basicTag)) { - enumValue = metaEnum.value(i); - break; - } - } - - // inform the plugin developer about the error in the plugin json file - if (enumValue == -1) { - qCWarning(dcDeviceManager()) << QString("\"%1\" plugin:").arg(pluginName()).toLatin1().data() << QString("Invalid basicTag \"%1\" in json file.").arg(basicTag).toLatin1().data(); - return QPair(false, DeviceClass::BasicTagDevice); - } - - return QPair(true, (DeviceClass::BasicTag)enumValue); -} - -QPair DevicePlugin::loadAndVerifyDeviceIcon(const QString &deviceIcon) const -{ - if (deviceIcon.isEmpty()) - return QPair(true, DeviceClass::DeviceIconNone); - - QMetaObject metaObject = DeviceClass::staticMetaObject; - int enumIndex = metaObject.indexOfEnumerator(QString("DeviceIcon").toLatin1().data()); - QMetaEnum metaEnum = metaObject.enumerator(enumIndex); - - int enumValue = -1; - for (int i = 0; i < metaEnum.keyCount(); i++) { - if (QString(metaEnum.valueToKey(metaEnum.value(i))) == QString("DeviceIcon" + deviceIcon)) { - enumValue = metaEnum.value(i); - break; - } - } - - // inform the plugin developer about the error in the plugin json file - if (enumValue == -1) { - qCWarning(dcDeviceManager()) << QString("\"%1\" plugin:").arg(pluginName()).toLatin1().data() << QString("Invalid deviceIcon \"%1\" in json file.").arg(deviceIcon).toLatin1().data(); - return QPair(false, DeviceClass::DeviceIconNone); - } - - return QPair(true, (DeviceClass::DeviceIcon)enumValue); -} - Interfaces DevicePlugin::allInterfaces() { Interfaces ret; diff --git a/libnymea/plugin/deviceplugin.h b/libnymea/plugin/deviceplugin.h index b3a19389..1d5c7845 100644 --- a/libnymea/plugin/deviceplugin.h +++ b/libnymea/plugin/deviceplugin.h @@ -113,8 +113,6 @@ private: // load and verify enum values QPair loadAndVerifyUnit(const QString &unitString) const; QPair loadAndVerifyInputType(const QString &inputType) const; - QPair loadAndVerifyBasicTag(const QString &basicTag) const; - QPair loadAndVerifyDeviceIcon(const QString &deviceIcon) const; // FIXME: This is expensive because it will open all the files. // Once DeviceManager is in libnymea-core this should probably be there too. diff --git a/libnymea/types/deviceclass.cpp b/libnymea/types/deviceclass.cpp index 80b31f8d..5c17e3f4 100644 --- a/libnymea/types/deviceclass.cpp +++ b/libnymea/types/deviceclass.cpp @@ -62,103 +62,6 @@ During the setup, a button has to be pushed in order to pair the \l{Device}. */ -/*! \enum DeviceClass::BasicTag - - This enum type specifies the basic tags which describe the categories of the DeviceClass. - A DeviceClass can have multiple tags. - - \value BasicTagService - The \l{DeviceClass} describes a service. - \value BasicTagDevice - The \l{DeviceClass} describes a real device. - \value BasicTagSensor - The \l{DeviceClass} describes a sensor. Any device which can measure or detect something is a sensor. - \value BasicTagActuator - The \l{DeviceClass} describes an actuator. Any device which can do something is an actuator. - \value BasicTagLighting - The \l{DeviceClass} describes a lighting device. - \value BasicTagEnergy - The \l{DeviceClass} describes an energy device. - \value BasicTagMultimedia - The \l{DeviceClass} describes a multimedia device/service. - \value BasicTagWeather - The \l{DeviceClass} describes a weather device/service. - \value BasicTagGateway - The \l{DeviceClass} describes a gateway device. - \value BasicTagHeating - The \l{DeviceClass} describes a heating device. - \value BasicTagCooling - The \l{DeviceClass} describes a cooling device. - \value BasicTagNotification - The \l{DeviceClass} describes a notification service. - \value BasicTagSecurity - The \l{DeviceClass} describes a security device/service. - \value BasicTagTime - The \l{DeviceClass} describes a time device/service. - \value BasicTagShading - The \l{DeviceClass} describes a shading device. - \value BasicTagAppliance - The \l{DeviceClass} describes an appliance. - \value BasicTagCamera - The \l{DeviceClass} describes a camera device. - \value BasicTagLock - The \l{DeviceClass} describes a lock device. - -*/ - -/*! \enum DeviceClass::DeviceIcon - - This enum type specifies the default device icon of the DeviceClass. - Each client should have an icon set containing this list of icon types. - If a client want's to offer special icons for a special DeviceClass or device type, - he can bring it's own icon for a special DeviceClassId. If there is no special icon - defined, he can fallback to the default icon. - - \value DeviceIconNone - \value DeviceIconBed - \value DeviceIconBlinds - \value DeviceIconCeilingLamp - \value DeviceIconCouch - \value DeviceIconDeskLamp - \value DeviceIconDesk - \value DeviceIconHifi - \value DeviceIconPower - \value DeviceIconEnergy - \value DeviceIconRadio - \value DeviceIconSmartPhone - \value DeviceIconSocket - \value DeviceIconStandardLamp - \value DeviceIconSun - \value DeviceIconTablet - \value DeviceIconThermometer - \value DeviceIconTune - \value DeviceIconTv - \value DeviceIconBattery - \value DeviceIconDishwasher - \value DeviceIconWashingMachine - \value DeviceIconLaundryDryer - \value DeviceIconIrHeater - \value DeviceIconRadiator - \value DeviceIconSwitch - \value DeviceIconMotionDetectors - \value DeviceIconWeather - \value DeviceIconTime - \value DeviceIconLightBulb - \value DeviceIconGateway - \value DeviceIconMail - \value DeviceIconNetwork - \value DeviceIconCloud - \value DeviceIconGarage, - \value DeviceIconRollerShutter -*/ - -/*! \fn void DeviceClass::setBasicTags(const QList &basicTags); - Set the list of \a basicTags of this DeviceClass. -*/ - -/*! \fn void DeviceClass::setDeviceIcon(const DeviceIcon &deviceIcon); - Set the \a deviceIcon of this DeviceClass. -*/ #include "deviceclass.h" @@ -170,10 +73,6 @@ DeviceClass::DeviceClass(const PluginId &pluginId, const VendorId &vendorId, con m_id(id), m_vendorId(vendorId), m_pluginId(pluginId), - m_criticalStateTypeId(StateTypeId()), - m_primaryStateTypeId(StateTypeId()), - m_primaryActionTypeId(ActionTypeId()), - m_deviceIcon(DeviceIconPower), m_createMethods(CreateMethodUser), m_setupMethod(SetupMethodJustAdd) { @@ -228,76 +127,6 @@ void DeviceClass::setDisplayName(const QString &displayName) m_displayName = displayName; } -/*! Returns the critical \l{StateTypeId} of this \l{DeviceClass}. - * A critical \l{State} describes the state which disables the whole device (i.e. connected, available or reachable). */ -StateTypeId DeviceClass::criticalStateTypeId() const -{ - return m_criticalStateTypeId; -} - -/*! Set the \a criticalStateTypeId of this \l{DeviceClass}. */ -void DeviceClass::setCriticalStateTypeId(const StateTypeId &criticalStateTypeId) -{ - m_criticalStateTypeId = criticalStateTypeId; -} - -/*! Returns the primary \l{StateTypeId} of this \l{DeviceClass}. */ -StateTypeId DeviceClass::primaryStateTypeId() const -{ - return m_primaryStateTypeId; -} - -/*! Set the \a primaryStateTypeId of this \l{DeviceClass}. */ -void DeviceClass::setPrimaryStateTypeId(const StateTypeId &primaryStateTypeId) -{ - m_primaryStateTypeId = primaryStateTypeId; -} - -/*! Returns the primary \l{ActionTypeId} of this \l{DeviceClass}. */ -ActionTypeId DeviceClass::primaryActionTypeId() const -{ - return m_primaryActionTypeId; -} - -/*! Set the \a primaryActionTypeId of this \l{DeviceClass}. */ -void DeviceClass::setPrimaryActionTypeId(const ActionTypeId &primaryActionTypeId) -{ - m_primaryActionTypeId = primaryActionTypeId; -} - -/*! Returns the default \l{DeviceIcon} of this \l{DeviceClass}. */ -DeviceClass::DeviceIcon DeviceClass::deviceIcon() const -{ - return m_deviceIcon; -} - -/*! Set the \a deviceIcon of this \l{DeviceClass}. - - \sa DeviceClass::DeviceIcon -*/ -void DeviceClass::setDeviceIcon(const DeviceClass::DeviceIcon &deviceIcon) -{ - m_deviceIcon = deviceIcon; -} - -/*! Returns the list of basicTags of this DeviceClass. - - \sa DeviceClass::BasicTag -*/ -QList DeviceClass::basicTags() const -{ - return m_basicTags; -} - -/*! Set the list of \a basicTags of this DeviceClass. - - \sa DeviceClass::BasicTag -*/ -void DeviceClass::setBasicTags(const QList &basicTags) -{ - m_basicTags = basicTags; -} - /*! Returns the statesTypes of this DeviceClass. \{Device}{Devices} created from this \l{DeviceClass} must have their states matching to this template. */ StateTypes DeviceClass::stateTypes() const @@ -474,10 +303,9 @@ bool DeviceClass::operator==(const DeviceClass &deviceClass) const /*! Returns a list of all valid JSON properties a DeviceClass JSON definition can have. */ QStringList DeviceClass::typeProperties() { - return QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" << "deviceIcon" - << "interfaces" << "basicTags" << "pairingInfo" << "criticalStateTypeId" - << "primaryStateTypeId" << "primaryActionTypeId" << "discoveryParamTypes" - << "discoveryParamTypes" << "paramTypes" << "stateTypes" << "actionTypes" << "eventTypes"; + return QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" + << "interfaces" << "pairingInfo" << "discoveryParamTypes" << "discoveryParamTypes" + << "paramTypes" << "stateTypes" << "actionTypes" << "eventTypes"; } /*! Returns a list of mandatory JSON properties a DeviceClass JSON definition must have. */ diff --git a/libnymea/types/deviceclass.h b/libnymea/types/deviceclass.h index 43f1eb97..1c95ca33 100644 --- a/libnymea/types/deviceclass.h +++ b/libnymea/types/deviceclass.h @@ -41,8 +41,7 @@ class LIBNYMEA_EXPORT DeviceClass Q_ENUMS(CreateMethod) Q_ENUMS(SetupMethod) Q_ENUMS(BasicTag) - Q_ENUMS(DeviceIcon) - Q_FLAGS(CreateMethods) + Q_ENUMS(CreateMethods) public: enum CreateMethod { @@ -59,66 +58,6 @@ public: SetupMethodPushButton }; - enum BasicTag { - BasicTagService, - BasicTagDevice, - BasicTagSensor, - BasicTagActuator, - BasicTagLighting, - BasicTagEnergy, - BasicTagMultimedia, - BasicTagWeather, - BasicTagGateway, - BasicTagHeating, - BasicTagCooling, - BasicTagNotification, - BasicTagSecurity, - BasicTagTime, - BasicTagShading, - BasicTagAppliance, - BasicTagCamera, - BasicTagLock - }; - - enum DeviceIcon { - DeviceIconNone, - DeviceIconBed, - DeviceIconBlinds, - DeviceIconCeilingLamp, - DeviceIconCouch, - DeviceIconDeskLamp, - DeviceIconDesk, - DeviceIconHifi, - DeviceIconPower, - DeviceIconEnergy, - DeviceIconRadio, - DeviceIconSmartPhone, - DeviceIconSocket, - DeviceIconStandardLamp, - DeviceIconSun, - DeviceIconTablet, - DeviceIconThermometer, - DeviceIconTune, - DeviceIconTv, - DeviceIconBattery, - DeviceIconDishwasher, - DeviceIconWashingMachine, - DeviceIconLaundryDryer, - DeviceIconIrHeater, - DeviceIconRadiator, - DeviceIconSwitch, - DeviceIconMotionDetectors, - DeviceIconWeather, - DeviceIconTime, - DeviceIconLightBulb, - DeviceIconGateway, - DeviceIconMail, - DeviceIconNetwork, - DeviceIconCloud, - DeviceIconGarage, - DeviceIconRollerShutter - }; - DeviceClass(const PluginId &pluginId = PluginId(), const VendorId &vendorId = VendorId(), const DeviceClassId &id = DeviceClassId()); DeviceClassId id() const; @@ -132,21 +71,6 @@ public: QString displayName() const; void setDisplayName(const QString &displayName); - StateTypeId criticalStateTypeId() const; - void setCriticalStateTypeId(const StateTypeId &criticalStateTypeId); - - StateTypeId primaryStateTypeId() const; - void setPrimaryStateTypeId(const StateTypeId &primaryStateTypeId); - - ActionTypeId primaryActionTypeId() const; - void setPrimaryActionTypeId(const ActionTypeId &primaryActionTypeId); - - DeviceIcon deviceIcon() const; - void setDeviceIcon(const DeviceIcon &deviceIcon); - - QList basicTags() const; - void setBasicTags(const QList &basicTags); - StateTypes stateTypes() const; StateType getStateType(const StateTypeId &stateTypeId); void setStateTypes(const QList &stateTypes); @@ -189,11 +113,6 @@ private: PluginId m_pluginId; QString m_name; QString m_displayName; - StateTypeId m_criticalStateTypeId; - StateTypeId m_primaryStateTypeId; - ActionTypeId m_primaryActionTypeId; - DeviceIcon m_deviceIcon; - QList m_basicTags; QList m_stateTypes; QList m_eventTypes; QList m_actionTypes; diff --git a/libnymea/types/eventtype.cpp b/libnymea/types/eventtype.cpp index 31468899..b7a10fa1 100644 --- a/libnymea/types/eventtype.cpp +++ b/libnymea/types/eventtype.cpp @@ -36,9 +36,7 @@ /*! Constructs a EventType object with the given \a id. */ EventType::EventType(const EventTypeId &id): m_id(id), - m_index(0), - m_ruleRelevant(true), - m_graphRelevant(false) + m_index(0) { } @@ -100,30 +98,6 @@ void EventType::setParamTypes(const ParamTypes ¶mTypes) m_paramTypes = paramTypes; } -/*! Returns true if this EventType is relevant for the rule from a user perspective. */ -bool EventType::ruleRelevant() const -{ - return m_ruleRelevant; -} - -/*! Sets this EventType relevant for the rule from a user perspective to \a ruleRelevant. */ -void EventType::setRuleRelevant(const bool &ruleRelevant) -{ - m_ruleRelevant = ruleRelevant; -} - -/*! Returns true if this EventType is interesting to visualize the logs in a graph/chart from a user perspective. */ -bool EventType::graphRelevant() const -{ - return m_graphRelevant; -} - -/*! Sets this EventType \a graphRelevant to inform the client application if this \l{EventType} is interesting to visualize the logs in a graph/chart. */ -void EventType::setGraphRelevant(const bool &graphRelevant) -{ - m_graphRelevant = graphRelevant; -} - /*! Returns true if this EventType has a valid id and name */ bool EventType::isValid() const { @@ -133,7 +107,7 @@ bool EventType::isValid() const /*! Returns a list of all valid JSON properties a EventType JSON definition can have. */ QStringList EventType::typeProperties() { - return QStringList() << "id" << "name" << "displayName" << "paramTypes" << "ruleRelevant" << "graphRelevant"; + return QStringList() << "id" << "name" << "displayName" << "paramTypes"; } /*! Returns a list of mandatory JSON properties a EventType JSON definition must have. */ diff --git a/libnymea/types/eventtype.h b/libnymea/types/eventtype.h index e0a06162..c50ec651 100644 --- a/libnymea/types/eventtype.h +++ b/libnymea/types/eventtype.h @@ -49,12 +49,6 @@ public: ParamTypes paramTypes() const; void setParamTypes(const ParamTypes ¶mTypes); - bool ruleRelevant() const; - void setRuleRelevant(const bool &ruleRelevant); - - bool graphRelevant() const; - void setGraphRelevant(const bool &graphRelevant); - bool isValid() const; static QStringList typeProperties(); @@ -66,8 +60,6 @@ private: QString m_displayName; int m_index; QList m_paramTypes; - bool m_ruleRelevant; - bool m_graphRelevant; }; class EventTypes: public QList diff --git a/libnymea/types/statetype.cpp b/libnymea/types/statetype.cpp index cad8e0d4..acbd55c9 100644 --- a/libnymea/types/statetype.cpp +++ b/libnymea/types/statetype.cpp @@ -160,30 +160,6 @@ void StateType::setUnit(const Types::Unit &unit) m_unit = unit; } -/*! Returns true if this StateType is relevant for the rule from a user perspective. */ -bool StateType::ruleRelevant() const -{ - return m_ruleRelevant; -} - -/*! Sets this StateType relevant for the rule from a user perspective to \a ruleRelevant. */ -void StateType::setRuleRelevant(const bool &ruleRelevant) -{ - m_ruleRelevant = ruleRelevant; -} - -/*! Returns true if this StateType is interesting to visualize the logs in a graph/chart from a user perspective. */ -bool StateType::graphRelevant() const -{ - return m_graphRelevant; -} - -/*! Sets this StateType \a graphRelevant to inform the client application if this \l{StateType} is interesting to visualize the logs in a graph/chart. */ -void StateType::setGraphRelevant(const bool &graphRelevant) -{ - m_graphRelevant = graphRelevant; -} - /*! Returns true if this StateType is to be cached. This means, the last state value will be stored to disk upon shutdown and restored on reboot. If this is false, states will be initialized with the default value on each boot. By default all states are cached by the system. */ bool StateType::cached() const { @@ -200,8 +176,8 @@ void StateType::setCached(bool cached) QStringList StateType::typeProperties() { return QStringList() << "id" << "name" << "displayName" << "displayNameEvent" << "type" << "defaultValue" - << "cached" << "ruleRelevant" << "eventRuleRelevant" << "graphRelevant" << "unit" - << "minValue" << "maxValue" << "possibleValues" << "writable" << "displayNameAction"; + << "cached" << "unit" << "minValue" << "maxValue" << "possibleValues" << "writable" + << "displayNameAction"; } /*! Returns a list of mandatory properties a DeviceClass definition must have. */ diff --git a/libnymea/types/statetype.h b/libnymea/types/statetype.h index 278c4ac3..88d65a16 100644 --- a/libnymea/types/statetype.h +++ b/libnymea/types/statetype.h @@ -64,12 +64,6 @@ public: Types::Unit unit() const; void setUnit(const Types::Unit &unit); - bool ruleRelevant() const; - void setRuleRelevant(const bool &ruleRelevant); - - bool graphRelevant() const; - void setGraphRelevant(const bool &graphRelevant); - bool cached() const; void setCached(bool cached); @@ -87,8 +81,6 @@ private: QVariant m_maxValue; QVariantList m_possibleValues; Types::Unit m_unit = Types::UnitNone; - bool m_ruleRelevant = true; - bool m_graphRelevant = false; bool m_cached = true; }; diff --git a/nymea.pri b/nymea.pri index c3f55acf..dfd7162c 100644 --- a/nymea.pri +++ b/nymea.pri @@ -5,8 +5,8 @@ NYMEA_VERSION_STRING=$$system('dpkg-parsechangelog | sed -n -e "s/^Version: //p" NYMEA_PLUGINS_PATH=/usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')/nymea/plugins/ # define protocol versions -JSON_PROTOCOL_VERSION_MAJOR=1 -JSON_PROTOCOL_VERSION_MINOR=14 +JSON_PROTOCOL_VERSION_MAJOR=2 +JSON_PROTOCOL_VERSION_MINOR=0 REST_API_VERSION=1 COPYRIGHT_YEAR_FROM=2013 diff --git a/nymea.pro b/nymea.pro index 86fb8226..fc6a7443 100644 --- a/nymea.pro +++ b/nymea.pro @@ -15,11 +15,10 @@ doc.commands += cd $$top_srcdir/doc; ./generate-interfaces-qdoc.py; doc.commands += cd $$top_srcdir/doc; ./generate-api-qdoc.py; doc.commands += cd $$top_srcdir/doc; qdoc --highlighting config.qdocconf; cp -r images/* html/images/; \ cp -r favicons/* html/; cp -r $$top_srcdir/doc/html $$top_builddir/ +QMAKE_EXTRA_TARGETS += doc licensecheck.commands = $$top_srcdir/tests/auto/checklicenseheaders.sh $$top_srcdir - -test.depends = licensecheck -test.commands = LD_LIBRARY_PATH=$$top_builddir/libnymea-core:$$top_builddir/libnymea make check +QMAKE_EXTRA_TARGETS += licensecheck # Translations: # make lupdate to update .ts files @@ -42,7 +41,11 @@ translations.path = /usr/share/nymea/translations translations.depends = lrelease INSTALLS += translations -QMAKE_EXTRA_TARGETS += licensecheck doc test lupdate lrelease +QMAKE_EXTRA_TARGETS += lupdate lrelease + +test.depends = licensecheck lrelease +test.commands = LD_LIBRARY_PATH=$$top_builddir/libnymea-core:$$top_builddir/libnymea make check +QMAKE_EXTRA_TARGETS += test # Show doc files in project tree OTHER_FILES += doc/*.qdoc* \ diff --git a/plugins/mock/devicepluginmock.json b/plugins/mock/devicepluginmock.json index 91a73636..bfc78cd4 100644 --- a/plugins/mock/devicepluginmock.json +++ b/plugins/mock/devicepluginmock.json @@ -30,16 +30,8 @@ "id": "753f0d32-0468-4d08-82ed-1964aab03298", "name": "mock", "displayName": "Mock Device", - "deviceIcon": "Tune", "interfaces": ["system", "light", "gateway", "battery"], - "basicTags": [ - "Device", - "Actuator", - "Gateway" - ], "createMethods": ["user", "discovery"], - "primaryActionTypeId": "defd3ed6-1a0d-400b-8879-a0202cf39935", - "primaryStateTypeId": "80baec19-54de-4948-ac46-31eabfaceb83", "discoveryParamTypes": [ { "id": "d222adb4-2f9c-4c3f-8655-76400d0fb6ce", @@ -80,7 +72,6 @@ "displayName": "Dummy int state", "displayNameEvent": "Dummy int state changed", "defaultValue": 10, - "graphRelevant": true, "type": "int" }, { @@ -136,8 +127,7 @@ { "id": "45bf3752-0fc6-46b9-89fd-ffd878b5b22b", "name": "mockEvent1", - "displayName": "Mock Event 1", - "graphRelevant": true + "displayName": "Mock Event 1" }, { "id": "863d5920-b1cf-4eb9-88bd-8f7b8583b1cf", @@ -201,15 +191,7 @@ "name": "mockDeviceAuto", "displayName": "Mock Device (Auto created)", "interfaces": ["system"], - "basicTags": [ - "Device", - "Actuator", - "Gateway" - ], "createMethods": ["auto"], - "primaryActionTypeId": "defd3ed6-1a0d-400b-8879-a0202cf39935", - "primaryStateTypeId": "80baec19-54de-4948-ac46-31eabfaceb83", - "deviceIcon": "Tune", "paramTypes": [ { "id": "d4f06047-125e-4479-9810-b54c189917f5", @@ -240,7 +222,6 @@ "displayName": "Dummy int state", "displayNameEvent": "Dummy int state changed", "defaultValue": 10, - "graphRelevant": true, "type": "int" }, { @@ -321,14 +302,8 @@ "name": "mockPushButton", "displayName": "Mock Device (Push Button)", "interfaces": ["system"], - "basicTags": [ - "Device", - "Actuator", - "Gateway" - ], "createMethods": ["discovery"], "setupMethod": "pushButton", - "deviceIcon": "Tune", "pairingInfo": "Wait 3 second before you continue, the push button will be pressed automatically.", "paramTypes": [ ], "discoveryParamTypes": [ @@ -350,8 +325,6 @@ "displayNameAction": "Set color", "type": "QColor", "defaultValue": "#000000", - "ruleRelevant": false, - "eventRuleRelevant": false, "writable": true }, { @@ -363,7 +336,6 @@ "type": "int", "unit": "Percentage", "defaultValue": 0, - "ruleRelevant": false, "minValue": 0, "maxValue": 100, "writable": true @@ -419,13 +391,7 @@ "id": "296f1fd4-e893-46b2-8a42-50d1bceb8730", "name": "mockDisplayPin", "displayName": "Mock Device (Display Pin)", - "deviceIcon": "Tune", "interfaces": ["system"], - "basicTags": [ - "Device", - "Actuator", - "Gateway" - ], "createMethods": ["discovery"], "setupMethod": "displayPin", "pairingInfo": "Please enter the secret which normaly will be displayed on the device. For the mockdevice the pin is 243681.", @@ -459,8 +425,6 @@ "displayNameAction": "Set color", "type": "QColor", "defaultValue": "#000000", - "ruleRelevant": false, - "eventRuleRelevant": false, "writable": true }, { @@ -472,7 +436,6 @@ "type": "int", "unit": "Percentage", "defaultValue": 0, - "ruleRelevant": false, "minValue": 0, "maxValue": 100, "writable": true @@ -528,13 +491,7 @@ "id": "a71fbde9-9a38-4bf8-beab-c8aade2608ba", "name": "mockParent", "displayName": "Mock Device (Parent)", - "deviceIcon": "Tune", "interfaces": ["system"], - "basicTags": [ - "Device", - "Actuator", - "Gateway" - ], "createMethods": ["user"], "paramTypes": [ ], "stateTypes": [ @@ -556,10 +513,6 @@ "displayName": "Mock Device (Child)", "createMethods": ["auto"], "paramTypes": [], - "basicTags": [ - "Device", - "Actuator" - ], "stateTypes": [ { "id": "d24ede5f-4064-4898-bb84-cfb533b1fbc0", @@ -577,10 +530,6 @@ "id": "515ffdf1-55e5-498d-9abc-4e2fe768f3a9", "name": "mockInputType", "displayName": "Mock Device (InputTypes)", - "deviceIcon": "Tune", - "basicTags": [ - "Device" - ], "createMethods": ["user"], "paramTypes": [ { diff --git a/tests/auto/api.json b/tests/auto/api.json index ec176e34..d6df40d4 100644 --- a/tests/auto/api.json +++ b/tests/auto/api.json @@ -1,4 +1,4 @@ -1.14 +2.0 { "methods": { "Actions.ExecuteAction": { @@ -1008,6 +1008,15 @@ "deviceId": "Uuid" } }, + "Devices.PluginConfigurationChanged": { + "description": "Emitted whenever a plugin's configuration is changed.", + "params": { + "configuration": [ + "$ref:Param" + ], + "pluginId": "Uuid" + } + }, "Devices.StateChanged": { "description": "Emitted whenever a State of a device changes.", "params": { @@ -1155,26 +1164,6 @@ "$ref:ParamType" ] }, - "BasicTag": [ - "BasicTagService", - "BasicTagDevice", - "BasicTagSensor", - "BasicTagActuator", - "BasicTagLighting", - "BasicTagEnergy", - "BasicTagMultimedia", - "BasicTagWeather", - "BasicTagGateway", - "BasicTagHeating", - "BasicTagCooling", - "BasicTagNotification", - "BasicTagSecurity", - "BasicTagTime", - "BasicTagShading", - "BasicTagAppliance", - "BasicTagCamera", - "BasicTagLock" - ], "BasicType": [ "Uuid", "String", @@ -1235,13 +1224,9 @@ "actionTypes": [ "$ref:ActionType" ], - "basicTags": [ - "$ref:BasicTag" - ], "createMethods": [ "$ref:CreateMethod" ], - "deviceIcon": "$ref:DeviceIcon", "discoveryParamTypes": [ "$ref:ParamType" ], @@ -1254,9 +1239,6 @@ "String" ], "name": "String", - "o:criticalStateTypeId": "Uuid", - "o:primaryActionTypeId": "Uuid", - "o:primaryStateTypeId": "Uuid", "paramTypes": [ "$ref:ParamType" ], @@ -1302,44 +1284,6 @@ "DeviceErrorPairingTransactionIdNotFound", "DeviceErrorParameterNotWritable" ], - "DeviceIcon": [ - "DeviceIconNone", - "DeviceIconBed", - "DeviceIconBlinds", - "DeviceIconCeilingLamp", - "DeviceIconCouch", - "DeviceIconDeskLamp", - "DeviceIconDesk", - "DeviceIconHifi", - "DeviceIconPower", - "DeviceIconEnergy", - "DeviceIconRadio", - "DeviceIconSmartPhone", - "DeviceIconSocket", - "DeviceIconStandardLamp", - "DeviceIconSun", - "DeviceIconTablet", - "DeviceIconThermometer", - "DeviceIconTune", - "DeviceIconTv", - "DeviceIconBattery", - "DeviceIconDishwasher", - "DeviceIconWashingMachine", - "DeviceIconLaundryDryer", - "DeviceIconIrHeater", - "DeviceIconRadiator", - "DeviceIconSwitch", - "DeviceIconMotionDetectors", - "DeviceIconWeather", - "DeviceIconTime", - "DeviceIconLightBulb", - "DeviceIconGateway", - "DeviceIconMail", - "DeviceIconNetwork", - "DeviceIconCloud", - "DeviceIconGarage", - "DeviceIconRollerShutter" - ], "Event": { "deviceId": "Uuid", "eventTypeId": "Uuid", @@ -1361,8 +1305,6 @@ "id": "Uuid", "index": "Int", "name": "String", - "o:graphRelevant": "Bool", - "o:ruleRelevant": "Bool", "paramTypes": [ "$ref:ParamType" ] @@ -1619,13 +1561,11 @@ "id": "Uuid", "index": "Int", "name": "String", - "o:graphRelevant": "Bool", "o:maxValue": "Variant", "o:minValue": "Variant", "o:possibleValues": [ "Variant" ], - "o:ruleRelevant": "Bool", "o:unit": "$ref:Unit", "type": "$ref:BasicType" }, diff --git a/tests/auto/jsonrpc/testjsonrpc.cpp b/tests/auto/jsonrpc/testjsonrpc.cpp index 07c5e4cf..3c4c6f93 100644 --- a/tests/auto/jsonrpc/testjsonrpc.cpp +++ b/tests/auto/jsonrpc/testjsonrpc.cpp @@ -133,25 +133,22 @@ void TestJSONRPC::initTestCase() { NymeaTestBase::initTestCase(); QLoggingCategory::setFilterRules("*.debug=false\n" -// "JsonRpc*.debug=true\n" +// "JsonRpcTraffic.debug=true\n" + "JsonRpc.debug=true\n" "Translations.debug=true\n" "Tests.debug=true"); } void TestJSONRPC::testHandshake() { - // first test if the handshake message is auto-sent upon connecting - QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); - QUuid newClientId = QUuid::createUuid(); m_mockTcpServer->clientConnected(newClientId); - QVERIFY2(spy.count() > 0, "Did not get the handshake message upon connect."); - QVERIFY2(spy.first().first() == newClientId, "Handshake message addressed at the wrong client."); + qApp->processEvents(); - QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); - QVariantMap handShake = jsonDoc.toVariant().toMap(); + // Check the Hello reply + QVariantMap handShake = injectAndWait("JSONRPC.Hello", QVariantMap(), newClientId).toMap(); QString nymeaVersionString(NYMEA_VERSION_STRING); - QVERIFY2(handShake.value("version").toString() == nymeaVersionString, "Handshake version doesn't match nymea version."); + QVERIFY2(handShake.value("params").toMap().value("version").toString() == nymeaVersionString, "Handshake version doesn't match nymea version."); // Check whether pushButtonAuth is disabled QCOMPARE(handShake.value("pushButtonAuthAvailable").toBool(), false); @@ -164,11 +161,12 @@ void TestJSONRPC::testHandshake() handShake = injectAndWait("JSONRPC.Hello").toMap(); QCOMPARE(handShake.value("params").toMap().value("version").toString(), nymeaVersionString); - m_mockTcpServer->clientDisconnected(newClientId); - - // Check whether pushButtonAuth is now + // Check whether pushButtonAuth is now enabled QCOMPARE(handShake.value("params").toMap().value("pushButtonAuthAvailable").toBool(), true); + emit m_mockTcpServer->clientDisconnected(newClientId); + + // And now check if it is sent again when calling JSONRPC.Hello handShake = injectAndWait("JSONRPC.Hello").toMap(); QCOMPARE(handShake.value("params").toMap().value("version").toString(), nymeaVersionString); @@ -222,8 +220,11 @@ void TestJSONRPC::testInitialSetup() QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); QVERIFY(spy.isValid()); + QSignalSpy connectedSpy(m_mockTcpServer, &MockTcpServer::clientConnected); + QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected); // Introspect call should work in any case + qCDebug(dcTests()) << "Calling Introspect, expecting success"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Introspect\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -231,12 +232,13 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); QVariantMap response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling introspect on uninitialized instance:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests()) << "Result:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); // Hello call should work in any case too spy.clear(); + qCDebug(dcTests()) << "Calling Hello, expecting success"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Hello\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -244,12 +246,14 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Hello on uninitialized instance:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests()) << "Result:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(response.value("params").toMap().value("initialSetupRequired").toBool(), true); // Any other call should fail with "unauthorized" even if we use a previously valid token spy.clear(); + disconnectedSpy.clear(); + qCDebug(dcTests()) << "Calling Version, expecting failure (unauthenticated)"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Version\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -257,13 +261,31 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Version on uninitialized instance:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests()) << "Result:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("unauthorized")); + // Connection should terminate + if (disconnectedSpy.count() == 0) disconnectedSpy.wait(); + QCOMPARE(disconnectedSpy.count(), 1); + qCDebug(dcTests()) << "Mock client disconnected"; + connectedSpy.clear(); + emit m_mockTcpServer->clientConnected(m_clientId); + if (connectedSpy.count() == 0) connectedSpy.wait(); + QCOMPARE(connectedSpy.count(), 1); + qCDebug(dcTests()) << "Mock client connected"; + + spy.clear(); + m_mockTcpServer->injectData(m_clientId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (spy.count() == 0) { + spy.wait(); + } + QVERIFY(spy.count() == 1); + // Except CreateUser // But it should still fail when giving a an invalid username spy.clear(); + qCDebug(dcTests()) << "Calling CreateUser, expecting failure (bad username)"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"dummy\", \"password\": \"DummyPW1!\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -271,12 +293,13 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling CreateUser on uninitialized instance with invalid user:" << response.value("status").toString() << response.value("params").toMap().value("error").toString(); + qCDebug(dcTests()) << "Calling CreateUser on uninitialized instance with invalid user:" << response.value("status").toString() << response.value("params").toMap().value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 0); // or when giving a bad password spy.clear(); + qCDebug(dcTests()) << "Calling CreateUser, expecting failure (bad password)"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"dummy@guh.io\", \"password\": \"weak\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -284,12 +307,13 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling CreateUser on uninitialized instance with weak password:" << response.value("status").toString() << response.value("params").toMap().value("error").toString(); + qCDebug(dcTests()) << "Calling CreateUser on uninitialized instance with weak password:" << response.value("status").toString() << response.value("params").toMap().value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 0); // Now lets play by the rules (with an uppercase email) spy.clear(); + qCDebug(dcTests()) << "Calling CreateUser, expecting success"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"Dummy@guh.io\", \"password\": \"DummyPW1!\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -297,12 +321,13 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling CreateUser on uninitialized instance:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests) << "Calling CreateUser on uninitialized instance:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 1); // Now that we have a user, initialSetup should be false in the Hello call spy.clear(); + qCDebug(dcTests()) << "Calling Hello, expecting success"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Hello\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -310,12 +335,14 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Hello on initialized instance:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests) << "Calling Hello on initialized instance:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(response.value("params").toMap().value("initialSetupRequired").toBool(), false); // Calls should still fail, given we didn't get a new token yet spy.clear(); + disconnectedSpy.clear(); + qCDebug(dcTests()) << "Calling Version, expecting failure (bad token)"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Version\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -323,11 +350,30 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Version with old token:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests) << "Calling Version with old token:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("unauthorized")); + // Connection should terminate + if (disconnectedSpy.count() == 0) disconnectedSpy.wait(); + QCOMPARE(disconnectedSpy.count(), 1); + qCDebug(dcTests()) << "Mock client disconnected"; + connectedSpy.clear(); + emit m_mockTcpServer->clientConnected(m_clientId); + if (connectedSpy.count() == 0) connectedSpy.wait(); + QCOMPARE(connectedSpy.count(), 1); + qCDebug(dcTests()) << "Mock client connected"; + + spy.clear(); + m_mockTcpServer->injectData(m_clientId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (spy.count() == 0) { + spy.wait(); + } + QVERIFY(spy.count() == 1); + + // Now lets authenticate with a wrong user spy.clear(); + qCDebug(dcTests()) << "Calling Authenticate, expecting failure (bad user)"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Authenticate\", \"params\": {\"username\": \"Dummy@wrong.domain\", \"password\": \"DummyPW1!\", \"deviceName\": \"testcase\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -335,7 +381,7 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Authenticate with wrong user:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); + qCDebug(dcTests()) << "Calling Authenticate with wrong user:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(response.value("params").toMap().value("success").toBool(), false); QVERIFY(response.value("params").toMap().value("token").toByteArray().isEmpty()); @@ -343,6 +389,7 @@ void TestJSONRPC::testInitialSetup() // Now lets authenticate with a wrong password spy.clear(); + qCDebug(dcTests()) << "Calling Authenticate, expecting failure (bad password)"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Authenticate\", \"params\": {\"username\": \"Dummy@guh.io\", \"password\": \"wrongpw\", \"deviceName\": \"testcase\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -350,7 +397,7 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Authenticate with wrong password:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); + qCDebug(dcTests()) << "Calling Authenticate with wrong password:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(response.value("params").toMap().value("success").toBool(), false); QVERIFY(response.value("params").toMap().value("token").toByteArray().isEmpty()); @@ -358,6 +405,7 @@ void TestJSONRPC::testInitialSetup() // Now lets authenticate for real (but intentionally use a lowercase email here, should still work) spy.clear(); + qCDebug(dcTests()) << "Calling Authenticate, expecting success"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Authenticate\", \"params\": {\"username\": \"dummy@guh.io\", \"password\": \"DummyPW1!\", \"deviceName\": \"testcase\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -365,7 +413,7 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Authenticate with valid credentials:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); + qCDebug(dcTests()) << "Calling Authenticate with valid credentials:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(response.value("params").toMap().value("success").toBool(), true); m_apiToken = response.value("params").toMap().value("token").toByteArray(); @@ -373,6 +421,7 @@ void TestJSONRPC::testInitialSetup() // Now do a Version call with the valid token and it should work spy.clear(); + qCDebug(dcTests()) << "Calling Version, expecting success"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Version\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -380,15 +429,19 @@ void TestJSONRPC::testInitialSetup() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests()) << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); } void TestJSONRPC::testRevokeToken() { - QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + QSignalSpy spy(m_mockTcpServer, &MockTcpServer::outgoingData); QVERIFY(spy.isValid()); + QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected); + QVERIFY(disconnectedSpy.isValid()); + QSignalSpy connectedSpy(m_mockTcpServer, &MockTcpServer::clientConnected); + QVERIFY(connectedSpy.isValid()); // Now get all the tokens spy.clear(); @@ -399,7 +452,7 @@ void TestJSONRPC::testRevokeToken() QVERIFY(spy.count() == 1); QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); QVariantMap response = jsonDoc.toVariant().toMap(); - qWarning() << "Getting existing Tokens" << response.value("status").toString() << response; + qCDebug(dcTests()) << "Getting existing Tokens" << response.value("status").toString() << response; QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QVariantList tokenList = response.value("params").toMap().value("tokenInfoList").toList(); QCOMPARE(tokenList.count(), 1); @@ -414,7 +467,7 @@ void TestJSONRPC::testRevokeToken() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Authenticate with valid credentials:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); + qCDebug(dcTests()) << "Calling Authenticate with valid credentials:" << response.value("params").toMap().value("success").toString() << response.value("params").toMap().value("token").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); QCOMPARE(response.value("params").toMap().value("success").toBool(), true); QByteArray newToken = response.value("params").toMap().value("token").toByteArray(); @@ -429,7 +482,7 @@ void TestJSONRPC::testRevokeToken() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests()) << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); // Now get all the tokens using the old token @@ -441,7 +494,7 @@ void TestJSONRPC::testRevokeToken() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Tokens" << response.value("status").toString(); + qCDebug(dcTests()) << "Calling Tokens" << response.value("status").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("success")); tokenList = response.value("params").toMap().value("tokenInfoList").toList(); QCOMPARE(tokenList.count(), 2); @@ -464,11 +517,12 @@ void TestJSONRPC::testRevokeToken() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling RemoveToken" << response.value("status").toString() << response; + qCDebug(dcTests()) << "Calling RemoveToken" << response.value("status").toString() << response; QCOMPARE(response.value("status").toString(), QStringLiteral("success")); // Do a call with the now removed token, it should be forbidden spy.clear(); + disconnectedSpy.clear(); m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + newToken + "\", \"method\": \"JSONRPC.Version\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -476,8 +530,19 @@ void TestJSONRPC::testRevokeToken() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant().toMap(); - qWarning() << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString(); + qCDebug(dcTests()) << "Calling Version with valid token:" << response.value("status").toString() << response.value("error").toString(); QCOMPARE(response.value("status").toString(), QStringLiteral("unauthorized")); + + // And connection should drop + if (disconnectedSpy.count() == 0) disconnectedSpy.wait(); + QCOMPARE(disconnectedSpy.count(), 1); + + // Connect again to not impact subsequent tests... + connectedSpy.clear(); + emit m_mockTcpServer->clientConnected(m_clientId); + if (connectedSpy.count() == 0) connectedSpy.wait(); + QCOMPARE(connectedSpy.count(), 1); + injectAndWait("JSONRPC.Hello"); } void TestJSONRPC::testBasicCall_data() @@ -492,9 +557,9 @@ void TestJSONRPC::testBasicCall_data() QTest::newRow("missing id") << QByteArray("{\"method\":\"JSONRPC.Introspect\"}\n") << false << false; QTest::newRow("missing method") << QByteArray("{\"id\":42}\n") << true << false; QTest::newRow("borked") << QByteArray("{\"id\":42, \"method\":\"JSO}\n") << false << false; - QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"JSONRPC.Foobar\"}\n") << true << false; - QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"FOO.Introspect\"}\n") << true << false; - QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"JSONRPCIntrospect\"}\n") << true << false; + QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"JSONRPC.Foobar\", \"token\": \"" + m_apiToken + "\"}\n") << true << false; + QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"FOO.Introspect\", \"token\": \"" + m_apiToken + "\"}\n") << true << false; + QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"JSONRPCIntrospect\", \"token\": \"" + m_apiToken + "\"}\n") << true << false; QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"JSONRPC.Introspect\", \"params\":{\"törööö\":\"chooo-chooo\"}}\n") << true << false; } @@ -594,7 +659,7 @@ void TestJSONRPC::deviceAddedRemovedNotifications() params.insert("name", "Mock device"); params.insert("deviceParams", deviceParams); QVariant response = injectAndWait("Devices.AddConfiguredDevice", params); - clientSpy.wait(2000); + if (clientSpy.count() == 0) clientSpy.wait(); verifyDeviceError(response); QVariantMap notificationDeviceMap = checkNotification(clientSpy, "Devices.DeviceAdded").toMap().value("params").toMap().value("device").toMap(); @@ -614,7 +679,7 @@ void TestJSONRPC::deviceAddedRemovedNotifications() params.clear(); response.clear(); clientSpy.clear(); params.insert("deviceId", deviceId); response = injectAndWait("Devices.RemoveConfiguredDevice", params); - clientSpy.wait(2000); + if (clientSpy.count() == 0) clientSpy.wait(); verifyDeviceError(response); checkNotification(clientSpy, "Devices.DeviceRemoved"); @@ -659,7 +724,7 @@ void TestJSONRPC::ruleAddedRemovedNotifications() params.insert("stateEvaluator", stateEvaluator); QVariant response = injectAndWait("Rules.AddRule", params); - clientSpy.wait(2000); + if (clientSpy.count() == 0) clientSpy.wait(); QVariantMap notificationRuleMap = checkNotification(clientSpy, "Rules.RuleAdded").toMap().value("params").toMap().value("rule").toMap(); verifyRuleError(response); @@ -678,7 +743,7 @@ void TestJSONRPC::ruleAddedRemovedNotifications() params.clear(); response.clear(); clientSpy.clear(); params.insert("ruleId", ruleId); response = injectAndWait("Rules.RemoveRule", params); - clientSpy.wait(2000); + if (clientSpy.count() == 0) clientSpy.wait(); checkNotification(clientSpy, "Devices.DeviceRemoved"); verifyRuleError(response); @@ -719,7 +784,7 @@ void TestJSONRPC::ruleActiveChangedNotifications() QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); response = injectAndWait("Rules.AddRule", params); - clientSpy.wait(); + if (clientSpy.count() == 0) clientSpy.wait(); QVariant notificationVariant = checkNotification(clientSpy, "Rules.RuleAdded"); verifyRuleError(response); @@ -743,7 +808,7 @@ void TestJSONRPC::ruleActiveChangedNotifications() QNetworkReply *reply = nam.get(request); connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); - spy.wait(); + if (spy.count() == 0) spy.wait(); notificationVariant = checkNotification(clientSpy, "Rules.RuleActiveChanged"); verifyRuleError(response); @@ -756,12 +821,12 @@ void TestJSONRPC::ruleActiveChangedNotifications() qDebug() << "setting mock int state to 42"; QNetworkRequest request2(QUrl(QString("http://localhost:%1/setstate?%2=%3").arg(m_mockDevice1Port).arg(mockIntStateId.toString()).arg(42))); QNetworkReply *reply2 = nam.get(request2); - spy.wait(); + if (spy.count() == 0) spy.wait(); QCOMPARE(spy.count(), 1); connect(reply2, SIGNAL(finished()), reply2, SLOT(deleteLater())); - clientSpy.wait(); + if (clientSpy.count() == 0) clientSpy.wait(); notificationVariant = checkNotification(clientSpy, "Rules.RuleActiveChanged"); verifyRuleError(response); @@ -773,7 +838,7 @@ void TestJSONRPC::ruleActiveChangedNotifications() params.insert("ruleId", ruleId); response = injectAndWait("Rules.RemoveRule", params); - clientSpy.wait(); + if (clientSpy.count() == 0) clientSpy.wait(); notificationVariant = checkNotification(clientSpy, "Rules.RuleRemoved"); checkNotification(clientSpy, "Logging.LogDatabaseUpdated"); verifyRuleError(response); @@ -807,7 +872,7 @@ void TestJSONRPC::deviceChangedNotifications() response = injectAndWait("Devices.AddConfiguredDevice", params); DeviceId deviceId = DeviceId(response.toMap().value("params").toMap().value("deviceId").toString()); QVERIFY(!deviceId.isNull()); - clientSpy.wait(); + if (clientSpy.count() == 0) clientSpy.wait(); verifyDeviceError(response); QVariantMap notificationDeviceMap = checkNotification(clientSpy, "Devices.DeviceAdded").toMap().value("params").toMap().value("device").toMap(); @@ -831,7 +896,7 @@ void TestJSONRPC::deviceChangedNotifications() params.insert("deviceId", deviceId); params.insert("deviceParams", newDeviceParams); response = injectAndWait("Devices.ReconfigureDevice", params); - clientSpy.wait(2000); + if (clientSpy.count() == 0) clientSpy.wait(); verifyDeviceError(response); QVariantMap reconfigureDeviceNotificationMap = checkNotification(clientSpy, "Devices.DeviceChanged").toMap().value("params").toMap().value("device").toMap(); QCOMPARE(reconfigureDeviceNotificationMap.value("deviceClassId").toString(), mockDeviceClassId.toString()); @@ -848,7 +913,7 @@ void TestJSONRPC::deviceChangedNotifications() params.insert("deviceId", deviceId); params.insert("name", deviceName); response = injectAndWait("Devices.EditDevice", params); - clientSpy.wait(2000); + if (clientSpy.count() == 0) clientSpy.wait(); verifyDeviceError(response); QVariantMap editDeviceNotificationMap = checkNotification(clientSpy, "Devices.DeviceChanged").toMap().value("params").toMap().value("device").toMap(); QCOMPARE(editDeviceNotificationMap.value("deviceClassId").toString(), mockDeviceClassId.toString()); @@ -860,7 +925,7 @@ void TestJSONRPC::deviceChangedNotifications() params.clear(); response.clear(); clientSpy.clear(); params.insert("deviceId", deviceId); response = injectAndWait("Devices.RemoveConfiguredDevice", params); - clientSpy.wait(); + if (clientSpy.count() == 0) clientSpy.wait(); verifyDeviceError(response); checkNotification(clientSpy, "Devices.DeviceRemoved"); checkNotification(clientSpy, "Logging.LogDatabaseUpdated"); @@ -882,7 +947,7 @@ void TestJSONRPC::stateChangeEmitsNotifications() QNetworkReply *reply = nam.get(request); connect(reply, SIGNAL(finished()), reply, SLOT(deleteLater())); QSignalSpy replySpy(reply, SIGNAL(finished())); - replySpy.wait(); + if (replySpy.count() == 0) replySpy.wait(); // Make sure the notification contains all the stuff we expect QVariantList stateChangedVariants = checkNotifications(clientSpy, "Devices.StateChanged"); @@ -1001,9 +1066,7 @@ void TestJSONRPC::testPushButtonAuth() pushButtonAgent.sendButtonPressed(); - if (clientSpy.count() == 0) { - clientSpy.wait(); - } + if (clientSpy.count() == 0) clientSpy.wait(); QVariantMap rsp = checkNotification(clientSpy, "JSONRPC.PushButtonAuthFinished").toMap(); QCOMPARE(rsp.value("params").toMap().value("transactionId").toInt(), transactionId); @@ -1023,6 +1086,9 @@ void TestJSONRPC::testPushButtonAuthInterrupt() // Create a new clientId for mallory and connect it to the server QUuid malloryId = QUuid::createUuid(); m_mockTcpServer->clientConnected(malloryId); + QSignalSpy responseSpy(m_mockTcpServer, &MockTcpServer::outgoingData); + m_mockTcpServer->injectData(malloryId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (responseSpy.count() == 0) responseSpy.wait(); // Snoop in on everything the TCP server sends to its clients. QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); @@ -1120,11 +1186,13 @@ void TestJSONRPC::testPushButtonAuthConnectionDrop() pushButtonAgent.init(); // Snoop in on everything the TCP server sends to its clients. - QSignalSpy clientSpy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + QSignalSpy clientSpy(m_mockTcpServer, &MockTcpServer::outgoingData); // Create a new clientId for alice and connect it to the server QUuid aliceId = QUuid::createUuid(); m_mockTcpServer->clientConnected(aliceId); + m_mockTcpServer->injectData(aliceId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (clientSpy.count() == 0) clientSpy.wait(); // request push button auth for client 1 (alice) and check for OK reply QVariantMap params; @@ -1139,6 +1207,9 @@ void TestJSONRPC::testPushButtonAuthConnectionDrop() // Create a new clientId for bob and connect it to the server QUuid bobId = QUuid::createUuid(); m_mockTcpServer->clientConnected(bobId); + clientSpy.clear(); + m_mockTcpServer->injectData(bobId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (clientSpy.count() == 0) clientSpy.wait(); // request push button auth for client 2 (bob) and check for OK reply params.clear(); @@ -1175,7 +1246,7 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth() NymeaCore::instance()->userManager()->removeUser(""); QVERIFY(NymeaCore::instance()->userManager()->initRequired()); - QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray))); + QSignalSpy spy(m_mockTcpServer, &MockTcpServer::outgoingData); QVERIFY(spy.isValid()); PushButtonAgent pushButtonAgent; @@ -1183,6 +1254,7 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth() // Hello call should work in any case, telling us initial setup is required spy.clear(); + qCDebug(dcTests()) << "Calling Hello on uninitialized instance"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Hello\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -1190,13 +1262,16 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth() QVERIFY(spy.count() == 1); QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); QVariant response = jsonDoc.toVariant(); - qWarning() << "Calling Hello on uninitialized instance:" << response.toMap().value("status").toString() << response.toMap().value("error").toString(); + qCDebug(dcTests()) << "Result:" << response.toMap().value("status").toString() << response.toMap().value("error").toString(); QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("success")); QCOMPARE(response.toMap().value("params").toMap().value("initialSetupRequired").toBool(), true); // request push button auth for alice and check for OK reply QUuid aliceId = QUuid::createUuid(); m_mockTcpServer->clientConnected(aliceId); + spy.clear(); + m_mockTcpServer->injectData(aliceId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (spy.count() == 0) spy.wait(); QVariantMap params; params.insert("deviceName", "alice"); @@ -1223,6 +1298,7 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth() // initialSetupRequired should be false in Hello call now spy.clear(); + qCDebug(dcTests()) << "Calling Hello on uninitialized instance"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Hello\"}\n"); if (spy.count() == 0) { spy.wait(); @@ -1230,13 +1306,15 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant(); - qWarning() << "Calling Hello on uninitialized instance:" << response.toMap().value("status").toString() << response.toMap().value("error").toString(); + qCDebug(dcTests()) << "Result:" << response.toMap().value("status").toString() << response.toMap().value("error").toString(); QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("success")); QCOMPARE(response.toMap().value("params").toMap().value("initialSetupRequired").toBool(), false); // CreateUser without a token should fail now even though there are 0 users in the DB spy.clear(); + QSignalSpy disconnectedSpy(m_mockTcpServer, &MockTcpServer::clientDisconnected); + qCDebug(dcTests()) << "Calling CreateUser on uninitialized instance"; m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"Dummy@guh.io\", \"password\": \"DummyPW1!\"}}\n"); if (spy.count() == 0) { spy.wait(); @@ -1244,10 +1322,19 @@ void TestJSONRPC::testInitialSetupWithPushButtonAuth() QVERIFY(spy.count() == 1); jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray()); response = jsonDoc.toVariant(); - qWarning() << "Calling CreateUser on uninitialized instance:" << response.toMap().value("status").toString() << response.toMap().value("error").toString(); + qCDebug(dcTests()) << "Result:" << response.toMap().value("status").toString() << response.toMap().value("error").toString(); QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("unauthorized")); QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 0); + // Connection should drop + if (disconnectedSpy.isEmpty()) disconnectedSpy.wait(); + QCOMPARE(disconnectedSpy.count(), 1); + + // Reconnect to not impact subsequent tests + m_mockTcpServer->clientConnected(m_clientId); + spy.clear(); + m_mockTcpServer->injectData(m_clientId, "{\"id\": 0, \"method\": \"JSONRPC.Hello\"}"); + if (spy.isEmpty()) spy.wait(); } void TestJSONRPC::testDataFragmentation_data() @@ -1302,12 +1389,10 @@ void TestJSONRPC::testGarbageData() for (int i = 0; i < 1024; i++) { data.append("a"); } - for (int i = 0; i < 11; i ++) { + for (int i = 0; i < 11 && spy.count() == 0; i ++) { m_mockTcpServer->injectData(m_clientId, data); } - QCOMPARE(spy.count(), 1); - } #include "testjsonrpc.moc" diff --git a/tests/auto/nymeatestbase.cpp b/tests/auto/nymeatestbase.cpp index 8064b733..1fbdf188 100644 --- a/tests/auto/nymeatestbase.cpp +++ b/tests/auto/nymeatestbase.cpp @@ -143,9 +143,11 @@ void NymeaTestBase::initTestCase() m_clientId = QUuid::createUuid(); m_mockTcpServer->clientConnected(m_clientId); + QVariant response = injectAndWait("JSONRPC.Hello"); + createMockDevice(); - QVariant response = injectAndWait("Devices.GetConfiguredDevices", {}); + response = injectAndWait("Devices.GetConfiguredDevices", {}); foreach (const QVariant &device, response.toMap().value("params").toMap().value("devices").toList()) { if (device.toMap().value("deviceClassId").toUuid() == mockDeviceAutoClassId) { m_mockDeviceAutoId = DeviceId(device.toMap().value("id").toString()); @@ -442,6 +444,8 @@ void NymeaTestBase::restartServer() coreSpy.wait(); m_mockTcpServer = MockTcpServer::servers().first(); m_mockTcpServer->clientConnected(m_clientId); + + injectAndWait("JSONRPC.Hello"); } void NymeaTestBase::clearLoggingDatabase() diff --git a/tests/auto/versioning/testversioning.cpp b/tests/auto/versioning/testversioning.cpp index 0a0b0ec9..3f96eab1 100644 --- a/tests/auto/versioning/testversioning.cpp +++ b/tests/auto/versioning/testversioning.cpp @@ -91,7 +91,7 @@ void TestVersioning::apiChangeBumpsVersion() p.waitForFinished(); QByteArray apiDiff = p.readAll(); - qDebug() << "API Differences:" << endl << qUtf8Printable(apiDiff); + qCDebug(dcTests()) << "API Differences:" << endl << qUtf8Printable(apiDiff); if (oldVersion == newVersionStripped && oldApi != newApi) { QVERIFY2(false, "JSONRPC API has changed but version is still the same. You need to bump the API version."); diff --git a/tests/auto/websocketserver/testwebsocketserver.cpp b/tests/auto/websocketserver/testwebsocketserver.cpp index 8b773185..3fd3403c 100644 --- a/tests/auto/websocketserver/testwebsocketserver.cpp +++ b/tests/auto/websocketserver/testwebsocketserver.cpp @@ -81,8 +81,14 @@ void TestWebSocketServer::testHandshake() { QWebSocket *socket = new QWebSocket("nymea tests", QWebSocketProtocol::Version13); connect(socket, &QWebSocket::sslErrors, this, &TestWebSocketServer::sslErrors); - QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString))); + + QSignalSpy connectedSpy(socket, &QWebSocket::connected); socket->open(QUrl(QStringLiteral("wss://localhost:4444"))); + connectedSpy.wait(); + + QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString))); + socket->sendTextMessage("{\"id\":0, \"method\": \"JSONRPC.Hello\"}"); + spy.wait(); QVERIFY2(spy.count() > 0, "Did not get the handshake message upon connect."); QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().first().toByteArray()); @@ -90,8 +96,8 @@ void TestWebSocketServer::testHandshake() QString nymeaVersionString(NYMEA_VERSION_STRING); QString jsonProtocolVersionString(JSON_PROTOCOL_VERSION); - QCOMPARE(handShake.value("version").toString(), nymeaVersionString); - QCOMPARE(handShake.value("protocol version").toString(), jsonProtocolVersionString); + QCOMPARE(handShake.value("params").toMap().value("version").toString(), nymeaVersionString); + QCOMPARE(handShake.value("params").toMap().value("protocol version").toString(), jsonProtocolVersionString); socket->close(); socket->deleteLater(); @@ -173,6 +179,10 @@ QVariant TestWebSocketServer::injectSocketAndWait(const QString &method, const Q } QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString))); + socket->sendTextMessage("{\"id\":0, \"method\": \"JSONRPC.Hello\"}"); + spy.wait(); + + spy.clear(); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); spy.wait(); @@ -214,6 +224,11 @@ QVariant TestWebSocketServer::injectSocketData(const QByteArray &data) } QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString))); + + socket->sendTextMessage("{\"id\":0, \"method\": \"JSONRPC.Hello\"}"); + spy.wait(); + + spy.clear(); socket->sendTextMessage(QString(data)); spy.wait();