weave_unittest.cc revision 7e894da1c5fcc4fafa6cc69b612c9c14f78aed51
1// Copyright 2015 The Weave Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <weave/device.h>
6
7#include <gmock/gmock.h>
8#include <gtest/gtest.h>
9#include <weave/provider/test/fake_task_runner.h>
10#include <weave/provider/test/mock_bluetooth.h>
11#include <weave/provider/test/mock_config_store.h>
12#include <weave/provider/test/mock_dns_service_discovery.h>
13#include <weave/provider/test/mock_http_client.h>
14#include <weave/provider/test/mock_http_server.h>
15#include <weave/provider/test/mock_network.h>
16#include <weave/provider/test/mock_wifi.h>
17#include <weave/test/mock_command.h>
18#include <weave/test/mock_device.h>
19#include <weave/test/unittest_utils.h>
20
21#include "src/bind_lambda.h"
22
23using testing::_;
24using testing::AtLeast;
25using testing::AtMost;
26using testing::HasSubstr;
27using testing::InSequence;
28using testing::Invoke;
29using testing::InvokeWithoutArgs;
30using testing::MatchesRegex;
31using testing::Mock;
32using testing::Return;
33using testing::ReturnRefOfCopy;
34using testing::StartsWith;
35using testing::StrictMock;
36using testing::WithArgs;
37
38namespace weave {
39
40namespace {
41
42using provider::HttpClient;
43using provider::Network;
44using provider::test::MockHttpClientResponse;
45using test::CreateDictionaryValue;
46using test::ValueToString;
47
48const char kCommandDefs[] = R"({
49  "base": {
50    "reboot": {},
51    "_shutdown": {
52      "parameters": {},
53      "results": {}
54    }
55  }
56})";
57
58const char kDeviceResource[] = R"({
59  "kind": "weave#device",
60  "id": "CLOUD_ID",
61  "channel": {
62    "supportedType": "pull"
63  },
64  "deviceKind": "vendor",
65  "modelManifestId": "ABCDE",
66  "systemName": "",
67  "name": "TEST_NAME",
68  "displayName": "",
69  "description": "Developer device",
70  "stateValidationEnabled": true,
71  "commandDefs":{
72    "base": {
73      "reboot": {
74        "minimalRole": "user",
75        "parameters": {"delay": {"type": "integer"}},
76        "results": {}
77      },
78      "shutdown": {
79        "minimalRole": "user",
80        "parameters": {},
81        "results": {}
82      }
83    }
84  },
85  "state":{
86    "base":{
87      "firmwareVersion":"FIRMWARE_VERSION",
88      "localAnonymousAccessMaxRole":"viewer",
89      "localDiscoveryEnabled":true,
90      "localPairingEnabled":true,
91      "network":{
92      }
93    },
94    "power": {"battery_level":44}
95  }
96})";
97
98const char kRegistrationResponse[] = R"({
99  "kind": "weave#registrationTicket",
100  "id": "TICKET_ID",
101  "deviceId": "CLOUD_ID",
102  "oauthClientId": "CLIENT_ID",
103  "userEmail": "USER@gmail.com",
104  "creationTimeMs": "1440087183738",
105  "expirationTimeMs": "1440087423738"
106})";
107
108const char kRegistrationFinalResponse[] = R"({
109  "kind": "weave#registrationTicket",
110  "id": "TICKET_ID",
111  "deviceId": "CLOUD_ID",
112  "oauthClientId": "CLIENT_ID",
113  "userEmail": "USER@gmail.com",
114  "robotAccountEmail": "ROBO@gmail.com",
115  "robotAccountAuthorizationCode": "AUTH_CODE",
116  "creationTimeMs": "1440087183738",
117  "expirationTimeMs": "1440087423738"
118})";
119
120const char kAuthTokenResponse[] = R"({
121  "access_token" : "ACCESS_TOKEN",
122  "token_type" : "Bearer",
123  "expires_in" : 3599,
124  "refresh_token" : "REFRESH_TOKEN"
125})";
126
127const char kStateDefs[] = R"({"power": {"battery_level":"integer"}})";
128
129const char kStateDefaults[] = R"({"power": {"battery_level":44}})";
130
131MATCHER_P(MatchTxt, txt, "") {
132  std::vector<std::string> txt_copy = txt;
133  std::sort(txt_copy.begin(), txt_copy.end());
134  std::vector<std::string> arg_copy = arg;
135  std::sort(arg_copy.begin(), arg_copy.end());
136  return (arg_copy == txt_copy);
137}
138
139template <class Map>
140std::set<typename Map::key_type> GetKeys(const Map& map) {
141  std::set<typename Map::key_type> result;
142  for (const auto& pair : map)
143    result.insert(pair.first);
144  return result;
145}
146
147}  // namespace
148
149class WeaveTest : public ::testing::Test {
150 protected:
151  void SetUp() override {}
152
153  void ExpectRequest(HttpClient::Method method,
154                     const std::string& url,
155                     const std::string& json_response) {
156    EXPECT_CALL(http_client_, SendRequest(method, url, _, _, _))
157        .WillOnce(WithArgs<4>(Invoke([json_response](
158            const HttpClient::SendRequestCallback& callback) {
159          std::unique_ptr<provider::test::MockHttpClientResponse> response{
160              new StrictMock<provider::test::MockHttpClientResponse>};
161          EXPECT_CALL(*response, GetStatusCode())
162              .Times(AtLeast(1))
163              .WillRepeatedly(Return(200));
164          EXPECT_CALL(*response, GetContentType())
165              .Times(AtLeast(1))
166              .WillRepeatedly(Return("application/json; charset=utf-8"));
167          EXPECT_CALL(*response, GetData())
168              .Times(AtLeast(1))
169              .WillRepeatedly(Return(json_response));
170          callback.Run(std::move(response), nullptr);
171        })));
172  }
173
174  void InitConfigStore() {
175    EXPECT_CALL(config_store_, SaveSettings("")).WillRepeatedly(Return());
176  }
177
178  void InitNetwork() {
179    EXPECT_CALL(network_, AddConnectionChangedCallback(_))
180        .WillRepeatedly(Invoke(
181            [this](const provider::Network::ConnectionChangedCallback& cb) {
182              network_callbacks_.push_back(cb);
183            }));
184    EXPECT_CALL(network_, GetConnectionState())
185        .WillRepeatedly(Return(Network::State::kOffline));
186  }
187
188  void InitDnsSd() {
189    EXPECT_CALL(dns_sd_, PublishService(_, _, _)).WillRepeatedly(Return());
190    EXPECT_CALL(dns_sd_, StopPublishing("_privet._tcp")).WillOnce(Return());
191  }
192
193  void InitDnsSdPublishing(bool registered, const std::string& flags) {
194    std::vector<std::string> txt{
195        {"id=TEST_DEVICE_ID"},         {"flags=" + flags}, {"mmid=ABCDE"},
196        {"services=developmentBoard"}, {"txtvers=3"},      {"ty=TEST_NAME"}};
197    if (registered) {
198      txt.push_back("gcd_id=CLOUD_ID");
199
200      // During registration device may announce itself twice:
201      // 1. with GCD ID but not connected (DB)
202      // 2. with GCD ID and connected (BB)
203      EXPECT_CALL(dns_sd_, PublishService("_privet._tcp", 11, MatchTxt(txt)))
204          .Times(AtMost(1))
205          .WillOnce(Return());
206
207      txt[1] = "flags=BB";
208    }
209
210    EXPECT_CALL(dns_sd_, PublishService("_privet._tcp", 11, MatchTxt(txt)))
211        .Times(AtMost(1))
212        .WillOnce(Return());
213  }
214
215  void InitHttpServer() {
216    EXPECT_CALL(http_server_, GetHttpPort()).WillRepeatedly(Return(11));
217    EXPECT_CALL(http_server_, GetHttpsPort()).WillRepeatedly(Return(12));
218    EXPECT_CALL(http_server_, GetRequestTimeout())
219        .WillRepeatedly(Return(base::TimeDelta::Max()));
220    EXPECT_CALL(http_server_, GetHttpsCertificateFingerprint())
221        .WillRepeatedly(Return(std::vector<uint8_t>{1, 2, 3}));
222    EXPECT_CALL(http_server_, AddHttpRequestHandler(_, _))
223        .WillRepeatedly(Invoke(
224            [this](const std::string& path_prefix,
225                   const provider::HttpServer::RequestHandlerCallback& cb) {
226              http_handlers_[path_prefix] = cb;
227            }));
228    EXPECT_CALL(http_server_, AddHttpsRequestHandler(_, _))
229        .WillRepeatedly(Invoke(
230            [this](const std::string& path_prefix,
231                   const provider::HttpServer::RequestHandlerCallback& cb) {
232              https_handlers_[path_prefix] = cb;
233            }));
234  }
235
236  void InitDefaultExpectations() {
237    InitConfigStore();
238    InitNetwork();
239    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
240        .WillOnce(Return());
241    InitHttpServer();
242    InitDnsSd();
243  }
244
245  void StartDevice() {
246    device_ = weave::Device::Create(&config_store_, &task_runner_,
247                                    &http_client_, &network_, &dns_sd_,
248                                    &http_server_, &wifi_, &bluetooth_);
249
250    EXPECT_EQ((std::set<std::string>{
251                  "/privet/info", "/privet/v3/pairing/cancel",
252                  "/privet/v3/pairing/confirm", "/privet/v3/pairing/start"}),
253              GetKeys(http_handlers_));
254    EXPECT_EQ((std::set<std::string>{
255                  "/privet/info", "/privet/v3/auth",
256                  "/privet/v3/checkForUpdates", "/privet/v3/commandDefs",
257                  "/privet/v3/commands/cancel", "/privet/v3/commands/execute",
258                  "/privet/v3/commands/list", "/privet/v3/commands/status",
259                  "/privet/v3/pairing/cancel", "/privet/v3/pairing/confirm",
260                  "/privet/v3/pairing/start", "/privet/v3/setup/start",
261                  "/privet/v3/setup/status", "/privet/v3/state"}),
262              GetKeys(https_handlers_));
263
264    device_->AddCommandDefinitionsFromJson(kCommandDefs);
265    device_->AddStateDefinitionsFromJson(kStateDefs);
266    device_->SetStatePropertiesFromJson(kStateDefaults, nullptr);
267
268    task_runner_.Run();
269  }
270
271  void NotifyNetworkChanged(provider::Network::State state,
272                            base::TimeDelta delay) {
273    auto task = [this, state] {
274      EXPECT_CALL(network_, GetConnectionState()).WillRepeatedly(Return(state));
275      for (const auto& cb : network_callbacks_)
276        cb.Run();
277    };
278
279    task_runner_.PostDelayedTask(FROM_HERE, base::Bind(task), delay);
280  }
281
282  std::map<std::string, provider::HttpServer::RequestHandlerCallback>
283      http_handlers_;
284  std::map<std::string, provider::HttpServer::RequestHandlerCallback>
285      https_handlers_;
286
287  StrictMock<provider::test::MockConfigStore> config_store_;
288  StrictMock<provider::test::FakeTaskRunner> task_runner_;
289  StrictMock<provider::test::MockHttpClient> http_client_;
290  StrictMock<provider::test::MockNetwork> network_;
291  StrictMock<provider::test::MockDnsServiceDiscovery> dns_sd_;
292  StrictMock<provider::test::MockHttpServer> http_server_;
293  StrictMock<provider::test::MockWifi> wifi_;
294  StrictMock<provider::test::MockBluetooth> bluetooth_;
295
296  std::vector<provider::Network::ConnectionChangedCallback> network_callbacks_;
297
298  std::unique_ptr<weave::Device> device_;
299};
300
301TEST_F(WeaveTest, Mocks) {
302  // Test checks if mock implements entire interface and mock can be
303  // instantiated.
304  test::MockDevice device;
305  test::MockCommand command;
306}
307
308TEST_F(WeaveTest, StartMinimal) {
309  InitConfigStore();
310  device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_,
311                                  &network_, nullptr, nullptr, &wifi_, nullptr);
312}
313
314TEST_F(WeaveTest, StartNoWifi) {
315  InitConfigStore();
316  InitNetwork();
317  InitHttpServer();
318  InitDnsSd();
319  InitDnsSdPublishing(false, "CB");
320
321  device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_,
322                                  &network_, &dns_sd_, &http_server_, nullptr,
323                                  &bluetooth_);
324  device_->AddCommandDefinitionsFromJson(kCommandDefs);
325
326  task_runner_.Run();
327}
328
329class WeaveBasicTest : public WeaveTest {
330 public:
331  void SetUp() override {
332    WeaveTest::SetUp();
333
334    InitDefaultExpectations();
335    InitDnsSdPublishing(false, "DB");
336  }
337};
338
339TEST_F(WeaveBasicTest, Start) {
340  StartDevice();
341}
342
343TEST_F(WeaveBasicTest, Register) {
344  EXPECT_CALL(network_, OpenSslSocket(_, _, _)).WillRepeatedly(Return());
345  StartDevice();
346
347  auto draft = CreateDictionaryValue(kDeviceResource);
348  auto response = CreateDictionaryValue(kRegistrationResponse);
349  response->Set("deviceDraft", draft->DeepCopy());
350  ExpectRequest(HttpClient::Method::kPatch,
351                "https://www.googleapis.com/weave/v1/registrationTickets/"
352                "TICKET_ID?key=TEST_API_KEY",
353                ValueToString(*response));
354
355  response = CreateDictionaryValue(kRegistrationFinalResponse);
356  response->Set("deviceDraft", draft->DeepCopy());
357  ExpectRequest(HttpClient::Method::kPost,
358                "https://www.googleapis.com/weave/v1/registrationTickets/"
359                "TICKET_ID/finalize?key=TEST_API_KEY",
360                ValueToString(*response));
361
362  ExpectRequest(HttpClient::Method::kPost,
363                "https://accounts.google.com/o/oauth2/token",
364                kAuthTokenResponse);
365
366  InitDnsSdPublishing(true, "DB");
367
368  bool done = false;
369  device_->Register("TICKET_ID", base::Bind([this, &done](ErrorPtr error) {
370                      EXPECT_FALSE(error);
371                      done = true;
372                      task_runner_.Break();
373                      EXPECT_EQ("CLOUD_ID", device_->GetSettings().cloud_id);
374                    }));
375  task_runner_.Run();
376  EXPECT_TRUE(done);
377}
378
379class WeaveWiFiSetupTest : public WeaveTest {
380 public:
381  void SetUp() override {
382    WeaveTest::SetUp();
383
384    InitConfigStore();
385    InitHttpServer();
386    InitNetwork();
387    InitDnsSd();
388
389    EXPECT_CALL(network_, GetConnectionState())
390        .WillRepeatedly(Return(provider::Network::State::kOnline));
391  }
392};
393
394TEST_F(WeaveWiFiSetupTest, StartOnlineNoPrevSsid) {
395  StartDevice();
396
397  // Short disconnect.
398  NotifyNetworkChanged(provider::Network::State::kOffline, {});
399  NotifyNetworkChanged(provider::Network::State::kOnline,
400                       base::TimeDelta::FromSeconds(10));
401  task_runner_.Run();
402
403  // Long disconnect.
404  NotifyNetworkChanged(Network::State::kOffline, {});
405  auto offline_from = task_runner_.GetClock()->Now();
406  EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
407      .WillOnce(InvokeWithoutArgs([this, offline_from]() {
408        EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
409                  base::TimeDelta::FromMinutes(1));
410        task_runner_.Break();
411      }));
412  task_runner_.Run();
413}
414
415// If device has previously configured WiFi it will run AP for limited time
416// after which it will try to re-connect.
417TEST_F(WeaveWiFiSetupTest, StartOnlineWithPrevSsid) {
418  EXPECT_CALL(config_store_, LoadSettings())
419      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
420  StartDevice();
421
422  // Long disconnect.
423  NotifyNetworkChanged(Network::State::kOffline, {});
424
425  for (int i = 0; i < 5; ++i) {
426    auto offline_from = task_runner_.GetClock()->Now();
427    // Temporarily offline mode.
428    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
429        .WillOnce(InvokeWithoutArgs([this, &offline_from]() {
430          EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
431                    base::TimeDelta::FromMinutes(1));
432          task_runner_.Break();
433        }));
434    task_runner_.Run();
435
436    // Try to reconnect again.
437    offline_from = task_runner_.GetClock()->Now();
438    EXPECT_CALL(wifi_, StopAccessPoint())
439        .WillOnce(InvokeWithoutArgs([this, offline_from]() {
440          EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
441                    base::TimeDelta::FromMinutes(5));
442          task_runner_.Break();
443        }));
444    task_runner_.Run();
445  }
446
447  NotifyNetworkChanged(Network::State::kOnline, {});
448  task_runner_.Run();
449}
450
451TEST_F(WeaveWiFiSetupTest, StartOfflineWithSsid) {
452  EXPECT_CALL(config_store_, LoadSettings())
453      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
454  EXPECT_CALL(network_, GetConnectionState())
455      .WillRepeatedly(Return(Network::State::kOffline));
456
457  auto offline_from = task_runner_.GetClock()->Now();
458  EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
459      .WillOnce(InvokeWithoutArgs([this, &offline_from]() {
460        EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
461                  base::TimeDelta::FromMinutes(1));
462        task_runner_.Break();
463      }));
464
465  StartDevice();
466}
467
468TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithNoSsid) {
469  EXPECT_CALL(network_, GetConnectionState())
470      .WillRepeatedly(Return(Network::State::kOffline));
471  NotifyNetworkChanged(provider::Network::State::kOnline,
472                       base::TimeDelta::FromHours(15));
473
474  {
475    InSequence s;
476    auto time_stamp = task_runner_.GetClock()->Now();
477
478    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
479        .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
480          EXPECT_LE(task_runner_.GetClock()->Now() - time_stamp,
481                    base::TimeDelta::FromMinutes(1));
482          time_stamp = task_runner_.GetClock()->Now();
483        }));
484
485    EXPECT_CALL(wifi_, StopAccessPoint())
486        .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
487          EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
488                    base::TimeDelta::FromMinutes(5));
489          time_stamp = task_runner_.GetClock()->Now();
490          task_runner_.Break();
491        }));
492  }
493
494  StartDevice();
495}
496
497TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithSsid) {
498  EXPECT_CALL(config_store_, LoadSettings())
499      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
500  EXPECT_CALL(network_, GetConnectionState())
501      .WillRepeatedly(Return(Network::State::kOffline));
502  NotifyNetworkChanged(provider::Network::State::kOnline,
503                       base::TimeDelta::FromHours(15));
504
505  {
506    InSequence s;
507    auto time_stamp = task_runner_.GetClock()->Now();
508    for (size_t i = 0; i < 10; ++i) {
509      EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
510          .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
511            EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
512                      base::TimeDelta::FromMinutes(1));
513            time_stamp = task_runner_.GetClock()->Now();
514          }));
515
516      EXPECT_CALL(wifi_, StopAccessPoint())
517          .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
518            EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
519                      base::TimeDelta::FromMinutes(5));
520            time_stamp = task_runner_.GetClock()->Now();
521          }));
522    }
523
524    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
525        .WillOnce(InvokeWithoutArgs([this]() { task_runner_.Break(); }));
526  }
527
528  StartDevice();
529}
530
531}  // namespace weave
532