diff --git a/include/hueplusplus/BridgeConfig.h b/include/hueplusplus/BridgeConfig.h index 65596c3..bf00e57 100644 --- a/include/hueplusplus/BridgeConfig.h +++ b/include/hueplusplus/BridgeConfig.h @@ -27,6 +27,7 @@ namespace hueplusplus { +//! \brief API version consisting of major, minor and patch version struct Version { int major; @@ -34,31 +35,62 @@ struct Version int patch; }; +//! \brief User that is whitelisted for Hue API usage struct WhitelistedUser { + //! \brief API username of the user std::string key; + //! \brief Name provided on user creation std::string name; + //! \brief Last time the user was used time::AbsoluteTime lastUsed; + //! \brief Time the user was created time::AbsoluteTime created; }; +//! \brief General bridge configuration properties. class BridgeConfig { public: + //! \brief Construct BridgeConfig BridgeConfig(std::shared_ptr baseCache, std::chrono::steady_clock::duration refreshDuration); + + //! \brief Refreshes internal cached state. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed void refresh(); + //! \brief Get the list of whitelisted users + //! \returns All users authorized for API access std::vector getWhitelistedUsers() const; + //! \brief Remove user from the whitelist + //! \param userKey The API username of the user to remove + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed void removeUser(const std::string& userKey); + //! \brief Get link button state + //! \returns true when link button was pressed in the last 30 seconds. + //! + //! Indicates whether new users can be added currently. bool getLinkButton() const; + //! \brief Set the link button state to pressed void pressLinkButton(); + //! \brief Add the closest lamp to the network void touchLink(); + //! \brief Get bridge MAC address std::string getMACAddress() const; + //! \brief Get current (of last refresh) UTC time of the bridge time::AbsoluteTime getUTCTime() const; + //! \brief Get configured timezone for the bridge + //! \note For times not in UTC, the timezone of the program and the bridge are assumed to be identical. std::string getTimezone() const; protected: diff --git a/include/hueplusplus/Hue.h b/include/hueplusplus/Hue.h index beb8928..cacfec6 100644 --- a/include/hueplusplus/Hue.h +++ b/include/hueplusplus/Hue.h @@ -156,6 +156,10 @@ public: //! Should only be called rarely, as a full refresh is costly and usually not necessary. //! Instead refresh only the parts you are interested in or rely on periodic refreshes //! that happen automatically when calling non-const methods. + //! \throws std::system_error when system or socket operations fail + //! \throws HueException when response contained no body + //! \throws HueAPIResponseException when response contains an error + //! \throws nlohmann::json::parse_error when response could not be parsed void refresh(); //! \brief Function to get the ip address of the hue bridge diff --git a/src/Hue.cpp b/src/Hue.cpp index 7eefb95..f06400f 100644 --- a/src/Hue.cpp +++ b/src/Hue.cpp @@ -282,5 +282,6 @@ void Hue::setHttpHandler(std::shared_ptr handler) scheduleList = CreateableResourceList(stateCache, "schedules", refreshDuration); sceneList = CreateableResourceList(stateCache, "scenes", refreshDuration); bridgeConfig = BridgeConfig(stateCache, refreshDuration); + stateCache->refresh(); } } // namespace hueplusplus diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fc22cbc..5681156 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -48,7 +48,7 @@ set(TEST_SOURCES test_SimpleColorHueStrategy.cpp test_SimpleColorTemperatureStrategy.cpp test_StateTransaction.cpp - test_TimePattern.cpp "test_ColorUnits.cpp") + test_TimePattern.cpp "test_ColorUnits.cpp" "test_BridgeConfig.cpp") set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") diff --git a/test/test_BridgeConfig.cpp b/test/test_BridgeConfig.cpp new file mode 100644 index 0000000..5b3a391 --- /dev/null +++ b/test/test_BridgeConfig.cpp @@ -0,0 +1,195 @@ +/** + \file test_BridgeConfig.cpp + Copyright Notice\n + Copyright (C) 2020 Jan Rogall - developer\n + + This file is part of hueplusplus. + + hueplusplus is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + hueplusplus 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with hueplusplus. If not, see . +**/ + +#include + +#include + +#include "testhelper.h" + +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +TEST(BridgeConfig, refresh) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/config", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + config.refresh(); +} + +TEST(BridgeConfig, getWhitelistedUsers) +{ + const nlohmann::json state {{"config", + {{"whitelist", + {{"abcd", + {{"name", "User A"}, {"last use date", "2020-04-01T10:00:04"}, + {"create date", "2020-01-01T12:00:00"}}}, + {"cdef", + {{"name", "User B"}, {"last use date", "2020-03-05T14:00:00"}, + {"create date", "2020-02-01T02:03:40"}}}}}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + std::vector users = config.getWhitelistedUsers(); + EXPECT_THAT(users, + UnorderedElementsAre(Truly([](const WhitelistedUser& u) { return u.key == "abcd" && u.name == "User A"; }), + Truly([](const WhitelistedUser& u) { return u.key == "cdef" && u.name == "User B"; }))); +} + +TEST(BridgeConfig, removeUser) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + const std::string userKey = "abcd"; + EXPECT_CALL(*handler, + DELETEJson("/api/" + getBridgeUsername() + "/config/whitelist/" + userKey, nlohmann::json::object(), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {"/config/whitelist/" + userKey + " deleted"})); + config.removeUser(userKey); +} + +TEST(BridgeConfig, getLinkButton) +{ + const nlohmann::json state {{"config", {{"linkbutton", true}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_TRUE(config.getLinkButton()); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/config", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{"linkbutton", false}})); + config.refresh(); + EXPECT_FALSE(config.getLinkButton()); +} + +TEST(BridgeConfig, pressLinkButton) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/config", nlohmann::json {{"linkbutton", true}}, getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/config/linkbutton", true}}}}})); + config.pressLinkButton(); +} + +TEST(BridgeConfig, touchLink) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/config", nlohmann::json {{"touchlink", true}}, getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/config/touchlink", true}}}}})); + config.touchLink(); +} + +TEST(BridgeConfig, getMACAddress) +{ + const nlohmann::json state {{"config", {{"mac", getBridgeMac()}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_EQ(getBridgeMac(), config.getMACAddress()); +} + +TEST(BridgeConfig, getUTCTime) +{ + const std::string utc = "2020-06-01T10:00:00"; + const nlohmann::json state {{"config", {{"UTC", utc}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_EQ(time::AbsoluteTime::parseUTC(utc).getBaseTime(), config.getUTCTime().getBaseTime()); +} + +TEST(BridgeConfig, getTimezone) +{ + const std::string timezone = "ab"; + const nlohmann::json state {{"config", {{"timezone", timezone}}}}; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + auto baseCache = std::make_shared("", commands, std::chrono::steady_clock::duration::max()); + EXPECT_CALL( + *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + baseCache->refresh(); + BridgeConfig config(baseCache, std::chrono::steady_clock::duration::max()); + + EXPECT_EQ(timezone, config.getTimezone()); +} diff --git a/test/test_Hue.cpp b/test/test_Hue.cpp index ece39ab..7f5c86a 100644 --- a/test/test_Hue.cpp +++ b/test/test_Hue.cpp @@ -208,20 +208,16 @@ TEST(Hue, requestUsername) Hue test_bridge(getBridgeIp(), getBridgePort(), "", handler); - std::string username = test_bridge.requestUsername(); - - EXPECT_EQ(username, test_bridge.getUsername()) << "Returned username not matching"; - EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching"; - 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) .WillOnce(Return(hue_bridge_state)); + std::string username = test_bridge.requestUsername(); - test_bridge.lights().getAll(); + EXPECT_EQ(username, test_bridge.getUsername()) << "Returned username not matching"; + EXPECT_EQ(test_bridge.getBridgeIP(), getBridgeIp()) << "Bridge IP not matching"; + EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; } }