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