Commit 65ddf85814bc7269767b6371c11d5aeb84937009

Authored by Jojo-1000
Committed by Moritz Wirger
1 parent 6a9b8f2c

Implement searching for new sensors and lights.

include/hueplusplus/Bridge.h
... ... @@ -129,10 +129,10 @@ class Bridge
129 129 friend class BridgeFinder;
130 130  
131 131 public:
132   - using LightList = ResourceList<Light, int>;
  132 + using LightList = SearchableResourceList<Light>;
133 133 using GroupList = GroupResourceList<Group, CreateGroup>;
134   - using ScheduleList = CreateableResourceList<Schedule, int, CreateSchedule>;
135   - using SceneList = CreateableResourceList<Scene, std::string, CreateScene>;
  134 + using ScheduleList = CreateableResourceList<ResourceList<Schedule, int>, CreateSchedule>;
  135 + using SceneList = CreateableResourceList<ResourceList<Scene, std::string>, CreateScene>;
136 136  
137 137 public:
138 138 //! \brief Constructor of Bridge class
... ...
include/hueplusplus/NewDeviceList.h 0 → 100644
  1 +/**
  2 + \file NewDeviceList.h
  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 +#ifndef INCLUDE_HUEPLUSPLUS_NEW_DEVICE_LIST_H
  23 +#define INCLUDE_HUEPLUSPLUS_NEW_DEVICE_LIST_H
  24 +
  25 +#include <map>
  26 +#include <string>
  27 +
  28 +#include "TimePattern.h"
  29 +
  30 +#include "json/json.hpp"
  31 +
  32 +namespace hueplusplus
  33 +{
  34 +//! \brief List of new devices found during the last scan
  35 +class NewDeviceList
  36 +{
  37 +public:
  38 + //! \brief Construct from data
  39 + NewDeviceList(const std::string& lastScan, const std::map<int, std::string>& devices);
  40 +
  41 + //! \brief Get a map of id and name of new devices
  42 + const std::map<int, std::string>& getNewDevices() const;
  43 +
  44 + //! \brief Get whether a last scan time is available
  45 + //!
  46 + //! This can be false if there was no scan since the last restart
  47 + //! or if the scan is still running.
  48 + bool hasLastScanTime() const;
  49 + //! \brief Get whether scan is currently active
  50 + //!
  51 + //! When scan is active, no last scan time is available
  52 + bool isScanActive();
  53 + //! \brief Get time when last scan was completed
  54 + //! \throws HueException when no time is available or timestamp is invalid
  55 + //! \note Must only be called when \ref hasLastScanTime() is true.
  56 + time::AbsoluteTime getLastScanTime() const;
  57 +
  58 + //! \brief Parse from json response
  59 + //! \throws std::invalid_argument when json is invalid.
  60 + //! \throws nlohmann::json::exception when json is invalid.
  61 + static NewDeviceList parse(const nlohmann::json& json);
  62 +
  63 +private:
  64 + std::string lastScan;
  65 + std::map<int, std::string> devices;
  66 +};
  67 +} // namespace hueplusplus
  68 +
  69 +#endif
... ...
include/hueplusplus/ResourceList.h
... ... @@ -29,21 +29,24 @@
29 29  
30 30 #include "APICache.h"
31 31 #include "HueException.h"
  32 +#include "NewDeviceList.h"
32 33 #include "Utils.h"
33 34  
34 35 namespace hueplusplus
35 36 {
36 37 //! \brief Handles a list of a certain API resource
37 38 //! \tparam Resource Resource type that is in the list
38   -//! \tparam IdType Type of the resource id. int or std::string
  39 +//! \tparam IdT Type of the resource id. int or std::string
39 40 //!
40 41 //! The resources are assumed to be in an object with ids as keys.
41 42 //! The Resource class needs a constructor that accepts \c id, HueCommandAPI and \c refreshDuration;
42 43 //! otherwise a factory function needs to be provided that takes \c id and the JSON state.
43   -template <typename Resource, typename IdType>
  44 +template <typename Resource, typename IdT>
44 45 class ResourceList
45 46 {
46 47 public:
  48 + using ResourceType = Resource;
  49 + using IdType = IdT;
47 50 static_assert(std::is_integral<IdType>::value || std::is_same<std::string, IdType>::value,
48 51 "IdType must be integral or string");
49 52  
... ... @@ -236,16 +239,59 @@ protected:
236 239 std::map<IdType, Resource> resources;
237 240 };
238 241  
239   -//! \brief Handles a ResourceList where Resources can be added by the user
  242 +//! \brief Handles a ResourceList of physical devices which can be searched for
240 243 //! \tparam Resource Resource type that is in the list
241   -//! \tparam IdType Type of the resource id. int or std::string
  244 +template <typename Resource>
  245 +class SearchableResourceList : public ResourceList<Resource, int>
  246 +{
  247 +public:
  248 + using ResourceList<Resource, int>::ResourceList;
  249 +
  250 + //! \brief Start search for new devices
  251 + //! \param deviceIds Serial numbers of the devices to search for (max. 10)
  252 + //!
  253 + //! Takes more than 40s. If many devices were found a second search command might be necessary.
  254 + void search(const std::vector<std::string>& deviceIds = {})
  255 + {
  256 + std::string requestPath = this->path;
  257 + // Remove trailing slash
  258 + requestPath.pop_back();
  259 + if (deviceIds.empty())
  260 + {
  261 + this->stateCache.getCommandAPI().POSTRequest(
  262 + requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__});
  263 + }
  264 + else
  265 + {
  266 + this->stateCache.getCommandAPI().POSTRequest(
  267 + requestPath, nlohmann::json {{"deviceid", deviceIds}}, FileInfo {__FILE__, __LINE__, __func__});
  268 + }
  269 + }
  270 +
  271 + //! \brief Get devices found in last search
  272 + NewDeviceList getNewDevices() const
  273 + {
  274 + nlohmann::json response = this->stateCache.getCommandAPI().GETRequest(
  275 + this->path + "new", nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__});
  276 + return NewDeviceList::parse(response);
  277 + }
  278 +
  279 +protected:
  280 + //! \brief Protected defaulted move constructor
  281 + SearchableResourceList(SearchableResourceList&&) = default;
  282 + //! \brief Protected defaulted move assignment
  283 + SearchableResourceList& operator=(SearchableResourceList&&) = default;
  284 +};
  285 +
  286 +//! \brief Handles a ResourceList where Resources can be added by the user
  287 +//! \tparam BaseResourceList Base resource list type (ResourceList or SearchableResourceList).
242 288 //! \tparam CreateType Type that provides parameters for creation.
243 289 //! Must have a const getRequest() function returning the JSON for the POST request.
244   -template <typename Resource, typename IdType, typename CreateType>
245   -class CreateableResourceList : public ResourceList<Resource, IdType>
  290 +template <typename BaseResourceList, typename CreateType>
  291 +class CreateableResourceList : public BaseResourceList
246 292 {
247 293 public:
248   - using ResourceList<Resource, IdType>::ResourceList;
  294 + using BaseResourceList::BaseResourceList;
249 295  
250 296 //! \brief Create a new resource
251 297 //! \param params Parameters for the new resource
... ... @@ -255,7 +301,7 @@ public:
255 301 //! \throws HueAPIResponseException when response contains an error
256 302 //! \throws nlohmann::json::parse_error when response could not be parsed
257 303 //! \throws std::invalid_argument when IdType is int and std::stoi fails
258   - IdType create(const CreateType& params)
  304 + typename BaseResourceList::IdType create(const CreateType& params)
259 305 {
260 306 std::string requestPath = this->path;
261 307 // Remove slash
... ... @@ -273,8 +319,9 @@ public:
273 319 this->stateCache.refresh();
274 320 return this->maybeStoi(idStr);
275 321 }
276   - return IdType {};
  322 + return BaseResourceList::IdType {};
277 323 }
  324 +
278 325 protected:
279 326 //! \brief Protected defaulted move constructor
280 327 CreateableResourceList(CreateableResourceList&&) = default;
... ... @@ -287,10 +334,12 @@ protected:
287 334 //! \tparam CreateType Type that provides parameters for creation.
288 335 //! Must have a const getRequest() function returning the JSON for the POST request.
289 336 template <typename Resource, typename CreateType>
290   -class GroupResourceList : public CreateableResourceList<Resource, int, CreateType>
  337 +class GroupResourceList : public CreateableResourceList<ResourceList<Resource, int>, CreateType>
291 338 {
  339 + using Base = CreateableResourceList<ResourceList<Resource, int>, CreateType>;
  340 +
292 341 public:
293   - using CreateableResourceList<Resource, int, CreateType>::CreateableResourceList;
  342 + using Base::Base;
294 343 //! \brief Get group, specially handles group 0
295 344 //! \see ResourceList::get
296 345 Resource& get(const int& id)
... ... @@ -311,7 +360,7 @@ public:
311 360 }
312 361 //! \brief Get group, specially handles group 0
313 362 //! \see ResourceList::exists
314   - bool exists(int id) const { return id == 0 || CreateableResourceList<Resource, int, CreateType>::exists(id); }
  363 + bool exists(int id) const { return id == 0 || Base::exists(id); }
315 364  
316 365 protected:
317 366 //! \brief Protected defaulted move constructor
... ...
include/hueplusplus/SensorList.h
... ... @@ -30,7 +30,7 @@ namespace hueplusplus
30 30 //! \brief Handles a list of Sensor%s with type specific getters
31 31 //!
32 32 //! Allows to directly get the requested sensor type or all sensors of a given type.
33   -class SensorList : public CreateableResourceList<Sensor, int, CreateSensor>
  33 +class SensorList : public CreateableResourceList<SearchableResourceList<Sensor>, CreateSensor>
34 34 {
35 35 public:
36 36 using CreateableResourceList::CreateableResourceList;
... ...
src/Bridge.cpp
... ... @@ -292,7 +292,7 @@ void Bridge::setHttpHandler(std::shared_ptr&lt;const IHttpHandler&gt; handler)
292 292 {
293 293 http_handler = handler;
294 294 stateCache = std::make_shared<APICache>("", HueCommandAPI(ip, port, username, handler), refreshDuration);
295   - lightList = ResourceList<Light, int>(stateCache, "lights", refreshDuration,
  295 + lightList = LightList(stateCache, "lights", refreshDuration,
296 296 [factory = LightFactory(stateCache->getCommandAPI(), refreshDuration)](
297 297 int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); });
298 298 groupList = GroupList(stateCache, "groups", refreshDuration);
... ...
src/CMakeLists.txt
... ... @@ -22,7 +22,7 @@ set(hueplusplus_SOURCES
22 22 StateTransaction.cpp
23 23 TimePattern.cpp
24 24 UPnP.cpp
25   - Utils.cpp "ZLLSensors.cpp" "CLIPSensors.cpp")
  25 + Utils.cpp "ZLLSensors.cpp" "CLIPSensors.cpp" "NewDeviceList.cpp")
26 26  
27 27 # on windows we want to compile the WinHttpHandler
28 28 if(WIN32)
... ...
src/NewDeviceList.cpp 0 → 100644
  1 +/**
  2 + \file NewDeviceList.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/NewDeviceList.h>
  23 +
  24 +namespace hueplusplus
  25 +{
  26 +NewDeviceList::NewDeviceList(const std::string& lastScan, const std::map<int, std::string>& devices)
  27 + : lastScan(lastScan), devices(devices)
  28 +{ }
  29 +const std::map<int, std::string>& NewDeviceList::getNewDevices() const
  30 +{
  31 + return devices;
  32 +}
  33 +bool NewDeviceList::hasLastScanTime() const
  34 +{
  35 + return !lastScan.empty() && lastScan != "none" && lastScan != "active";
  36 +}
  37 +bool NewDeviceList::isScanActive()
  38 +{
  39 + return lastScan == "active";
  40 +}
  41 +time::AbsoluteTime NewDeviceList::getLastScanTime() const
  42 +{
  43 + return time::AbsoluteTime::parseUTC(lastScan); // UTC? not clear in docs
  44 +}
  45 +NewDeviceList NewDeviceList::parse(const nlohmann::json& json)
  46 +{
  47 + std::map<int, std::string> devices;
  48 + std::string lastScan;
  49 + for (auto it = json.begin(); it != json.end(); ++it)
  50 + {
  51 + if (it.key() == "lastscan")
  52 + {
  53 + lastScan = it.value().get<std::string>();
  54 + }
  55 + else
  56 + {
  57 + int id = std::stoi(it.key());
  58 + devices.emplace(id, it.value().at("name").get<std::string>());
  59 + }
  60 + }
  61 + return NewDeviceList(lastScan, devices);
  62 +}
  63 +} // namespace hueplusplus
0 64 \ No newline at end of file
... ...
src/TimePattern.cpp
... ... @@ -33,16 +33,23 @@ namespace
33 33 {
34 34 std::tm timestampToTm(const std::string& timestamp)
35 35 {
36   - std::tm tm {};
37   - tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900;
38   - tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1;
39   - tm.tm_mday = std::stoi(timestamp.substr(8, 2));
40   - tm.tm_hour = std::stoi(timestamp.substr(11, 2));
41   - tm.tm_min = std::stoi(timestamp.substr(14, 2));
42   - tm.tm_sec = std::stoi(timestamp.substr(17, 2));
43   - // Auto detect daylight savings time
44   - tm.tm_isdst = -1;
45   - return tm;
  36 + try
  37 + {
  38 + std::tm tm {};
  39 + tm.tm_year = std::stoi(timestamp.substr(0, 4)) - 1900;
  40 + tm.tm_mon = std::stoi(timestamp.substr(5, 2)) - 1;
  41 + tm.tm_mday = std::stoi(timestamp.substr(8, 2));
  42 + tm.tm_hour = std::stoi(timestamp.substr(11, 2));
  43 + tm.tm_min = std::stoi(timestamp.substr(14, 2));
  44 + tm.tm_sec = std::stoi(timestamp.substr(17, 2));
  45 + // Auto detect daylight savings time
  46 + tm.tm_isdst = -1;
  47 + return tm;
  48 + }
  49 + catch (const std::invalid_argument& e)
  50 + {
  51 + throw HueException(CURRENT_FILE_INFO, std::string("Invalid argument: ") + e.what());
  52 + }
46 53 }
47 54 } // namespace
48 55  
... ...
test/CMakeLists.txt
... ... @@ -54,7 +54,7 @@ set(TEST_SOURCES
54 54 test_SimpleColorHueStrategy.cpp
55 55 test_SimpleColorTemperatureStrategy.cpp
56 56 test_StateTransaction.cpp
57   - test_TimePattern.cpp)
  57 + test_TimePattern.cpp "test_NewDeviceList.cpp")
58 58  
59 59 set(HuePlusPlus_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include")
60 60  
... ...
test/test_NewDeviceList.cpp 0 → 100644
  1 +/**
  2 + \file test_NewDeviceList.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/HueException.h>
  23 +#include <hueplusplus/NewDeviceList.h>
  24 +
  25 +#include <gtest/gtest.h>
  26 +
  27 +using namespace hueplusplus;
  28 +using namespace testing;
  29 +
  30 +TEST(NewDeviceList, Constructor)
  31 +{
  32 + {
  33 + NewDeviceList list("none", {});
  34 + EXPECT_TRUE(list.getNewDevices().empty());
  35 + EXPECT_FALSE(list.hasLastScanTime());
  36 + EXPECT_FALSE(list.isScanActive());
  37 + EXPECT_THROW(list.getLastScanTime(), HueException);
  38 + }
  39 + {
  40 + const std::map<int, std::string> devices = {{1, "a"}, {2, "b"}, {3, "c"}};
  41 + NewDeviceList list("active", devices);
  42 + EXPECT_FALSE(list.hasLastScanTime());
  43 + EXPECT_TRUE(list.isScanActive());
  44 + EXPECT_EQ(devices, list.getNewDevices());
  45 + EXPECT_THROW(list.getLastScanTime(), HueException);
  46 + }
  47 + {
  48 + const std::string timestamp = "2020-03-01T00:10:00";
  49 + NewDeviceList list(timestamp, {});
  50 + EXPECT_TRUE(list.hasLastScanTime());
  51 + EXPECT_FALSE(list.isScanActive());
  52 + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), list.getLastScanTime().getBaseTime());
  53 + }
  54 +}
  55 +
  56 +TEST(NewDeviceList, parse)
  57 +{
  58 + {
  59 + NewDeviceList list = NewDeviceList::parse({});
  60 + EXPECT_FALSE(list.hasLastScanTime());
  61 + EXPECT_FALSE(list.isScanActive());
  62 + EXPECT_TRUE(list.getNewDevices().empty());
  63 + EXPECT_THROW(list.getLastScanTime(), HueException);
  64 + }
  65 + {
  66 + const std::map<int, std::string> devices = {{1, "a"}, {2, "b"}, {3, "c"}};
  67 + const std::string timestamp = "2020-03-01T00:10:00";
  68 + NewDeviceList list = NewDeviceList::parse(
  69 + {{"1", {{"name", "a"}}}, {"2", {{"name", "b"}}}, {"3", {{"name", "c"}}}, {"lastscan", timestamp}});
  70 + EXPECT_TRUE(list.hasLastScanTime());
  71 + EXPECT_FALSE(list.isScanActive());
  72 + EXPECT_EQ(time::AbsoluteTime::parseUTC(timestamp).getBaseTime(), list.getLastScanTime().getBaseTime());
  73 + EXPECT_EQ(devices, list.getNewDevices());
  74 + }
  75 +}
0 76 \ No newline at end of file
... ...
test/test_ResourceList.cpp
... ... @@ -32,9 +32,9 @@ using namespace testing;
32 32 class TestResource
33 33 {
34 34 public:
35   - TestResource(int id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) {}
  35 + TestResource(int id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration) : id(id) { }
36 36  
37   - void refresh(bool force = false) {}
  37 + void refresh(bool force = false) { }
38 38  
39 39 public:
40 40 int id;
... ... @@ -42,15 +42,15 @@ public:
42 42 class TestResourceFactory
43 43 {
44 44 public:
45   - void refresh(bool force = false) {}
  45 + void refresh(bool force = false) { }
46 46 };
47 47 class TestStringResource
48 48 {
49 49 public:
50 50 TestStringResource(const std::string& id, HueCommandAPI api, std::chrono::steady_clock::duration refreshDuration)
51 51 : id(id)
52   - {}
53   - void refresh(bool force = false) {}
  52 + { }
  53 + void refresh(bool force = false) { }
54 54  
55 55 public:
56 56 std::string id;
... ... @@ -149,12 +149,12 @@ TEST(ResourceList, get)
149 149 TestStringResource& r2 = list.get(id);
150 150 EXPECT_EQ(id, r2.id);
151 151 }
152   -
  152 +
153 153 {
154 154 ResourceList<TestResourceFactory, int> list(commands, path, std::chrono::steady_clock::duration::max());
155 155  
156 156 const int id = 2;
157   - const nlohmann::json response = { {std::to_string(id), {{"resource", "state"}}} };
  157 + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}};
158 158 EXPECT_CALL(*handler,
159 159 GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort()))
160 160 .WillOnce(Return(response));
... ... @@ -162,10 +162,10 @@ TEST(ResourceList, get)
162 162 }
163 163 {
164 164 ResourceList<TestResourceFactory, int> list(commands, path, std::chrono::steady_clock::duration::max(),
165   - [](int, const nlohmann::json&) {return TestResourceFactory(); });
  165 + [](int, const nlohmann::json&) { return TestResourceFactory(); });
166 166  
167 167 const int id = 2;
168   - const nlohmann::json response = { {std::to_string(id), {{"resource", "state"}}} };
  168 + const nlohmann::json response = {{std::to_string(id), {{"resource", "state"}}}};
169 169 EXPECT_CALL(*handler,
170 170 GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort()))
171 171 .WillOnce(Return(response));
... ... @@ -238,22 +238,59 @@ TEST(ResourceList, remove)
238 238 EXPECT_FALSE(list.remove(id));
239 239 }
240 240  
  241 +TEST(SearchableResourceList, search)
  242 +{
  243 + auto handler = std::make_shared<MockHttpHandler>();
  244 + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler);
  245 +
  246 + const std::string path = "/resources";
  247 + SearchableResourceList<TestResource> list(commands, path, std::chrono::steady_clock::duration::max());
  248 + const nlohmann::json response = {{{"success", {{path, "Searching for new devices"}}}}};
  249 + EXPECT_CALL(*handler,
  250 + POSTJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort()))
  251 + .WillOnce(Return(response));
  252 + list.search();
  253 +
  254 + EXPECT_CALL(*handler,
  255 + POSTJson("/api/" + getBridgeUsername() + path, nlohmann::json({{"deviceid", {"abcd", "def", "fgh"}}}),
  256 + getBridgeIp(), getBridgePort()))
  257 + .WillOnce(Return(response));
  258 + list.search({"abcd", "def", "fgh"});
  259 +}
  260 +
  261 +TEST(SearchableResourceList, getNewDevices)
  262 +{
  263 + auto handler = std::make_shared<MockHttpHandler>();
  264 + HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler);
  265 +
  266 + const std::string path = "/resources";
  267 + SearchableResourceList<TestResource> list(commands, path, std::chrono::steady_clock::duration::max());
  268 + const nlohmann::json response = {{"lastscan", "active"}, {"1", {{"name", "A"}}}};
  269 + EXPECT_CALL(*handler,
  270 + GETJson(
  271 + "/api/" + getBridgeUsername() + path + "/new", nlohmann::json::object(), getBridgeIp(), getBridgePort()))
  272 + .WillOnce(Return(response));
  273 + NewDeviceList newDevices = list.getNewDevices();
  274 + EXPECT_TRUE(newDevices.isScanActive());
  275 + EXPECT_THAT(newDevices.getNewDevices(), ElementsAre(std::make_pair(1, "A")));
  276 +}
  277 +
241 278 TEST(CreateableResourceList, create)
242 279 {
243 280 auto handler = std::make_shared<MockHttpHandler>();
244 281 HueCommandAPI commands(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler);
245 282  
246 283 const std::string path = "/resources";
247   - const nlohmann::json response = { {{"success", {{"id", path + "/2"}}}} };
248   - const nlohmann::json request = { {"name", "bla"} };
249   - CreateableResourceList<TestResource, int, TestCreateType> list(commands, path, std::chrono::steady_clock::duration::max());
250   - EXPECT_CALL(*handler, POSTJson(
251   - "/api/" + getBridgeUsername() + path, request, getBridgeIp(), getBridgePort()))
  284 + const nlohmann::json response = {{{"success", {{"id", path + "/2"}}}}};
  285 + const nlohmann::json request = {{"name", "bla"}};
  286 + CreateableResourceList<ResourceList<TestResource, int>, TestCreateType> list(
  287 + commands, path, std::chrono::steady_clock::duration::max());
  288 + EXPECT_CALL(*handler, POSTJson("/api/" + getBridgeUsername() + path, request, getBridgeIp(), getBridgePort()))
252 289 .WillOnce(Return(response))
253 290 .WillOnce(Return(nlohmann::json()));
254   - EXPECT_CALL(*handler, GETJson(
255   - "/api/" + getBridgeUsername() + path, _, getBridgeIp(), getBridgePort()))
256   - .Times(AnyNumber()).WillRepeatedly(Return(nlohmann::json::object()));
  291 + EXPECT_CALL(*handler, GETJson("/api/" + getBridgeUsername() + path, _, getBridgeIp(), getBridgePort()))
  292 + .Times(AnyNumber())
  293 + .WillRepeatedly(Return(nlohmann::json::object()));
257 294 TestCreateType params;
258 295 EXPECT_CALL(params, getRequest()).Times(2).WillRepeatedly(Return(request));
259 296 EXPECT_EQ(2, list.create(params));
... ...