diff --git a/include/hueplusplus/CLIPSensors.h b/include/hueplusplus/CLIPSensors.h index 5b955cd..f57f947 100644 --- a/include/hueplusplus/CLIPSensors.h +++ b/include/hueplusplus/CLIPSensors.h @@ -45,14 +45,15 @@ public: void setURL(const std::string& url); time::AbsoluteTime getLastUpdated() const; + protected: - BaseCLIP(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit BaseCLIP(Sensor sensor) : BaseDevice(std::move(sensor)) { } }; class CLIPSwitch : public BaseCLIP { public: - CLIPSwitch(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPSwitch(Sensor sensor) : BaseCLIP(std::move(sensor)) { } int getButtonEvent() const; void setButtonEvent(int code); @@ -62,7 +63,7 @@ public: class CLIPOpenClose : public BaseCLIP { public: - CLIPOpenClose(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPOpenClose(Sensor sensor) : BaseCLIP(std::move(sensor)) { } bool isOpen() const; void setOpen(bool open); @@ -73,7 +74,7 @@ public: class CLIPPresence : public BaseCLIP { public: - CLIPPresence(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPPresence(Sensor sensor) : BaseCLIP(std::move(sensor)) { } bool getPresence() const; void setPresence(bool presence); @@ -84,7 +85,7 @@ public: class CLIPTemperature : public BaseCLIP { public: - CLIPTemperature(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPTemperature(Sensor sensor) : BaseCLIP(std::move(sensor)) { } int getTemperature() const; void setTemperature(int temperature); @@ -94,7 +95,7 @@ public: class CLIPHumidity : public BaseCLIP { public: - CLIPHumidity(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPHumidity(Sensor sensor) : BaseCLIP(std::move(sensor)) { } int getHumidity() const; void setHumidity(int humidity); @@ -104,7 +105,7 @@ public: class CLIPLightLevel : public BaseCLIP { public: - CLIPLightLevel(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPLightLevel(Sensor sensor) : BaseCLIP(std::move(sensor)) { } int getDarkThreshold() const; void setDarkThreshold(int threshold); @@ -122,7 +123,7 @@ public: class CLIPGenericFlag : public BaseCLIP { public: - CLIPGenericFlag(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPGenericFlag(Sensor sensor) : BaseCLIP(std::move(sensor)) { } bool getFlag() const; void setFlag(bool flag); @@ -132,7 +133,7 @@ public: class CLIPGenericStatus : public BaseCLIP { public: - CLIPGenericStatus(Sensor sensor) : BaseCLIP(std::move(sensor)) { } + explicit CLIPGenericStatus(Sensor sensor) : BaseCLIP(std::move(sensor)) { } int getStatus() const; void setStatus(int status); diff --git a/include/hueplusplus/Sensor.h b/include/hueplusplus/Sensor.h index 0567b22..5ed3690 100644 --- a/include/hueplusplus/Sensor.h +++ b/include/hueplusplus/Sensor.h @@ -2,6 +2,7 @@ \file Sensor.h Copyright Notice\n Copyright (C) 2020 Stefan Herbrechtsmeier - developer\n + Copyright (C) 2020 Jan Rogall - developer\n This file is part of hueplusplus. @@ -45,13 +46,15 @@ Alert alertFromString(const std::string& s); //! class Sensor : public BaseDevice { - friend class Bridge; - public: - //! \brief std dtor - ~Sensor() = default; + //! \brief Construct Sensor. + //! \param id Integer that specifies the id of this sensor + //! \param commands HueCommandAPI for communication with the bridge + //! \param refreshDuration Time between refreshing the cached state. + Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); - bool hasSwupdate() const; + //!\name Config attributes + ///@{ bool hasOn() const; // Check whether sensor is on. Does not update when off @@ -70,34 +73,36 @@ public: bool hasReachable() const; bool isReachable() const; - time::AbsoluteTime getLastUpdated() const; - bool hasUserTest() const; void setUserTest(bool enabled); bool hasURL() const; std::string getURL() const; void setURL(const std::string& url); - + std::vector getPendingConfig() const; - + bool hasLEDIndication() const; bool getLEDIndication() const; void setLEDIndication(bool on); - nlohmann::json getState() const; - void setStateAttribute(const std::string& key, const nlohmann::json& value); - nlohmann::json getConfig() const; void setConfigAttribute(const std::string& key, const nlohmann::json& value); + ///@} + + time::AbsoluteTime getLastUpdated() const; + + nlohmann::json getState() const; + void setStateAttribute(const std::string& key, const nlohmann::json& value); + bool isCertified() const; bool isPrimary() const; template T asSensorType() const & { - if (getType() != T::type_str) + if (getType() != T::typeStr) { throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Sensor type does not match: " + getType()); } @@ -112,14 +117,6 @@ public: } return T(std::move(*this)); } - -protected: - //! \brief Protected ctor that is used by \ref Bridge class. - //! - //! \param id Integer that specifies the id of this sensor - //! \param commands HueCommandAPI for communication with the bridge - //! \param refreshDuration Time between refreshing the cached state. - Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); }; class CreateSensor @@ -142,7 +139,7 @@ namespace sensors class DaylightSensor : public BaseDevice { public: - DaylightSensor(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit DaylightSensor(Sensor sensor) : BaseDevice(std::move(sensor)) { } bool isOn() const; void setOn(bool on); diff --git a/include/hueplusplus/SensorList.h b/include/hueplusplus/SensorList.h index e2409da..3886af9 100644 --- a/include/hueplusplus/SensorList.h +++ b/include/hueplusplus/SensorList.h @@ -40,12 +40,15 @@ public: template std::vector getAllByType() { + nlohmann::json state = this->stateCache.getValue(); std::vector result; - std::string type = T::typeStr; - // TODO: Maybe only parse the sensors with correct type - for (Sensor& s : getAll()) + for (auto it = state.begin(); it != state.end(); ++it) { - result.push_back(s.asSensorType()); + // Only parse the sensors with the correct type + if (it->value("type", "") == T::typeStr) + { + result.push_back(get(maybeStoi(it.key())).asSensorType()); + } } return result; } diff --git a/include/hueplusplus/ZLLSensors.h b/include/hueplusplus/ZLLSensors.h index 247a04e..c521608 100644 --- a/include/hueplusplus/ZLLSensors.h +++ b/include/hueplusplus/ZLLSensors.h @@ -31,7 +31,7 @@ namespace sensors class ZGPSwitch : public BaseDevice { public: - ZGPSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit ZGPSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } bool isOn() const; void setOn(bool on); @@ -48,7 +48,7 @@ public: class ZLLSwitch : public BaseDevice { public: - ZLLSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit ZLLSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } bool isOn() const; void setOn(bool on); @@ -87,7 +87,7 @@ public: class ZLLPresence : public BaseDevice { public: - ZLLPresence(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit ZLLPresence(Sensor sensor) : BaseDevice(std::move(sensor)) { } bool isOn() const; void setOn(bool on); @@ -113,7 +113,7 @@ public: class ZLLTemperature : public BaseDevice { public: - ZLLTemperature(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit ZLLTemperature(Sensor sensor) : BaseDevice(std::move(sensor)) { } bool isOn() const; void setOn(bool on); @@ -133,7 +133,7 @@ public: class ZLLLightLevel : public BaseDevice { public: - ZLLLightLevel(Sensor sensor) : BaseDevice(std::move(sensor)) { } + explicit ZLLLightLevel(Sensor sensor) : BaseDevice(std::move(sensor)) { } bool isOn() const; void setOn(bool on); diff --git a/src/Bridge.cpp b/src/Bridge.cpp index 7e456b3..9ed505b 100644 --- a/src/Bridge.cpp +++ b/src/Bridge.cpp @@ -36,6 +36,7 @@ #include "hueplusplus/UPnP.h" #include "hueplusplus/Utils.h" + namespace hueplusplus { BridgeFinder::BridgeFinder(std::shared_ptr handler) : http_handler(std::move(handler)) { } diff --git a/src/Sensor.cpp b/src/Sensor.cpp index 14f80ab..09672c4 100644 --- a/src/Sensor.cpp +++ b/src/Sensor.cpp @@ -57,11 +57,6 @@ Alert alertFromString(const std::string& s) } } -bool Sensor::hasSwupdate() const -{ - return state.getValue().at("config").count("swupdate") != 0; -} - bool Sensor::hasOn() const { return state.getValue().at("config").count("on") != 0; @@ -124,7 +119,7 @@ void Sensor::sendAlert(Alert type) alertStr = "none"; break; } - sendPutRequest("/state", nlohmann::json {{"alert", alertStr}}, CURRENT_FILE_INFO); + sendPutRequest("/config", nlohmann::json {{"alert", alertStr}}, CURRENT_FILE_INFO); } bool Sensor::hasReachable() const { @@ -205,7 +200,7 @@ nlohmann::json Sensor::getState() const } void Sensor::setStateAttribute(const std::string& key, const nlohmann::json& value) { - sendPutRequest("/state", nlohmann::json {{"key", value}}, CURRENT_FILE_INFO); + sendPutRequest("/state", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); } nlohmann::json Sensor::getConfig() const @@ -215,7 +210,7 @@ nlohmann::json Sensor::getConfig() const void Sensor::setConfigAttribute(const std::string& key, const nlohmann::json& value) { - sendPutRequest("/config", nlohmann::json {{"key", value}}, CURRENT_FILE_INFO); + sendPutRequest("/config", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); } bool Sensor::isCertified() const diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f0317a3..245dd4f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,6 +31,7 @@ target_compile_features(gtest PUBLIC cxx_std_14) # define all test sources set(TEST_SOURCES test_APICache.cpp + test_BaseDevice.cpp test_BaseHttpHandler.cpp test_Bridge.cpp test_BridgeConfig.cpp @@ -46,6 +47,8 @@ set(TEST_SOURCES test_ResourceList.cpp test_Scene.cpp test_Schedule.cpp + test_Sensor.cpp + test_SensorList.cpp test_SimpleBrightnessStrategy.cpp test_SimpleColorHueStrategy.cpp test_SimpleColorTemperatureStrategy.cpp diff --git a/test/test_BaseDevice.cpp b/test/test_BaseDevice.cpp new file mode 100644 index 0000000..d43bdd1 --- /dev/null +++ b/test/test_BaseDevice.cpp @@ -0,0 +1,125 @@ +/** + \file test_BaseDevice.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 "hueplusplus/BaseDevice.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class TestDevice : public BaseDevice +{ +public: + TestDevice(int id, const HueCommandAPI& commands, const std::string& path, + std::chrono::steady_clock::duration refreshDuration) + : BaseDevice(id, commands, path, refreshDuration) + { } +}; + +class BaseDeviceTest : public Test +{ +protected: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json state; + std::string path = "/test/"; + +protected: + BaseDeviceTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + state({{"type", "testType"}, {"name", "Test name"}, {"swversion", "1.2.3.4"}, {"modelid", "TEST"}, + {"manufacturername", "testManuf"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, + {"productname", "Test type"}}) + { } + + TestDevice getDevice(int id) + { + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + path + std::to_string(id), _, getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + return TestDevice(id, commands, path, std::chrono::steady_clock::duration::max()); + } +}; + +TEST_F(BaseDeviceTest, getId) +{ + const int id = 1; + EXPECT_EQ(id, getDevice(id).getId()); +} + +TEST_F(BaseDeviceTest, getName) +{ + EXPECT_EQ("Test name", getDevice(1).getName()); +} + +TEST_F(BaseDeviceTest, getType) +{ + EXPECT_EQ("testType", getDevice(1).getType()); +} + +TEST_F(BaseDeviceTest, getModelId) +{ + EXPECT_EQ("TEST", getDevice(1).getModelId()); +} + +TEST_F(BaseDeviceTest, getUId) +{ + EXPECT_EQ("00:00:00:00:00:00:00:00-00", getDevice(1).getUId()); + state.erase("uniqueid"); + EXPECT_EQ("", getDevice(1).getUId()); +} + +TEST_F(BaseDeviceTest, getManufacturername) +{ + EXPECT_EQ("testManuf", getDevice(1).getManufacturername()); + state.erase("manufacturername"); + EXPECT_EQ("", getDevice(1).getManufacturername()); +} + +TEST_F(BaseDeviceTest, getProductname) +{ + EXPECT_EQ("Test type", getDevice(1).getProductname()); + state.erase("productname"); + EXPECT_EQ("", getDevice(1).getProductname()); +} + +TEST_F(BaseDeviceTest, getSwVersion) +{ + EXPECT_EQ("1.2.3.4", getDevice(1).getSwVersion()); +} + +TEST_F(BaseDeviceTest, setName) +{ + const std::string name = "asdbsdakfl"; + const nlohmann::json request = {{"name", name}}; + const nlohmann::json response = { {{"success", {{"/lights/1/name", name}}}} }; + + TestDevice device = getDevice(1); + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + path + "1/name", request, getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_TRUE(device.setName(name)); +} \ No newline at end of file diff --git a/test/test_Bridge.cpp b/test/test_Bridge.cpp index c214b8c..2bbdf71 100644 --- a/test/test_Bridge.cpp +++ b/test/test_Bridge.cpp @@ -35,13 +35,13 @@ using namespace hueplusplus; -class HueFinderTest : public ::testing::Test +class BridgeFinderTest : public ::testing::Test { protected: std::shared_ptr handler; protected: - HueFinderTest() : handler(std::make_shared()) + BridgeFinderTest() : handler(std::make_shared()) { using namespace ::testing; @@ -59,10 +59,10 @@ protected: .Times(AtLeast(1)) .WillRepeatedly(Return(getBridgeXml())); } - ~HueFinderTest() {}; + ~BridgeFinderTest() {}; }; -TEST_F(HueFinderTest, FindBridges) +TEST_F(BridgeFinderTest, FindBridges) { BridgeFinder finder(handler); std::vector bridges = finder.FindBridges(); @@ -85,7 +85,7 @@ TEST_F(HueFinderTest, FindBridges) EXPECT_TRUE(bridges.empty()); } -TEST_F(HueFinderTest, GetBridge) +TEST_F(BridgeFinderTest, GetBridge) { using namespace ::testing; nlohmann::json request {{"devicetype", "HuePlusPlus#User"}}; @@ -120,7 +120,7 @@ TEST_F(HueFinderTest, GetBridge) Mock::VerifyAndClearExpectations(handler.get()); } -TEST_F(HueFinderTest, AddUsername) +TEST_F(BridgeFinderTest, AddUsername) { BridgeFinder finder(handler); std::vector bridges = finder.FindBridges(); @@ -133,7 +133,7 @@ TEST_F(HueFinderTest, AddUsername) EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; } -TEST_F(HueFinderTest, GetAllUsernames) +TEST_F(BridgeFinderTest, GetAllUsernames) { BridgeFinder finder(handler); std::vector bridges = finder.FindBridges(); diff --git a/test/test_Sensor.cpp b/test/test_Sensor.cpp new file mode 100644 index 0000000..14a464a --- /dev/null +++ b/test/test_Sensor.cpp @@ -0,0 +1,257 @@ +/** + \file test_Sensor.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; + +class SensorTest : public Test +{ +protected: + std::shared_ptr handler; + HueCommandAPI commands; + nlohmann::json state; + +protected: + SensorTest() + : handler(std::make_shared()), + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), + state({{"type", "testSensor"}, {"name", "Test sensor"}, {"swversion", "1.2.3.4"}, {"modelid", "test"}, + {"manufacturername", "testManuf"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, + {"productname", "Test sensor"}, {"config", nlohmann::json::object()}, + {"state", nlohmann::json::object()}}) + { } + + Sensor getSensor(int id = 1) + { + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors/" + std::to_string(id), _, getBridgeIp(), getBridgePort())) + .WillOnce(Return(state)); + return Sensor(id, commands, std::chrono::steady_clock::duration::max()); + } +}; + +TEST(Alert, alertFromString) +{ + EXPECT_EQ(Alert::none, alertFromString("none")); + EXPECT_EQ(Alert::select, alertFromString("select")); + EXPECT_EQ(Alert::lselect, alertFromString("lselect")); + EXPECT_EQ(Alert::none, alertFromString("anything")); +} + +TEST(Alert, alertToString) +{ + EXPECT_EQ("none", alertToString(Alert::none)); + EXPECT_EQ("select", alertToString(Alert::select)); + EXPECT_EQ("lselect", alertToString(Alert::lselect)); +} + +TEST_F(SensorTest, On) +{ + EXPECT_FALSE(getSensor().hasOn()); + state["config"]["on"] = true; + EXPECT_TRUE(getSensor().hasOn()); + EXPECT_TRUE(getSensor().isOn()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"on", false}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/on", false}}}}})); + getSensor().setOn(false); +} + +TEST_F(SensorTest, BatteryState) +{ + EXPECT_FALSE(getSensor().hasBatteryState()); + state["config"]["battery"] = 90; + EXPECT_TRUE(getSensor().hasBatteryState()); + EXPECT_EQ(90, getSensor().getBatteryState()); + + int percent = 10; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"battery", percent}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/battery", percent}}}}})); + getSensor().setBatteryState(percent); +} + +TEST_F(SensorTest, Alert) +{ + EXPECT_FALSE(getSensor().hasAlert()); + state["config"]["alert"] = "none"; + EXPECT_TRUE(getSensor().hasAlert()); + EXPECT_EQ(Alert::none, getSensor().getLastAlert()); + + std::string alert = "lselect"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"alert", alert}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/alert", alert}}}}})); + getSensor().sendAlert(Alert::lselect); +} + +TEST_F(SensorTest, Reachable) +{ + EXPECT_FALSE(getSensor().hasReachable()); + state["config"]["reachable"] = false; + EXPECT_TRUE(getSensor().hasReachable()); + EXPECT_FALSE(getSensor().isReachable()); +} + +TEST_F(SensorTest, UserTest) +{ + EXPECT_FALSE(getSensor().hasUserTest()); + state["config"]["usertest"] = false; + EXPECT_TRUE(getSensor().hasUserTest()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"usertest", true}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/usertest", true}}}}})); + getSensor().setUserTest(true); +} + +TEST_F(SensorTest, URL) +{ + EXPECT_FALSE(getSensor().hasURL()); + const std::string url = "https://abc"; + state["config"]["url"] = url; + EXPECT_TRUE(getSensor().hasURL()); + EXPECT_EQ(url, getSensor().getURL()); + + std::string newUrl = "https://cde"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"url", newUrl}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/url", newUrl}}}}})); + getSensor().setURL(newUrl); +} + +TEST_F(SensorTest, getPendingConfig) +{ + EXPECT_TRUE(getSensor().getPendingConfig().empty()); + state["config"]["pending"] = nullptr; + EXPECT_TRUE(getSensor().getPendingConfig().empty()); + + state["config"]["pending"] = {"abc", "cde", "def"}; + + EXPECT_THAT(getSensor().getPendingConfig(), UnorderedElementsAre("abc", "cde", "def")); +} + +TEST_F(SensorTest, LEDIndication) +{ + EXPECT_FALSE(getSensor().hasLEDIndication()); + state["config"]["ledindication"] = true; + EXPECT_TRUE(getSensor().hasLEDIndication()); + EXPECT_TRUE(getSensor().getLEDIndication()); + + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"ledindication", false}}), + getBridgeIp(), getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/ledindication", false}}}}})); + getSensor().setLEDIndication(false); +} + +TEST_F(SensorTest, getConfig) +{ + EXPECT_EQ(state["config"], getSensor().getConfig()); + state["config"]["attribute"] = false; + EXPECT_EQ(state["config"], getSensor().getConfig()); +} + +TEST_F(SensorTest, setConfigAttribute) +{ + const std::string key = "attribute"; + const nlohmann::json value = "some value"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{key, value}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/" + key, value}}}}})); + getSensor().setConfigAttribute(key, value); +} + +TEST_F(SensorTest, getLastUpdated) +{ + time::AbsoluteTime none = getSensor().getLastUpdated(); + EXPECT_EQ(std::chrono::seconds(0), none.getBaseTime().time_since_epoch()); + + const std::string timestamp = "2020-05-02T12:00:01"; + state["state"]["lastupdated"] = timestamp; + time::AbsoluteTime time = time::AbsoluteTime::parseUTC(timestamp); + EXPECT_EQ(time.getBaseTime(), getSensor().getLastUpdated().getBaseTime()); +} + +TEST_F(SensorTest, getState) +{ + nlohmann::json stateContent = {{"bla", "bla"}}; + state["state"] = stateContent; + EXPECT_EQ(stateContent, getSensor().getState()); +} + +TEST_F(SensorTest, setStateAttribute) +{ + const std::string key = "attribute"; + const nlohmann::json value = "some value"; + EXPECT_CALL(*handler, + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/state", nlohmann::json({{key, value}}), getBridgeIp(), + getBridgePort())) + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/state/" + key, value}}}}})); + getSensor().setStateAttribute(key, value); +} + +TEST_F(SensorTest, isCertified) +{ + EXPECT_FALSE(getSensor().isCertified()); + state["capabilities"]["certified"] = true; + EXPECT_TRUE(getSensor().isCertified()); +} + +TEST_F(SensorTest, isPrimary) +{ + EXPECT_FALSE(getSensor().isPrimary()); + state["capabilities"]["primary"] = true; + EXPECT_TRUE(getSensor().isPrimary()); +} + +TEST_F(SensorTest, asSensorType) +{ + // Test both rvalue and const access + { + const Sensor s = getSensor(); + EXPECT_THROW(s.asSensorType(), HueException); + } + EXPECT_THROW(getSensor().asSensorType(), HueException); + + state["type"] = sensors::DaylightSensor::typeStr; + sensors::DaylightSensor ds = getSensor().asSensorType(); + EXPECT_EQ(1, ds.getId()); + const Sensor s = getSensor(); + ds = s.asSensorType(); + EXPECT_EQ(s.getId(), ds.getId()); +} diff --git a/test/test_SensorList.cpp b/test/test_SensorList.cpp new file mode 100644 index 0000000..8461579 --- /dev/null +++ b/test/test_SensorList.cpp @@ -0,0 +1,113 @@ +/** + \file test_SensorList.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 "testhelper.h" + +#include "hueplusplus/SensorList.h" +#include "mocks/mock_HttpHandler.h" + +using namespace hueplusplus; +using namespace testing; + +class BlaSensor +{ +public: + BlaSensor(Sensor s) { } + + static constexpr const char* typeStr = "bla"; +}; + +TEST(SensorList, getAsType) +{ + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + SensorList sensors {commands, "/sensors", std::chrono::steady_clock::duration::max()}; + + const int id = 2; + const nlohmann::json response = {{std::to_string(id), {{"type", "Daylight"}}}}; + + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_CALL(*handler, + GETJson("/api/" + getBridgeUsername() + "/sensors/" + std::to_string(id), nlohmann::json::object(), + getBridgeIp(), getBridgePort())) + .Times(2) + .WillRepeatedly(Return(nlohmann::json {{"type", "Daylight"}})); + + sensors::DaylightSensor daylightSensor = sensors.getAsType(id); + EXPECT_THROW(sensors.getAsType(2), HueException); +} + +TEST(SensorList, getAllByType) +{ + + auto handler = std::make_shared(); + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); + + SensorList sensors {commands, "/sensors", std::chrono::steady_clock::duration::max()}; + + // Empty + { + const nlohmann::json response = nlohmann::json::object(); + + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_TRUE(sensors.getAllByType().empty()); + } + // Not matching + { + const nlohmann::json response = {{"1", {{"type", "stuff"}}}}; + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + sensors.refresh(); + EXPECT_TRUE(sensors.getAllByType().empty()); + } + // Some matching (daylight maybe not the best example, because there is always exactly one) + { + const nlohmann::json response = {{"1", {{"type", "stuff"}}}, {"2", {{"type", "Daylight"}}}, + {"3", {{"type", "stuff"}}}, {"4", {{"type", "Daylight"}}}}; + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response)); + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors/2", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response["2"])); + EXPECT_CALL(*handler, + GETJson( + "/api/" + getBridgeUsername() + "/sensors/4", nlohmann::json::object(), getBridgeIp(), getBridgePort())) + .WillOnce(Return(response["4"])); + sensors.refresh(); + std::vector result = sensors.getAllByType(); + EXPECT_THAT(result, + UnorderedElementsAre(Truly([](const auto& s) { return s.getId() == 2; }), + Truly([](const auto& s) { return s.getId() == 4; }))); + } +}