Commit 0b28967aa4b875c627b521fb94313df82d3aff5b

Authored by Jojo-1000
Committed by Moritz Wirger
1 parent 765b3315

Add tests for rule, fix bugs.

include/hueplusplus/Rule.h
... ... @@ -141,6 +141,10 @@ private:
141 141 class CreateRule
142 142 {
143 143 public:
  144 + //! \brief Construct with necessary parameters
  145 + //! \param conditions Conditions for the rule. Must not be empty
  146 + //! \param actions Actions for the rule. Must not be empty
  147 + CreateRule(const std::vector<Condition>& conditions, const std::vector<Action>& actions);
144 148 //! \brief Set name
145 149 //! \see Rule::setName
146 150 CreateRule& setName(const std::string& name);
... ... @@ -149,13 +153,6 @@ public:
149 153 //! \see Rule::setEnabled
150 154 CreateRule& setStatus(bool enabled);
151 155  
152   - //! \brief Set conditions
153   - //! \see Rule::setConditions
154   - CreateRule& setConditions(const std::vector<Condition>& conditions);
155   - //! \brief Set actions
156   - //! \see Rule::setActions
157   - CreateRule& setActions(const std::vector<Action>& actions);
158   -
159 156 //! \brief Get request to create the rule.
160 157 //! \returns JSON request for a POST to create the new rule.
161 158 nlohmann::json getRequest() const;
... ...
include/hueplusplus/TimePattern.h
... ... @@ -85,7 +85,7 @@ public:
85 85  
86 86 //! \brief Get formatted string as expected by Hue API
87 87 //! \returns Timestamp in the format
88   - //! <code>YYYY-MM-DD</code><strong>T</strong><code>hh:mm:ss</code>
  88 + //! <code>YYYY-MM-DD</code><strong>T</strong><code>hh:mm:ss</code> in local timezone
89 89 std::string toString() const;
90 90  
91 91 //! \brief Parse AbsoluteTime from formatted string in local timezone
... ...
src/CMakeLists.txt
1 1 set(hueplusplus_SOURCES
  2 + Action.cpp
2 3 APICache.cpp
3 4 BaseDevice.cpp
4 5 BaseHttpHandler.cpp
5 6 Bridge.cpp
6 7 BridgeConfig.cpp
  8 + CLIPSensors.cpp
7 9 ColorUnits.cpp
8 10 ExtendedColorHueStrategy.cpp
9 11 ExtendedColorTemperatureStrategy.cpp
... ... @@ -13,6 +15,8 @@ set(hueplusplus_SOURCES
13 15 HueException.cpp
14 16 Light.cpp
15 17 ModelPictures.cpp
  18 + NewDeviceList.cpp
  19 + Rule.cpp
16 20 Scene.cpp
17 21 Schedule.cpp
18 22 Sensor.cpp
... ... @@ -22,7 +26,8 @@ set(hueplusplus_SOURCES
22 26 StateTransaction.cpp
23 27 TimePattern.cpp
24 28 UPnP.cpp
25   - Utils.cpp "ZLLSensors.cpp" "CLIPSensors.cpp" "NewDeviceList.cpp" "Action.cpp")
  29 + Utils.cpp
  30 + ZLLSensors.cpp)
26 31  
27 32 # on windows we want to compile the WinHttpHandler
28 33 if(WIN32)
... ...
src/Rule.cpp
... ... @@ -120,7 +120,7 @@ Condition Condition::parse(const nlohmann::json&amp; json)
120 120 {
121 121 op = Operator::notIn;
122 122 }
123   - else
  123 + else if(opStr != "eq")
124 124 {
125 125 throw HueException(CURRENT_FILE_INFO, "Unknown condition operator: " + opStr);
126 126 }
... ... @@ -129,8 +129,10 @@ Condition Condition::parse(const nlohmann::json&amp; json)
129 129 }
130 130  
131 131 Rule::Rule(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration)
132   - : id(id), state("/rules/" + id, commands, refreshDuration)
133   -{ }
  132 + : id(id), state("/rules/" + std::to_string(id), commands, refreshDuration)
  133 +{
  134 + state.refresh();
  135 +}
134 136  
135 137 void Rule::refresh(bool force)
136 138 {
... ... @@ -163,12 +165,17 @@ void Rule::setName(const std::string&amp; name)
163 165  
164 166 time::AbsoluteTime Rule::getCreated() const
165 167 {
166   - return time::AbsoluteTime::parseUTC(state.getValue().at("creationtime").get<std::string>());
  168 + return time::AbsoluteTime::parseUTC(state.getValue().at("created").get<std::string>());
167 169 }
168 170  
169 171 time::AbsoluteTime Rule::getLastTriggered() const
170 172 {
171   - return time::AbsoluteTime::parseUTC(state.getValue().at("lasttriggered").get<std::string>());
  173 + const std::string lasttriggered = state.getValue().value("lasttriggered", "none");
  174 + if (lasttriggered.empty() || lasttriggered == "none")
  175 + {
  176 + return time::AbsoluteTime(std::chrono::system_clock::time_point(std::chrono::seconds(0)));
  177 + }
  178 + return time::AbsoluteTime::parseUTC(lasttriggered);
172 179 }
173 180  
174 181 int Rule::getTimesTriggered() const
... ... @@ -184,6 +191,7 @@ bool Rule::isEnabled() const
184 191 void Rule::setEnabled(bool enabled)
185 192 {
186 193 sendPutRequest({{"status", enabled ? "enabled" : "disabled"}}, CURRENT_FILE_INFO);
  194 + refresh(true);
187 195 }
188 196  
189 197 std::string Rule::getOwner() const
... ... @@ -222,6 +230,7 @@ void Rule::setConditions(const std::vector&lt;Condition&gt;&amp; conditions)
222 230 }
223 231  
224 232 sendPutRequest({{"conditions", json}}, CURRENT_FILE_INFO);
  233 + refresh(true);
225 234 }
226 235  
227 236 void Rule::setActions(const std::vector<Action>& actions)
... ... @@ -233,26 +242,15 @@ void Rule::setActions(const std::vector&lt;Action&gt;&amp; actions)
233 242 }
234 243  
235 244 sendPutRequest({{"actions", json}}, CURRENT_FILE_INFO);
  245 + refresh(true);
236 246 }
237 247  
238 248 nlohmann::json Rule::sendPutRequest(const nlohmann::json& request, FileInfo fileInfo)
239 249 {
240   - return state.getCommandAPI().PUTRequest("/groups/" + std::to_string(id), request, std::move(fileInfo));
241   -}
242   -
243   -CreateRule& CreateRule::setName(const std::string& name)
244   -{
245   - request["name"] = name;
246   - return *this;
  250 + return state.getCommandAPI().PUTRequest("/rules/" + std::to_string(id), request, std::move(fileInfo));
247 251 }
248 252  
249   -CreateRule& CreateRule::setStatus(bool enabled)
250   -{
251   - request["status"] = enabled ? "enabled" : "disabled";
252   - return *this;
253   -}
254   -
255   -CreateRule& CreateRule::setConditions(const std::vector<Condition>& conditions)
  253 +CreateRule::CreateRule(const std::vector<Condition>& conditions, const std::vector<Action>& actions)
256 254 {
257 255 nlohmann::json conditionsJson;
258 256 for (const Condition& c : conditions)
... ... @@ -260,17 +258,23 @@ CreateRule&amp; CreateRule::setConditions(const std::vector&lt;Condition&gt;&amp; conditions)
260 258 conditionsJson.push_back(c.toJson());
261 259 }
262 260 request["conditions"] = conditionsJson;
263   - return *this;
264   -}
265   -
266   -CreateRule& CreateRule::setActions(const std::vector<Action>& actions)
267   -{
268 261 nlohmann::json actionsJson;
269 262 for (const Action& a : actions)
270 263 {
271 264 actionsJson.push_back(a.toJson());
272 265 }
273 266 request["actions"] = actionsJson;
  267 +}
  268 +
  269 +CreateRule& CreateRule::setName(const std::string& name)
  270 +{
  271 + request["name"] = name;
  272 + return *this;
  273 +}
  274 +
  275 +CreateRule& CreateRule::setStatus(bool enabled)
  276 +{
  277 + request["status"] = enabled ? "enabled" : "disabled";
274 278 return *this;
275 279 }
276 280  
... ...
test/CMakeLists.txt
... ... @@ -30,6 +30,7 @@ target_compile_features(gtest PUBLIC cxx_std_14)
30 30  
31 31 # define all test sources
32 32 set(TEST_SOURCES
  33 + test_Action.cpp
33 34 test_APICache.cpp
34 35 test_BaseDevice.cpp
35 36 test_BaseHttpHandler.cpp
... ... @@ -44,8 +45,10 @@ set(TEST_SOURCES
44 45 test_Light.cpp
45 46 test_LightFactory.cpp
46 47 test_Main.cpp
  48 + test_NewDeviceList.cpp
47 49 test_UPnP.cpp
48 50 test_ResourceList.cpp
  51 + test_Rule.cpp
49 52 test_Scene.cpp
50 53 test_Schedule.cpp
51 54 test_Sensor.cpp
... ... @@ -54,7 +57,7 @@ set(TEST_SOURCES
54 57 test_SimpleColorHueStrategy.cpp
55 58 test_SimpleColorTemperatureStrategy.cpp
56 59 test_StateTransaction.cpp
57   - test_TimePattern.cpp "test_NewDeviceList.cpp" "test_Action.cpp")
  60 + test_TimePattern.cpp)
58 61  
59 62 set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include")
60 63  
... ...
test/test_Rule.cpp 0 โ†’ 100644
  1 +/**
  2 + \file test_Rule.cpp
  3 + Copyright Notice\n
  4 + Copyright (C) 2020 Jan Rogall - developer\n
  5 +
  6 + This file is part of hueplusplus.
  7 +
  8 + hueplusplus is free software: you can redistribute it and/or modify
  9 + it under the terms of the GNU Lesser General Public License as published by
  10 + the Free Software Foundation, either version 3 of the License, or
  11 + (at your option) any later version.
  12 +
  13 + hueplusplus is distributed in the hope that it will be useful,
  14 + but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16 + GNU Lesser General Public License for more details.
  17 +
  18 + You should have received a copy of the GNU Lesser General Public License
  19 + along with hueplusplus. If not, see <http://www.gnu.org/licenses/>.
  20 +**/
  21 +
  22 +#include <hueplusplus/Rule.h>
  23 +
  24 +#include <gtest/gtest.h>
  25 +
  26 +#include "testhelper.h"
  27 +
  28 +#include "mocks/mock_HttpHandler.h"
  29 +
  30 +using namespace hueplusplus;
  31 +using namespace testing;
  32 +
  33 +TEST(Condition, Constructor)
  34 +{
  35 + const std::string address = "/api/abcd/test";
  36 + const std::string value = "test value";
  37 + Condition condition(address, Condition::Operator::eq, value);
  38 + EXPECT_EQ(address, condition.getAddress());
  39 + EXPECT_EQ(Condition::Operator::eq, condition.getOperator());
  40 + EXPECT_EQ(value, condition.getValue());
  41 +}
  42 +
  43 +TEST(Condition, toJson)
  44 +{
  45 + Condition condition("/abcd", Condition::Operator::lt, "3");
  46 + EXPECT_EQ(nlohmann::json({{"address", "/abcd"}, {"operator", "lt"}, {"value", "3"}}), condition.toJson());
  47 +}
  48 +
  49 +TEST(Condition, parse)
  50 +{
  51 + Condition condition = Condition::parse(nlohmann::json({{"address", "/abcd"}, {"operator", "lt"}, {"value", "3"}}));
  52 + EXPECT_EQ("/abcd", condition.getAddress());
  53 + EXPECT_EQ(Condition::Operator::lt, condition.getOperator());
  54 + EXPECT_EQ("3", condition.getValue());
  55 + EXPECT_THROW(Condition::parse(nlohmann::json({{"address", "/abcd"}, {"operator", "something"}, {"value", "3"}})),
  56 + HueException);
  57 +}
  58 +
  59 +TEST(Condition, operatorString)
  60 +{
  61 + using Op = Condition::Operator;
  62 + std::map<Op, std::string> values = {{Op::eq, "eq"}, {Op::gt, "gt"}, {Op::lt, "lt"}, {Op::dx, "dx"},
  63 + {Op::ddx, "ddx"}, {Op::stable, "stable"}, {Op::notStable, "not stable"}, {Op::in, "in"}, {Op::notIn, "not in"}};
  64 +
  65 + for (const auto& pair : values)
  66 + {
  67 + Condition c("", pair.first, "");
  68 + // Check that correct string is
  69 + EXPECT_EQ(pair.second, c.toJson().at("operator"));
  70 + EXPECT_EQ(pair.first,
  71 + Condition::parse(nlohmann::json {{"address", "/abcd"}, {"operator", pair.second}, {"value", "3"}})
  72 + .getOperator());
  73 + }
  74 +}
  75 +
  76 +class RuleTest : public Test
  77 +{
  78 +protected:
  79 + std::shared_ptr<MockHttpHandler> handler;
  80 + HueCommandAPI commands;
  81 + nlohmann::json ruleState;
  82 +
  83 + RuleTest()
  84 + : handler(std::make_shared<MockHttpHandler>()),
  85 + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler),
  86 + ruleState({{"name", "Rule 1"}, {"owner", "testOwner"}, {"created", "2020-06-01T10:00:00"},
  87 + {"lasttriggered", "none"}, {"timestriggered", 0}, {"status", "enabled"},
  88 + {"conditions", {{{"address", "testAddress"}, {"operator", "eq"}, {"value", "10"}}}},
  89 + {"actions", {{{"address", "testAction"}, {"method", "PUT"}, {"body", {}}}}}})
  90 + { }
  91 +
  92 + void expectGetState(int id)
  93 + {
  94 + EXPECT_CALL(*handler,
  95 + GETJson("/api/" + getBridgeUsername() + "/rules/" + std::to_string(id), _, getBridgeIp(), getBridgePort()))
  96 + .WillOnce(Return(ruleState));
  97 + }
  98 +
  99 + Rule getRule(int id = 1)
  100 + {
  101 + expectGetState(id);
  102 + return Rule(id, commands, std::chrono::steady_clock::duration::max());
  103 + }
  104 +};
  105 +
  106 +TEST_F(RuleTest, getName)
  107 +{
  108 + const std::string name = "Rule name";
  109 + ruleState["name"] = name;
  110 + const Rule rule = getRule();
  111 + EXPECT_EQ(name, rule.getName());
  112 +}
  113 +
  114 +TEST_F(RuleTest, setName)
  115 +{
  116 + Rule rule = getRule();
  117 + const std::string name = "Test rule";
  118 + nlohmann::json request = {{"name", name}};
  119 + nlohmann::json response = {{"success", {"/rules/1/name", name}}};
  120 + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/rules/1", request, getBridgeIp(), getBridgePort()))
  121 + .WillOnce(Return(response));
  122 + expectGetState(1);
  123 + rule.setName(name);
  124 +}
  125 +
  126 +TEST_F(RuleTest, getCreated)
  127 +{
  128 + const std::string timestamp = "2020-06-01T10:00:00";
  129 + ruleState["created"] = timestamp;
  130 + const Rule rule = getRule();
  131 + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), rule.getCreated().getBaseTime());
  132 +}
  133 +
  134 +TEST_F(RuleTest, getLastTriggered)
  135 +{
  136 + const std::string timestamp = "2020-06-01T10:00:00";
  137 + ruleState["lasttriggered"] = timestamp;
  138 + const Rule rule = getRule();
  139 + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), rule.getLastTriggered().getBaseTime());
  140 + ruleState["lasttriggered"] = "none";
  141 + const Rule rule2 = getRule();
  142 + EXPECT_EQ(std::chrono::system_clock::time_point(std::chrono::seconds(0)), rule2.getLastTriggered().getBaseTime());
  143 +}
  144 +
  145 +TEST_F(RuleTest, getTimesTriggered)
  146 +{
  147 + const int times = 20;
  148 + ruleState["timestriggered"] = times;
  149 + EXPECT_EQ(times, getRule().getTimesTriggered());
  150 +}
  151 +
  152 +TEST_F(RuleTest, isEnabled)
  153 +{
  154 + ruleState["status"] = "enabled";
  155 + EXPECT_TRUE(getRule().isEnabled());
  156 + ruleState["status"] = "disabled";
  157 + EXPECT_FALSE(getRule().isEnabled());
  158 +}
  159 +
  160 +TEST_F(RuleTest, setEnabled)
  161 +{
  162 + Rule rule = getRule();
  163 + {
  164 + nlohmann::json request = {{"status", "enabled"}};
  165 + nlohmann::json response = {{"success", {"/rules/1/status", "enabled"}}};
  166 + EXPECT_CALL(
  167 + *handler, PUTJson("/api/" + getBridgeUsername() + "/rules/1", request, getBridgeIp(), getBridgePort()))
  168 + .WillOnce(Return(response));
  169 + expectGetState(1);
  170 + rule.setEnabled(true);
  171 + }
  172 + {
  173 + nlohmann::json request = {{"status", "disabled"}};
  174 + nlohmann::json response = {{"success", {"/rules/1/status", "disabled"}}};
  175 + EXPECT_CALL(
  176 + *handler, PUTJson("/api/" + getBridgeUsername() + "/rules/1", request, getBridgeIp(), getBridgePort()))
  177 + .WillOnce(Return(response));
  178 + expectGetState(1);
  179 + rule.setEnabled(false);
  180 + }
  181 +}
  182 +
  183 +TEST_F(RuleTest, getOwner)
  184 +{
  185 + const std::string owner = "testowner";
  186 + ruleState["owner"] = owner;
  187 + EXPECT_EQ(owner, getRule().getOwner());
  188 +}
  189 +
  190 +TEST_F(RuleTest, getConditions)
  191 +{
  192 + std::vector<Condition> conditions
  193 + = {Condition("/a/b/c", Condition::Operator::eq, "12"), Condition("/d/c", Condition::Operator::dx, "")};
  194 + ruleState["conditions"] = {conditions[0].toJson(), conditions[1].toJson()};
  195 + const std::vector<Condition> result = getRule().getConditions();
  196 + ASSERT_EQ(2, result.size());
  197 + EXPECT_EQ(conditions[0].toJson(), result[0].toJson());
  198 + EXPECT_EQ(conditions[1].toJson(), result[1].toJson());
  199 +}
  200 +
  201 +TEST_F(RuleTest, getActions)
  202 +{
  203 + nlohmann::json action0 {{"address", "/a/b"}, {"method", "PUT"}, {"body", {{"value", "test"}}}};
  204 + nlohmann::json action1 {{"address", "/c/d"}, {"method", "POST"}, {"body", {{"32", 1}}}};
  205 +
  206 + ruleState["actions"] = {action0, action1};
  207 + const std::vector<hueplusplus::Action> result = getRule().getActions();
  208 + ASSERT_EQ(2, result.size());
  209 + EXPECT_EQ(action0, result[0].toJson());
  210 + EXPECT_EQ(action1, result[1].toJson());
  211 +}
  212 +
  213 +TEST_F(RuleTest, setConditions)
  214 +{
  215 + std::vector<Condition> conditions
  216 + = {Condition("/a/b/c", Condition::Operator::eq, "12"), Condition("/d/c", Condition::Operator::dx, "")};
  217 + const nlohmann::json request = {{"conditions", {conditions[0].toJson(), conditions[1].toJson()}}};
  218 +
  219 + Rule rule = getRule();
  220 + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/rules/1", request, getBridgeIp(), getBridgePort()));
  221 + expectGetState(1);
  222 + rule.setConditions(conditions);
  223 +}
  224 +
  225 +TEST_F(RuleTest, setActions)
  226 +{
  227 + using hueplusplus::Action;
  228 + nlohmann::json action0 {{"address", "/a/b"}, {"method", "PUT"}, {"body", {{"value", "test"}}}};
  229 + nlohmann::json action1 {{"address", "/c/d"}, {"method", "POST"}, {"body", {{"32", 1}}}};
  230 + const nlohmann::json request = {{"actions", {action0, action1}}};
  231 +
  232 + const std::vector<Action> actions = {Action(action0), Action(action1)};
  233 +
  234 + Rule rule = getRule();
  235 + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/rules/1", request, getBridgeIp(), getBridgePort()));
  236 + expectGetState(1);
  237 + rule.setActions(actions);
  238 +}
  239 +
  240 +TEST(CreateRule, setName)
  241 +{
  242 + const std::string name = "New rule";
  243 + const nlohmann::json request = {{"conditions", {}}, {"actions", {}}, {"name", name}};
  244 + EXPECT_EQ(request, CreateRule({}, {}).setName(name).getRequest());
  245 +}
  246 +
  247 +TEST(CreateRule, setStatus)
  248 +{
  249 + {
  250 + const nlohmann::json request = {{"conditions", {}}, {"actions", {}}, {"status", "enabled"}};
  251 + EXPECT_EQ(request, CreateRule({}, {}).setStatus(true).getRequest());
  252 + }
  253 + {
  254 + const nlohmann::json request = {{"conditions", {}}, {"actions", {}}, {"status", "disabled"}};
  255 + EXPECT_EQ(request, CreateRule({}, {}).setStatus(false).getRequest());
  256 + }
  257 +}
  258 +
  259 +TEST(CreateRule, Constructor)
  260 +{
  261 + using hueplusplus::Action;
  262 + std::vector<Condition> conditions
  263 + = {Condition("/a/b/c", Condition::Operator::eq, "12"), Condition("/d/c", Condition::Operator::dx, "")};
  264 + nlohmann::json action0 {{"address", "/a/b"}, {"method", "PUT"}, {"body", {{"value", "test"}}}};
  265 + nlohmann::json action1 {{"address", "/c/d"}, {"method", "POST"}, {"body", {{"32", 1}}}};
  266 + const std::vector<Action> actions = {Action(action0), Action(action1)};
  267 + const nlohmann::json request
  268 + = {{"conditions", {conditions[0].toJson(), conditions[1].toJson()}}, {"actions", {action0, action1}}};
  269 + EXPECT_EQ(request, CreateRule(conditions, actions).getRequest());
  270 +}
... ...