Commit a90248d34c5ffdc2c1272b9207389e16c22b9b8e
Committed by
Moritz Wirger
1 parent
bcac6298
Add tests for BaseDevice, Sensor and SensorList.
Fix some errors in these classes. Make sensor constructors explicit.
Showing
11 changed files
with
550 additions
and
55 deletions
include/hueplusplus/CLIPSensors.h
| ... | ... | @@ -45,14 +45,15 @@ public: |
| 45 | 45 | void setURL(const std::string& url); |
| 46 | 46 | |
| 47 | 47 | time::AbsoluteTime getLastUpdated() const; |
| 48 | + | |
| 48 | 49 | protected: |
| 49 | - BaseCLIP(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 50 | + explicit BaseCLIP(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 50 | 51 | }; |
| 51 | 52 | |
| 52 | 53 | class CLIPSwitch : public BaseCLIP |
| 53 | 54 | { |
| 54 | 55 | public: |
| 55 | - CLIPSwitch(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 56 | + explicit CLIPSwitch(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 56 | 57 | |
| 57 | 58 | int getButtonEvent() const; |
| 58 | 59 | void setButtonEvent(int code); |
| ... | ... | @@ -62,7 +63,7 @@ public: |
| 62 | 63 | class CLIPOpenClose : public BaseCLIP |
| 63 | 64 | { |
| 64 | 65 | public: |
| 65 | - CLIPOpenClose(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 66 | + explicit CLIPOpenClose(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 66 | 67 | |
| 67 | 68 | bool isOpen() const; |
| 68 | 69 | void setOpen(bool open); |
| ... | ... | @@ -73,7 +74,7 @@ public: |
| 73 | 74 | class CLIPPresence : public BaseCLIP |
| 74 | 75 | { |
| 75 | 76 | public: |
| 76 | - CLIPPresence(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 77 | + explicit CLIPPresence(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 77 | 78 | |
| 78 | 79 | bool getPresence() const; |
| 79 | 80 | void setPresence(bool presence); |
| ... | ... | @@ -84,7 +85,7 @@ public: |
| 84 | 85 | class CLIPTemperature : public BaseCLIP |
| 85 | 86 | { |
| 86 | 87 | public: |
| 87 | - CLIPTemperature(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 88 | + explicit CLIPTemperature(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 88 | 89 | |
| 89 | 90 | int getTemperature() const; |
| 90 | 91 | void setTemperature(int temperature); |
| ... | ... | @@ -94,7 +95,7 @@ public: |
| 94 | 95 | class CLIPHumidity : public BaseCLIP |
| 95 | 96 | { |
| 96 | 97 | public: |
| 97 | - CLIPHumidity(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 98 | + explicit CLIPHumidity(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 98 | 99 | |
| 99 | 100 | int getHumidity() const; |
| 100 | 101 | void setHumidity(int humidity); |
| ... | ... | @@ -104,7 +105,7 @@ public: |
| 104 | 105 | class CLIPLightLevel : public BaseCLIP |
| 105 | 106 | { |
| 106 | 107 | public: |
| 107 | - CLIPLightLevel(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 108 | + explicit CLIPLightLevel(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 108 | 109 | |
| 109 | 110 | int getDarkThreshold() const; |
| 110 | 111 | void setDarkThreshold(int threshold); |
| ... | ... | @@ -122,7 +123,7 @@ public: |
| 122 | 123 | class CLIPGenericFlag : public BaseCLIP |
| 123 | 124 | { |
| 124 | 125 | public: |
| 125 | - CLIPGenericFlag(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 126 | + explicit CLIPGenericFlag(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 126 | 127 | |
| 127 | 128 | bool getFlag() const; |
| 128 | 129 | void setFlag(bool flag); |
| ... | ... | @@ -132,7 +133,7 @@ public: |
| 132 | 133 | class CLIPGenericStatus : public BaseCLIP |
| 133 | 134 | { |
| 134 | 135 | public: |
| 135 | - CLIPGenericStatus(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 136 | + explicit CLIPGenericStatus(Sensor sensor) : BaseCLIP(std::move(sensor)) { } | |
| 136 | 137 | |
| 137 | 138 | int getStatus() const; |
| 138 | 139 | void setStatus(int status); | ... | ... |
include/hueplusplus/Sensor.h
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | \file Sensor.h |
| 3 | 3 | Copyright Notice\n |
| 4 | 4 | Copyright (C) 2020 Stefan Herbrechtsmeier - developer\n |
| 5 | + Copyright (C) 2020 Jan Rogall - developer\n | |
| 5 | 6 | |
| 6 | 7 | This file is part of hueplusplus. |
| 7 | 8 | |
| ... | ... | @@ -45,13 +46,15 @@ Alert alertFromString(const std::string& s); |
| 45 | 46 | //! |
| 46 | 47 | class Sensor : public BaseDevice |
| 47 | 48 | { |
| 48 | - friend class Bridge; | |
| 49 | - | |
| 50 | 49 | public: |
| 51 | - //! \brief std dtor | |
| 52 | - ~Sensor() = default; | |
| 50 | + //! \brief Construct Sensor. | |
| 51 | + //! \param id Integer that specifies the id of this sensor | |
| 52 | + //! \param commands HueCommandAPI for communication with the bridge | |
| 53 | + //! \param refreshDuration Time between refreshing the cached state. | |
| 54 | + Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); | |
| 53 | 55 | |
| 54 | - bool hasSwupdate() const; | |
| 56 | + //!\name Config attributes | |
| 57 | + ///@{ | |
| 55 | 58 | |
| 56 | 59 | bool hasOn() const; |
| 57 | 60 | // Check whether sensor is on. Does not update when off |
| ... | ... | @@ -70,34 +73,36 @@ public: |
| 70 | 73 | bool hasReachable() const; |
| 71 | 74 | bool isReachable() const; |
| 72 | 75 | |
| 73 | - time::AbsoluteTime getLastUpdated() const; | |
| 74 | - | |
| 75 | 76 | bool hasUserTest() const; |
| 76 | 77 | void setUserTest(bool enabled); |
| 77 | 78 | |
| 78 | 79 | bool hasURL() const; |
| 79 | 80 | std::string getURL() const; |
| 80 | 81 | void setURL(const std::string& url); |
| 81 | - | |
| 82 | + | |
| 82 | 83 | std::vector<std::string> getPendingConfig() const; |
| 83 | - | |
| 84 | + | |
| 84 | 85 | bool hasLEDIndication() const; |
| 85 | 86 | bool getLEDIndication() const; |
| 86 | 87 | void setLEDIndication(bool on); |
| 87 | 88 | |
| 88 | - nlohmann::json getState() const; | |
| 89 | - void setStateAttribute(const std::string& key, const nlohmann::json& value); | |
| 90 | - | |
| 91 | 89 | nlohmann::json getConfig() const; |
| 92 | 90 | void setConfigAttribute(const std::string& key, const nlohmann::json& value); |
| 93 | 91 | |
| 92 | + ///@} | |
| 93 | + | |
| 94 | + time::AbsoluteTime getLastUpdated() const; | |
| 95 | + | |
| 96 | + nlohmann::json getState() const; | |
| 97 | + void setStateAttribute(const std::string& key, const nlohmann::json& value); | |
| 98 | + | |
| 94 | 99 | bool isCertified() const; |
| 95 | 100 | bool isPrimary() const; |
| 96 | 101 | |
| 97 | 102 | template <typename T> |
| 98 | 103 | T asSensorType() const & |
| 99 | 104 | { |
| 100 | - if (getType() != T::type_str) | |
| 105 | + if (getType() != T::typeStr) | |
| 101 | 106 | { |
| 102 | 107 | throw HueException(FileInfo {__FILE__, __LINE__, __func__}, "Sensor type does not match: " + getType()); |
| 103 | 108 | } |
| ... | ... | @@ -112,14 +117,6 @@ public: |
| 112 | 117 | } |
| 113 | 118 | return T(std::move(*this)); |
| 114 | 119 | } |
| 115 | - | |
| 116 | -protected: | |
| 117 | - //! \brief Protected ctor that is used by \ref Bridge class. | |
| 118 | - //! | |
| 119 | - //! \param id Integer that specifies the id of this sensor | |
| 120 | - //! \param commands HueCommandAPI for communication with the bridge | |
| 121 | - //! \param refreshDuration Time between refreshing the cached state. | |
| 122 | - Sensor(int id, const HueCommandAPI& commands, std::chrono::steady_clock::duration refreshDuration); | |
| 123 | 120 | }; |
| 124 | 121 | |
| 125 | 122 | class CreateSensor |
| ... | ... | @@ -142,7 +139,7 @@ namespace sensors |
| 142 | 139 | class DaylightSensor : public BaseDevice |
| 143 | 140 | { |
| 144 | 141 | public: |
| 145 | - DaylightSensor(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 142 | + explicit DaylightSensor(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 146 | 143 | |
| 147 | 144 | bool isOn() const; |
| 148 | 145 | void setOn(bool on); | ... | ... |
include/hueplusplus/SensorList.h
| ... | ... | @@ -40,12 +40,15 @@ public: |
| 40 | 40 | template <typename T> |
| 41 | 41 | std::vector<T> getAllByType() |
| 42 | 42 | { |
| 43 | + nlohmann::json state = this->stateCache.getValue(); | |
| 43 | 44 | std::vector<T> result; |
| 44 | - std::string type = T::typeStr; | |
| 45 | - // TODO: Maybe only parse the sensors with correct type | |
| 46 | - for (Sensor& s : getAll()) | |
| 45 | + for (auto it = state.begin(); it != state.end(); ++it) | |
| 47 | 46 | { |
| 48 | - result.push_back(s.asSensorType<T>()); | |
| 47 | + // Only parse the sensors with the correct type | |
| 48 | + if (it->value("type", "") == T::typeStr) | |
| 49 | + { | |
| 50 | + result.push_back(get(maybeStoi(it.key())).asSensorType<T>()); | |
| 51 | + } | |
| 49 | 52 | } |
| 50 | 53 | return result; |
| 51 | 54 | } | ... | ... |
include/hueplusplus/ZLLSensors.h
| ... | ... | @@ -31,7 +31,7 @@ namespace sensors |
| 31 | 31 | class ZGPSwitch : public BaseDevice |
| 32 | 32 | { |
| 33 | 33 | public: |
| 34 | - ZGPSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 34 | + explicit ZGPSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 35 | 35 | |
| 36 | 36 | bool isOn() const; |
| 37 | 37 | void setOn(bool on); |
| ... | ... | @@ -48,7 +48,7 @@ public: |
| 48 | 48 | class ZLLSwitch : public BaseDevice |
| 49 | 49 | { |
| 50 | 50 | public: |
| 51 | - ZLLSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 51 | + explicit ZLLSwitch(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 52 | 52 | |
| 53 | 53 | bool isOn() const; |
| 54 | 54 | void setOn(bool on); |
| ... | ... | @@ -87,7 +87,7 @@ public: |
| 87 | 87 | class ZLLPresence : public BaseDevice |
| 88 | 88 | { |
| 89 | 89 | public: |
| 90 | - ZLLPresence(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 90 | + explicit ZLLPresence(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 91 | 91 | bool isOn() const; |
| 92 | 92 | void setOn(bool on); |
| 93 | 93 | |
| ... | ... | @@ -113,7 +113,7 @@ public: |
| 113 | 113 | class ZLLTemperature : public BaseDevice |
| 114 | 114 | { |
| 115 | 115 | public: |
| 116 | - ZLLTemperature(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 116 | + explicit ZLLTemperature(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 117 | 117 | |
| 118 | 118 | bool isOn() const; |
| 119 | 119 | void setOn(bool on); |
| ... | ... | @@ -133,7 +133,7 @@ public: |
| 133 | 133 | class ZLLLightLevel : public BaseDevice |
| 134 | 134 | { |
| 135 | 135 | public: |
| 136 | - ZLLLightLevel(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 136 | + explicit ZLLLightLevel(Sensor sensor) : BaseDevice(std::move(sensor)) { } | |
| 137 | 137 | |
| 138 | 138 | bool isOn() const; |
| 139 | 139 | void setOn(bool on); | ... | ... |
src/Bridge.cpp
src/Sensor.cpp
| ... | ... | @@ -57,11 +57,6 @@ Alert alertFromString(const std::string& s) |
| 57 | 57 | } |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | -bool Sensor::hasSwupdate() const | |
| 61 | -{ | |
| 62 | - return state.getValue().at("config").count("swupdate") != 0; | |
| 63 | -} | |
| 64 | - | |
| 65 | 60 | bool Sensor::hasOn() const |
| 66 | 61 | { |
| 67 | 62 | return state.getValue().at("config").count("on") != 0; |
| ... | ... | @@ -124,7 +119,7 @@ void Sensor::sendAlert(Alert type) |
| 124 | 119 | alertStr = "none"; |
| 125 | 120 | break; |
| 126 | 121 | } |
| 127 | - sendPutRequest("/state", nlohmann::json {{"alert", alertStr}}, CURRENT_FILE_INFO); | |
| 122 | + sendPutRequest("/config", nlohmann::json {{"alert", alertStr}}, CURRENT_FILE_INFO); | |
| 128 | 123 | } |
| 129 | 124 | bool Sensor::hasReachable() const |
| 130 | 125 | { |
| ... | ... | @@ -205,7 +200,7 @@ nlohmann::json Sensor::getState() const |
| 205 | 200 | } |
| 206 | 201 | void Sensor::setStateAttribute(const std::string& key, const nlohmann::json& value) |
| 207 | 202 | { |
| 208 | - sendPutRequest("/state", nlohmann::json {{"key", value}}, CURRENT_FILE_INFO); | |
| 203 | + sendPutRequest("/state", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); | |
| 209 | 204 | } |
| 210 | 205 | |
| 211 | 206 | nlohmann::json Sensor::getConfig() const |
| ... | ... | @@ -215,7 +210,7 @@ nlohmann::json Sensor::getConfig() const |
| 215 | 210 | |
| 216 | 211 | void Sensor::setConfigAttribute(const std::string& key, const nlohmann::json& value) |
| 217 | 212 | { |
| 218 | - sendPutRequest("/config", nlohmann::json {{"key", value}}, CURRENT_FILE_INFO); | |
| 213 | + sendPutRequest("/config", nlohmann::json {{key, value}}, CURRENT_FILE_INFO); | |
| 219 | 214 | } |
| 220 | 215 | |
| 221 | 216 | bool Sensor::isCertified() const | ... | ... |
test/CMakeLists.txt
| ... | ... | @@ -31,6 +31,7 @@ target_compile_features(gtest PUBLIC cxx_std_14) |
| 31 | 31 | # define all test sources |
| 32 | 32 | set(TEST_SOURCES |
| 33 | 33 | test_APICache.cpp |
| 34 | + test_BaseDevice.cpp | |
| 34 | 35 | test_BaseHttpHandler.cpp |
| 35 | 36 | test_Bridge.cpp |
| 36 | 37 | test_BridgeConfig.cpp |
| ... | ... | @@ -46,6 +47,8 @@ set(TEST_SOURCES |
| 46 | 47 | test_ResourceList.cpp |
| 47 | 48 | test_Scene.cpp |
| 48 | 49 | test_Schedule.cpp |
| 50 | + test_Sensor.cpp | |
| 51 | + test_SensorList.cpp | |
| 49 | 52 | test_SimpleBrightnessStrategy.cpp |
| 50 | 53 | test_SimpleColorHueStrategy.cpp |
| 51 | 54 | test_SimpleColorTemperatureStrategy.cpp | ... | ... |
test/test_BaseDevice.cpp
0 โ 100644
| 1 | +/** | |
| 2 | + \file test_BaseDevice.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 <gmock/gmock.h> | |
| 23 | +#include <gtest/gtest.h> | |
| 24 | + | |
| 25 | +#include "testhelper.h" | |
| 26 | + | |
| 27 | +#include "hueplusplus/BaseDevice.h" | |
| 28 | +#include "mocks/mock_HttpHandler.h" | |
| 29 | + | |
| 30 | +using namespace hueplusplus; | |
| 31 | +using namespace testing; | |
| 32 | + | |
| 33 | +class TestDevice : public BaseDevice | |
| 34 | +{ | |
| 35 | +public: | |
| 36 | + TestDevice(int id, const HueCommandAPI& commands, const std::string& path, | |
| 37 | + std::chrono::steady_clock::duration refreshDuration) | |
| 38 | + : BaseDevice(id, commands, path, refreshDuration) | |
| 39 | + { } | |
| 40 | +}; | |
| 41 | + | |
| 42 | +class BaseDeviceTest : public Test | |
| 43 | +{ | |
| 44 | +protected: | |
| 45 | + std::shared_ptr<MockHttpHandler> handler; | |
| 46 | + HueCommandAPI commands; | |
| 47 | + nlohmann::json state; | |
| 48 | + std::string path = "/test/"; | |
| 49 | + | |
| 50 | +protected: | |
| 51 | + BaseDeviceTest() | |
| 52 | + : handler(std::make_shared<MockHttpHandler>()), | |
| 53 | + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), | |
| 54 | + state({{"type", "testType"}, {"name", "Test name"}, {"swversion", "1.2.3.4"}, {"modelid", "TEST"}, | |
| 55 | + {"manufacturername", "testManuf"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, | |
| 56 | + {"productname", "Test type"}}) | |
| 57 | + { } | |
| 58 | + | |
| 59 | + TestDevice getDevice(int id) | |
| 60 | + { | |
| 61 | + EXPECT_CALL(*handler, | |
| 62 | + GETJson("/api/" + getBridgeUsername() + path + std::to_string(id), _, getBridgeIp(), getBridgePort())) | |
| 63 | + .WillOnce(Return(state)); | |
| 64 | + return TestDevice(id, commands, path, std::chrono::steady_clock::duration::max()); | |
| 65 | + } | |
| 66 | +}; | |
| 67 | + | |
| 68 | +TEST_F(BaseDeviceTest, getId) | |
| 69 | +{ | |
| 70 | + const int id = 1; | |
| 71 | + EXPECT_EQ(id, getDevice(id).getId()); | |
| 72 | +} | |
| 73 | + | |
| 74 | +TEST_F(BaseDeviceTest, getName) | |
| 75 | +{ | |
| 76 | + EXPECT_EQ("Test name", getDevice(1).getName()); | |
| 77 | +} | |
| 78 | + | |
| 79 | +TEST_F(BaseDeviceTest, getType) | |
| 80 | +{ | |
| 81 | + EXPECT_EQ("testType", getDevice(1).getType()); | |
| 82 | +} | |
| 83 | + | |
| 84 | +TEST_F(BaseDeviceTest, getModelId) | |
| 85 | +{ | |
| 86 | + EXPECT_EQ("TEST", getDevice(1).getModelId()); | |
| 87 | +} | |
| 88 | + | |
| 89 | +TEST_F(BaseDeviceTest, getUId) | |
| 90 | +{ | |
| 91 | + EXPECT_EQ("00:00:00:00:00:00:00:00-00", getDevice(1).getUId()); | |
| 92 | + state.erase("uniqueid"); | |
| 93 | + EXPECT_EQ("", getDevice(1).getUId()); | |
| 94 | +} | |
| 95 | + | |
| 96 | +TEST_F(BaseDeviceTest, getManufacturername) | |
| 97 | +{ | |
| 98 | + EXPECT_EQ("testManuf", getDevice(1).getManufacturername()); | |
| 99 | + state.erase("manufacturername"); | |
| 100 | + EXPECT_EQ("", getDevice(1).getManufacturername()); | |
| 101 | +} | |
| 102 | + | |
| 103 | +TEST_F(BaseDeviceTest, getProductname) | |
| 104 | +{ | |
| 105 | + EXPECT_EQ("Test type", getDevice(1).getProductname()); | |
| 106 | + state.erase("productname"); | |
| 107 | + EXPECT_EQ("", getDevice(1).getProductname()); | |
| 108 | +} | |
| 109 | + | |
| 110 | +TEST_F(BaseDeviceTest, getSwVersion) | |
| 111 | +{ | |
| 112 | + EXPECT_EQ("1.2.3.4", getDevice(1).getSwVersion()); | |
| 113 | +} | |
| 114 | + | |
| 115 | +TEST_F(BaseDeviceTest, setName) | |
| 116 | +{ | |
| 117 | + const std::string name = "asdbsdakfl"; | |
| 118 | + const nlohmann::json request = {{"name", name}}; | |
| 119 | + const nlohmann::json response = { {{"success", {{"/lights/1/name", name}}}} }; | |
| 120 | + | |
| 121 | + TestDevice device = getDevice(1); | |
| 122 | + EXPECT_CALL(*handler, PUTJson("/api/" + getBridgeUsername() + path + "1/name", request, getBridgeIp(), getBridgePort())) | |
| 123 | + .WillOnce(Return(response)); | |
| 124 | + EXPECT_TRUE(device.setName(name)); | |
| 125 | +} | |
| 0 | 126 | \ No newline at end of file | ... | ... |
test/test_Bridge.cpp
| ... | ... | @@ -35,13 +35,13 @@ |
| 35 | 35 | |
| 36 | 36 | using namespace hueplusplus; |
| 37 | 37 | |
| 38 | -class HueFinderTest : public ::testing::Test | |
| 38 | +class BridgeFinderTest : public ::testing::Test | |
| 39 | 39 | { |
| 40 | 40 | protected: |
| 41 | 41 | std::shared_ptr<MockHttpHandler> handler; |
| 42 | 42 | |
| 43 | 43 | protected: |
| 44 | - HueFinderTest() : handler(std::make_shared<MockHttpHandler>()) | |
| 44 | + BridgeFinderTest() : handler(std::make_shared<MockHttpHandler>()) | |
| 45 | 45 | { |
| 46 | 46 | using namespace ::testing; |
| 47 | 47 | |
| ... | ... | @@ -59,10 +59,10 @@ protected: |
| 59 | 59 | .Times(AtLeast(1)) |
| 60 | 60 | .WillRepeatedly(Return(getBridgeXml())); |
| 61 | 61 | } |
| 62 | - ~HueFinderTest() {}; | |
| 62 | + ~BridgeFinderTest() {}; | |
| 63 | 63 | }; |
| 64 | 64 | |
| 65 | -TEST_F(HueFinderTest, FindBridges) | |
| 65 | +TEST_F(BridgeFinderTest, FindBridges) | |
| 66 | 66 | { |
| 67 | 67 | BridgeFinder finder(handler); |
| 68 | 68 | std::vector<BridgeFinder::BridgeIdentification> bridges = finder.FindBridges(); |
| ... | ... | @@ -85,7 +85,7 @@ TEST_F(HueFinderTest, FindBridges) |
| 85 | 85 | EXPECT_TRUE(bridges.empty()); |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | -TEST_F(HueFinderTest, GetBridge) | |
| 88 | +TEST_F(BridgeFinderTest, GetBridge) | |
| 89 | 89 | { |
| 90 | 90 | using namespace ::testing; |
| 91 | 91 | nlohmann::json request {{"devicetype", "HuePlusPlus#User"}}; |
| ... | ... | @@ -120,7 +120,7 @@ TEST_F(HueFinderTest, GetBridge) |
| 120 | 120 | Mock::VerifyAndClearExpectations(handler.get()); |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | -TEST_F(HueFinderTest, AddUsername) | |
| 123 | +TEST_F(BridgeFinderTest, AddUsername) | |
| 124 | 124 | { |
| 125 | 125 | BridgeFinder finder(handler); |
| 126 | 126 | std::vector<BridgeFinder::BridgeIdentification> bridges = finder.FindBridges(); |
| ... | ... | @@ -133,7 +133,7 @@ TEST_F(HueFinderTest, AddUsername) |
| 133 | 133 | EXPECT_EQ(test_bridge.getUsername(), getBridgeUsername()) << "Bridge username not matching"; |
| 134 | 134 | } |
| 135 | 135 | |
| 136 | -TEST_F(HueFinderTest, GetAllUsernames) | |
| 136 | +TEST_F(BridgeFinderTest, GetAllUsernames) | |
| 137 | 137 | { |
| 138 | 138 | BridgeFinder finder(handler); |
| 139 | 139 | std::vector<BridgeFinder::BridgeIdentification> bridges = finder.FindBridges(); | ... | ... |
test/test_Sensor.cpp
0 โ 100644
| 1 | +/** | |
| 2 | + \file test_Sensor.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/Sensor.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 | +class SensorTest : public Test | |
| 34 | +{ | |
| 35 | +protected: | |
| 36 | + std::shared_ptr<MockHttpHandler> handler; | |
| 37 | + HueCommandAPI commands; | |
| 38 | + nlohmann::json state; | |
| 39 | + | |
| 40 | +protected: | |
| 41 | + SensorTest() | |
| 42 | + : handler(std::make_shared<MockHttpHandler>()), | |
| 43 | + commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler), | |
| 44 | + state({{"type", "testSensor"}, {"name", "Test sensor"}, {"swversion", "1.2.3.4"}, {"modelid", "test"}, | |
| 45 | + {"manufacturername", "testManuf"}, {"uniqueid", "00:00:00:00:00:00:00:00-00"}, | |
| 46 | + {"productname", "Test sensor"}, {"config", nlohmann::json::object()}, | |
| 47 | + {"state", nlohmann::json::object()}}) | |
| 48 | + { } | |
| 49 | + | |
| 50 | + Sensor getSensor(int id = 1) | |
| 51 | + { | |
| 52 | + EXPECT_CALL(*handler, | |
| 53 | + GETJson( | |
| 54 | + "/api/" + getBridgeUsername() + "/sensors/" + std::to_string(id), _, getBridgeIp(), getBridgePort())) | |
| 55 | + .WillOnce(Return(state)); | |
| 56 | + return Sensor(id, commands, std::chrono::steady_clock::duration::max()); | |
| 57 | + } | |
| 58 | +}; | |
| 59 | + | |
| 60 | +TEST(Alert, alertFromString) | |
| 61 | +{ | |
| 62 | + EXPECT_EQ(Alert::none, alertFromString("none")); | |
| 63 | + EXPECT_EQ(Alert::select, alertFromString("select")); | |
| 64 | + EXPECT_EQ(Alert::lselect, alertFromString("lselect")); | |
| 65 | + EXPECT_EQ(Alert::none, alertFromString("anything")); | |
| 66 | +} | |
| 67 | + | |
| 68 | +TEST(Alert, alertToString) | |
| 69 | +{ | |
| 70 | + EXPECT_EQ("none", alertToString(Alert::none)); | |
| 71 | + EXPECT_EQ("select", alertToString(Alert::select)); | |
| 72 | + EXPECT_EQ("lselect", alertToString(Alert::lselect)); | |
| 73 | +} | |
| 74 | + | |
| 75 | +TEST_F(SensorTest, On) | |
| 76 | +{ | |
| 77 | + EXPECT_FALSE(getSensor().hasOn()); | |
| 78 | + state["config"]["on"] = true; | |
| 79 | + EXPECT_TRUE(getSensor().hasOn()); | |
| 80 | + EXPECT_TRUE(getSensor().isOn()); | |
| 81 | + | |
| 82 | + EXPECT_CALL(*handler, | |
| 83 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"on", false}}), getBridgeIp(), | |
| 84 | + getBridgePort())) | |
| 85 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/on", false}}}}})); | |
| 86 | + getSensor().setOn(false); | |
| 87 | +} | |
| 88 | + | |
| 89 | +TEST_F(SensorTest, BatteryState) | |
| 90 | +{ | |
| 91 | + EXPECT_FALSE(getSensor().hasBatteryState()); | |
| 92 | + state["config"]["battery"] = 90; | |
| 93 | + EXPECT_TRUE(getSensor().hasBatteryState()); | |
| 94 | + EXPECT_EQ(90, getSensor().getBatteryState()); | |
| 95 | + | |
| 96 | + int percent = 10; | |
| 97 | + EXPECT_CALL(*handler, | |
| 98 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"battery", percent}}), | |
| 99 | + getBridgeIp(), getBridgePort())) | |
| 100 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/battery", percent}}}}})); | |
| 101 | + getSensor().setBatteryState(percent); | |
| 102 | +} | |
| 103 | + | |
| 104 | +TEST_F(SensorTest, Alert) | |
| 105 | +{ | |
| 106 | + EXPECT_FALSE(getSensor().hasAlert()); | |
| 107 | + state["config"]["alert"] = "none"; | |
| 108 | + EXPECT_TRUE(getSensor().hasAlert()); | |
| 109 | + EXPECT_EQ(Alert::none, getSensor().getLastAlert()); | |
| 110 | + | |
| 111 | + std::string alert = "lselect"; | |
| 112 | + EXPECT_CALL(*handler, | |
| 113 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"alert", alert}}), getBridgeIp(), | |
| 114 | + getBridgePort())) | |
| 115 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/alert", alert}}}}})); | |
| 116 | + getSensor().sendAlert(Alert::lselect); | |
| 117 | +} | |
| 118 | + | |
| 119 | +TEST_F(SensorTest, Reachable) | |
| 120 | +{ | |
| 121 | + EXPECT_FALSE(getSensor().hasReachable()); | |
| 122 | + state["config"]["reachable"] = false; | |
| 123 | + EXPECT_TRUE(getSensor().hasReachable()); | |
| 124 | + EXPECT_FALSE(getSensor().isReachable()); | |
| 125 | +} | |
| 126 | + | |
| 127 | +TEST_F(SensorTest, UserTest) | |
| 128 | +{ | |
| 129 | + EXPECT_FALSE(getSensor().hasUserTest()); | |
| 130 | + state["config"]["usertest"] = false; | |
| 131 | + EXPECT_TRUE(getSensor().hasUserTest()); | |
| 132 | + | |
| 133 | + EXPECT_CALL(*handler, | |
| 134 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"usertest", true}}), | |
| 135 | + getBridgeIp(), getBridgePort())) | |
| 136 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/usertest", true}}}}})); | |
| 137 | + getSensor().setUserTest(true); | |
| 138 | +} | |
| 139 | + | |
| 140 | +TEST_F(SensorTest, URL) | |
| 141 | +{ | |
| 142 | + EXPECT_FALSE(getSensor().hasURL()); | |
| 143 | + const std::string url = "https://abc"; | |
| 144 | + state["config"]["url"] = url; | |
| 145 | + EXPECT_TRUE(getSensor().hasURL()); | |
| 146 | + EXPECT_EQ(url, getSensor().getURL()); | |
| 147 | + | |
| 148 | + std::string newUrl = "https://cde"; | |
| 149 | + EXPECT_CALL(*handler, | |
| 150 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"url", newUrl}}), getBridgeIp(), | |
| 151 | + getBridgePort())) | |
| 152 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/url", newUrl}}}}})); | |
| 153 | + getSensor().setURL(newUrl); | |
| 154 | +} | |
| 155 | + | |
| 156 | +TEST_F(SensorTest, getPendingConfig) | |
| 157 | +{ | |
| 158 | + EXPECT_TRUE(getSensor().getPendingConfig().empty()); | |
| 159 | + state["config"]["pending"] = nullptr; | |
| 160 | + EXPECT_TRUE(getSensor().getPendingConfig().empty()); | |
| 161 | + | |
| 162 | + state["config"]["pending"] = {"abc", "cde", "def"}; | |
| 163 | + | |
| 164 | + EXPECT_THAT(getSensor().getPendingConfig(), UnorderedElementsAre("abc", "cde", "def")); | |
| 165 | +} | |
| 166 | + | |
| 167 | +TEST_F(SensorTest, LEDIndication) | |
| 168 | +{ | |
| 169 | + EXPECT_FALSE(getSensor().hasLEDIndication()); | |
| 170 | + state["config"]["ledindication"] = true; | |
| 171 | + EXPECT_TRUE(getSensor().hasLEDIndication()); | |
| 172 | + EXPECT_TRUE(getSensor().getLEDIndication()); | |
| 173 | + | |
| 174 | + EXPECT_CALL(*handler, | |
| 175 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{"ledindication", false}}), | |
| 176 | + getBridgeIp(), getBridgePort())) | |
| 177 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/ledindication", false}}}}})); | |
| 178 | + getSensor().setLEDIndication(false); | |
| 179 | +} | |
| 180 | + | |
| 181 | +TEST_F(SensorTest, getConfig) | |
| 182 | +{ | |
| 183 | + EXPECT_EQ(state["config"], getSensor().getConfig()); | |
| 184 | + state["config"]["attribute"] = false; | |
| 185 | + EXPECT_EQ(state["config"], getSensor().getConfig()); | |
| 186 | +} | |
| 187 | + | |
| 188 | +TEST_F(SensorTest, setConfigAttribute) | |
| 189 | +{ | |
| 190 | + const std::string key = "attribute"; | |
| 191 | + const nlohmann::json value = "some value"; | |
| 192 | + EXPECT_CALL(*handler, | |
| 193 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/config", nlohmann::json({{key, value}}), getBridgeIp(), | |
| 194 | + getBridgePort())) | |
| 195 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/config/" + key, value}}}}})); | |
| 196 | + getSensor().setConfigAttribute(key, value); | |
| 197 | +} | |
| 198 | + | |
| 199 | +TEST_F(SensorTest, getLastUpdated) | |
| 200 | +{ | |
| 201 | + time::AbsoluteTime none = getSensor().getLastUpdated(); | |
| 202 | + EXPECT_EQ(std::chrono::seconds(0), none.getBaseTime().time_since_epoch()); | |
| 203 | + | |
| 204 | + const std::string timestamp = "2020-05-02T12:00:01"; | |
| 205 | + state["state"]["lastupdated"] = timestamp; | |
| 206 | + time::AbsoluteTime time = time::AbsoluteTime::parseUTC(timestamp); | |
| 207 | + EXPECT_EQ(time.getBaseTime(), getSensor().getLastUpdated().getBaseTime()); | |
| 208 | +} | |
| 209 | + | |
| 210 | +TEST_F(SensorTest, getState) | |
| 211 | +{ | |
| 212 | + nlohmann::json stateContent = {{"bla", "bla"}}; | |
| 213 | + state["state"] = stateContent; | |
| 214 | + EXPECT_EQ(stateContent, getSensor().getState()); | |
| 215 | +} | |
| 216 | + | |
| 217 | +TEST_F(SensorTest, setStateAttribute) | |
| 218 | +{ | |
| 219 | + const std::string key = "attribute"; | |
| 220 | + const nlohmann::json value = "some value"; | |
| 221 | + EXPECT_CALL(*handler, | |
| 222 | + PUTJson("/api/" + getBridgeUsername() + "/sensors/1/state", nlohmann::json({{key, value}}), getBridgeIp(), | |
| 223 | + getBridgePort())) | |
| 224 | + .WillOnce(Return(nlohmann::json {{{"success", {{"/sensors/1/state/" + key, value}}}}})); | |
| 225 | + getSensor().setStateAttribute(key, value); | |
| 226 | +} | |
| 227 | + | |
| 228 | +TEST_F(SensorTest, isCertified) | |
| 229 | +{ | |
| 230 | + EXPECT_FALSE(getSensor().isCertified()); | |
| 231 | + state["capabilities"]["certified"] = true; | |
| 232 | + EXPECT_TRUE(getSensor().isCertified()); | |
| 233 | +} | |
| 234 | + | |
| 235 | +TEST_F(SensorTest, isPrimary) | |
| 236 | +{ | |
| 237 | + EXPECT_FALSE(getSensor().isPrimary()); | |
| 238 | + state["capabilities"]["primary"] = true; | |
| 239 | + EXPECT_TRUE(getSensor().isPrimary()); | |
| 240 | +} | |
| 241 | + | |
| 242 | +TEST_F(SensorTest, asSensorType) | |
| 243 | +{ | |
| 244 | + // Test both rvalue and const access | |
| 245 | + { | |
| 246 | + const Sensor s = getSensor(); | |
| 247 | + EXPECT_THROW(s.asSensorType<sensors::DaylightSensor>(), HueException); | |
| 248 | + } | |
| 249 | + EXPECT_THROW(getSensor().asSensorType<sensors::DaylightSensor>(), HueException); | |
| 250 | + | |
| 251 | + state["type"] = sensors::DaylightSensor::typeStr; | |
| 252 | + sensors::DaylightSensor ds = getSensor().asSensorType<sensors::DaylightSensor>(); | |
| 253 | + EXPECT_EQ(1, ds.getId()); | |
| 254 | + const Sensor s = getSensor(); | |
| 255 | + ds = s.asSensorType<sensors::DaylightSensor>(); | |
| 256 | + EXPECT_EQ(s.getId(), ds.getId()); | |
| 257 | +} | ... | ... |
test/test_SensorList.cpp
0 โ 100644
| 1 | +/** | |
| 2 | + \file test_SensorList.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 <gtest/gtest.h> | |
| 23 | + | |
| 24 | +#include "testhelper.h" | |
| 25 | + | |
| 26 | +#include "hueplusplus/SensorList.h" | |
| 27 | +#include "mocks/mock_HttpHandler.h" | |
| 28 | + | |
| 29 | +using namespace hueplusplus; | |
| 30 | +using namespace testing; | |
| 31 | + | |
| 32 | +class BlaSensor | |
| 33 | +{ | |
| 34 | +public: | |
| 35 | + BlaSensor(Sensor s) { } | |
| 36 | + | |
| 37 | + static constexpr const char* typeStr = "bla"; | |
| 38 | +}; | |
| 39 | + | |
| 40 | +TEST(SensorList, getAsType) | |
| 41 | +{ | |
| 42 | + auto handler = std::make_shared<MockHttpHandler>(); | |
| 43 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | |
| 44 | + | |
| 45 | + SensorList sensors {commands, "/sensors", std::chrono::steady_clock::duration::max()}; | |
| 46 | + | |
| 47 | + const int id = 2; | |
| 48 | + const nlohmann::json response = {{std::to_string(id), {{"type", "Daylight"}}}}; | |
| 49 | + | |
| 50 | + EXPECT_CALL(*handler, | |
| 51 | + GETJson("/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | |
| 52 | + .WillOnce(Return(response)); | |
| 53 | + EXPECT_CALL(*handler, | |
| 54 | + GETJson("/api/" + getBridgeUsername() + "/sensors/" + std::to_string(id), nlohmann::json::object(), | |
| 55 | + getBridgeIp(), getBridgePort())) | |
| 56 | + .Times(2) | |
| 57 | + .WillRepeatedly(Return(nlohmann::json {{"type", "Daylight"}})); | |
| 58 | + | |
| 59 | + sensors::DaylightSensor daylightSensor = sensors.getAsType<sensors::DaylightSensor>(id); | |
| 60 | + EXPECT_THROW(sensors.getAsType<BlaSensor>(2), HueException); | |
| 61 | +} | |
| 62 | + | |
| 63 | +TEST(SensorList, getAllByType) | |
| 64 | +{ | |
| 65 | + | |
| 66 | + auto handler = std::make_shared<MockHttpHandler>(); | |
| 67 | + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); | |
| 68 | + | |
| 69 | + SensorList sensors {commands, "/sensors", std::chrono::steady_clock::duration::max()}; | |
| 70 | + | |
| 71 | + // Empty | |
| 72 | + { | |
| 73 | + const nlohmann::json response = nlohmann::json::object(); | |
| 74 | + | |
| 75 | + EXPECT_CALL(*handler, | |
| 76 | + GETJson( | |
| 77 | + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | |
| 78 | + .WillOnce(Return(response)); | |
| 79 | + EXPECT_TRUE(sensors.getAllByType<sensors::DaylightSensor>().empty()); | |
| 80 | + } | |
| 81 | + // Not matching | |
| 82 | + { | |
| 83 | + const nlohmann::json response = {{"1", {{"type", "stuff"}}}}; | |
| 84 | + EXPECT_CALL(*handler, | |
| 85 | + GETJson( | |
| 86 | + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | |
| 87 | + .WillOnce(Return(response)); | |
| 88 | + sensors.refresh(); | |
| 89 | + EXPECT_TRUE(sensors.getAllByType<sensors::DaylightSensor>().empty()); | |
| 90 | + } | |
| 91 | + // Some matching (daylight maybe not the best example, because there is always exactly one) | |
| 92 | + { | |
| 93 | + const nlohmann::json response = {{"1", {{"type", "stuff"}}}, {"2", {{"type", "Daylight"}}}, | |
| 94 | + {"3", {{"type", "stuff"}}}, {"4", {{"type", "Daylight"}}}}; | |
| 95 | + EXPECT_CALL(*handler, | |
| 96 | + GETJson( | |
| 97 | + "/api/" + getBridgeUsername() + "/sensors", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | |
| 98 | + .WillOnce(Return(response)); | |
| 99 | + EXPECT_CALL(*handler, | |
| 100 | + GETJson( | |
| 101 | + "/api/" + getBridgeUsername() + "/sensors/2", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | |
| 102 | + .WillOnce(Return(response["2"])); | |
| 103 | + EXPECT_CALL(*handler, | |
| 104 | + GETJson( | |
| 105 | + "/api/" + getBridgeUsername() + "/sensors/4", nlohmann::json::object(), getBridgeIp(), getBridgePort())) | |
| 106 | + .WillOnce(Return(response["4"])); | |
| 107 | + sensors.refresh(); | |
| 108 | + std::vector<sensors::DaylightSensor> result = sensors.getAllByType<sensors::DaylightSensor>(); | |
| 109 | + EXPECT_THAT(result, | |
| 110 | + UnorderedElementsAre(Truly([](const auto& s) { return s.getId() == 2; }), | |
| 111 | + Truly([](const auto& s) { return s.getId() == 4; }))); | |
| 112 | + } | |
| 113 | +} | ... | ... |