diff --git a/include/hueplusplus/Group.h b/include/hueplusplus/Group.h index 8e7426e..7ac73d6 100644 --- a/include/hueplusplus/Group.h +++ b/include/hueplusplus/Group.h @@ -91,7 +91,7 @@ public: void incrementHue(int increment, uint8_t transition = 4); void incrementColorTemperature(int increment, uint8_t transition = 4); void incrementColorXY(float incX, float incY, uint8_t transition = 4); - void setScene(const std::string& scene, uint8_t transition = 4); + void setScene(const std::string& scene); protected: nlohmann::json SendPutRequest(const nlohmann::json& request, const std::string& subPath, FileInfo fileInfo); @@ -101,6 +101,27 @@ protected: APICache state; HueCommandAPI commands; }; + +class CreateGroup +{ +public: + static CreateGroup LightGroup(const std::vector& lights, const std::string& name = ""); + static CreateGroup Room( + const std::vector& lights, const std::string& name = "", const std::string& roomType = ""); + static CreateGroup Entertainment(const std::vector& lights, const std::string& name = ""); + + nlohmann::json getRequest() const; + +protected: + CreateGroup( + const std::vector& lights, const std::string& name, const std::string& type, const std::string& roomType); + +private: + std::vector lights; + std::string name; + std::string type; + std::string roomType; +}; } // namespace hueplusplus #endif \ No newline at end of file diff --git a/include/hueplusplus/Hue.h b/include/hueplusplus/Hue.h index 8b05f3e..1fcb581 100644 --- a/include/hueplusplus/Hue.h +++ b/include/hueplusplus/Hue.h @@ -224,9 +224,12 @@ public: std::vector> getAllGroups(); Group& getGroup(int id); + bool removeGroup(int id); bool groupExists(int id); bool groupExists(int id) const; + int createGroup(const CreateGroup& params); + //! \brief Const function that returns the picture name of a given light id //! //! \note This will not update the local state of the bridge. diff --git a/include/hueplusplus/HueCommandAPI.h b/include/hueplusplus/HueCommandAPI.h index 405b6b0..1cb6cb6 100644 --- a/include/hueplusplus/HueCommandAPI.h +++ b/include/hueplusplus/HueCommandAPI.h @@ -96,6 +96,18 @@ public: nlohmann::json DELETERequest(const std::string& path, const nlohmann::json& request) const; nlohmann::json DELETERequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const; + //! \brief Sends a HTTP POST request to the bridge and returns the response + //! + //! This function will block until at least \ref minDelay has passed to any previous request + //! \param path API request path (appended after /api/{username}) + //! \param request Request to the api, may be empty + //! \returns The return value of the underlying \ref IHttpHandler::POSTJson call + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contains no body + //! \throws HueAPIResponseException when response contains an error + nlohmann::json POSTRequest(const std::string& path, const nlohmann::json& request) const; + nlohmann::json POSTRequest(const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const; + private: struct TimeoutData { diff --git a/src/Group.cpp b/src/Group.cpp index 18f6419..21e04e9 100644 --- a/src/Group.cpp +++ b/src/Group.cpp @@ -46,6 +46,7 @@ void Group::setName(const std::string& name) { nlohmann::json request = {{"name", name}}; SendPutRequest(request, "", CURRENT_FILE_INFO); + Refresh(); } void Group::setLights(const std::vector& ids) @@ -56,6 +57,7 @@ void Group::setLights(const std::vector& ids) lights.push_back(std::to_string(id)); } SendPutRequest({{"lights", lights}}, "", CURRENT_FILE_INFO); + Refresh(); } bool Group::getAllOn() @@ -144,12 +146,7 @@ StateTransaction Group::transaction() void Group::setOn(bool on, uint8_t transition) { - nlohmann::json request = {{"on", on}}; - if (transition != 4) - { - request["transition"] = transition; - } - SendPutRequest(request, "/action", CURRENT_FILE_INFO); + transaction().setOn(on).setTransition(transition).commit(); } void Group::setBrightness(uint8_t brightness, uint8_t transition) @@ -202,7 +199,7 @@ void Group::incrementColorXY(float incX, float incY, uint8_t transition) transaction().incrementColorXY(incX, incY).setTransition(transition).commit(); } -void Group::setScene(const std::string& scene, uint8_t transition) +void Group::setScene(const std::string& scene) { SendPutRequest({{"scene", scene}}, "/action", CURRENT_FILE_INFO); } @@ -220,6 +217,7 @@ std::string Group::getRoomType() const void Group::setRoomType(const std::string& type) { SendPutRequest({{"class", type}}, "", CURRENT_FILE_INFO); + Refresh(); } std::string Group::getModelId() const @@ -231,4 +229,44 @@ std::string Group::getUniqueId() const { return state.GetValue().at("uniqueid").get(); } + +CreateGroup CreateGroup::LightGroup(const std::vector& lights, const std::string& name) +{ + return CreateGroup(lights, name, "LightGroup", ""); +} + +CreateGroup CreateGroup::Room(const std::vector& lights, const std::string& name, const std::string& roomType) +{ + return CreateGroup(lights, name, "Room", roomType); +} + +CreateGroup CreateGroup::Entertainment(const std::vector& lights, const std::string& name) +{ + return CreateGroup(lights, name, "Entertainment", ""); +} + +nlohmann::json CreateGroup::getRequest() const +{ + nlohmann::json lightStrings = nlohmann::json::array(); + for (int light : lights) + { + lightStrings.push_back(std::to_string(light)); + } + nlohmann::json result = {{"lights", lightStrings}, {"type", type}}; + if (!name.empty()) + { + result["name"] = name; + } + if (!roomType.empty()) + { + result["class"] = roomType; + } + return result; +} + +CreateGroup::CreateGroup( + const std::vector& lights, const std::string& name, const std::string& type, const std::string& roomType) + : lights(lights), name(name), type(type), roomType(roomType) +{} + } // namespace hueplusplus diff --git a/src/Hue.cpp b/src/Hue.cpp index d1525a8..488541a 100644 --- a/src/Hue.cpp +++ b/src/Hue.cpp @@ -307,6 +307,18 @@ Group& Hue::getGroup(int id) return groups.emplace(id, Group(id, commands, stateCache.GetRefreshDuration())).first->second; } +bool Hue::removeGroup(int id) +{ + nlohmann::json result + = commands.DELETERequest("/groups/" + std::to_string(id), nlohmann::json::object(), CURRENT_FILE_INFO); + bool success = utils::safeGetMember(result, 0, "success") == "/groups/" + std::to_string(id) + " deleted"; + if (success && groups.count(id) != 0) + { + groups.erase(id); + } + return success; +} + bool Hue::groupExists(int id) { auto pos = lights.find(id); @@ -335,6 +347,17 @@ bool Hue::groupExists(int id) const return false; } +int Hue::createGroup(const CreateGroup& params) +{ + nlohmann::json response = commands.POSTRequest("/groups", params.getRequest(), CURRENT_FILE_INFO); + nlohmann::json id = utils::safeGetMember(response, 0, "success", "id"); + if (id.is_string()) + { + return std::stoi(id.get()); + } + return 0; +} + bool Hue::lightExists(int id) { auto pos = lights.find(id); diff --git a/src/HueCommandAPI.cpp b/src/HueCommandAPI.cpp index 7562c1c..93b5349 100644 --- a/src/HueCommandAPI.cpp +++ b/src/HueCommandAPI.cpp @@ -70,7 +70,7 @@ HueCommandAPI::HueCommandAPI( port(port), username(username), httpHandler(std::move(httpHandler)), - timeout(new TimeoutData{std::chrono::steady_clock::now(), {}}) + timeout(new TimeoutData {std::chrono::steady_clock::now(), {}}) {} nlohmann::json HueCommandAPI::PUTRequest(const std::string& path, const nlohmann::json& request) const @@ -81,8 +81,9 @@ nlohmann::json HueCommandAPI::PUTRequest(const std::string& path, const nlohmann nlohmann::json HueCommandAPI::PUTRequest( const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const { - return HandleError(std::move(fileInfo), - RunWithTimeout(timeout, minDelay, [&]() { return httpHandler->PUTJson(CombinedPath(path), request, ip); })); + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, minDelay, [&]() { + return httpHandler->PUTJson(CombinedPath(path), request, ip, port); + })); } nlohmann::json HueCommandAPI::GETRequest(const std::string& path, const nlohmann::json& request) const @@ -93,8 +94,9 @@ nlohmann::json HueCommandAPI::GETRequest(const std::string& path, const nlohmann nlohmann::json HueCommandAPI::GETRequest( const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const { - return HandleError(std::move(fileInfo), - RunWithTimeout(timeout, minDelay, [&]() { return httpHandler->GETJson(CombinedPath(path), request, ip); })); + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, minDelay, [&]() { + return httpHandler->GETJson(CombinedPath(path), request, ip, port); + })); } nlohmann::json HueCommandAPI::DELETERequest(const std::string& path, const nlohmann::json& request) const @@ -105,8 +107,22 @@ nlohmann::json HueCommandAPI::DELETERequest(const std::string& path, const nlohm nlohmann::json HueCommandAPI::DELETERequest( const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const { - return HandleError(std::move(fileInfo), - RunWithTimeout(timeout, minDelay, [&]() { return httpHandler->DELETEJson(CombinedPath(path), request, ip); })); + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, minDelay, [&]() { + return httpHandler->DELETEJson(CombinedPath(path), request, ip, port); + })); +} + +nlohmann::json HueCommandAPI::POSTRequest(const std::string& path, const nlohmann::json& request) const +{ + return POSTRequest(path, request, CURRENT_FILE_INFO); +} + +nlohmann::json HueCommandAPI::POSTRequest( + const std::string& path, const nlohmann::json& request, FileInfo fileInfo) const +{ + return HandleError(std::move(fileInfo), RunWithTimeout(timeout, minDelay, [&]() { + return httpHandler->POSTJson(CombinedPath(path), request, ip, port); + })); } nlohmann::json HueCommandAPI::HandleError(FileInfo fileInfo, const nlohmann::json& response) const diff --git a/src/HueLight.cpp b/src/HueLight.cpp index 3ff0c87..52294ef 100644 --- a/src/HueLight.cpp +++ b/src/HueLight.cpp @@ -112,6 +112,7 @@ bool HueLight::setName(const std::string& name) nlohmann::json request = nlohmann::json::object(); request["name"] = name; nlohmann::json reply = SendPutRequest(request, "/name", CURRENT_FILE_INFO); + state.Refresh(); // Check whether request was successful (returned name is not necessarily the actually set name) // If it already exists, a number is added, if it is too long to be returned, "Updated" is returned diff --git a/test/test_Group.cpp b/test/test_Group.cpp index e40094a..2df8058 100644 --- a/test/test_Group.cpp +++ b/test/test_Group.cpp @@ -155,7 +155,7 @@ TEST_F(GroupTest, getActionHueSaturation) const int id = 1; expectGetState(id); Group group(id, commands, std::chrono::steady_clock::duration::max()); - std::pair hueSat{hue, sat}; + std::pair hueSat {hue, sat}; EXPECT_EQ(hueSat, group.getActionHueSaturation()); EXPECT_EQ(hueSat, Const(group).getActionHueSaturation()); } @@ -183,7 +183,7 @@ TEST_F(GroupTest, getActionColorXY) const int id = 1; expectGetState(id); Group group(id, commands, std::chrono::steady_clock::duration::max()); - std::pair xy{x, y}; + std::pair xy {x, y}; EXPECT_EQ(xy, group.getActionColorXY()); EXPECT_EQ(xy, Const(group).getActionColorXY()); } @@ -196,3 +196,86 @@ TEST_F(GroupTest, getActionColorMode) EXPECT_EQ(colormode, group.getActionColorMode()); EXPECT_EQ(colormode, Const(group).getActionColorMode()); } + +TEST_F(GroupTest, setName) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string name = "Test group"; + nlohmann::json request = {{"name", name}}; + nlohmann::json response = {{"success", {"/groups/1/name", name}}}; + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + group.setName(name); +} + +TEST_F(GroupTest, setLights) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const nlohmann::json lights = {"2", "4", "5"}; + nlohmann::json request = {{"lights", lights}}; + nlohmann::json response = {{"success", {"/groups/1/lights", lights}}}; + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + group.setLights(std::vector {2, 4, 5}); +} + +TEST_F(GroupTest, setRoomType) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string type = "LivingRoom"; + nlohmann::json request = {{"class", type}}; + nlohmann::json response = {{"success", {"/groups/1/class", type}}}; + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + expectGetState(id); + group.setRoomType(type); +} + +TEST_F(GroupTest, setScene) +{ + const int id = 1; + expectGetState(id); + Group group(id, commands, std::chrono::steady_clock::duration::max()); + const std::string scene = "testScene"; + nlohmann::json request = {{"scene", scene}}; + nlohmann::json response = {{"success", {"/groups/1/action/scene", scene}}}; + EXPECT_CALL( + *handler, PUTJson("/api/" + getBridgeUsername() + "/groups/1/action", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + (id); + group.setScene(scene); +} + +TEST(CreateGroup, LightGroup) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "LightGroup"}, {"name", "Name"}}), + CreateGroup::LightGroup({1}, "Name").getRequest()); + EXPECT_EQ( + nlohmann::json({{"lights", {"2", "4"}}, {"type", "LightGroup"}}), CreateGroup::LightGroup({2, 4}).getRequest()); +} + +TEST(CreateGroup, Entertainment) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Entertainment"}, {"name", "Name"}}), + CreateGroup::Entertainment({1}, "Name").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"2", "4"}}, {"type", "Entertainment"}}), + CreateGroup::Entertainment({2, 4}).getRequest()); +} + +TEST(CreateGroup, Room) +{ + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Room"}, {"name", "Name"}, {"class", "Bedroom"}}), + CreateGroup::Room({1}, "Name", "Bedroom").getRequest()); + EXPECT_EQ(nlohmann::json({{"lights", {"1"}}, {"type", "Room"}, {"name", "Name"}}), + CreateGroup::Room({1}, "Name").getRequest()); + EXPECT_EQ( + nlohmann::json({{"lights", {"2", "4"}}, {"type", "Room"}}), CreateGroup::Room({2, 4}).getRequest()); +} \ No newline at end of file diff --git a/test/test_Hue.cpp b/test/test_Hue.cpp index 8312b0f..147c730 100644 --- a/test/test_Hue.cpp +++ b/test/test_Hue.cpp @@ -58,7 +58,7 @@ protected: .Times(AtLeast(1)) .WillRepeatedly(Return(getBridgeXml())); } - ~HueFinderTest(){}; + ~HueFinderTest() {}; }; TEST_F(HueFinderTest, FindBridges) @@ -87,7 +87,7 @@ TEST_F(HueFinderTest, FindBridges) TEST_F(HueFinderTest, GetBridge) { using namespace ::testing; - nlohmann::json request{{"devicetype", "HuePlusPlus#User"}}; + nlohmann::json request {{"devicetype", "HuePlusPlus#User"}}; nlohmann::json errorResponse = {{{"error", {{"type", 101}, {"address", ""}, {"description", "link button not pressed"}}}}}; @@ -117,7 +117,7 @@ TEST_F(HueFinderTest, GetBridge) EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; // Verify that username is correctly set in api requests - nlohmann::json hue_bridge_state{{"lights", {}}}; + nlohmann::json hue_bridge_state {{"lights", {}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(1) @@ -166,7 +166,7 @@ TEST(Hue, requestUsername) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json request{{"devicetype", "HuePlusPlus#User"}}; + nlohmann::json request {{"devicetype", "HuePlusPlus#User"}}; { nlohmann::json errorResponse @@ -223,7 +223,7 @@ TEST(Hue, requestUsername) EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; // Verify that username is correctly set in api requests - nlohmann::json hue_bridge_state{{"lights", {}}}; + nlohmann::json hue_bridge_state {{"lights", {}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) .Times(1) @@ -264,7 +264,7 @@ TEST(Hue, getLight) // Test exception ASSERT_THROW(test_bridge.getLight(1), HueException); - nlohmann::json hue_bridge_state{{"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, @@ -396,7 +396,7 @@ TEST(Hue, removeLight) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{{"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, @@ -439,7 +439,7 @@ TEST(Hue, getAllLights) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{{"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, @@ -470,7 +470,7 @@ TEST(Hue, lightExists) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{{"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, @@ -502,11 +502,182 @@ TEST(Hue, lightExists) EXPECT_EQ(true, const_test_bridge2.lightExists(1)); } +TEST(Hue, getGroup) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1); + + Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // Test exception + ASSERT_THROW(test_bridge.getGroup(1), HueException); + + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillOnce(Return(hue_bridge_state)); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); + + // Refresh cache + test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // Test when correct data is sent + Group test_group_1 = test_bridge.getGroup(1); + EXPECT_EQ(test_group_1.getName(), "Group 1"); + EXPECT_EQ(test_group_1.getType(), "LightGroup"); + + // Test again to check whether group is returned directly + test_group_1 = test_bridge.getGroup(1); + EXPECT_EQ(test_group_1.getName(), "Group 1"); + EXPECT_EQ(test_group_1.getType(), "LightGroup"); +} + +TEST(Hue, removeGroup) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillOnce(Return(hue_bridge_state)); + + Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(1) + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); + + nlohmann::json return_answer; + return_answer = nlohmann::json::array(); + return_answer[0] = nlohmann::json::object(); + return_answer[0]["success"] = "/groups/1 deleted"; + EXPECT_CALL(*handler, + DELETEJson( + "/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillOnce(Return(return_answer)) + .WillOnce(Return(nlohmann::json())); + + // Test when correct data is sent + Group test_group_1 = test_bridge.getGroup(1); + + EXPECT_EQ(test_bridge.removeGroup(1), true); + + EXPECT_EQ(test_bridge.removeGroup(1), false); +} + +TEST(Hue, groupExists) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); + + Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + EXPECT_EQ(true, test_bridge.groupExists(1)); + EXPECT_EQ(false, test_bridge.groupExists(2)); + + const Hue const_test_bridge1 = test_bridge; + EXPECT_EQ(true, const_test_bridge1.groupExists(1)); + EXPECT_EQ(false, const_test_bridge1.groupExists(2)); + + test_bridge.getGroup(1); + const Hue const_test_bridge2 = test_bridge; + EXPECT_EQ(true, test_bridge.groupExists(1)); + EXPECT_EQ(true, const_test_bridge2.groupExists(1)); +} + +TEST(Hue, getAllGroups) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + nlohmann::json hue_bridge_state {{"groups", + {{"1", + {{"name", "Group 1"}, {"type", "LightGroup"}, {"lights", {"1", "2", "3"}}, + {"action", + {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"}, {"hue", 200}, + {"sat", 254}, {"effect", "none"}, {"xy", {0.f, 0.f}}}}, + {"state", {{"any_on", true}, {"all_on", true}}}}}}}}; + + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state)); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(AtLeast(1)) + .WillRepeatedly(Return(hue_bridge_state["groups"]["1"])); + + Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + std::vector> test_groups = test_bridge.getAllGroups(); + ASSERT_EQ(1, test_groups.size()); + EXPECT_EQ(test_groups[0].get().getName(), "Group 1"); + EXPECT_EQ(test_groups[0].get().getType(), "LightGroup"); +} + +TEST(Hue, createGroup) +{ + using namespace ::testing; + std::shared_ptr handler = std::make_shared(); + Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + CreateGroup create = CreateGroup::Room({2, 3}, "Nice room", "LivingRoom"); + nlohmann::json request = create.getRequest(); + const int id = 4; + nlohmann::json response = {{{"success", {{"id", std::to_string(id)}}}}}; + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + "/groups", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_EQ(id, test_bridge.createGroup(create)); + + response = {}; + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + "/groups", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_EQ(0, test_bridge.createGroup(create)); +} + TEST(Hue, getPictureOfLight) { using namespace ::testing; std::shared_ptr handler = std::make_shared(); - nlohmann::json hue_bridge_state{{"lights", + nlohmann::json hue_bridge_state {{"lights", {{"1", {{"state", {{"on", true}, {"bri", 254}, {"ct", 366}, {"alert", "none"}, {"colormode", "ct"},