diff --git a/server/guhcore.cpp b/server/guhcore.cpp index f3619147..445e0931 100644 --- a/server/guhcore.cpp +++ b/server/guhcore.cpp @@ -397,8 +397,7 @@ GuhCore::GuhCore(QObject *parent) : connect(m_ruleEngine, &RuleEngine::ruleRemoved, this, &GuhCore::ruleRemoved); connect(m_ruleEngine, &RuleEngine::ruleConfigurationChanged, this, &GuhCore::ruleConfigurationChanged); - connect(m_timeManager, &TimeManager::timeChanged, this, &GuhCore::onTimeChanged); - connect(m_timeManager, &TimeManager::dateChanged, this, &GuhCore::onDateChanged); + connect(m_timeManager, &TimeManager::dateTimeChanged, this, &GuhCore::onDateTimeChanged); connect(m_timeManager, &TimeManager::tick, m_deviceManager, &DeviceManager::timeTick); m_logger->logSystemEvent(true); @@ -462,14 +461,30 @@ void GuhCore::gotEvent(const Event &event) executeRuleActions(actions); } -void GuhCore::onTimeChanged(const QTime ¤tTime) +void GuhCore::onDateTimeChanged(const QDateTime &dateTime) { - qCDebug(dcTimeManager) << currentTime.toString("hh:mm"); -} + qCDebug(dcTimeManager) << dateTime.toString("dd.MM.yyyy hh:mm"); -void GuhCore::onDateChanged(const QDate ¤tDate) -{ - qCDebug(dcTimeManager) << currentDate.toString("dd.MM.yyyy"); + QList actions; + foreach (const Rule &rule, m_ruleEngine->evaluateTime(dateTime)) { + // TimeEvent based + if (!rule.timeDescriptor().timeEventItems().isEmpty()) { + m_logger->logRuleTriggered(rule); + foreach (const RuleAction &action, rule.actions()) { + actions.append(action); + } + } else { + // Calendar based rule + m_logger->logRuleActiveChanged(rule); + emit ruleActiveChanged(rule); + if (rule.active()) { + actions.append(rule.actions()); + } else { + actions.append(rule.exitActions()); + } + } + } + executeRuleActions(actions); } /*! Return the instance of the log engine */ diff --git a/server/guhcore.h b/server/guhcore.h index 0e2484e8..3ced0f83 100644 --- a/server/guhcore.h +++ b/server/guhcore.h @@ -103,8 +103,7 @@ private: private slots: void gotEvent(const Event &event); - void onTimeChanged(const QTime ¤tTime); - void onDateChanged(const QDate ¤tDate); + void onDateTimeChanged(const QDateTime &dateTime); void actionExecutionFinished(const ActionId &id, DeviceManager::DeviceError status); }; diff --git a/server/jsonrpc/jsontypes.cpp b/server/jsonrpc/jsontypes.cpp index 6a882002..f7f065c2 100644 --- a/server/jsonrpc/jsontypes.cpp +++ b/server/jsonrpc/jsontypes.cpp @@ -1067,6 +1067,7 @@ ParamList JsonTypes::unpackParams(const QVariantList ¶mList) return params; } +/*! Returns a \l{Rule} created from the given \a ruleMap. */ Rule JsonTypes::unpackRule(const QVariantMap &ruleMap) { // The rule id will only be valid if unpacking for edit @@ -1121,6 +1122,7 @@ Rule JsonTypes::unpackRule(const QVariantMap &ruleMap) return rule; } +/*! Returns a \l{RuleAction} created from the given \a ruleActionMap. */ RuleAction JsonTypes::unpackRuleAction(const QVariantMap &ruleActionMap) { RuleAction action(ActionTypeId(ruleActionMap.value("actionTypeId").toString()), DeviceId(ruleActionMap.value("deviceId").toString())); diff --git a/server/rule.cpp b/server/rule.cpp index cb0ca4e2..03690af1 100644 --- a/server/rule.cpp +++ b/server/rule.cpp @@ -39,7 +39,7 @@ namespace guhserver { -/*! Constructs an empty, invalid rule. */ +/*! Constructs an empty, invalid \l{Rule}. */ Rule::Rule(): m_id(RuleId()), m_name(QString()), @@ -55,12 +55,13 @@ Rule::Rule(): } -/*! Returns the id of this Rule. */ +/*! Returns the id of this \l{Rule}. */ RuleId Rule::id() const { return m_id; } +/*! Sets the \a ruleId of this \l{Rule}. */ void Rule::setId(const RuleId &ruleId) { m_id = ruleId; @@ -72,6 +73,7 @@ QString Rule::name() const return m_name; } +/*! Sets the \a name of this \l{Rule}. */ void Rule::setName(const QString &name) { m_name = name; @@ -89,6 +91,7 @@ TimeDescriptor Rule::timeDescriptor() const return m_timeDescriptor; } +/*! Sets the \a timeDescriptor of this \l{Rule}. */ void Rule::setTimeDescriptor(const TimeDescriptor &timeDescriptor) { m_timeDescriptor = timeDescriptor; @@ -100,6 +103,7 @@ StateEvaluator Rule::stateEvaluator() const return m_stateEvaluator; } +/*! Sets the \a stateEvaluator of this \l{Rule}. */ void Rule::setStateEvaluator(const StateEvaluator &stateEvaluator) { m_stateEvaluator = stateEvaluator; @@ -111,6 +115,7 @@ QList Rule::eventDescriptors() const return m_eventDescriptors; } +/*! Sets the \a eventDescriptors of this \l{Rule}. */ void Rule::setEventDescriptors(const QList &eventDescriptors) { m_eventDescriptors = eventDescriptors; @@ -122,6 +127,7 @@ QList Rule::actions() const return m_actions; } +/*! Sets the \a actions of this \l{Rule}. */ void Rule::setActions(const QList actions) { m_actions = actions; @@ -133,6 +139,7 @@ QList Rule::exitActions() const return m_exitActions; } +/*! Sets the \a exitActions of this \l{Rule}. */ void Rule::setExitActions(const QList exitActions) { m_exitActions = exitActions; @@ -162,11 +169,13 @@ void Rule::setExecutable(const bool &executable) m_executable = executable; } +/*! Returns true if this \l{Rule} is valid. A \l{Rule} with a valid \l{id()} is valid. */ bool Rule::isValid() const { return !m_id.isNull(); } +/*! Returns true if this \l{Rule} is consistent. */ bool Rule::isConsistent() const { // check if this rules is based on any event and contains exit actions @@ -184,7 +193,6 @@ bool Rule::isConsistent() const return true; } - void Rule::setActive(const bool &active) { m_active = active; diff --git a/server/ruleengine.cpp b/server/ruleengine.cpp index 34fe7d3f..93b32ca8 100644 --- a/server/ruleengine.cpp +++ b/server/ruleengine.cpp @@ -74,6 +74,14 @@ The types of the \l{RuleActionParam} and the corresponding \l{Event} \l{Param} do not match. \value RuleErrorNotExecutable This rule is not executable. + \value RuleErrorInvalidRepeatingOption + One of the given \l{RepeatingOption}{RepeatingOption} is not valid. + \value RuleErrorInvalidCalendarItem + One of the given \l{CalendarItem}{CalendarItems} is not valid. + \value RuleErrorInvalidTimeDescriptor + One of the given \l{TimeDescriptor}{TimeDescriptors} is not valid. + \value RuleErrorInvalidTimeEventItem + One of the given \l{TimeEventItem}{TimeEventItems} is not valid. \value RuleErrorContainsEventBasesAction This rule contains an \l{Action} which depends on an \l{Event} value. This \l{Rule} cannot execute the \l{Action}{Actions} without the \l{Event} value. @@ -238,7 +246,7 @@ QList RuleEngine::evaluateEvent(const Event &event) qCDebug(dcRuleEngine) << "got event:" << event << device->name() << event.eventTypeId(); QList rules; - foreach (const RuleId &id, m_ruleIds) { + foreach (const RuleId &id, ruleIds()) { Rule rule = m_rules.value(id); if (!rule.enabled()) continue; @@ -278,14 +286,65 @@ QList RuleEngine::evaluateEvent(const Event &event) return rules; } +/*! Ask the Engine to evaluate all the rules for the given \a dateTime. + This will search all the \l{Rule}{Rules} triggered by the given \a dateTime + and evaluate their \l{CalendarItem}{CalendarItems} and \l{TimeEventItem}{TimeEventItems}. + It will return a list of all \l{Rule}{Rules} that are triggered or change its active state. +*/ QList RuleEngine::evaluateTime(const QDateTime &dateTime) { - Q_UNUSED(dateTime) QList rules; + foreach (const Rule &r, m_rules.values()) { + Rule rule = m_rules.value(r.id()); + if (!rule.enabled()) + continue; + + // check if this rule is time based + if (!rule.timeDescriptor().isEmpty()) { + + // check if this rule is based on calendarItems + if (!rule.timeDescriptor().calendarItems().isEmpty()) { + qCDebug(dcRuleEngine()) << "Evaluate CalendarItem against" << dateTime.toString("dd:MM:yyyy hh:mm") << "for rule" << rule.id().toString(); + bool active = rule.timeDescriptor().evaluate(dateTime); + if (active) { + if (m_activeRules.contains(rule.id())) { + qCDebug(dcRuleEngine) << "Rule" << rule.id().toString() << "still active."; + } else { + qCDebug(dcRuleEngine) << "Rule" << rule.id().toString() << "active."; + rule.setActive(true); + m_rules[rule.id()] = rule; + m_activeRules.append(rule.id()); + rules.append(rule); + } + } else { + if (m_activeRules.contains(rule.id())) { + qCDebug(dcRuleEngine) << "Rule" << rule.id().toString() << "inactive."; + rule.setActive(false); + m_rules[rule.id()] = rule; + m_activeRules.removeAll(rule.id()); + rules.append(rule); + } + } + } + + // check if this rule is based on timeEventItems + if (!rule.timeDescriptor().timeEventItems().isEmpty()) { + bool valid = rule.timeDescriptor().evaluate(dateTime); + qCDebug(dcRuleEngine()) << "Result:" << (valid ? "valid" : "invalid"); + if (valid) { + rules.append(rule); + } + } + } + } + return rules; } +/*! Add the given \a rule to the system. If the rule will be added + from an edit request, the parameter \a fromEdit will be true. +*/ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) { if (rule.id().isNull()) @@ -333,7 +392,7 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) // Check time descriptor if (!rule.timeDescriptor().isEmpty()) { - if (rule.timeDescriptor().isValid()) { + if (!rule.timeDescriptor().isValid()) { qCDebug(dcRuleEngine()) << "Cannot create rule. Got invalid timeDescriptor."; return RuleErrorInvalidTimeDescriptor; } @@ -484,6 +543,9 @@ RuleEngine::RuleError RuleEngine::addRule(const Rule &rule, bool fromEdit) return RuleErrorNoError; } +/*! Edit the given \a rule in the system. The rule with the \l{RuleId} from the given \a rule + will be removed from the system and readded with the new parameters in the given \a rule. +*/ RuleEngine::RuleError RuleEngine::editRule(const Rule &rule) { if (rule.id().isNull()) diff --git a/server/time/calendaritem.cpp b/server/time/calendaritem.cpp index d17e40cb..77437645 100644 --- a/server/time/calendaritem.cpp +++ b/server/time/calendaritem.cpp @@ -29,6 +29,7 @@ */ #include "calendaritem.h" +#include "loggingcategories.h" namespace guhserver { @@ -102,19 +103,16 @@ bool CalendarItem::isValid() const /*! Returns true, if the given \a dateTime matches this \l{CalendarItem}. */ bool CalendarItem::evaluate(const QDateTime &dateTime) const { + qCDebug(dcRuleEngine()) << "Evaluate CalendarItem"; + if (!isValid()) return false; if (!repeatingOption().isValid()) return false; - if (m_dateTime.isValid() && !repeatingOption().isEmtpy()) - return false; - - // Only check repeating option mode if this is not a timedate calendarItem, - // which can only be valid once and is not repeatable. - if (!m_dateTime.isValid()) { - switch (repeatingOption().mode()) { + if (m_startTime.isValid()) { + switch (m_repeatingOption.mode()) { case RepeatingOption::RepeatingModeNone: // If there is no repeating option, we assume it is meant daily. return evaluateDaily(dateTime); @@ -137,9 +135,20 @@ bool CalendarItem::evaluate(const QDateTime &dateTime) const bool CalendarItem::evaluateHourly(const QDateTime &dateTime) const { + qCDebug(dcRuleEngine()) << "Evaluate CalendarItem Hourly"; + + // If the duration is longer than a hour, this calendar item is always true + // 1 hour has 60 minutes + if (duration() >= 60) + return true; + QDateTime startDateTime = QDateTime(dateTime.date(), QTime(dateTime.time().hour(), startTime().minute())); QDateTime endDateTime = startDateTime.addSecs(duration() * 60); + qCDebug(dcRuleEngine()) << "current time" << dateTime.toString("hh:mm"); + qCDebug(dcRuleEngine()) << "startTime" << startDateTime.toString("hh:mm"); + qCDebug(dcRuleEngine()) << "endTime" << endDateTime.toString("hh:mm"); + bool timeValid = dateTime >= startDateTime && dateTime < endDateTime; bool weekdayValid = repeatingOption().evaluateWeekDay(dateTime); bool monthdayValid = repeatingOption().evaluateMonthDay(dateTime); @@ -149,6 +158,8 @@ bool CalendarItem::evaluateHourly(const QDateTime &dateTime) const bool CalendarItem::evaluateDaily(const QDateTime &dateTime) const { + qCDebug(dcRuleEngine()) << "Evaluate CalendarItem Hourly"; + // If the duration is longer than a day, this calendar item is always true // 1 day has 1440 minutes if (duration() >= 1440) diff --git a/server/time/timedescriptor.cpp b/server/time/timedescriptor.cpp index 1c376ca9..613c25cc 100644 --- a/server/time/timedescriptor.cpp +++ b/server/time/timedescriptor.cpp @@ -69,7 +69,7 @@ void TimeDescriptor::setCalendarItems(const QList &calendarItems) /*! Returns true if either the calendarItems list is not empty or the timeEventItems list.*/ bool TimeDescriptor::isValid() const { - return !m_timeEventItems.isEmpty() && !m_calendarItems.isEmpty(); + return !m_timeEventItems.isEmpty() != !m_calendarItems.isEmpty(); } /*! Returns true if the calendarItems list and the timeEventItems list is empty.*/ @@ -84,7 +84,23 @@ bool TimeDescriptor::isEmpty() const */ bool TimeDescriptor::evaluate(const QDateTime &dateTime) const { - Q_UNUSED(dateTime) + // If there are calendarItems (always OR connected) + if (!m_calendarItems.isEmpty()) { + foreach (const CalendarItem &calendarItem, m_calendarItems) { + if (calendarItem.evaluate(dateTime)) { + return true; + } + } + } + + // If there are timeEventItems (always OR connected) + if (!m_timeEventItems.isEmpty()) { + foreach (const TimeEventItem &timeEventItem, m_timeEventItems) { + if (timeEventItem.evaluate(dateTime)) { + return true; + } + } + } return false; } diff --git a/server/time/timemanager.cpp b/server/time/timemanager.cpp index 072dd5fe..ae465f45 100644 --- a/server/time/timemanager.cpp +++ b/server/time/timemanager.cpp @@ -30,12 +30,8 @@ Represents the central time tick. Will be emitted every second. */ -/*! \fn void guhserver::TimeManager::dateChanged(const QDate ¤tDate); - Will be emitted when the \a currentDate has changed. -*/ - -/*! \fn void guhserver::TimeManager::timeChanged(const QTime ¤tTime); - Will be emitted when the \a currentTime has changed. +/*! \fn void guhserver::TimeManager::dateTimeChanged(const QDateTime &dateTime); + Will be emitted when the \a dateTime has changed. */ #include "timemanager.h" @@ -106,6 +102,13 @@ void TimeManager::stopTimer() // Stop clock (used for testing) m_guhTimer->stop(); } + +void TimeManager::setTime(const QDateTime &dateTime) +{ + // This method will only be called for testing + emit tick(); + emit dateTimeChanged(dateTime.toTimeZone(m_timeZone)); +} #endif void TimeManager::guhTimeout() @@ -113,17 +116,11 @@ void TimeManager::guhTimeout() // tick for deviceManager emit tick(); - QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - // Minute based guh time + QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); if (m_dateTime.time().minute() != currentDateTime.toTimeZone(m_timeZone).time().minute()) { m_dateTime = currentDateTime; - emit timeChanged(m_dateTime.toTimeZone(m_timeZone).time()); - } - - // check if day changed - if (m_dateTime.date() != currentDateTime.toTimeZone(m_timeZone).date()) { - emit dateChanged(m_dateTime.toTimeZone(m_timeZone).date()); + emit dateTimeChanged(m_dateTime.toTimeZone(m_timeZone)); } } diff --git a/server/time/timemanager.h b/server/time/timemanager.h index 2c656423..ba84e08c 100644 --- a/server/time/timemanager.h +++ b/server/time/timemanager.h @@ -43,6 +43,7 @@ public: #ifdef TESTING_ENABLED void stopTimer(); + void setTime(const QDateTime &dateTime); #endif private: @@ -52,8 +53,7 @@ private: signals: void tick(); - void dateChanged(const QDate ¤tDate); - void timeChanged(const QTime ¤tTime); + void dateTimeChanged(const QDateTime &dateTime); private slots: void guhTimeout(); diff --git a/tests/auto/rules/testrules.cpp b/tests/auto/rules/testrules.cpp index 942c5b86..4274a750 100644 --- a/tests/auto/rules/testrules.cpp +++ b/tests/auto/rules/testrules.cpp @@ -141,7 +141,6 @@ void TestRules::verifyRuleExecuted(const ActionTypeId &actionTypeId) qDebug() << "have action history" << actionHistory; QVERIFY2(actionTypeId == ActionTypeId(actionHistory), "Action not triggered"); reply->deleteLater(); - } void TestRules::verifyRuleNotExecuted() @@ -157,7 +156,6 @@ void TestRules::verifyRuleNotExecuted() qDebug() << "have action history" << actionHistory; QVERIFY2(actionHistory.isEmpty(), "Action is triggered while it should not have been."); reply->deleteLater(); - } diff --git a/tests/auto/timemanager/testtimemanager.cpp b/tests/auto/timemanager/testtimemanager.cpp index 993980c0..11259ccb 100644 --- a/tests/auto/timemanager/testtimemanager.cpp +++ b/tests/auto/timemanager/testtimemanager.cpp @@ -25,10 +25,13 @@ #include "devicemanager.h" #include "mocktcpserver.h" -#include -#include #include #include +#include +#include +#include +#include +#include using namespace guhserver; @@ -43,7 +46,14 @@ private slots: void addTimeDescriptor_data(); void addTimeDescriptor(); + void testCalendarItemHourly(); + private: + void verifyRuleExecuted(const ActionTypeId &actionTypeId); + void verifyRuleNotExecuted(); + + void cleanupMockHistory(); + QVariantMap createTimeEventItem(const QString &time = QString(), const QVariantMap &repeatingOption = QVariantMap()) const; QVariantMap createTimeEventItem(const int &dateTime, const QVariantMap &repeatingOption = QVariantMap()) const; QVariantMap createTimeDescriptorTimeEvent(const QVariantMap &timeEventItem) const; @@ -53,7 +63,6 @@ private: QVariantMap createCalendarItem(const int &dateTime, const uint &duration = 0, const QVariantMap &repeatingOption = QVariantMap()) const; QVariantMap createTimeDescriptorCalendar(const QVariantMap &calendarItem) const; QVariantMap createTimeDescriptorCalendar(const QVariantList &calendarItems) const; - }; void TestTimeManager::changeTimeZone_data() @@ -114,6 +123,10 @@ void TestTimeManager::addTimeDescriptor_data() repeatingOptionMonthlyMultiple.insert("monthDays", QVariantList() << 20 << 14 << 5); // invalid RepeatingOptions + QVariantMap repeatingOptionInvalidNone; + repeatingOptionInvalidNone.insert("mode", "RepeatingModeNone"); + repeatingOptionInvalidNone.insert("monthDays", QVariantList() << 13 << 12 << 27); + QVariantMap repeatingOptionInvalidWeekly; repeatingOptionInvalidWeekly.insert("mode", "RepeatingModeWeekly"); repeatingOptionInvalidWeekly.insert("monthDays", QVariantList() << 12 << 2 << 7); @@ -168,6 +181,7 @@ void TestTimeManager::addTimeDescriptor_data() QTest::newRow("valid: timeEventItem - monthly - multiple days") << createTimeDescriptorTimeEvent(createTimeEventItem("23:00", repeatingOptionMonthlyMultiple)) << RuleEngine::RuleErrorNoError; QTest::newRow("invalid: calendarItem empty") << createTimeDescriptorCalendar(createCalendarItem()) << RuleEngine::RuleErrorInvalidCalendarItem; + QTest::newRow("invalid: calendarItem none") << createTimeDescriptorCalendar(createCalendarItem("00:12", 12, repeatingOptionInvalidNone)) << RuleEngine::RuleErrorInvalidRepeatingOption; QTest::newRow("invalid: calendarItem - dateTime + repeatingOption") << createTimeDescriptorCalendar(createCalendarItem(QDateTime::currentDateTime().toTime_t(), 5, repeatingOptionDaily)) << RuleEngine::RuleErrorInvalidCalendarItem; QTest::newRow("invalid: calendarItem invalid time") << createTimeDescriptorCalendar(createCalendarItem("35:80", 5)) << RuleEngine::RuleErrorInvalidCalendarItem; QTest::newRow("invalid: calendarItem invalid duration") << createTimeDescriptorCalendar(createCalendarItem("12:00", 0)) << RuleEngine::RuleErrorInvalidCalendarItem; @@ -179,6 +193,7 @@ void TestTimeManager::addTimeDescriptor_data() QTest::newRow("invalid: calendarItem - invalid monthdays (to big)") << createTimeDescriptorCalendar(createCalendarItem("13:13", 5, repeatingOptionInvalidMonthDays2)) << RuleEngine::RuleErrorInvalidRepeatingOption; QTest::newRow("invalid: timeEventItem empty") << createTimeDescriptorTimeEvent(createTimeEventItem()) << RuleEngine::RuleErrorInvalidTimeEventItem; + QTest::newRow("invalid: timeEventItem none") << createTimeDescriptorTimeEvent(createTimeEventItem("00:12", repeatingOptionInvalidNone)) << RuleEngine::RuleErrorInvalidRepeatingOption; QTest::newRow("invalid: timeEventItem - dateTime + repeatingOption") << createTimeDescriptorTimeEvent(createTimeEventItem(QDateTime::currentDateTime().toTime_t(), repeatingOptionDaily)) << RuleEngine::RuleErrorInvalidTimeEventItem; QTest::newRow("invalid: timeEventItem invalid time") << createTimeDescriptorTimeEvent(createTimeEventItem("35:80")) << RuleEngine::RuleErrorInvalidTimeEventItem; QTest::newRow("invalid: timeEventItem - monthly - weekDays") << createTimeDescriptorTimeEvent(createTimeEventItem("13:13", repeatingOptionInvalidMonthly)) << RuleEngine::RuleErrorInvalidRepeatingOption; @@ -211,11 +226,120 @@ void TestTimeManager::addTimeDescriptor() // Print rule RuleId newRuleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toString()); - QVariantMap params; - params.insert("ruleId", newRuleId); - response = injectAndWait("Rules.GetRuleDetails", params); - QVariantMap rule = response.toMap().value("params").toMap().value("rule").toMap(); - qDebug() << QJsonDocument::fromVariant(rule).toJson(); + + // REMOVE rule + QVariantMap removeParams; + removeParams.insert("ruleId", newRuleId); + response = injectAndWait("Rules.RemoveRule", removeParams); + verifyRuleError(response); +} + +void TestTimeManager::testCalendarItemHourly() +{ + GuhCore::instance()->timeManager()->stopTimer(); + qDebug() << GuhCore::instance()->timeManager()->currentDate().toString(); + qDebug() << GuhCore::instance()->timeManager()->currentTime().toString(); + qDebug() << GuhCore::instance()->timeManager()->currentDateTime().toString(); + + QVariantMap ruleMap; QVariantMap action; QVariantMap exitAction; QVariantMap repeatingOptionHourly; + repeatingOptionHourly.insert("mode", "RepeatingModeHourly"); + action.insert("actionTypeId", mockActionIdNoParams); + action.insert("deviceId", m_mockDeviceId); + action.insert("ruleActionParams", QVariantList()); + exitAction.insert("actionTypeId", mockActionIdWithParams); + QVariantList actionParams; + QVariantMap param1; + param1.insert("name", "mockActionParam1"); + param1.insert("value", 5); + actionParams.append(param1); + QVariantMap param2; + param2.insert("name", "mockActionParam2"); + param2.insert("value", true); + actionParams.append(param2); + exitAction.insert("deviceId", m_mockDeviceId); + exitAction.insert("ruleActionParams", actionParams); + ruleMap.insert("name", "Time based hourly calendar rule"); + ruleMap.insert("timeDescriptor", createTimeDescriptorCalendar(createCalendarItem("08:05", 5))); + ruleMap.insert("actions", QVariantList() << action); + ruleMap.insert("exitActions", QVariantList() << exitAction); + + QVariant response = injectAndWait("Rules.AddRule", ruleMap); + verifyRuleError(response); + RuleId ruleId = RuleId(response.toMap().value("params").toMap().value("ruleId").toString()); + + QDateTime currentDateTime = GuhCore::instance()->timeManager()->currentDateTime(); + + // check the next 24 hours + QDateTime future = QDateTime(currentDateTime.date(), QTime(8, 4)); + for (int i = 0; i < 24; i++) { + // inactive + GuhCore::instance()->timeManager()->setTime(future); + verifyRuleNotExecuted(); + // active + GuhCore::instance()->timeManager()->setTime(QDateTime(currentDateTime.date(), QTime(future.time().hour(), 5))); + verifyRuleExecuted(mockActionIdNoParams); + cleanupMockHistory(); + // active unchanged + GuhCore::instance()->timeManager()->setTime(QDateTime(currentDateTime.date(), QTime(future.time().hour(), 7))); + verifyRuleNotExecuted(); + // inactive + GuhCore::instance()->timeManager()->setTime(QDateTime(currentDateTime.date(), QTime(future.time().hour(), 10))); + verifyRuleExecuted(mockActionIdWithParams); + cleanupMockHistory(); + // inactive unchanged + GuhCore::instance()->timeManager()->setTime(QDateTime(currentDateTime.date(), QTime(future.time().hour(), 11))); + verifyRuleNotExecuted(); + + // One hour "Back to the future" + future = future.addSecs(60*60); + } + + // REMOVE rule + QVariantMap removeParams; + removeParams.insert("ruleId", ruleId); + response = injectAndWait("Rules.RemoveRule", removeParams); + verifyRuleError(response); +} + +void TestTimeManager::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(QString::number(m_mockDevice1Port)))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + + QByteArray actionHistory = reply->readAll(); + qDebug() << "have action history" << actionHistory; + QVERIFY2(actionTypeId == ActionTypeId(actionHistory), "Action not triggered"); + reply->deleteLater(); +} + +void TestTimeManager::verifyRuleNotExecuted() +{ + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/actionhistory").arg(QString::number(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 TestTimeManager::cleanupMockHistory() { + QNetworkAccessManager nam; + QSignalSpy spy(&nam, SIGNAL(finished(QNetworkReply*))); + QNetworkRequest request(QUrl(QString("http://localhost:%1/clearactionhistory").arg(QString::number(m_mockDevice1Port)))); + QNetworkReply *reply = nam.get(request); + spy.wait(); + QCOMPARE(spy.count(), 1); + reply->deleteLater(); } QVariantMap TestTimeManager::createTimeEventItem(const QString &time, const QVariantMap &repeatingOption) const