| // Copyright 2015 The Weave Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include <weave/device.h> | 
 |  | 
 | #include <gmock/gmock.h> | 
 | #include <gtest/gtest.h> | 
 | #include <weave/provider/test/fake_task_runner.h> | 
 | #include <weave/provider/test/mock_bluetooth.h> | 
 | #include <weave/provider/test/mock_config_store.h> | 
 | #include <weave/provider/test/mock_dns_service_discovery.h> | 
 | #include <weave/provider/test/mock_http_client.h> | 
 | #include <weave/provider/test/mock_http_server.h> | 
 | #include <weave/provider/test/mock_network.h> | 
 | #include <weave/provider/test/mock_wifi.h> | 
 | #include <weave/test/mock_command.h> | 
 | #include <weave/test/mock_device.h> | 
 | #include <weave/test/unittest_utils.h> | 
 |  | 
 | #include "src/bind_lambda.h" | 
 |  | 
 | using testing::_; | 
 | using testing::AtLeast; | 
 | using testing::AtMost; | 
 | using testing::HasSubstr; | 
 | using testing::InSequence; | 
 | using testing::Invoke; | 
 | using testing::InvokeWithoutArgs; | 
 | using testing::MatchesRegex; | 
 | using testing::Mock; | 
 | using testing::Return; | 
 | using testing::ReturnRefOfCopy; | 
 | using testing::StartsWith; | 
 | using testing::StrictMock; | 
 | using testing::WithArgs; | 
 |  | 
 | namespace weave { | 
 |  | 
 | namespace { | 
 |  | 
 | using provider::HttpClient; | 
 | using provider::Network; | 
 | using provider::test::MockHttpClientResponse; | 
 | using test::CreateDictionaryValue; | 
 | using test::ValueToString; | 
 |  | 
 | const char kTraitDefs[] = R"({ | 
 |   "trait1": { | 
 |     "commands": { | 
 |       "reboot": { | 
 |         "minimalRole": "user" | 
 |       }, | 
 |       "shutdown": { | 
 |         "minimalRole": "user", | 
 |         "parameters": {}, | 
 |         "results": {} | 
 |       } | 
 |     }, | 
 |     "state": { | 
 |       "firmwareVersion": {"type": "string"} | 
 |     } | 
 |   }, | 
 |   "trait2": { | 
 |     "state": { | 
 |       "battery_level": {"type": "integer"} | 
 |     } | 
 |   } | 
 | })"; | 
 |  | 
 | const char kDeviceResource[] = R"({ | 
 |   "kind": "weave#device", | 
 |   "id": "CLOUD_ID", | 
 |   "channel": { | 
 |     "supportedType": "pull" | 
 |   }, | 
 |   "deviceKind": "vendor", | 
 |   "modelManifestId": "ABCDE", | 
 |   "systemName": "", | 
 |   "name": "TEST_NAME", | 
 |   "displayName": "", | 
 |   "description": "Developer device", | 
 |   "stateValidationEnabled": true, | 
 |   "commandDefs":{ | 
 |     "trait1": { | 
 |       "reboot": { | 
 |         "minimalRole": "user", | 
 |         "parameters": {"delay": {"type": "integer"}}, | 
 |         "results": {} | 
 |       }, | 
 |       "shutdown": { | 
 |         "minimalRole": "user", | 
 |         "parameters": {}, | 
 |         "results": {} | 
 |       } | 
 |     } | 
 |   }, | 
 |   "state":{ | 
 |     "trait1": {"firmwareVersion":"FIRMWARE_VERSION"}, | 
 |     "trait2": {"battery_level":44} | 
 |   }, | 
 |   "traits": { | 
 |     "trait1": { | 
 |       "commands": { | 
 |         "reboot": { | 
 |           "minimalRole": "user" | 
 |         }, | 
 |         "shutdown": { | 
 |           "minimalRole": "user", | 
 |           "parameters": {}, | 
 |           "results": {} | 
 |         } | 
 |       }, | 
 |       "state": { | 
 |         "firmwareVersion": {"type": "string"} | 
 |       } | 
 |     }, | 
 |     "trait2": { | 
 |       "state": { | 
 |         "battery_level": {"type": "integer"} | 
 |       } | 
 |     } | 
 |   }, | 
 |   "components": { | 
 |     "myComponent": { | 
 |       "traits": ["trait1", "trait2"], | 
 |       "state": { | 
 |         "trait1": {"firmwareVersion":"FIRMWARE_VERSION"}, | 
 |         "trait2": {"battery_level":44} | 
 |       } | 
 |     } | 
 |   } | 
 | })"; | 
 |  | 
 | const char kRegistrationResponse[] = R"({ | 
 |   "kind": "weave#registrationTicket", | 
 |   "id": "TICKET_ID", | 
 |   "deviceId": "CLOUD_ID", | 
 |   "oauthClientId": "CLIENT_ID", | 
 |   "userEmail": "USER@gmail.com", | 
 |   "creationTimeMs": "1440087183738", | 
 |   "expirationTimeMs": "1440087423738" | 
 | })"; | 
 |  | 
 | const char kRegistrationFinalResponse[] = R"({ | 
 |   "kind": "weave#registrationTicket", | 
 |   "id": "TICKET_ID", | 
 |   "deviceId": "CLOUD_ID", | 
 |   "oauthClientId": "CLIENT_ID", | 
 |   "userEmail": "USER@gmail.com", | 
 |   "robotAccountEmail": "ROBO@gmail.com", | 
 |   "robotAccountAuthorizationCode": "AUTH_CODE", | 
 |   "creationTimeMs": "1440087183738", | 
 |   "expirationTimeMs": "1440087423738" | 
 | })"; | 
 |  | 
 | const char kAuthTokenResponse[] = R"({ | 
 |   "access_token" : "ACCESS_TOKEN", | 
 |   "token_type" : "Bearer", | 
 |   "expires_in" : 3599, | 
 |   "refresh_token" : "REFRESH_TOKEN" | 
 | })"; | 
 |  | 
 | MATCHER_P(MatchTxt, txt, "") { | 
 |   std::vector<std::string> txt_copy = txt; | 
 |   std::sort(txt_copy.begin(), txt_copy.end()); | 
 |   std::vector<std::string> arg_copy = arg; | 
 |   std::sort(arg_copy.begin(), arg_copy.end()); | 
 |   return (arg_copy == txt_copy); | 
 | } | 
 |  | 
 | template <class Map> | 
 | std::set<typename Map::key_type> GetKeys(const Map& map) { | 
 |   std::set<typename Map::key_type> result; | 
 |   for (const auto& pair : map) | 
 |     result.insert(pair.first); | 
 |   return result; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class WeaveTest : public ::testing::Test { | 
 |  protected: | 
 |   void SetUp() override { | 
 |     EXPECT_CALL(wifi_, IsWifi24Supported()).WillRepeatedly(Return(true)); | 
 |     EXPECT_CALL(wifi_, IsWifi50Supported()).WillRepeatedly(Return(false)); | 
 |   } | 
 |  | 
 |   template <class UrlMatcher> | 
 |   void ExpectRequest(HttpClient::Method method, | 
 |                      const UrlMatcher& url_matcher, | 
 |                      const std::string& json_response) { | 
 |     EXPECT_CALL(http_client_, SendRequest(method, url_matcher, _, _, _)) | 
 |         .WillOnce(WithArgs<4>(Invoke( | 
 |             [json_response](const HttpClient::SendRequestCallback& callback) { | 
 |               std::unique_ptr<provider::test::MockHttpClientResponse> response{ | 
 |                   new StrictMock<provider::test::MockHttpClientResponse>}; | 
 |               EXPECT_CALL(*response, GetStatusCode()) | 
 |                   .Times(AtLeast(1)) | 
 |                   .WillRepeatedly(Return(200)); | 
 |               EXPECT_CALL(*response, GetContentType()) | 
 |                   .Times(AtLeast(1)) | 
 |                   .WillRepeatedly(Return("application/json; charset=utf-8")); | 
 |               EXPECT_CALL(*response, GetData()) | 
 |                   .WillRepeatedly(Return(json_response)); | 
 |               callback.Run(std::move(response), nullptr); | 
 |             }))); | 
 |   } | 
 |  | 
 |   void InitNetwork() { | 
 |     EXPECT_CALL(network_, AddConnectionChangedCallback(_)) | 
 |         .WillRepeatedly(Invoke( | 
 |             [this](const provider::Network::ConnectionChangedCallback& cb) { | 
 |               network_callbacks_.push_back(cb); | 
 |             })); | 
 |     EXPECT_CALL(network_, GetConnectionState()) | 
 |         .WillRepeatedly(Return(Network::State::kOffline)); | 
 |   } | 
 |  | 
 |   void InitDnsSd() { | 
 |     EXPECT_CALL(dns_sd_, PublishService(_, _, _)).WillRepeatedly(Return()); | 
 |     EXPECT_CALL(dns_sd_, StopPublishing("_privet._tcp")).WillOnce(Return()); | 
 |   } | 
 |  | 
 |   void InitDnsSdPublishing(bool registered, const std::string& flags) { | 
 |     std::vector<std::string> txt{ | 
 |         {"id=TEST_DEVICE_ID"},         {"flags=" + flags}, {"mmid=ABCDE"}, | 
 |         {"services=developmentBoard"}, {"txtvers=3"},      {"ty=TEST_NAME"}}; | 
 |     if (registered) { | 
 |       txt.push_back("gcd_id=CLOUD_ID"); | 
 |  | 
 |       // During registration device may announce itself twice: | 
 |       // 1. with GCD ID but not connected (DB) | 
 |       // 2. with GCD ID and connected (BB) | 
 |       EXPECT_CALL(dns_sd_, PublishService("_privet._tcp", 11, MatchTxt(txt))) | 
 |           .Times(AtMost(1)) | 
 |           .WillOnce(Return()); | 
 |  | 
 |       txt[1] = "flags=BB"; | 
 |     } | 
 |  | 
 |     EXPECT_CALL(dns_sd_, PublishService("_privet._tcp", 11, MatchTxt(txt))) | 
 |         .Times(AtMost(1)) | 
 |         .WillOnce(Return()); | 
 |   } | 
 |  | 
 |   void InitHttpServer() { | 
 |     EXPECT_CALL(http_server_, GetHttpPort()).WillRepeatedly(Return(11)); | 
 |     EXPECT_CALL(http_server_, GetHttpsPort()).WillRepeatedly(Return(12)); | 
 |     EXPECT_CALL(http_server_, GetRequestTimeout()) | 
 |         .WillRepeatedly(Return(base::TimeDelta::Max())); | 
 |     EXPECT_CALL(http_server_, GetHttpsCertificateFingerprint()) | 
 |         .WillRepeatedly(Return(std::vector<uint8_t>{1, 2, 3})); | 
 |     EXPECT_CALL(http_server_, AddHttpRequestHandler(_, _)) | 
 |         .WillRepeatedly(Invoke( | 
 |             [this](const std::string& path_prefix, | 
 |                    const provider::HttpServer::RequestHandlerCallback& cb) { | 
 |               http_handlers_[path_prefix] = cb; | 
 |             })); | 
 |     EXPECT_CALL(http_server_, AddHttpsRequestHandler(_, _)) | 
 |         .WillRepeatedly(Invoke( | 
 |             [this](const std::string& path_prefix, | 
 |                    const provider::HttpServer::RequestHandlerCallback& cb) { | 
 |               https_handlers_[path_prefix] = cb; | 
 |             })); | 
 |     EXPECT_CALL(http_server_, RemoveHttpRequestHandler(_)) | 
 |         .WillRepeatedly(Invoke([this](const std::string& path_prefix) { | 
 |           http_handlers_.erase(path_prefix); | 
 |         })); | 
 |     EXPECT_CALL(http_server_, RemoveHttpsRequestHandler(_)) | 
 |         .WillRepeatedly(Invoke([this](const std::string& path_prefix) { | 
 |           https_handlers_.erase(path_prefix); | 
 |         })); | 
 |   } | 
 |  | 
 |   void InitDefaultExpectations() { | 
 |     InitNetwork(); | 
 |     EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |         .WillOnce(Return()); | 
 |     InitHttpServer(); | 
 |     InitDnsSd(); | 
 |   } | 
 |  | 
 |   void StartDevice() { | 
 |     device_ = weave::Device::Create(&config_store_, &task_runner_, | 
 |                                     &http_client_, &network_, &dns_sd_, | 
 |                                     &http_server_, &wifi_, &bluetooth_); | 
 |  | 
 |     EXPECT_EQ((std::set<std::string>{ | 
 |                   // clang-format off | 
 |                   "/privet/info", | 
 |                   "/privet/v3/pairing/cancel", | 
 |                   "/privet/v3/pairing/confirm", | 
 |                   "/privet/v3/pairing/start", | 
 |                   // clang-format on | 
 |               }), | 
 |               GetKeys(http_handlers_)); | 
 |     EXPECT_EQ((std::set<std::string>{ | 
 |                   // clang-format off | 
 |                   "/privet/info", | 
 |                   "/privet/v3/accessControl/claim", | 
 |                   "/privet/v3/accessControl/confirm", | 
 |                   "/privet/v3/auth", | 
 |                   "/privet/v3/checkForUpdates", | 
 |                   "/privet/v3/commands/cancel", | 
 |                   "/privet/v3/commands/execute", | 
 |                   "/privet/v3/commands/list", | 
 |                   "/privet/v3/commands/status", | 
 |                   "/privet/v3/components", | 
 |                   "/privet/v3/pairing/cancel", | 
 |                   "/privet/v3/pairing/confirm", | 
 |                   "/privet/v3/pairing/start", | 
 |                   "/privet/v3/setup/start", | 
 |                   "/privet/v3/setup/status", | 
 |                   "/privet/v3/traits", | 
 |                   // clang-format on | 
 |               }), | 
 |               GetKeys(https_handlers_)); | 
 |  | 
 |     device_->AddTraitDefinitionsFromJson(kTraitDefs); | 
 |     EXPECT_TRUE( | 
 |         device_->AddComponent("myComponent", {"trait1", "trait2"}, nullptr)); | 
 |     EXPECT_TRUE(device_->SetStatePropertiesFromJson( | 
 |         "myComponent", R"({"trait2": {"battery_level":44}})", nullptr)); | 
 |  | 
 |     task_runner_.Run(); | 
 |   } | 
 |  | 
 |   void NotifyNetworkChanged(provider::Network::State state, | 
 |                             base::TimeDelta delay) { | 
 |     auto task = [this, state] { | 
 |       EXPECT_CALL(network_, GetConnectionState()).WillRepeatedly(Return(state)); | 
 |       for (const auto& cb : network_callbacks_) | 
 |         cb.Run(); | 
 |     }; | 
 |  | 
 |     task_runner_.PostDelayedTask(FROM_HERE, base::Bind(task), delay); | 
 |   } | 
 |  | 
 |   std::map<std::string, provider::HttpServer::RequestHandlerCallback> | 
 |       http_handlers_; | 
 |   std::map<std::string, provider::HttpServer::RequestHandlerCallback> | 
 |       https_handlers_; | 
 |  | 
 |   StrictMock<provider::test::MockConfigStore> config_store_; | 
 |   StrictMock<provider::test::FakeTaskRunner> task_runner_; | 
 |   StrictMock<provider::test::MockHttpClient> http_client_; | 
 |   StrictMock<provider::test::MockNetwork> network_; | 
 |   StrictMock<provider::test::MockDnsServiceDiscovery> dns_sd_; | 
 |   StrictMock<provider::test::MockHttpServer> http_server_; | 
 |   StrictMock<provider::test::MockWifi> wifi_; | 
 |   StrictMock<provider::test::MockBluetooth> bluetooth_; | 
 |  | 
 |   std::vector<provider::Network::ConnectionChangedCallback> network_callbacks_; | 
 |  | 
 |   std::unique_ptr<weave::Device> device_; | 
 | }; | 
 |  | 
 | TEST_F(WeaveTest, Mocks) { | 
 |   // Test checks if mock implements entire interface and mock can be | 
 |   // instantiated. | 
 |   test::MockDevice device; | 
 |   test::MockCommand command; | 
 | } | 
 |  | 
 | TEST_F(WeaveTest, StartMinimal) { | 
 |   device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_, | 
 |                                   &network_, nullptr, nullptr, &wifi_, nullptr); | 
 | } | 
 |  | 
 | TEST_F(WeaveTest, StartNoWifi) { | 
 |   InitNetwork(); | 
 |   InitHttpServer(); | 
 |   InitDnsSd(); | 
 |   InitDnsSdPublishing(false, "CB"); | 
 |  | 
 |   device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_, | 
 |                                   &network_, &dns_sd_, &http_server_, nullptr, | 
 |                                   &bluetooth_); | 
 |   device_->AddTraitDefinitionsFromJson(kTraitDefs); | 
 |   EXPECT_TRUE( | 
 |       device_->AddComponent("myComponent", {"trait1", "trait2"}, nullptr)); | 
 |  | 
 |   task_runner_.Run(); | 
 | } | 
 |  | 
 | class WeaveBasicTest : public WeaveTest { | 
 |  public: | 
 |   void SetUp() override { | 
 |     WeaveTest::SetUp(); | 
 |  | 
 |     InitDefaultExpectations(); | 
 |     InitDnsSdPublishing(false, "DB"); | 
 |   } | 
 | }; | 
 |  | 
 | TEST_F(WeaveBasicTest, Start) { | 
 |   StartDevice(); | 
 | } | 
 |  | 
 | TEST_F(WeaveBasicTest, Register) { | 
 |   EXPECT_CALL(network_, OpenSslSocket(_, _, _)).WillRepeatedly(Return()); | 
 |   StartDevice(); | 
 |  | 
 |   auto draft = CreateDictionaryValue(kDeviceResource); | 
 |   auto response = CreateDictionaryValue(kRegistrationResponse); | 
 |   response->Set("deviceDraft", draft->CreateDeepCopy()); | 
 |   ExpectRequest(HttpClient::Method::kPatch, | 
 |                 "https://www.googleapis.com/weave/v1/registrationTickets/" | 
 |                 "TICKET_ID?key=TEST_API_KEY", | 
 |                 ValueToString(*response)); | 
 |  | 
 |   response = CreateDictionaryValue(kRegistrationFinalResponse); | 
 |   response->Set("deviceDraft", draft->CreateDeepCopy()); | 
 |   ExpectRequest(HttpClient::Method::kPost, | 
 |                 "https://www.googleapis.com/weave/v1/registrationTickets/" | 
 |                 "TICKET_ID/finalize?key=TEST_API_KEY", | 
 |                 ValueToString(*response)); | 
 |  | 
 |   ExpectRequest(HttpClient::Method::kPost, | 
 |                 "https://accounts.google.com/o/oauth2/token", | 
 |                 kAuthTokenResponse); | 
 |  | 
 |   ExpectRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"), | 
 |                 {}); | 
 |  | 
 |   InitDnsSdPublishing(true, "DB"); | 
 |  | 
 |   bool done = false; | 
 |   device_->Register(RegistrationData{"TICKET_ID"}, | 
 |                     base::Bind([this, &done](ErrorPtr error) { | 
 |                       EXPECT_FALSE(error); | 
 |                       done = true; | 
 |                       task_runner_.Break(); | 
 |                       EXPECT_EQ("CLOUD_ID", device_->GetSettings().cloud_id); | 
 |                     })); | 
 |   task_runner_.Run(); | 
 |   EXPECT_TRUE(done); | 
 |  | 
 |   done = false; | 
 |   device_->Register(RegistrationData{"TICKET_ID2"}, | 
 |                     base::Bind([this, &done](ErrorPtr error) { | 
 |                       EXPECT_TRUE(error->HasError("already_registered")); | 
 |                       done = true; | 
 |                       task_runner_.Break(); | 
 |                       EXPECT_EQ("CLOUD_ID", device_->GetSettings().cloud_id); | 
 |                     })); | 
 |   task_runner_.Run(); | 
 |   EXPECT_TRUE(done); | 
 | } | 
 |  | 
 | class WeaveWiFiSetupTest : public WeaveTest { | 
 |  public: | 
 |   void SetUp() override { | 
 |     WeaveTest::SetUp(); | 
 |  | 
 |     InitHttpServer(); | 
 |     InitNetwork(); | 
 |     InitDnsSd(); | 
 |  | 
 |     EXPECT_CALL(network_, GetConnectionState()) | 
 |         .WillRepeatedly(Return(provider::Network::State::kOnline)); | 
 |   } | 
 | }; | 
 |  | 
 | TEST_F(WeaveWiFiSetupTest, StartOnlineNoPrevSsid) { | 
 |   StartDevice(); | 
 |  | 
 |   // Short disconnect. | 
 |   NotifyNetworkChanged(provider::Network::State::kOffline, {}); | 
 |   NotifyNetworkChanged(provider::Network::State::kOnline, | 
 |                        base::TimeDelta::FromSeconds(10)); | 
 |   task_runner_.Run(); | 
 |  | 
 |   // Long disconnect. | 
 |   NotifyNetworkChanged(Network::State::kOffline, {}); | 
 |   auto offline_from = task_runner_.GetClock()->Now(); | 
 |   EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |       .WillOnce(InvokeWithoutArgs([this, offline_from]() { | 
 |         EXPECT_GT(task_runner_.GetClock()->Now() - offline_from, | 
 |                   base::TimeDelta::FromMinutes(1)); | 
 |         task_runner_.Break(); | 
 |       })); | 
 |   task_runner_.Run(); | 
 | } | 
 |  | 
 | // If device has previously configured WiFi it will run AP for limited time | 
 | // after which it will try to re-connect. | 
 | TEST_F(WeaveWiFiSetupTest, StartOnlineWithPrevSsid) { | 
 |   EXPECT_CALL(config_store_, LoadSettings()) | 
 |       .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})")); | 
 |   StartDevice(); | 
 |  | 
 |   // Long disconnect. | 
 |   NotifyNetworkChanged(Network::State::kOffline, {}); | 
 |  | 
 |   for (int i = 0; i < 5; ++i) { | 
 |     auto offline_from = task_runner_.GetClock()->Now(); | 
 |     // Temporarily offline mode. | 
 |     EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |         .WillOnce(InvokeWithoutArgs([this, &offline_from]() { | 
 |           EXPECT_GT(task_runner_.GetClock()->Now() - offline_from, | 
 |                     base::TimeDelta::FromMinutes(1)); | 
 |           task_runner_.Break(); | 
 |         })); | 
 |     task_runner_.Run(); | 
 |  | 
 |     // Try to reconnect again. | 
 |     offline_from = task_runner_.GetClock()->Now(); | 
 |     EXPECT_CALL(wifi_, StopAccessPoint()) | 
 |         .WillOnce(InvokeWithoutArgs([this, offline_from]() { | 
 |           EXPECT_GT(task_runner_.GetClock()->Now() - offline_from, | 
 |                     base::TimeDelta::FromMinutes(5)); | 
 |           task_runner_.Break(); | 
 |         })); | 
 |     task_runner_.Run(); | 
 |   } | 
 |  | 
 |   NotifyNetworkChanged(Network::State::kOnline, {}); | 
 |   task_runner_.Run(); | 
 | } | 
 |  | 
 | TEST_F(WeaveWiFiSetupTest, StartOfflineWithSsid) { | 
 |   EXPECT_CALL(config_store_, LoadSettings()) | 
 |       .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})")); | 
 |   EXPECT_CALL(network_, GetConnectionState()) | 
 |       .WillRepeatedly(Return(Network::State::kOffline)); | 
 |  | 
 |   auto offline_from = task_runner_.GetClock()->Now(); | 
 |   EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |       .WillOnce(InvokeWithoutArgs([this, &offline_from]() { | 
 |         EXPECT_GT(task_runner_.GetClock()->Now() - offline_from, | 
 |                   base::TimeDelta::FromMinutes(1)); | 
 |         task_runner_.Break(); | 
 |       })); | 
 |  | 
 |   StartDevice(); | 
 | } | 
 |  | 
 | TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithNoSsid) { | 
 |   EXPECT_CALL(network_, GetConnectionState()) | 
 |       .WillRepeatedly(Return(Network::State::kOffline)); | 
 |   NotifyNetworkChanged(provider::Network::State::kOnline, | 
 |                        base::TimeDelta::FromHours(15)); | 
 |  | 
 |   { | 
 |     InSequence s; | 
 |     auto time_stamp = task_runner_.GetClock()->Now(); | 
 |  | 
 |     EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |         .WillOnce(InvokeWithoutArgs([this, &time_stamp]() { | 
 |           EXPECT_LE(task_runner_.GetClock()->Now() - time_stamp, | 
 |                     base::TimeDelta::FromMinutes(1)); | 
 |           time_stamp = task_runner_.GetClock()->Now(); | 
 |         })); | 
 |  | 
 |     EXPECT_CALL(wifi_, StopAccessPoint()) | 
 |         .WillOnce(InvokeWithoutArgs([this, &time_stamp]() { | 
 |           EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp, | 
 |                     base::TimeDelta::FromMinutes(5)); | 
 |           time_stamp = task_runner_.GetClock()->Now(); | 
 |           task_runner_.Break(); | 
 |         })); | 
 |   } | 
 |  | 
 |   StartDevice(); | 
 | } | 
 |  | 
 | TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithSsid) { | 
 |   EXPECT_CALL(config_store_, LoadSettings()) | 
 |       .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})")); | 
 |   EXPECT_CALL(network_, GetConnectionState()) | 
 |       .WillRepeatedly(Return(Network::State::kOffline)); | 
 |   NotifyNetworkChanged(provider::Network::State::kOnline, | 
 |                        base::TimeDelta::FromHours(15)); | 
 |  | 
 |   { | 
 |     InSequence s; | 
 |     auto time_stamp = task_runner_.GetClock()->Now(); | 
 |     for (size_t i = 0; i < 10; ++i) { | 
 |       EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |           .WillOnce(InvokeWithoutArgs([this, &time_stamp]() { | 
 |             EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp, | 
 |                       base::TimeDelta::FromMinutes(1)); | 
 |             time_stamp = task_runner_.GetClock()->Now(); | 
 |           })); | 
 |  | 
 |       EXPECT_CALL(wifi_, StopAccessPoint()) | 
 |           .WillOnce(InvokeWithoutArgs([this, &time_stamp]() { | 
 |             EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp, | 
 |                       base::TimeDelta::FromMinutes(5)); | 
 |             time_stamp = task_runner_.GetClock()->Now(); | 
 |           })); | 
 |     } | 
 |  | 
 |     EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv"))) | 
 |         .WillOnce(InvokeWithoutArgs([this]() { task_runner_.Break(); })); | 
 |   } | 
 |  | 
 |   StartDevice(); | 
 | } | 
 |  | 
 | }  // namespace weave |