diff --git a/data/config/guhd.conf b/data/config/guhd.conf index 38c23999..4c1347f3 100644 --- a/data/config/guhd.conf +++ b/data/config/guhd.conf @@ -1,11 +1,13 @@ [JSONRPC] -port=12345 +port=1234 interfaces="lo","all" ip="IPv4", "IPv6" [Webserver] -port=3001 +port=3000 publicFolder=/usr/share/guh-webinterface/ +https=false + [GPIO] rf433rx=27 diff --git a/guh.pri b/guh.pri index 1c40da81..95849cda 100644 --- a/guh.pri +++ b/guh.pri @@ -28,9 +28,8 @@ enable433gpio { # check webserver support equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 3) { - DEFINES += WEBSERVER + DEFINES += WEBSOCKET } - top_srcdir=$$PWD top_builddir=$$shadowed($$PWD) diff --git a/guh.pro b/guh.pro index 4dad53fd..9a885c30 100644 --- a/guh.pro +++ b/guh.pro @@ -34,10 +34,10 @@ coverage { message("Building coverage.") } -contains(DEFINES, WEBSERVER){ - message("Building guh with webserver.") +contains(DEFINES, WEBSOCKET){ + message("Building guh with websocket.") } else { - message("Building guh without webserver.") + message("Building guh without websocket.") } contains(DEFINES, GPIO433){ diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 16e47c4a..6afbb074 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -1288,6 +1288,168 @@ QPair JsonTypes::validateBasicType(const QVariant &variant) return report(false, QString("Error validating basic type %1.").arg(variant.toString())); } +QVariant::Type JsonTypes::getActionParamType(const ActionTypeId &actionTypeId, const QString ¶mName) +{ + foreach (const DeviceClass &deviceClass, GuhCore::instance()->supportedDevices()) { + foreach (const ActionType &actionType, deviceClass.actionTypes()) { + if (actionType.id() == actionTypeId) { + foreach (const ParamType ¶mType, actionType.paramTypes()) { + if (paramType.name() == paramName) { + return paramType.type(); + } + } + } + } + } + return QVariant::Invalid; +} + +QVariant::Type JsonTypes::getEventParamType(const EventTypeId &eventTypeId, const QString ¶mName) +{ + foreach (const DeviceClass &deviceClass, GuhCore::instance()->supportedDevices()) { + foreach (const EventType &eventType, deviceClass.eventTypes()) { + if (eventType.id() == eventTypeId) { + foreach (const ParamType ¶mType, eventType.paramTypes()) { + // get ParamType of Event + if (paramType.name() == paramName) { + return paramType.type(); + } + } + } + } + } + return QVariant::Invalid; +} + +bool JsonTypes::checkEventDescriptors(const QList eventDescriptors, const EventTypeId &eventTypeId) +{ + foreach (const EventDescriptor eventDescriptor, eventDescriptors) { + if (eventDescriptor.eventTypeId() == eventTypeId) { + return true; + } + } + return false; +} + +RuleEngine::RuleError JsonTypes::verifyRuleConsistency(const QVariantMap ¶ms) +{ + // check if there are an eventDescriptor and an eventDescriptorList + if (params.contains("eventDescriptor") && params.contains("eventDescriptorList")) { + qCWarning(dcJsonRpc) << "Only one of eventDesciptor or eventDescriptorList may be used."; + return RuleEngine::RuleErrorInvalidParameter; + } + + // check if this rules is based on any event and contains exit actions + if (params.contains("eventDescriptor") || params.contains("eventDescriptorList")) { + if (params.contains("exitActions")) { + qCWarning(dcJsonRpc) << "The exitActions will never be executed if the rule contains an eventDescriptor."; + return RuleEngine::RuleErrorInvalidRuleFormat; + } + } + + // check if there are any actions + if (params.value("actions").toList().isEmpty()) { + qCWarning(dcJsonRpc) << "Rule actions missing. A rule without actions has no effect."; + return RuleEngine::RuleErrorMissingParameter; + } + + // TODO: check if events and stateEvaluators are missing + + return RuleEngine::RuleErrorNoError; +} + +QPair, RuleEngine::RuleError> JsonTypes::verifyEventDescriptors(const QVariantMap ¶ms) +{ + // Check and unpack eventDescriptors + QList eventDescriptorList = QList(); + if (params.contains("eventDescriptor")) { + QVariantMap eventMap = params.value("eventDescriptor").toMap(); + qCDebug(dcJsonRpc) << "unpacking eventDescriptor" << eventMap; + eventDescriptorList.append(JsonTypes::unpackEventDescriptor(eventMap)); + } else if (params.contains("eventDescriptorList")) { + QVariantList eventDescriptors = params.value("eventDescriptorList").toList(); + qCDebug(dcJsonRpc) << "unpacking eventDescriptorList:" << eventDescriptors; + foreach (const QVariant &eventVariant, eventDescriptors) { + QVariantMap eventMap = eventVariant.toMap(); + eventDescriptorList.append(JsonTypes::unpackEventDescriptor(eventMap)); + } + } + return QPair, RuleEngine::RuleError>(eventDescriptorList, RuleEngine::RuleErrorNoError); +} + +QPair, RuleEngine::RuleError> JsonTypes::verifyActions(const QVariantMap ¶ms, const QList &eventDescriptorList) +{ + QList actions; + QVariantList actionList = params.value("actions").toList(); + qCDebug(dcJsonRpc) << "unpacking actions:" << actionList; + foreach (const QVariant &actionVariant, actionList) { + QVariantMap actionMap = actionVariant.toMap(); + RuleAction action(ActionTypeId(actionMap.value("actionTypeId").toString()), DeviceId(actionMap.value("deviceId").toString())); + RuleActionParamList actionParamList = JsonTypes::unpackRuleActionParams(actionMap.value("ruleActionParams").toList()); + foreach (const RuleActionParam &ruleActionParam, actionParamList) { + if (!ruleActionParam.isValid()) { + qCWarning(dcJsonRpc) << "got an actionParam with value AND eventTypeId!"; + return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorInvalidRuleActionParameter); + } + } + qCDebug(dcJsonRpc) << "params in exitAction" << action.ruleActionParams(); + action.setRuleActionParams(actionParamList); + actions.append(action); + } + + // check possible eventTypeIds in params + foreach (const RuleAction &ruleAction, actions) { + if (ruleAction.isEventBased()) { + foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { + if (ruleActionParam.eventTypeId() != EventTypeId()) { + // We have an eventTypeId + if (eventDescriptorList.isEmpty()) { + qCWarning(dcJsonRpc) << "RuleAction" << ruleAction.actionTypeId() << "contains an eventTypeId, but there are no eventDescriptors."; + return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorInvalidRuleActionParameter); + } + // now check if this eventType is in the eventDescriptorList of this rule + if (!checkEventDescriptors(eventDescriptorList, ruleActionParam.eventTypeId())) { + qCWarning(dcJsonRpc) << "eventTypeId from RuleAction" << ruleAction.actionTypeId() << "missing in eventDescriptors."; + return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorInvalidRuleActionParameter); + } + + // check if the param type of the event and the action match + QVariant::Type eventParamType = getEventParamType(ruleActionParam.eventTypeId(), ruleActionParam.eventParamName()); + QVariant::Type actionParamType = getActionParamType(ruleAction.actionTypeId(), ruleActionParam.name()); + if (eventParamType != actionParamType) { + qCWarning(dcJsonRpc) << "RuleActionParam" << ruleActionParam.name() << " and given event param " << ruleActionParam.eventParamName() << "have not the same type:"; + qCWarning(dcJsonRpc) << " -> actionParamType:" << actionParamType; + qCWarning(dcJsonRpc) << " -> eventParamType:" << eventParamType; + return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorTypesNotMatching); + } + } + } + } + } + return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorNoError); +} + +QPair, RuleEngine::RuleError> JsonTypes::verifyExitActions(const QVariantMap ¶ms) +{ + QList exitActions; + if (params.contains("exitActions")) { + QVariantList exitActionList = params.value("exitActions").toList(); + qCDebug(dcJsonRpc) << "unpacking exitActions:" << exitActionList; + foreach (const QVariant &actionVariant, exitActionList) { + QVariantMap actionMap = actionVariant.toMap(); + RuleAction action(ActionTypeId(actionMap.value("actionTypeId").toString()), DeviceId(actionMap.value("deviceId").toString())); + if (action.isEventBased()) { + qCWarning(dcJsonRpc) << "got exitAction with a param value containing an eventTypeId!"; + return QPair, RuleEngine::RuleError>(exitActions, RuleEngine::RuleErrorInvalidRuleActionParameter); + } + qCDebug(dcJsonRpc) << "params in exitAction" << action.ruleActionParams(); + action.setRuleActionParams(JsonTypes::unpackRuleActionParams(actionMap.value("ruleActionParams").toList())); + exitActions.append(action); + } + } + return QPair, RuleEngine::RuleError>(exitActions, RuleEngine::RuleErrorNoError); +} + QPair JsonTypes::validateEnum(const QVariantList &enumDescription, const QVariant &value) { QStringList enumStrings; diff --git a/server/jsonrpc/jsontypes.h b/server/jsonrpc/jsontypes.h index 1ff795ef..1688a570 100644 --- a/server/jsonrpc/jsontypes.h +++ b/server/jsonrpc/jsontypes.h @@ -197,6 +197,15 @@ public: static QPair validateEnum(const QVariantList &enumList, const QVariant &value); static QPair validateBasicType(const QVariant &variant); + // rule validation helper methods + static bool checkEventDescriptors(const QList eventDescriptors, const EventTypeId &eventTypeId); + static QVariant::Type getActionParamType(const ActionTypeId &actionTypeId, const QString ¶mName); + static QVariant::Type getEventParamType(const EventTypeId &eventTypeId, const QString ¶mName); + static RuleEngine::RuleError verifyRuleConsistency(const QVariantMap ¶ms); + static QPair, RuleEngine::RuleError> verifyEventDescriptors(const QVariantMap ¶ms); + static QPair, RuleEngine::RuleError> verifyActions(const QVariantMap ¶ms, const QList &eventDescriptorList); + static QPair, RuleEngine::RuleError> verifyExitActions(const QVariantMap ¶ms); + private: static bool s_initialized; static void init(); diff --git a/server/jsonrpc/ruleshandler.cpp b/server/jsonrpc/ruleshandler.cpp index fd096fd4..49d671fb 100644 --- a/server/jsonrpc/ruleshandler.cpp +++ b/server/jsonrpc/ruleshandler.cpp @@ -175,7 +175,7 @@ JsonReply *RulesHandler::GetRuleDetails(const QVariantMap ¶ms) JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) { // check rule consistency - RuleEngine::RuleError ruleConsistencyError = verifyRuleConsistency(params); + RuleEngine::RuleError ruleConsistencyError = JsonTypes::verifyRuleConsistency(params); if (ruleConsistencyError != RuleEngine::RuleErrorNoError) { QVariantMap returns; returns.insert("ruleError", JsonTypes::ruleErrorToString(ruleConsistencyError)); @@ -183,7 +183,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) } // Check and upack eventDescriptorList - QPair, RuleEngine::RuleError> eventDescriptorVerification = verifyEventDescriptors(params); + QPair, RuleEngine::RuleError> eventDescriptorVerification = JsonTypes::verifyEventDescriptors(params); QList eventDescriptorList = eventDescriptorVerification.first; if (eventDescriptorVerification.second != RuleEngine::RuleErrorNoError) { QVariantMap returns; @@ -191,13 +191,12 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) return createReply(returns); } - // Check and unpack stateEvaluator qCDebug(dcJsonRpc) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap(); StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap()); // Check and unpack actions - QPair, RuleEngine::RuleError> actionsVerification = verifyActions(params, eventDescriptorList); + QPair, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList); QList actions = actionsVerification.first; if (actionsVerification.second != RuleEngine::RuleErrorNoError) { QVariantMap returns; @@ -206,7 +205,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) } // Check and unpack exitActions - QPair, RuleEngine::RuleError> exitActionsVerification = verifyExitActions(params); + QPair, RuleEngine::RuleError> exitActionsVerification = JsonTypes::verifyExitActions(params); QList exitActions = exitActionsVerification.first; if (exitActionsVerification.second != RuleEngine::RuleErrorNoError) { QVariantMap returns; @@ -230,7 +229,7 @@ JsonReply* RulesHandler::AddRule(const QVariantMap ¶ms) JsonReply *RulesHandler::EditRule(const QVariantMap ¶ms) { // check rule consistency - RuleEngine::RuleError ruleConsistencyError = verifyRuleConsistency(params); + RuleEngine::RuleError ruleConsistencyError = JsonTypes::verifyRuleConsistency(params); if (ruleConsistencyError != RuleEngine::RuleErrorNoError) { QVariantMap returns; returns.insert("ruleError", JsonTypes::ruleErrorToString(ruleConsistencyError)); @@ -238,7 +237,7 @@ JsonReply *RulesHandler::EditRule(const QVariantMap ¶ms) } // Check and upack eventDescriptorList - QPair, RuleEngine::RuleError> eventDescriptorVerification = verifyEventDescriptors(params); + QPair, RuleEngine::RuleError> eventDescriptorVerification = JsonTypes::verifyEventDescriptors(params); QList eventDescriptorList = eventDescriptorVerification.first; if (eventDescriptorVerification.second != RuleEngine::RuleErrorNoError) { QVariantMap returns; @@ -251,7 +250,7 @@ JsonReply *RulesHandler::EditRule(const QVariantMap ¶ms) StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap()); // Check and unpack actions - QPair, RuleEngine::RuleError> actionsVerification = verifyActions(params, eventDescriptorList); + QPair, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList); QList actions = actionsVerification.first; if (actionsVerification.second != RuleEngine::RuleErrorNoError) { QVariantMap returns; @@ -260,7 +259,7 @@ JsonReply *RulesHandler::EditRule(const QVariantMap ¶ms) } // Check and unpack exitActions - QPair, RuleEngine::RuleError> exitActionsVerification = verifyExitActions(params); + QPair, RuleEngine::RuleError> exitActionsVerification = JsonTypes::verifyExitActions(params); QList exitActions = exitActionsVerification.first; if (exitActionsVerification.second != RuleEngine::RuleErrorNoError) { QVariantMap returns; @@ -315,168 +314,6 @@ JsonReply *RulesHandler::DisableRule(const QVariantMap ¶ms) return createReply(statusToReply(GuhCore::instance()->disableRule(RuleId(params.value("ruleId").toString())))); } -QVariant::Type RulesHandler::getActionParamType(const ActionTypeId &actionTypeId, const QString ¶mName) -{ - foreach (const DeviceClass &deviceClass, GuhCore::instance()->supportedDevices()) { - foreach (const ActionType &actionType, deviceClass.actionTypes()) { - if (actionType.id() == actionTypeId) { - foreach (const ParamType ¶mType, actionType.paramTypes()) { - if (paramType.name() == paramName) { - return paramType.type(); - } - } - } - } - } - return QVariant::Invalid; -} - -QVariant::Type RulesHandler::getEventParamType(const EventTypeId &eventTypeId, const QString ¶mName) -{ - foreach (const DeviceClass &deviceClass, GuhCore::instance()->supportedDevices()) { - foreach (const EventType &eventType, deviceClass.eventTypes()) { - if (eventType.id() == eventTypeId) { - foreach (const ParamType ¶mType, eventType.paramTypes()) { - // get ParamType of Event - if (paramType.name() == paramName) { - return paramType.type(); - } - } - } - } - } - return QVariant::Invalid; -} - -bool RulesHandler::checkEventDescriptors(const QList eventDescriptors, const EventTypeId &eventTypeId) -{ - foreach (const EventDescriptor eventDescriptor, eventDescriptors) { - if (eventDescriptor.eventTypeId() == eventTypeId) { - return true; - } - } - return false; -} - -RuleEngine::RuleError RulesHandler::verifyRuleConsistency(const QVariantMap ¶ms) -{ - // check if there are an eventDescriptor and an eventDescriptorList - if (params.contains("eventDescriptor") && params.contains("eventDescriptorList")) { - qCWarning(dcJsonRpc) << "Only one of eventDesciptor or eventDescriptorList may be used."; - return RuleEngine::RuleErrorInvalidParameter; - } - - // check if this rules is based on any event and contains exit actions - if (params.contains("eventDescriptor") || params.contains("eventDescriptorList")) { - if (params.contains("exitActions")) { - qCWarning(dcJsonRpc) << "The exitActions will never be executed if the rule contains an eventDescriptor."; - return RuleEngine::RuleErrorInvalidRuleFormat; - } - } - - // check if there are any actions - if (params.value("actions").toList().isEmpty()) { - qCWarning(dcJsonRpc) << "Rule actions missing. A rule without actions has no effect."; - return RuleEngine::RuleErrorMissingParameter; - } - - // TODO: check if events and stateEvaluators are missing - - return RuleEngine::RuleErrorNoError; -} - -QPair, RuleEngine::RuleError> RulesHandler::verifyEventDescriptors(const QVariantMap ¶ms) -{ - // Check and unpack eventDescriptors - QList eventDescriptorList = QList(); - if (params.contains("eventDescriptor")) { - QVariantMap eventMap = params.value("eventDescriptor").toMap(); - qCDebug(dcJsonRpc) << "unpacking eventDescriptor" << eventMap; - eventDescriptorList.append(JsonTypes::unpackEventDescriptor(eventMap)); - } else if (params.contains("eventDescriptorList")) { - QVariantList eventDescriptors = params.value("eventDescriptorList").toList(); - qCDebug(dcJsonRpc) << "unpacking eventDescriptorList:" << eventDescriptors; - foreach (const QVariant &eventVariant, eventDescriptors) { - QVariantMap eventMap = eventVariant.toMap(); - eventDescriptorList.append(JsonTypes::unpackEventDescriptor(eventMap)); - } - } - return QPair, RuleEngine::RuleError>(eventDescriptorList, RuleEngine::RuleErrorNoError); -} - -QPair, RuleEngine::RuleError> RulesHandler::verifyActions(const QVariantMap ¶ms, const QList &eventDescriptorList) -{ - QList actions; - QVariantList actionList = params.value("actions").toList(); - qCDebug(dcJsonRpc) << "unpacking actions:" << actionList; - foreach (const QVariant &actionVariant, actionList) { - QVariantMap actionMap = actionVariant.toMap(); - RuleAction action(ActionTypeId(actionMap.value("actionTypeId").toString()), DeviceId(actionMap.value("deviceId").toString())); - RuleActionParamList actionParamList = JsonTypes::unpackRuleActionParams(actionMap.value("ruleActionParams").toList()); - foreach (const RuleActionParam &ruleActionParam, actionParamList) { - if (!ruleActionParam.isValid()) { - qCWarning(dcJsonRpc) << "got an actionParam with value AND eventTypeId!"; - return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorInvalidRuleActionParameter); - } - } - qCDebug(dcJsonRpc) << "params in exitAction" << action.ruleActionParams(); - action.setRuleActionParams(actionParamList); - actions.append(action); - } - - // check possible eventTypeIds in params - foreach (const RuleAction &ruleAction, actions) { - if (ruleAction.isEventBased()) { - foreach (const RuleActionParam &ruleActionParam, ruleAction.ruleActionParams()) { - if (ruleActionParam.eventTypeId() != EventTypeId()) { - // We have an eventTypeId - if (eventDescriptorList.isEmpty()) { - qCWarning(dcJsonRpc) << "RuleAction" << ruleAction.actionTypeId() << "contains an eventTypeId, but there are no eventDescriptors."; - return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorInvalidRuleActionParameter); - } - // now check if this eventType is in the eventDescriptorList of this rule - if (!checkEventDescriptors(eventDescriptorList, ruleActionParam.eventTypeId())) { - qCWarning(dcJsonRpc) << "eventTypeId from RuleAction" << ruleAction.actionTypeId() << "missing in eventDescriptors."; - return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorInvalidRuleActionParameter); - } - - // check if the param type of the event and the action match - QVariant::Type eventParamType = getEventParamType(ruleActionParam.eventTypeId(), ruleActionParam.eventParamName()); - QVariant::Type actionParamType = getActionParamType(ruleAction.actionTypeId(), ruleActionParam.name()); - if (eventParamType != actionParamType) { - qCWarning(dcJsonRpc) << "RuleActionParam" << ruleActionParam.name() << " and given event param " << ruleActionParam.eventParamName() << "have not the same type:"; - qCWarning(dcJsonRpc) << " -> actionParamType:" << actionParamType; - qCWarning(dcJsonRpc) << " -> eventParamType:" << eventParamType; - return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorTypesNotMatching); - } - } - } - } - } - return QPair, RuleEngine::RuleError>(actions, RuleEngine::RuleErrorNoError); -} - -QPair, RuleEngine::RuleError> RulesHandler::verifyExitActions(const QVariantMap ¶ms) -{ - QList exitActions; - if (params.contains("exitActions")) { - QVariantList exitActionList = params.value("exitActions").toList(); - qCDebug(dcJsonRpc) << "unpacking exitActions:" << exitActionList; - foreach (const QVariant &actionVariant, exitActionList) { - QVariantMap actionMap = actionVariant.toMap(); - RuleAction action(ActionTypeId(actionMap.value("actionTypeId").toString()), DeviceId(actionMap.value("deviceId").toString())); - if (action.isEventBased()) { - qCWarning(dcJsonRpc) << "got exitAction with a param value containing an eventTypeId!"; - return QPair, RuleEngine::RuleError>(exitActions, RuleEngine::RuleErrorInvalidRuleActionParameter); - } - qCDebug(dcJsonRpc) << "params in exitAction" << action.ruleActionParams(); - action.setRuleActionParams(JsonTypes::unpackRuleActionParams(actionMap.value("ruleActionParams").toList())); - exitActions.append(action); - } - } - return QPair, RuleEngine::RuleError>(exitActions, RuleEngine::RuleErrorNoError); -} - void RulesHandler::ruleRemovedNotification(const RuleId &ruleId) { QVariantMap params; diff --git a/server/jsonrpc/ruleshandler.h b/server/jsonrpc/ruleshandler.h index e14cd4b5..f97eee51 100644 --- a/server/jsonrpc/ruleshandler.h +++ b/server/jsonrpc/ruleshandler.h @@ -51,17 +51,6 @@ signals: void RuleActiveChanged(const QVariantMap ¶ms); void RuleConfigurationChanged(const QVariantMap ¶ms); -private: - QVariant::Type getActionParamType(const ActionTypeId &actionTypeId, const QString ¶mName); - QVariant::Type getEventParamType(const EventTypeId &eventTypeId, const QString ¶mName); - - bool checkEventDescriptors(const QList eventDescriptors, const EventTypeId &eventTypeId); - - RuleEngine::RuleError verifyRuleConsistency(const QVariantMap ¶ms); - QPair, RuleEngine::RuleError> verifyEventDescriptors(const QVariantMap ¶ms); - QPair, RuleEngine::RuleError> verifyActions(const QVariantMap ¶ms, const QList &eventDescriptorList); - QPair, RuleEngine::RuleError> verifyExitActions(const QVariantMap ¶ms); - private slots: void ruleRemovedNotification(const RuleId &ruleId); void ruleAddedNotification(const Rule &rule); diff --git a/server/rest/devicesresource.cpp b/server/rest/devicesresource.cpp index cd62a4fb..dc69d5f3 100644 --- a/server/rest/devicesresource.cpp +++ b/server/rest/devicesresource.cpp @@ -45,7 +45,6 @@ HttpReply *DevicesResource::proccessRequest(const HttpRequest &request, const QS { m_device = 0; - // get the main resource if (urlTokens.count() >= 4) { DeviceId deviceId = DeviceId(urlTokens.at(3)); diff --git a/server/rest/rulesresource.cpp b/server/rest/rulesresource.cpp index 3958ab09..bb0ff188 100644 --- a/server/rest/rulesresource.cpp +++ b/server/rest/rulesresource.cpp @@ -62,6 +62,9 @@ HttpReply *RulesResource::proccessRequest(const HttpRequest &request, const QStr case HttpRequest::Put: reply = proccessPutRequest(request, urlTokens); break; + case HttpRequest::Post: + reply = proccessPostRequest(request, urlTokens); + break; case HttpRequest::Delete: reply = proccessDeleteRequest(request, urlTokens); break; @@ -129,6 +132,14 @@ HttpReply *RulesResource::proccessPostRequest(const HttpRequest &request, const if (urlTokens.count() == 3) return addRule(request.payload()); + // POST /api/v1/rules/{ruleId}/enable + if (urlTokens.count() == 5 && urlTokens.at(4) == "enable") + return enableRule(m_ruleId); + + // POST /api/v1/rules/{ruleId}/disable + if (urlTokens.count() == 5 && urlTokens.at(4) == "disable") + return disableRule(m_ruleId); + return createErrorReply(HttpReply::NotImplemented); } @@ -179,19 +190,130 @@ HttpReply *RulesResource::removeRule(const RuleId &ruleId) const HttpReply *RulesResource::addRule(const QByteArray &payload) const { - Q_UNUSED(payload) qCDebug(dcRest) << "Add new rule"; + QPair verification = RestResource::verifyPayload(payload); + if (!verification.first) + return createErrorReply(HttpReply::BadRequest); - return createErrorReply(HttpReply::NotImplemented); + QVariantMap params = verification.second.toMap(); + + // check rule consistency + RuleEngine::RuleError ruleConsistencyError = JsonTypes::verifyRuleConsistency(params); + if (ruleConsistencyError != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + // Check and upack eventDescriptorList + QPair, RuleEngine::RuleError> eventDescriptorVerification = JsonTypes::verifyEventDescriptors(params); + QList eventDescriptorList = eventDescriptorVerification.first; + if (eventDescriptorVerification.second != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + // Check and unpack stateEvaluator + qCDebug(dcRest) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap(); + StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap()); + + // Check and unpack actions + QPair, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList); + QList actions = actionsVerification.first; + if (actionsVerification.second != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + + // Check and unpack exitActions + QPair, RuleEngine::RuleError> exitActionsVerification = JsonTypes::verifyExitActions(params); + QList exitActions = exitActionsVerification.first; + if (exitActionsVerification.second != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + QString name = params.value("name", QString()).toString(); + bool enabled = params.value("enabled", true).toBool(); + + RuleId newRuleId = RuleId::createRuleId(); + RuleEngine::RuleError status = GuhCore::instance()->addRule(newRuleId, name, eventDescriptorList, stateEvaluator, actions, exitActions, enabled); + + if (status == RuleEngine::RuleErrorNoError) { + QVariantMap returns; + returns.insert("ruleId", newRuleId.toString()); + HttpReply *reply = createSuccessReply(); + reply->setPayload(QJsonDocument::fromVariant(returns).toJson()); + return reply; + } + + return createErrorReply(HttpReply::BadRequest); +} + +HttpReply *RulesResource::enableRule(const RuleId &ruleId) const +{ + qCDebug(dcRest) << "Enable rule with id" << ruleId.toString(); + + RuleEngine::RuleError status = GuhCore::instance()->enableRule(ruleId); + if (status == RuleEngine::RuleErrorNoError) + return createSuccessReply(); + + return createErrorReply(HttpReply::BadRequest); +} + +HttpReply *RulesResource::disableRule(const RuleId &ruleId) const +{ + qCDebug(dcRest) << "Disable rule with id" << ruleId.toString(); + + RuleEngine::RuleError status = GuhCore::instance()->disableRule(ruleId); + if (status == RuleEngine::RuleErrorNoError) + return createSuccessReply(); + + return createErrorReply(HttpReply::BadRequest); } HttpReply *RulesResource::editRule(const RuleId &ruleId, const QByteArray &payload) const { - Q_UNUSED(payload) - qCDebug(dcRest) << "Edit rule with id" << ruleId; + qCDebug(dcRest) << "Edit rule with id" << ruleId.toString(); - return createErrorReply(HttpReply::NotImplemented); + QPair verification = RestResource::verifyPayload(payload); + if (!verification.first) + return createErrorReply(HttpReply::BadRequest); + + QVariantMap params = verification.second.toMap(); + + // check rule consistency + RuleEngine::RuleError ruleConsistencyError = JsonTypes::verifyRuleConsistency(params); + if (ruleConsistencyError != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + // Check and upack eventDescriptorList + QPair, RuleEngine::RuleError> eventDescriptorVerification = JsonTypes::verifyEventDescriptors(params); + QList eventDescriptorList = eventDescriptorVerification.first; + if (eventDescriptorVerification.second != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + // Check and unpack stateEvaluator + qCDebug(dcRest) << "unpacking stateEvaluator:" << params.value("stateEvaluator").toMap(); + StateEvaluator stateEvaluator = JsonTypes::unpackStateEvaluator(params.value("stateEvaluator").toMap()); + + // Check and unpack actions + QPair, RuleEngine::RuleError> actionsVerification = JsonTypes::verifyActions(params, eventDescriptorList); + QList actions = actionsVerification.first; + if (actionsVerification.second != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + // Check and unpack exitActions + QPair, RuleEngine::RuleError> exitActionsVerification = JsonTypes::verifyExitActions(params); + QList exitActions = exitActionsVerification.first; + if (exitActionsVerification.second != RuleEngine::RuleErrorNoError) + return createErrorReply(HttpReply::BadRequest); + + QString name = params.value("name", QString()).toString(); + bool enabled = params.value("enabled", true).toBool(); + + RuleEngine::RuleError status = GuhCore::instance()->editRule(ruleId, name, eventDescriptorList, stateEvaluator, actions, exitActions, enabled); + + if (status == RuleEngine::RuleErrorNoError) { + qCDebug(dcRest) << "Edit rule successfully finished"; + return createSuccessReply(); + } + + qCWarning(dcRest) << "Edit rule finished with error" << status; + return createErrorReply(HttpReply::BadRequest); } } diff --git a/server/rest/rulesresource.h b/server/rest/rulesresource.h index bca8e97e..4b2409e8 100644 --- a/server/rest/rulesresource.h +++ b/server/rest/rulesresource.h @@ -60,6 +60,8 @@ private: // Post methods HttpReply *addRule(const QByteArray &payload) const; + HttpReply *enableRule(const RuleId &ruleId) const; + HttpReply *disableRule(const RuleId &ruleId) const; // Put methods HttpReply *editRule(const RuleId &ruleId, const QByteArray &payload) const; diff --git a/server/server.pri b/server/server.pri index 849bee7e..973e1227 100644 --- a/server/server.pri +++ b/server/server.pri @@ -1,5 +1,5 @@ -contains(DEFINES, WEBSERVER){ +contains(DEFINES, WEBSOCKET){ QT += websockets } diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 589fbea3..aed5d7aa 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -1,6 +1,6 @@ -TEMPLATE=subdirs +TEMPLATE = subdirs -SUBDIRS=versioning \ +SUBDIRS = versioning \ devices \ jsonrpc \ events \ @@ -13,3 +13,4 @@ SUBDIRS=versioning \ restdeviceclasses \ restplugins \ restvendors \ + restrules \ diff --git a/tests/auto/restdeviceclasses/testrestdeviceclasses.cpp b/tests/auto/restdeviceclasses/testrestdeviceclasses.cpp index fc39a1bc..4c302daf 100644 --- a/tests/auto/restdeviceclasses/testrestdeviceclasses.cpp +++ b/tests/auto/restdeviceclasses/testrestdeviceclasses.cpp @@ -248,6 +248,7 @@ void TestRestDeviceClasses::discoverDevices() QNetworkAccessManager *nam = new QNetworkAccessManager(); QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); + // DISCOVER QUrl url(QString("http://localhost:3000/api/v1/deviceclasses/%1/discover").arg(deviceClassId.toString())); if (!discoveryParams.isEmpty()) { @@ -275,7 +276,7 @@ void TestRestDeviceClasses::discoverDevices() QVariantList foundDevices = jsonDoc.toVariant().toList(); QCOMPARE(foundDevices.count(), resultCount); - // add the discovered device + // ADD the discovered device request.setUrl(QUrl("http://localhost:3000/api/v1/devices")); DeviceDescriptorId descriptorId = DeviceDescriptorId(foundDevices.first().toMap().value("id").toString()); qDebug() << descriptorId; @@ -294,7 +295,7 @@ void TestRestDeviceClasses::discoverDevices() reply->deleteLater(); QCOMPARE(statusCode, expectedStatusCode); - // remove added device + // REMOVE added device jsonDoc = QJsonDocument::fromJson(data, &error); QCOMPARE(error.error, QJsonParseError::NoError); QVariantMap response = jsonDoc.toVariant().toMap(); diff --git a/tests/auto/restrules/restrules.pro b/tests/auto/restrules/restrules.pro new file mode 100644 index 00000000..a1009699 --- /dev/null +++ b/tests/auto/restrules/restrules.pro @@ -0,0 +1,5 @@ +include(../../../guh.pri) +include(../autotests.pri) + +TARGET = restrules +SOURCES += testrestrules.cpp diff --git a/tests/auto/restrules/testrestrules.cpp b/tests/auto/restrules/testrestrules.cpp new file mode 100644 index 00000000..8c91ffd0 --- /dev/null +++ b/tests/auto/restrules/testrestrules.cpp @@ -0,0 +1,911 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "guhtestbase.h" +#include "guhcore.h" +#include "devicemanager.h" +#include "mocktcpserver.h" +#include "webserver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace guhserver; + +class TestRestRules: public GuhTestBase +{ + Q_OBJECT + +private: + void cleanupMockHistory(); + void cleanupRules(); + + void verifyRuleExecuted(const ActionTypeId &actionTypeId); + void verifyRuleNotExecuted(); + +private slots: + void addRemoveRules_data(); + void addRemoveRules(); + + void editRules_data(); + void editRules(); + + void enableDisableRule(); + + void getRules(); +}; + +void TestRestRules::cleanupMockHistory() +{ + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/clearactionhistory").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + QNetworkReply *reply = nam.get(request); + spy.wait(500); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); +} + +void TestRestRules::cleanupRules() +{ + QNetworkAccessManager *nam = new QNetworkAccessManager(this); + QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); + + // Get all rules + QNetworkRequest request = QNetworkRequest(QUrl("http://localhost:3000/api/v1/rules")); + QNetworkReply *reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + QVariantList rulesList = jsonDoc.toVariant().toList(); + + // delete each rule + foreach (const QVariant &rule, rulesList) { + clientSpy.clear(); + QVariantMap ruleMap = rule.toMap(); + QNetworkRequest request(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleMap.value("id").toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->deleteResource(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + reply->deleteLater(); + } + nam->deleteLater(); +} + +void TestRestRules::verifyRuleExecuted(const ActionTypeId &actionTypeId) +{ + // Verify rule got executed + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + QNetworkReply *reply = nam.get(request); + spy.wait(500); + QCOMPARE(spy.count(), 1); + + QByteArray actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(actionTypeId == ActionTypeId(actionHistory), "Action not triggered"); + reply->deleteLater(); +} + +void TestRestRules::verifyRuleNotExecuted() +{ + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(m_mockDevice1Port))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + QByteArray actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(actionHistory.isEmpty(), "Action is triggered while it should not have been."); + reply->deleteLater(); +} + +void TestRestRules::addRemoveRules_data() +{ + // RuleAction + QVariantMap validActionNoParams; + validActionNoParams.insert("actionTypeId", mockActionIdNoParams); + validActionNoParams.insert("deviceId", m_mockDeviceId); + validActionNoParams.insert("ruleActionParams", QVariantList()); + + QVariantMap invalidAction; + invalidAction.insert("actionTypeId", ActionTypeId()); + invalidAction.insert("deviceId", m_mockDeviceId); + invalidAction.insert("ruleActionParams", QVariantList()); + + // RuleExitAction + QVariantMap validExitActionNoParams; + validExitActionNoParams.insert("actionTypeId", mockActionIdNoParams); + validExitActionNoParams.insert("deviceId", m_mockDeviceId); + validExitActionNoParams.insert("ruleActionParams", QVariantList()); + + QVariantMap invalidExitAction; + invalidExitAction.insert("actionTypeId", ActionTypeId()); + invalidExitAction.insert("deviceId", m_mockDeviceId); + invalidExitAction.insert("ruleActionParams", QVariantList()); + + // StateDescriptor + QVariantMap stateDescriptor; + stateDescriptor.insert("stateTypeId", mockIntStateId); + stateDescriptor.insert("deviceId", m_mockDeviceId); + stateDescriptor.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorLess)); + stateDescriptor.insert("value", "20"); + + // StateEvaluator + QVariantMap validStateEvaluator; + validStateEvaluator.insert("stateDescriptor", stateDescriptor); + validStateEvaluator.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + + QVariantMap invalidStateEvaluator; + stateDescriptor.remove("deviceId"); + invalidStateEvaluator.insert("stateDescriptor", stateDescriptor); + + // EventDescriptor + QVariantMap validEventDescriptor1; + validEventDescriptor1.insert("eventTypeId", mockEvent1Id); + validEventDescriptor1.insert("deviceId", m_mockDeviceId); + validEventDescriptor1.insert("paramDescriptors", QVariantList()); + + QVariantMap validEventDescriptor2; + validEventDescriptor2.insert("eventTypeId", mockEvent2Id); + validEventDescriptor2.insert("deviceId", m_mockDeviceId); + QVariantList params; + QVariantMap param1; + param1.insert("name", "mockParamInt"); + param1.insert("value", 3); + param1.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorEquals)); + params.append(param1); + validEventDescriptor2.insert("paramDescriptors", params); + + QVariantMap validEventDescriptor3; + validEventDescriptor3.insert("eventTypeId", mockEvent2Id); + validEventDescriptor3.insert("deviceId", m_mockDeviceId); + validEventDescriptor3.insert("paramDescriptors", QVariantList()); + + // EventDescriptorList + QVariantList eventDescriptorList; + eventDescriptorList.append(validEventDescriptor1); + eventDescriptorList.append(validEventDescriptor2); + + QVariantMap invalidEventDescriptor; + invalidEventDescriptor.insert("eventTypeId", mockEvent1Id); + invalidEventDescriptor.insert("deviceId", DeviceId()); + invalidEventDescriptor.insert("paramDescriptors", QVariantList()); + + // RuleAction event based + QVariantMap validActionEventBased; + validActionEventBased.insert("actionTypeId", mockActionIdWithParams); + validActionEventBased.insert("deviceId", m_mockDeviceId); + QVariantMap validActionEventBasedParam1; + validActionEventBasedParam1.insert("name", "mockActionParam1"); + validActionEventBasedParam1.insert("eventTypeId", mockEvent2Id); + validActionEventBasedParam1.insert("eventParamName", "mockParamInt"); + QVariantMap validActionEventBasedParam2; + validActionEventBasedParam2.insert("name", "mockActionParam2"); + validActionEventBasedParam2.insert("value", false); + validActionEventBased.insert("ruleActionParams", QVariantList() << validActionEventBasedParam1 << validActionEventBasedParam2); + + QVariantMap invalidActionEventBased; + invalidActionEventBased.insert("actionTypeId", mockActionIdNoParams); + invalidActionEventBased.insert("deviceId", m_mockDeviceId); + validActionEventBasedParam1.insert("value", 10); + invalidActionEventBased.insert("ruleActionParams", QVariantList() << validActionEventBasedParam1); + + QVariantMap invalidActionEventBased2; + invalidActionEventBased2.insert("actionTypeId", mockActionIdWithParams); + invalidActionEventBased2.insert("deviceId", m_mockDeviceId); + QVariantMap invalidActionEventBasedParam2; + invalidActionEventBasedParam2.insert("name", "mockActionParam1"); + invalidActionEventBasedParam2.insert("eventTypeId", mockEvent1Id); + invalidActionEventBasedParam2.insert("eventParamName", "value"); + QVariantMap invalidActionEventBasedParam3; + invalidActionEventBasedParam3.insert("name", "mockActionParam2"); + invalidActionEventBasedParam3.insert("value", 2); + invalidActionEventBased2.insert("ruleActionParams", QVariantList() << invalidActionEventBasedParam2 << invalidActionEventBasedParam3); + + QVariantMap invalidActionEventBased3; + invalidActionEventBased3.insert("actionTypeId", mockActionIdWithParams); + invalidActionEventBased3.insert("deviceId", m_mockDeviceId); + QVariantMap invalidActionEventBasedParam4; + invalidActionEventBasedParam4.insert("name", "mockActionParam1"); + invalidActionEventBasedParam4.insert("eventTypeId", mockEvent1Id); + invalidActionEventBasedParam4.insert("eventParamName", "mockParamInt"); + invalidActionEventBased3.insert("ruleActionParams", QVariantList() << invalidActionEventBasedParam4); + + QTest::addColumn("enabled"); + QTest::addColumn("action1"); + QTest::addColumn("exitAction1"); + QTest::addColumn("eventDescriptor"); + QTest::addColumn("eventDescriptorList"); + QTest::addColumn("stateEvaluator"); + QTest::addColumn("expectedStatusCode"); + QTest::addColumn("jsonError"); + QTest::addColumn("name"); + + // Rules with event based actions + QTest::newRow("valid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << validActionEventBased << QVariantMap() << validEventDescriptor3 << QVariantList() << QVariantMap() << 200 << true << "ActionEventRule1"; + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << invalidActionEventBased2 << QVariantMap() << validEventDescriptor3 << QVariantList() << QVariantMap() << 400 << false << "TestRule"; + + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), types not matching, name") << true << invalidActionEventBased3 << QVariantMap() << validEventDescriptor1 << QVariantList() << QVariantMap() << 400 << false << "TestRule"; + + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << invalidActionEventBased << QVariantMap() << validEventDescriptor2 << QVariantList() << QVariantMap() << 400 << false << "TestRule"; + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 StateEvaluator, name") << true << validActionEventBased << QVariantMap() << QVariantMap() << QVariantList() << validStateEvaluator << 400 << false << "TestRule"; + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << validActionEventBased << validActionEventBased << validEventDescriptor2 << QVariantList() << QVariantMap() << 400 << false << "TestRule"; + QTest::newRow("invalid rule. enabled, 1 Action, 1 ExitAction (EventBased), name") << true << validActionNoParams << validActionEventBased << validEventDescriptor2 << QVariantList() << QVariantMap() << 400 << false << "TestRule"; + + // Rules with exit actions + QTest::newRow("valid rule. enabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << 200 << true << "TestRule"; + QTest::newRow("valid rule. disabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << false << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << 200 << true << "TestRule"; + QTest::newRow("invalid rule. disabled, 1 Action, 1 invalid Exit Action, 1 StateEvaluator, name") << false << validActionNoParams << invalidExitAction << QVariantMap() << QVariantList() << validStateEvaluator << 400 << false << "TestRule"; + QTest::newRow("invalid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << 400 << false << "TestRule"; + QTest::newRow("invalid rule. 1 Action, 1 Exit Action, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << 400 << false << "TestRule"; + + // Rules without exit actions + QTest::newRow("valid rule. enabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << 200 << true << "TestRule"; + QTest::newRow("valid rule. diabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << false << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << 200 << true << "TestRule"; + QTest::newRow("valid rule. 2 EventDescriptors, 1 Action, name") << true << validActionNoParams << QVariantMap() << QVariantMap() << eventDescriptorList << validStateEvaluator << 200 << true << "TestRule"; + QTest::newRow("invalid rule: eventDescriptor and eventDescriptorList used") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << eventDescriptorList << validStateEvaluator << 400 << false << "TestRule"; + QTest::newRow("invalid action") << true << invalidAction << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << 400 << false << "TestRule"; + QTest::newRow("invalid event descriptor") << true << validActionNoParams << QVariantMap() << invalidEventDescriptor << QVariantList() << validStateEvaluator << 400 << false << "TestRule"; +} + +void TestRestRules::addRemoveRules() +{ + QFETCH(bool, enabled); + QFETCH(QVariantMap, action1); + QFETCH(QVariantMap, exitAction1); + QFETCH(QVariantMap, eventDescriptor); + QFETCH(QVariantList, eventDescriptorList); + QFETCH(QVariantMap, stateEvaluator); + QFETCH(int, expectedStatusCode); + QFETCH(bool, jsonError); + QFETCH(QString, name); + + Q_UNUSED(jsonError) + + // create add params for rule + QVariantMap params; + params.insert("name", name); + + QVariantList actions; + actions.append(action1); + params.insert("actions", actions); + + if (!eventDescriptor.isEmpty()) { + params.insert("eventDescriptor", eventDescriptor); + } + if (!eventDescriptorList.isEmpty()) { + params.insert("eventDescriptorList", eventDescriptorList); + } + + QVariantList exitActions; + if (!exitAction1.isEmpty()) { + exitActions.append(exitAction1); + params.insert("exitActions", exitActions); + } + params.insert("stateEvaluator", stateEvaluator); + if (!enabled) { + params.insert("enabled", enabled); + } + + QNetworkAccessManager *nam = new QNetworkAccessManager(this); + QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); + + // Get rules and verify there is no rule added + QNetworkRequest request; + request.setUrl(QUrl("http://localhost:3000/api/v1/rules")); + QNetworkReply *reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + QVariantList rulesList = jsonDoc.toVariant().toList(); + QVERIFY2(rulesList.count() == 0, "there should be no rules."); + + // ADD rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules"))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->post(request, QJsonDocument::fromVariant(params).toJson(QJsonDocument::Compact)); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, expectedStatusCode); + + if (expectedStatusCode != 200) + return; + + jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + QCOMPARE(error.error, QJsonParseError::NoError); + reply->deleteLater(); + + RuleId ruleId = RuleId(jsonDoc.toVariant().toMap().value("ruleId").toString()); + QVERIFY(!ruleId.isNull()); + + // GET rule details + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleId.toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + data = reply->readAll(); + reply->deleteLater(); + jsonDoc = QJsonDocument::fromJson(data, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + + // REMOVE rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleId.toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->deleteResource(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + reply->deleteLater(); + + nam->deleteLater(); +} + +void TestRestRules::editRules_data() +{ + // RuleAction + QVariantMap validActionNoParams; + validActionNoParams.insert("actionTypeId", mockActionIdNoParams); + validActionNoParams.insert("deviceId", m_mockDeviceId); + validActionNoParams.insert("ruleActionParams", QVariantList()); + + QVariantMap invalidAction; + invalidAction.insert("actionTypeId", ActionTypeId()); + invalidAction.insert("deviceId", m_mockDeviceId); + invalidAction.insert("ruleActionParams", QVariantList()); + + // RuleExitAction + QVariantMap validExitActionNoParams; + validExitActionNoParams.insert("actionTypeId", mockActionIdNoParams); + validExitActionNoParams.insert("deviceId", m_mockDeviceId); + validExitActionNoParams.insert("ruleActionParams", QVariantList()); + + QVariantMap invalidExitAction; + invalidExitAction.insert("actionTypeId", ActionTypeId()); + invalidExitAction.insert("deviceId", m_mockDeviceId); + invalidExitAction.insert("ruleActionParams", QVariantList()); + + // StateDescriptor + QVariantMap stateDescriptor; + stateDescriptor.insert("stateTypeId", mockIntStateId); + stateDescriptor.insert("deviceId", m_mockDeviceId); + stateDescriptor.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorLess)); + stateDescriptor.insert("value", "20"); + + // StateEvaluator + QVariantMap validStateEvaluator; + validStateEvaluator.insert("stateDescriptor", stateDescriptor); + validStateEvaluator.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + + QVariantMap invalidStateEvaluator; + stateDescriptor.remove("deviceId"); + invalidStateEvaluator.insert("stateDescriptor", stateDescriptor); + + // EventDescriptor + QVariantMap validEventDescriptor1; + validEventDescriptor1.insert("eventTypeId", mockEvent1Id); + validEventDescriptor1.insert("deviceId", m_mockDeviceId); + validEventDescriptor1.insert("paramDescriptors", QVariantList()); + + QVariantMap validEventDescriptor2; + validEventDescriptor2.insert("eventTypeId", mockEvent2Id); + validEventDescriptor2.insert("deviceId", m_mockDeviceId); + QVariantList params; + QVariantMap param1; + param1.insert("name", "mockParamInt"); + param1.insert("value", 3); + param1.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorEquals)); + params.append(param1); + validEventDescriptor2.insert("paramDescriptors", params); + + QVariantMap validEventDescriptor3; + validEventDescriptor3.insert("eventTypeId", mockEvent2Id); + validEventDescriptor3.insert("deviceId", m_mockDeviceId); + validEventDescriptor3.insert("paramDescriptors", QVariantList()); + + // EventDescriptorList + QVariantList eventDescriptorList; + eventDescriptorList.append(validEventDescriptor1); + eventDescriptorList.append(validEventDescriptor2); + + QVariantMap invalidEventDescriptor; + invalidEventDescriptor.insert("eventTypeId", mockEvent1Id); + invalidEventDescriptor.insert("deviceId", DeviceId()); + invalidEventDescriptor.insert("paramDescriptors", QVariantList()); + + // RuleAction event based + QVariantMap validActionEventBased; + validActionEventBased.insert("actionTypeId", mockActionIdWithParams); + validActionEventBased.insert("deviceId", m_mockDeviceId); + QVariantMap validActionEventBasedParam1; + validActionEventBasedParam1.insert("name", "mockActionParam1"); + validActionEventBasedParam1.insert("eventTypeId", mockEvent2Id); + validActionEventBasedParam1.insert("eventParamName", "mockParamInt"); + QVariantMap validActionEventBasedParam2; + validActionEventBasedParam2.insert("name", "mockActionParam2"); + validActionEventBasedParam2.insert("value", false); + validActionEventBased.insert("ruleActionParams", QVariantList() << validActionEventBasedParam1 << validActionEventBasedParam2); + + QVariantMap invalidActionEventBased; + invalidActionEventBased.insert("actionTypeId", mockActionIdNoParams); + invalidActionEventBased.insert("deviceId", m_mockDeviceId); + validActionEventBasedParam1.insert("value", 10); + invalidActionEventBased.insert("ruleActionParams", QVariantList() << validActionEventBasedParam1); + + QVariantMap invalidActionEventBased2; + invalidActionEventBased2.insert("actionTypeId", mockActionIdWithParams); + invalidActionEventBased2.insert("deviceId", m_mockDeviceId); + QVariantMap invalidActionEventBasedParam2; + invalidActionEventBasedParam2.insert("name", "mockActionParam1"); + invalidActionEventBasedParam2.insert("eventTypeId", mockEvent1Id); + invalidActionEventBasedParam2.insert("eventParamName", "value"); + QVariantMap invalidActionEventBasedParam3; + invalidActionEventBasedParam3.insert("name", "mockActionParam2"); + invalidActionEventBasedParam3.insert("value", 2); + invalidActionEventBased2.insert("ruleActionParams", QVariantList() << invalidActionEventBasedParam2 << invalidActionEventBasedParam3); + + QVariantMap invalidActionEventBased3; + invalidActionEventBased3.insert("actionTypeId", mockActionIdWithParams); + invalidActionEventBased3.insert("deviceId", m_mockDeviceId); + QVariantMap invalidActionEventBasedParam4; + invalidActionEventBasedParam4.insert("name", "mockActionParam1"); + invalidActionEventBasedParam4.insert("eventTypeId", mockEvent1Id); + invalidActionEventBasedParam4.insert("eventParamName", "mockParamInt"); + invalidActionEventBased3.insert("ruleActionParams", QVariantList() << invalidActionEventBasedParam4); + + QTest::addColumn("enabled"); + QTest::addColumn("action"); + QTest::addColumn("exitAction"); + QTest::addColumn("eventDescriptor"); + QTest::addColumn("eventDescriptorList"); + QTest::addColumn("stateEvaluator"); + QTest::addColumn("expectedStatusCode"); + QTest::addColumn("name"); + + // Rules with event based actions + QTest::newRow("valid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << validActionEventBased << QVariantMap() << validEventDescriptor3 << QVariantList() << QVariantMap() << 200 << "ActionEventRule1"; + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << invalidActionEventBased2 << QVariantMap() << validEventDescriptor3 << QVariantList() << QVariantMap() << 400 << "TestRule"; + + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), types not matching, name") << true << invalidActionEventBased3 << QVariantMap() << validEventDescriptor1 << QVariantList() << QVariantMap() << 400 << "TestRule"; + + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << invalidActionEventBased << QVariantMap() << validEventDescriptor2 << QVariantList() << QVariantMap() << 400 << "TestRule"; + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 StateEvaluator, name") << true << validActionEventBased << QVariantMap() << QVariantMap() << QVariantList() << validStateEvaluator << 400 << "TestRule"; + QTest::newRow("invalid rule. enabled, 1 Action (eventBased), 1 EventDescriptor, name") << true << validActionEventBased << validActionEventBased << validEventDescriptor2 << QVariantList() << QVariantMap() << 400 << "TestRule"; + QTest::newRow("invalid rule. enabled, 1 Action, 1 ExitAction (EventBased), name") << true << validActionNoParams << validActionEventBased << validEventDescriptor2 << QVariantList() << QVariantMap() << 400 << "TestRule"; + + // Rules with exit actions + QTest::newRow("valid rule. enabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << 200 << "TestRule"; + QTest::newRow("valid rule. disabled, 1 Action, 1 Exit Action, 1 StateEvaluator, name") << false << validActionNoParams << validExitActionNoParams << QVariantMap() << QVariantList() << validStateEvaluator << 200 << "TestRule"; + QTest::newRow("invalid rule. 1 Action, 1 Exit Action, 1 EventDescriptor, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << validEventDescriptor1 << QVariantList() << validStateEvaluator << 400 << "TestRule"; + QTest::newRow("invalid rule. 1 Action, 1 Exit Action, eventDescriptorList, 1 StateEvaluator, name") << true << validActionNoParams << validExitActionNoParams << QVariantMap() << eventDescriptorList << validStateEvaluator << 400 << "TestRule"; + + // Rules without exit actions + QTest::newRow("valid rule. enabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << 200 << "TestRule"; + QTest::newRow("valid rule. diabled, 1 EventDescriptor, StateEvaluator, 1 Action, name") << false << validActionNoParams << QVariantMap() << validEventDescriptor1 << QVariantList() << validStateEvaluator << 200 << "TestRule"; + QTest::newRow("valid rule. 2 EventDescriptors, 1 Action, name") << true << validActionNoParams << QVariantMap() << QVariantMap() << eventDescriptorList << validStateEvaluator << 200 << "TestRule"; + QTest::newRow("invalid rule: eventDescriptor and eventDescriptorList used") << true << validActionNoParams << QVariantMap() << validEventDescriptor1 << eventDescriptorList << validStateEvaluator << 400 << "TestRule"; +} + +void TestRestRules::editRules() +{ + QFETCH(bool, enabled); + QFETCH(QVariantMap, action); + QFETCH(QVariantMap, exitAction); + QFETCH(QVariantMap, eventDescriptor); + QFETCH(QVariantList, eventDescriptorList); + QFETCH(QVariantMap, stateEvaluator); + QFETCH(int, expectedStatusCode); + QFETCH(QString, name); + + // Add the rule we want to edit + QVariantList eventParamDescriptors; + QVariantMap eventDescriptor1; + eventDescriptor1.insert("eventTypeId", mockEvent1Id); + eventDescriptor1.insert("deviceId", m_mockDeviceId); + eventDescriptor1.insert("paramDescriptors", QVariantList()); + QVariantMap eventDescriptor2; + eventDescriptor2.insert("eventTypeId", mockEvent2Id); + eventDescriptor2.insert("deviceId", m_mockDeviceId); + eventDescriptor2.insert("paramDescriptors", QVariantList()); + QVariantMap eventParam1; + eventParam1.insert("name", "mockParamInt"); + eventParam1.insert("value", 3); + eventParam1.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorEquals)); + eventParamDescriptors.append(eventParam1); + eventDescriptor2.insert("paramDescriptors", eventParamDescriptors); + + QVariantList eventDescriptorList1; + eventDescriptorList1.append(eventDescriptor1); + eventDescriptorList1.append(eventDescriptor2); + + QVariantMap stateEvaluator0; + QVariantMap stateDescriptor1; + stateDescriptor1.insert("deviceId", m_mockDeviceId); + stateDescriptor1.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorEquals)); + stateDescriptor1.insert("stateTypeId", mockIntStateId); + stateDescriptor1.insert("value", 1); + QVariantMap stateDescriptor2; + stateDescriptor2.insert("deviceId", m_mockDeviceId); + stateDescriptor2.insert("operator", JsonTypes::valueOperatorToString(Types::ValueOperatorEquals)); + stateDescriptor2.insert("stateTypeId", mockBoolStateId); + stateDescriptor2.insert("value", true); + QVariantMap stateEvaluator1; + stateEvaluator1.insert("stateDescriptor", stateDescriptor1); + stateEvaluator1.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + QVariantMap stateEvaluator2; + stateEvaluator2.insert("stateDescriptor", stateDescriptor2); + stateEvaluator2.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + QVariantList childEvaluators; + childEvaluators.append(stateEvaluator1); + childEvaluators.append(stateEvaluator2); + stateEvaluator0.insert("childEvaluators", childEvaluators); + stateEvaluator0.insert("operator", JsonTypes::stateOperatorToString(Types::StateOperatorAnd)); + + QVariantMap action1; + action1.insert("actionTypeId", mockActionIdNoParams); + action1.insert("deviceId", m_mockDeviceId); + action1.insert("ruleActionParams", QVariantList()); + QVariantMap action2; + action2.insert("actionTypeId", mockActionIdWithParams); + qDebug() << "got action id" << mockActionIdWithParams; + action2.insert("deviceId", m_mockDeviceId); + QVariantList action2Params; + QVariantMap action2Param1; + action2Param1.insert("name", "mockActionParam1"); + action2Param1.insert("value", 5); + action2Params.append(action2Param1); + QVariantMap action2Param2; + action2Param2.insert("name", "mockActionParam2"); + action2Param2.insert("value", true); + action2Params.append(action2Param2); + action2.insert("ruleActionParams", action2Params); + + // RuleAction event based + QVariantMap validActionEventBased; + validActionEventBased.insert("actionTypeId", mockActionIdWithParams); + validActionEventBased.insert("deviceId", m_mockDeviceId); + QVariantMap validActionEventBasedParam1; + validActionEventBasedParam1.insert("name", "mockActionParam1"); + validActionEventBasedParam1.insert("eventTypeId", mockEvent2Id); + validActionEventBasedParam1.insert("eventParamName", "mockParamInt"); + QVariantMap validActionEventBasedParam2; + validActionEventBasedParam2.insert("name", "mockActionParam2"); + validActionEventBasedParam2.insert("value", false); + validActionEventBased.insert("ruleActionParams", QVariantList() << validActionEventBasedParam1 << validActionEventBasedParam2); + + QVariantList validEventDescriptors3; + QVariantMap validEventDescriptor3; + validEventDescriptor3.insert("eventTypeId", mockEvent2Id); + validEventDescriptor3.insert("deviceId", m_mockDeviceId); + validEventDescriptor3.insert("paramDescriptors", QVariantList()); + validEventDescriptors3.append(validEventDescriptor3); + + QVariantMap params; + QVariantList actions; + actions.append(action1); + actions.append(action2); + params.insert("actions", actions); + params.insert("eventDescriptorList", eventDescriptorList1); + params.insert("stateEvaluator", stateEvaluator0); + params.insert("name", "TestRule"); + + + QNetworkAccessManager *nam = new QNetworkAccessManager(this); + QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); + + // Get rules and verify there is no rule added + QNetworkRequest request; + request.setUrl(QUrl("http://localhost:3000/api/v1/rules")); + QNetworkReply *reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + QVariantList rulesList = jsonDoc.toVariant().toList(); + QVERIFY2(rulesList.count() == 0, "there should be no rules."); + + // ADD rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules"))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->post(request, QJsonDocument::fromVariant(params).toJson(QJsonDocument::Compact)); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + + jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + QCOMPARE(error.error, QJsonParseError::NoError); + reply->deleteLater(); + + RuleId ruleId = RuleId(jsonDoc.toVariant().toMap().value("ruleId").toString()); + QVERIFY(!ruleId.isNull()); + + // now create the new rule and edit the original one + params.clear(); + params.insert("ruleId", ruleId.toString()); + params.insert("name", name); + + if (!eventDescriptor.isEmpty()) { + params.insert("eventDescriptor", eventDescriptor); + } + if (!eventDescriptorList.isEmpty()) { + params.insert("eventDescriptorList", eventDescriptorList); + } + actions.clear(); + actions.append(action); + params.insert("actions", actions); + + QVariantList exitActions; + if (!exitAction.isEmpty()) { + exitActions.append(exitAction); + params.insert("exitActions", exitActions); + } + params.insert("stateEvaluator", stateEvaluator); + if (!enabled) { + params.insert("enabled", enabled); + } + + // EDIT rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleId.toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->put(request, QJsonDocument::fromVariant(params).toJson(QJsonDocument::Compact)); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, expectedStatusCode); + + if (expectedStatusCode == 200) { + // get edit rule and verify params + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules"))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + QCOMPARE(error.error, QJsonParseError::NoError); + reply->deleteLater(); + } + + // REMOVE rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleId.toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->deleteResource(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + reply->deleteLater(); + + + // check if removed + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleId.toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 404); + reply->deleteLater(); + + nam->deleteLater(); +} + +void TestRestRules::enableDisableRule() +{ + QVariantMap addRuleParams; + QVariantList events; + QVariantMap event1; + event1.insert("eventTypeId", mockEvent1Id); + event1.insert("deviceId", m_mockDeviceId); + events.append(event1); + addRuleParams.insert("eventDescriptorList", events); + addRuleParams.insert("name", "TestRule"); + + QVariantList actions; + QVariantMap action; + action.insert("actionTypeId", mockActionIdNoParams); + action.insert("deviceId", m_mockDeviceId); + actions.append(action); + addRuleParams.insert("actions", actions); + + QNetworkAccessManager *nam = new QNetworkAccessManager(this); + QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); + + // ADD rule + QNetworkRequest request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules"))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + QNetworkReply *reply = nam->post(request, QJsonDocument::fromVariant(addRuleParams).toJson(QJsonDocument::Compact)); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + + RuleId ruleId = RuleId(jsonDoc.toVariant().toMap().value("ruleId").toString()); + QVERIFY(!ruleId.isNull()); + + // ENABLE rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1/enable").arg(ruleId.toString()))); + reply = nam->post(request, QByteArray()); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + reply->deleteLater(); + + // Trigger an event + + // trigger event in mock device + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam->get(request); + clientSpy.wait(); + QCOMPARE(clientSpy.count(), 1); + reply->deleteLater(); + + verifyRuleExecuted(mockActionIdNoParams); + + cleanupMockHistory(); + + // DISABLE the rule + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1/disable").arg(ruleId.toString()))); + reply = nam->post(request, QByteArray()); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + reply->deleteLater(); + + + // trigger event in mock device + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam->get(request); + clientSpy.wait(); + QCOMPARE(clientSpy.count(), 1); + reply->deleteLater(); + + verifyRuleNotExecuted(); + + cleanupMockHistory(); + + // ENABLE again + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:3000/api/v1/rules/%1/enable").arg(ruleId.toString()))); + reply = nam->post(request, QByteArray()); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + reply->deleteLater(); + + // trigger event in mock device + clientSpy.clear(); + request = QNetworkRequest(QUrl(QString("http://localhost:%1/generateevent?eventtypeid=%2").arg(m_mockDevice1Port).arg(mockEvent1Id.toString()))); + reply = nam->get(request); + clientSpy.wait(); + QCOMPARE(clientSpy.count(), 1); + reply->deleteLater(); + + verifyRuleExecuted(mockActionIdNoParams); + + cleanupRules(); +} + +void TestRestRules::getRules() +{ + QNetworkAccessManager *nam = new QNetworkAccessManager(this); + QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); + + // Get all rules + QNetworkRequest request; + request.setUrl(QUrl("http://localhost:3000/api/v1/rules")); + QNetworkReply *reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + QByteArray data = reply->readAll(); + reply->deleteLater(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + QVariantList rulesList = jsonDoc.toVariant().toList(); + QVERIFY2(rulesList.count() == 0, "there should be at least one vendor."); + + // Get each of thouse rules individualy + foreach (const QVariant &rule, rulesList) { + QVariantMap ruleMap = rule.toMap(); + QNetworkRequest request(QUrl(QString("http://localhost:3000/api/v1/rules/%1").arg(ruleMap.value("id").toString()))); + request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json"); + clientSpy.clear(); + QNetworkReply *reply = nam->get(request); + clientSpy.wait(); + QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver"); + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QCOMPARE(statusCode, 200); + jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + QCOMPARE(error.error, QJsonParseError::NoError); + reply->deleteLater(); + + } + nam->deleteLater(); +} + + +#include "testrestrules.moc" +QTEST_MAIN(TestRestRules)