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