diff --git a/src/APICache.cpp b/src/APICache.cpp index ebd8653..74d627f 100644 --- a/src/APICache.cpp +++ b/src/APICache.cpp @@ -25,22 +25,35 @@ hueplusplus::APICache::APICache( const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh) - : path(path), - commands(commands), - refreshDuration(refresh), - lastRefresh(std::chrono::steady_clock::duration::zero()) + : path(path), commands(commands), refreshDuration(refresh), lastRefresh(std::chrono::steady_clock::duration::zero()) {} -void hueplusplus::APICache::Refresh() { +void hueplusplus::APICache::Refresh() +{ value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO); + lastRefresh = std::chrono::steady_clock::now(); } nlohmann::json& hueplusplus::APICache::GetValue() { - if (std::chrono::steady_clock::now() >= lastRefresh + refreshDuration) + using clock = std::chrono::steady_clock; + // Explicitly check for zero in case refreshDuration is duration::max() + // Negative duration causes overflow check to overflow itself + if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) { + // No value set yet Refresh(); } + // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). + // If addition would overflow, do not refresh + else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) + { + clock::time_point nextRefresh = lastRefresh + refreshDuration; + if (clock::now() >= nextRefresh) + { + Refresh(); + } + } return value; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1cedabc..d077247 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,6 +30,7 @@ target_compile_features(gtest PUBLIC cxx_std_14) # define all test sources set(TEST_SOURCES + test_APICache.cpp test_BaseHttpHandler.cpp test_ExtendedColorHueStrategy.cpp test_ExtendedColorTemperatureStrategy.cpp diff --git a/test/test_APICache.cpp b/test/test_APICache.cpp new file mode 100644 index 0000000..c673cc8 --- /dev/null +++ b/test/test_APICache.cpp @@ -0,0 +1,124 @@ +/** + \file test_Hue.cpp + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - 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 "testhelper.h" + +#include "hueplusplus/APICache.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; + +TEST(APICache, GetRefreshDuration) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + { + std::chrono::steady_clock::duration refresh = std::chrono::seconds(20); + APICache cache("", commands, refresh); + EXPECT_EQ(refresh, cache.GetRefreshDuration()); + } + { + std::chrono::steady_clock::duration refresh = std::chrono::seconds(0); + APICache cache("", commands, refresh); + EXPECT_EQ(refresh, cache.GetRefreshDuration()); + } + { + std::chrono::steady_clock::duration refresh = std::chrono::steady_clock::duration::max(); + APICache cache("", commands, refresh); + EXPECT_EQ(refresh, cache.GetRefreshDuration()); + } +} + +TEST(APICache, Refresh) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::seconds(10)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json::object())); + cache.Refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } + { + std::string path = ""; + APICache cache(path, commands, std::chrono::seconds(10)); + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json::object())); + cache.Refresh(); + cache.Refresh(); + Mock::VerifyAndClearExpectations(handler.get()); + } +} + +TEST(APICache, GetValue) +{ + using namespace ::testing; + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + // Always refresh + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::seconds(0)); + nlohmann::json value = { {"a", "b"} }; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(value)); + EXPECT_EQ(value, cache.GetValue()); + EXPECT_EQ(value, cache.GetValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // Only refresh once + { + std::string path = "/test/abc"; + APICache cache(path, commands, std::chrono::steady_clock::duration::max()); + nlohmann::json value = { {"a", "b"} }; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(value)); + EXPECT_EQ(value, cache.GetValue()); + EXPECT_EQ(value, cache.GetValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } + // No refresh with const + { + std::string path = "/test/abc"; + const APICache cache(path, commands, std::chrono::steady_clock::duration::max()); + nlohmann::json value = { {"a", "b"} }; + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .Times(0); + EXPECT_EQ(nullptr, cache.GetValue()); + Mock::VerifyAndClearExpectations(handler.get()); + } +} \ No newline at end of file diff --git a/test/test_Hue.cpp b/test/test_Hue.cpp index f7976fd..8312b0f 100644 --- a/test/test_Hue.cpp +++ b/test/test_Hue.cpp @@ -20,8 +20,6 @@ along with hueplusplus. If not, see . **/ -#include -#include #include #include @@ -285,6 +283,9 @@ TEST(Hue, getLight) .Times(AtLeast(1)) .WillRepeatedly(Return(hue_bridge_state["lights"]["1"])); + // Refresh cache + test_bridge = Hue(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + // Test when correct data is sent HueLight test_light_1 = test_bridge.getLight(1); EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); @@ -479,7 +480,7 @@ TEST(Hue, lightExists) {"uniqueid", "00:00:00:00:00:00:00:00-00"}, {"swversion", "5.50.1.19085"}}}}}}; EXPECT_CALL( *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) - .Times(AtLeast(2)) + .Times(AtLeast(1)) .WillRepeatedly(Return(hue_bridge_state)); EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort())) diff --git a/test/test_HueLight.cpp b/test/test_HueLight.cpp index ea23894..a8b3edd 100644 --- a/test/test_HueLight.cpp +++ b/test/test_HueLight.cpp @@ -54,6 +54,7 @@ protected: hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; + hue_bridge_state["lights"]["1"]["state"]["effect"] = "none"; hue_bridge_state["lights"]["1"]["swupdate"] = nlohmann::json::object(); hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = nullptr; @@ -77,6 +78,7 @@ protected: hue_bridge_state["lights"]["2"]["state"]["alert"] = "none"; hue_bridge_state["lights"]["2"]["state"]["colormode"] = "ct"; hue_bridge_state["lights"]["2"]["state"]["reachable"] = true; + hue_bridge_state["lights"]["2"]["state"]["effect"] = "none"; hue_bridge_state["lights"]["2"]["swupdate"] = nlohmann::json::object(); hue_bridge_state["lights"]["2"]["swupdate"]["state"] = "noupdates"; hue_bridge_state["lights"]["2"]["swupdate"]["lastinstall"] = nullptr; @@ -97,6 +99,7 @@ protected: hue_bridge_state["lights"]["3"]["state"]["alert"] = "none"; hue_bridge_state["lights"]["3"]["state"]["colormode"] = "ct"; hue_bridge_state["lights"]["3"]["state"]["reachable"] = true; + hue_bridge_state["lights"]["3"]["state"]["effect"] = "none"; hue_bridge_state["lights"]["3"]["swupdate"] = nlohmann::json::object(); hue_bridge_state["lights"]["3"]["swupdate"]["state"] = "noupdates"; hue_bridge_state["lights"]["3"]["swupdate"]["lastinstall"] = nullptr;