weave_unittest.cc revision 551a82bbefbfd6dc83309eec440a41ca737062b4
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  void ExpectRequest(HttpClient::Method method,
187                     const std::string& url,
188                     const std::string& json_response) {
189    EXPECT_CALL(http_client_, SendRequest(method, url, _, _, _))
190        .WillOnce(WithArgs<4>(Invoke([json_response](
191            const HttpClient::SendRequestCallback& callback) {
192          std::unique_ptr<provider::test::MockHttpClientResponse> response{
193              new StrictMock<provider::test::MockHttpClientResponse>};
194          EXPECT_CALL(*response, GetStatusCode())
195              .Times(AtLeast(1))
196              .WillRepeatedly(Return(200));
197          EXPECT_CALL(*response, GetContentType())
198              .Times(AtLeast(1))
199              .WillRepeatedly(Return("application/json; charset=utf-8"));
200          EXPECT_CALL(*response, GetData())
201              .Times(AtLeast(1))
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  InitDnsSdPublishing(true, "DB");
405
406  bool done = false;
407  device_->Register("TICKET_ID", base::Bind([this, &done](ErrorPtr error) {
408                      EXPECT_FALSE(error);
409                      done = true;
410                      task_runner_.Break();
411                      EXPECT_EQ("CLOUD_ID", device_->GetSettings().cloud_id);
412                    }));
413  task_runner_.Run();
414  EXPECT_TRUE(done);
415}
416
417class WeaveWiFiSetupTest : public WeaveTest {
418 public:
419  void SetUp() override {
420    WeaveTest::SetUp();
421
422    InitConfigStore();
423    InitHttpServer();
424    InitNetwork();
425    InitDnsSd();
426
427    EXPECT_CALL(network_, GetConnectionState())
428        .WillRepeatedly(Return(provider::Network::State::kOnline));
429  }
430};
431
432TEST_F(WeaveWiFiSetupTest, StartOnlineNoPrevSsid) {
433  StartDevice();
434
435  // Short disconnect.
436  NotifyNetworkChanged(provider::Network::State::kOffline, {});
437  NotifyNetworkChanged(provider::Network::State::kOnline,
438                       base::TimeDelta::FromSeconds(10));
439  task_runner_.Run();
440
441  // Long disconnect.
442  NotifyNetworkChanged(Network::State::kOffline, {});
443  auto offline_from = task_runner_.GetClock()->Now();
444  EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
445      .WillOnce(InvokeWithoutArgs([this, offline_from]() {
446        EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
447                  base::TimeDelta::FromMinutes(1));
448        task_runner_.Break();
449      }));
450  task_runner_.Run();
451}
452
453// If device has previously configured WiFi it will run AP for limited time
454// after which it will try to re-connect.
455TEST_F(WeaveWiFiSetupTest, StartOnlineWithPrevSsid) {
456  EXPECT_CALL(config_store_, LoadSettings())
457      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
458  StartDevice();
459
460  // Long disconnect.
461  NotifyNetworkChanged(Network::State::kOffline, {});
462
463  for (int i = 0; i < 5; ++i) {
464    auto offline_from = task_runner_.GetClock()->Now();
465    // Temporarily offline mode.
466    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
467        .WillOnce(InvokeWithoutArgs([this, &offline_from]() {
468          EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
469                    base::TimeDelta::FromMinutes(1));
470          task_runner_.Break();
471        }));
472    task_runner_.Run();
473
474    // Try to reconnect again.
475    offline_from = task_runner_.GetClock()->Now();
476    EXPECT_CALL(wifi_, StopAccessPoint())
477        .WillOnce(InvokeWithoutArgs([this, offline_from]() {
478          EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
479                    base::TimeDelta::FromMinutes(5));
480          task_runner_.Break();
481        }));
482    task_runner_.Run();
483  }
484
485  NotifyNetworkChanged(Network::State::kOnline, {});
486  task_runner_.Run();
487}
488
489TEST_F(WeaveWiFiSetupTest, StartOfflineWithSsid) {
490  EXPECT_CALL(config_store_, LoadSettings())
491      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
492  EXPECT_CALL(network_, GetConnectionState())
493      .WillRepeatedly(Return(Network::State::kOffline));
494
495  auto offline_from = task_runner_.GetClock()->Now();
496  EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
497      .WillOnce(InvokeWithoutArgs([this, &offline_from]() {
498        EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
499                  base::TimeDelta::FromMinutes(1));
500        task_runner_.Break();
501      }));
502
503  StartDevice();
504}
505
506TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithNoSsid) {
507  EXPECT_CALL(network_, GetConnectionState())
508      .WillRepeatedly(Return(Network::State::kOffline));
509  NotifyNetworkChanged(provider::Network::State::kOnline,
510                       base::TimeDelta::FromHours(15));
511
512  {
513    InSequence s;
514    auto time_stamp = task_runner_.GetClock()->Now();
515
516    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
517        .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
518          EXPECT_LE(task_runner_.GetClock()->Now() - time_stamp,
519                    base::TimeDelta::FromMinutes(1));
520          time_stamp = task_runner_.GetClock()->Now();
521        }));
522
523    EXPECT_CALL(wifi_, StopAccessPoint())
524        .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
525          EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
526                    base::TimeDelta::FromMinutes(5));
527          time_stamp = task_runner_.GetClock()->Now();
528          task_runner_.Break();
529        }));
530  }
531
532  StartDevice();
533}
534
535TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithSsid) {
536  EXPECT_CALL(config_store_, LoadSettings())
537      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
538  EXPECT_CALL(network_, GetConnectionState())
539      .WillRepeatedly(Return(Network::State::kOffline));
540  NotifyNetworkChanged(provider::Network::State::kOnline,
541                       base::TimeDelta::FromHours(15));
542
543  {
544    InSequence s;
545    auto time_stamp = task_runner_.GetClock()->Now();
546    for (size_t i = 0; i < 10; ++i) {
547      EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
548          .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
549            EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
550                      base::TimeDelta::FromMinutes(1));
551            time_stamp = task_runner_.GetClock()->Now();
552          }));
553
554      EXPECT_CALL(wifi_, StopAccessPoint())
555          .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
556            EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
557                      base::TimeDelta::FromMinutes(5));
558            time_stamp = task_runner_.GetClock()->Now();
559          }));
560    }
561
562    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
563        .WillOnce(InvokeWithoutArgs([this]() { task_runner_.Break(); }));
564  }
565
566  StartDevice();
567}
568
569}  // namespace weave
570