| // 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 "src/device_registration_info.h" | 
 |  | 
 | #include <base/json/json_reader.h> | 
 | #include <base/json/json_writer.h> | 
 | #include <base/values.h> | 
 | #include <gtest/gtest.h> | 
 | #include <weave/provider/test/fake_task_runner.h> | 
 | #include <weave/provider/test/mock_config_store.h> | 
 | #include <weave/provider/test/mock_http_client.h> | 
 | #include <weave/test/unittest_utils.h> | 
 |  | 
 | #include "src/bind_lambda.h" | 
 | #include "src/commands/command_manager.h" | 
 | #include "src/http_constants.h" | 
 | #include "src/states/mock_state_change_queue_interface.h" | 
 | #include "src/states/state_manager.h" | 
 |  | 
 | using testing::_; | 
 | using testing::AtLeast; | 
 | using testing::Invoke; | 
 | using testing::InvokeWithoutArgs; | 
 | using testing::Mock; | 
 | using testing::Return; | 
 | using testing::ReturnRef; | 
 | using testing::ReturnRefOfCopy; | 
 | using testing::SaveArg; | 
 | using testing::StrictMock; | 
 | using testing::WithArgs; | 
 |  | 
 | namespace weave { | 
 |  | 
 | using test::CreateDictionaryValue; | 
 | using test::CreateValue; | 
 | using provider::test::MockHttpClient; | 
 | using provider::test::MockHttpClientResponse; | 
 | using provider::HttpClient; | 
 |  | 
 | namespace { | 
 |  | 
 | namespace test_data { | 
 |  | 
 | const char kServiceURL[] = "http://gcd.server.com/"; | 
 | const char kOAuthURL[] = "http://oauth.server.com/"; | 
 | const char kApiKey[] = "GOadRdTf9FERf0k4w6EFOof56fUJ3kFDdFL3d7f"; | 
 | const char kClientId[] = | 
 |     "123543821385-sfjkjshdkjhfk234sdfsdfkskd" | 
 |     "fkjh7f.apps.googleusercontent.com"; | 
 | const char kClientSecret[] = "5sdGdGlfolGlrFKfdFlgP6FG"; | 
 | const char kDeviceId[] = "4a7ea2d1-b331-1e1f-b206-e863c7635196"; | 
 | const char kClaimTicketId[] = "RTcUE"; | 
 | const char kAccessToken[] = | 
 |     "ya29.1.AADtN_V-dLUM-sVZ0qVjG9Dxm5NgdS9J" | 
 |     "Mx_JLUqhC9bED_YFjzHZtYt65ZzXCS35NMAeaVZ" | 
 |     "Dei530-w0yE2urpQ"; | 
 | const char kRefreshToken[] = | 
 |     "1/zQmxR6PKNvhcxf9SjXUrCjcmCrcqRKXctc6cp" | 
 |     "1nI-GQ"; | 
 | const char kRobotAccountAuthCode[] = | 
 |     "4/Mf_ujEhPejVhOq-OxW9F5cSOnWzx." | 
 |     "YgciVjTYGscRshQV0ieZDAqiTIjMigI"; | 
 | const char kRobotAccountEmail[] = | 
 |     "6ed0b3f54f9bd619b942f4ad2441c252@" | 
 |     "clouddevices.gserviceaccount.com"; | 
 |  | 
 | }  // namespace test_data | 
 |  | 
 | std::string GetFormField(const std::string& data, const std::string& name) { | 
 |   EXPECT_FALSE(data.empty()); | 
 |   for (const auto& i : WebParamsDecode(data)) { | 
 |     if (i.first == name) | 
 |       return i.second; | 
 |   } | 
 |   return {}; | 
 | } | 
 |  | 
 | std::unique_ptr<HttpClient::Response> ReplyWithJson(int status_code, | 
 |                                                     const base::Value& json) { | 
 |   std::string text; | 
 |   base::JSONWriter::WriteWithOptions( | 
 |       json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &text); | 
 |  | 
 |   std::unique_ptr<MockHttpClientResponse> response{ | 
 |       new StrictMock<MockHttpClientResponse>}; | 
 |   EXPECT_CALL(*response, GetStatusCode()) | 
 |       .Times(AtLeast(1)) | 
 |       .WillRepeatedly(Return(status_code)); | 
 |   EXPECT_CALL(*response, GetContentType()) | 
 |       .Times(AtLeast(1)) | 
 |       .WillRepeatedly(Return(http::kJsonUtf8)); | 
 |   EXPECT_CALL(*response, GetData()) | 
 |       .Times(AtLeast(1)) | 
 |       .WillRepeatedly(Return(text)); | 
 |   return std::move(response); | 
 | } | 
 |  | 
 | std::pair<std::string, std::string> GetAuthHeader() { | 
 |   return {http::kAuthorization, | 
 |           std::string("Bearer ") + test_data::kAccessToken}; | 
 | } | 
 |  | 
 | std::pair<std::string, std::string> GetJsonHeader() { | 
 |   return {http::kContentType, http::kJsonUtf8}; | 
 | } | 
 |  | 
 | std::pair<std::string, std::string> GetFormHeader() { | 
 |   return {http::kContentType, http::kWwwFormUrlEncoded}; | 
 | } | 
 |  | 
 | }  // anonymous namespace | 
 |  | 
 | class DeviceRegistrationInfoTest : public ::testing::Test { | 
 |  protected: | 
 |   void SetUp() override { | 
 |     EXPECT_CALL(mock_state_change_queue_, GetLastStateChangeId()) | 
 |         .WillRepeatedly(Return(0)); | 
 |     EXPECT_CALL(mock_state_change_queue_, MockAddOnStateUpdatedCallback(_)) | 
 |         .WillRepeatedly(Return(nullptr)); | 
 |  | 
 |     command_manager_ = std::make_shared<CommandManager>(); | 
 |     state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_); | 
 |  | 
 |     std::unique_ptr<Config> config{new Config{&config_store_}}; | 
 |     config_ = config.get(); | 
 |     dev_reg_.reset(new DeviceRegistrationInfo{command_manager_, state_manager_, | 
 |                                               std::move(config), &task_runner_, | 
 |                                               &http_client_, nullptr}); | 
 |  | 
 |     ReloadDefaults(); | 
 |   } | 
 |  | 
 |   void ReloadDefaults() { | 
 |     EXPECT_CALL(config_store_, LoadDefaults(_)) | 
 |         .WillOnce(Invoke([](Settings* settings) { | 
 |           settings->client_id = test_data::kClientId; | 
 |           settings->client_secret = test_data::kClientSecret; | 
 |           settings->api_key = test_data::kApiKey; | 
 |           settings->oem_name = "Coffee Pot Maker"; | 
 |           settings->model_name = "Pot v1"; | 
 |           settings->name = "Coffee Pot"; | 
 |           settings->description = "Easy to clean"; | 
 |           settings->location = "Kitchen"; | 
 |           settings->local_anonymous_access_role = AuthScope::kViewer; | 
 |           settings->model_id = "AAAAA"; | 
 |           settings->oauth_url = test_data::kOAuthURL; | 
 |           settings->service_url = test_data::kServiceURL; | 
 |           return true; | 
 |         })); | 
 |     config_->Load(); | 
 |     dev_reg_->Start(); | 
 |   } | 
 |  | 
 |   void ReloadSettings() { | 
 |     base::DictionaryValue dict; | 
 |     dict.SetString("refresh_token", test_data::kRefreshToken); | 
 |     dict.SetString("cloud_id", test_data::kDeviceId); | 
 |     dict.SetString("robot_account", test_data::kRobotAccountEmail); | 
 |     std::string json_string; | 
 |     base::JSONWriter::WriteWithOptions( | 
 |         dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string); | 
 |     EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(json_string)); | 
 |     ReloadDefaults(); | 
 |   } | 
 |  | 
 |   void PublishCommands(const base::ListValue& commands) { | 
 |     dev_reg_->PublishCommands(commands, nullptr); | 
 |   } | 
 |  | 
 |   bool RefreshAccessToken(ErrorPtr* error) const { | 
 |     bool succeeded = false; | 
 |     auto callback = [&succeeded, &error](ErrorPtr in_error) { | 
 |       if (error) { | 
 |         *error = std::move(in_error); | 
 |         return; | 
 |       } | 
 |       succeeded = true; | 
 |     }; | 
 |     dev_reg_->RefreshAccessToken(base::Bind(callback)); | 
 |     return succeeded; | 
 |   } | 
 |  | 
 |   void SetAccessToken() { dev_reg_->access_token_ = test_data::kAccessToken; } | 
 |  | 
 |   GcdState GetGcdState() const { return dev_reg_->GetGcdState(); } | 
 |  | 
 |   bool HaveRegistrationCredentials() const { | 
 |     return dev_reg_->HaveRegistrationCredentials(); | 
 |   } | 
 |  | 
 |   provider::test::FakeTaskRunner task_runner_; | 
 |   provider::test::MockConfigStore config_store_; | 
 |   StrictMock<MockHttpClient> http_client_; | 
 |   base::DictionaryValue data_; | 
 |   Config* config_{nullptr}; | 
 |   std::unique_ptr<DeviceRegistrationInfo> dev_reg_; | 
 |   std::shared_ptr<CommandManager> command_manager_; | 
 |   StrictMock<MockStateChangeQueueInterface> mock_state_change_queue_; | 
 |   std::shared_ptr<StateManager> state_manager_; | 
 | }; | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, GetServiceURL) { | 
 |   EXPECT_EQ(test_data::kServiceURL, dev_reg_->GetServiceURL()); | 
 |   std::string url = test_data::kServiceURL; | 
 |   url += "registrationTickets"; | 
 |   EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets")); | 
 |   url += "?key="; | 
 |   url += test_data::kApiKey; | 
 |   EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets", | 
 |                                          {{"key", test_data::kApiKey}})); | 
 |   url += "&restart=true"; | 
 |   EXPECT_EQ(url, dev_reg_->GetServiceURL( | 
 |                      "registrationTickets", | 
 |                      { | 
 |                          {"key", test_data::kApiKey}, {"restart", "true"}, | 
 |                      })); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) { | 
 |   EXPECT_EQ(test_data::kOAuthURL, dev_reg_->GetOAuthURL()); | 
 |   std::string url = test_data::kOAuthURL; | 
 |   url += "auth?redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&"; | 
 |   url += "response_type=code&"; | 
 |   url += "client_id="; | 
 |   url += test_data::kClientId; | 
 |   EXPECT_EQ(url, dev_reg_->GetOAuthURL( | 
 |                      "auth", {{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"}, | 
 |                               {"response_type", "code"}, | 
 |                               {"client_id", test_data::kClientId}})); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) { | 
 |   EXPECT_FALSE(HaveRegistrationCredentials()); | 
 |   ReloadSettings(); | 
 |  | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"), | 
 |                   HttpClient::Headers{GetFormHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         EXPECT_EQ("refresh_token", GetFormField(data, "grant_type")); | 
 |         EXPECT_EQ(test_data::kRefreshToken, | 
 |                   GetFormField(data, "refresh_token")); | 
 |         EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id")); | 
 |         EXPECT_EQ(test_data::kClientSecret, | 
 |                   GetFormField(data, "client_secret")); | 
 |  | 
 |         base::DictionaryValue json; | 
 |         json.SetString("access_token", test_data::kAccessToken); | 
 |         json.SetInteger("expires_in", 3600); | 
 |  | 
 |         callback.Run(ReplyWithJson(200, json), nullptr); | 
 |       }))); | 
 |  | 
 |   EXPECT_TRUE(RefreshAccessToken(nullptr)); | 
 |   EXPECT_TRUE(HaveRegistrationCredentials()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) { | 
 |   ReloadSettings(); | 
 |   EXPECT_EQ(GcdState::kConnecting, GetGcdState()); | 
 |  | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"), | 
 |                   HttpClient::Headers{GetFormHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         EXPECT_EQ("refresh_token", GetFormField(data, "grant_type")); | 
 |         EXPECT_EQ(test_data::kRefreshToken, | 
 |                   GetFormField(data, "refresh_token")); | 
 |         EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id")); | 
 |         EXPECT_EQ(test_data::kClientSecret, | 
 |                   GetFormField(data, "client_secret")); | 
 |  | 
 |         base::DictionaryValue json; | 
 |         json.SetString("error", "unable_to_authenticate"); | 
 |         callback.Run(ReplyWithJson(400, json), nullptr); | 
 |       }))); | 
 |  | 
 |   ErrorPtr error; | 
 |   EXPECT_FALSE(RefreshAccessToken(&error)); | 
 |   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "unable_to_authenticate")); | 
 |   EXPECT_EQ(GcdState::kConnecting, GetGcdState()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, CheckDeregistration) { | 
 |   ReloadSettings(); | 
 |   EXPECT_EQ(GcdState::kConnecting, GetGcdState()); | 
 |  | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"), | 
 |                   HttpClient::Headers{GetFormHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         EXPECT_EQ("refresh_token", GetFormField(data, "grant_type")); | 
 |         EXPECT_EQ(test_data::kRefreshToken, | 
 |                   GetFormField(data, "refresh_token")); | 
 |         EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id")); | 
 |         EXPECT_EQ(test_data::kClientSecret, | 
 |                   GetFormField(data, "client_secret")); | 
 |  | 
 |         base::DictionaryValue json; | 
 |         json.SetString("error", "invalid_grant"); | 
 |         callback.Run(ReplyWithJson(400, json), nullptr); | 
 |       }))); | 
 |  | 
 |   ErrorPtr error; | 
 |   EXPECT_FALSE(RefreshAccessToken(&error)); | 
 |   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant")); | 
 |   EXPECT_EQ(GcdState::kInvalidCredentials, GetGcdState()); | 
 |   EXPECT_EQ(test_data::kDeviceId, dev_reg_->GetSettings().cloud_id); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) { | 
 |   ReloadSettings(); | 
 |   SetAccessToken(); | 
 |  | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kGet, dev_reg_->GetDeviceURL(), | 
 |                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>( | 
 |           Invoke([](const std::string& data, | 
 |                     const HttpClient::SendRequestCallback& callback) { | 
 |             base::DictionaryValue json; | 
 |             json.SetString("channel.supportedType", "xmpp"); | 
 |             json.SetString("deviceKind", "vendor"); | 
 |             json.SetString("id", test_data::kDeviceId); | 
 |             json.SetString("kind", "weave#device"); | 
 |             callback.Run(ReplyWithJson(200, json), nullptr); | 
 |           }))); | 
 |  | 
 |   bool succeeded = false; | 
 |   auto callback = [&succeeded, this](const base::DictionaryValue& info, | 
 |                                      ErrorPtr error) { | 
 |     EXPECT_FALSE(error); | 
 |     std::string id; | 
 |     EXPECT_TRUE(info.GetString("id", &id)); | 
 |     EXPECT_EQ(test_data::kDeviceId, id); | 
 |     succeeded = true; | 
 |   }; | 
 |   dev_reg_->GetDeviceInfo(base::Bind(callback)); | 
 |   EXPECT_TRUE(succeeded); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, RegisterDevice) { | 
 |   auto json_cmds = CreateDictionaryValue(R"({ | 
 |     'base': { | 
 |       'reboot': { | 
 |         'parameters': {'delay': {'minimum': 10, 'type': 'integer'}}, | 
 |         'minimalRole': 'user' | 
 |       } | 
 |     }, | 
 |     'robot': { | 
 |       '_jump': { | 
 |         'parameters': {'_height': {'type': 'integer'}}, | 
 |         'minimalRole': 'user' | 
 |       } | 
 |     } | 
 |   })"); | 
 |   EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, nullptr)); | 
 |  | 
 |   std::string ticket_url = dev_reg_->GetServiceURL("registrationTickets/") + | 
 |                            test_data::kClaimTicketId; | 
 |   EXPECT_CALL(http_client_, | 
 |               SendRequest(HttpClient::Method::kPatch, | 
 |                           ticket_url + "?key=" + test_data::kApiKey, | 
 |                           HttpClient::Headers{GetJsonHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         auto json = test::CreateDictionaryValue(data); | 
 |         EXPECT_NE(nullptr, json.get()); | 
 |         std::string value; | 
 |         EXPECT_TRUE(json->GetString("id", &value)); | 
 |         EXPECT_EQ(test_data::kClaimTicketId, value); | 
 |         EXPECT_TRUE( | 
 |             json->GetString("deviceDraft.channel.supportedType", &value)); | 
 |         EXPECT_EQ("pull", value); | 
 |         EXPECT_TRUE(json->GetString("oauthClientId", &value)); | 
 |         EXPECT_EQ(test_data::kClientId, value); | 
 |         EXPECT_TRUE(json->GetString("deviceDraft.description", &value)); | 
 |         EXPECT_EQ("Easy to clean", value); | 
 |         EXPECT_TRUE(json->GetString("deviceDraft.location", &value)); | 
 |         EXPECT_EQ("Kitchen", value); | 
 |         EXPECT_TRUE(json->GetString("deviceDraft.modelManifestId", &value)); | 
 |         EXPECT_EQ("AAAAA", value); | 
 |         EXPECT_TRUE(json->GetString("deviceDraft.name", &value)); | 
 |         EXPECT_EQ("Coffee Pot", value); | 
 |         base::DictionaryValue* commandDefs = nullptr; | 
 |         EXPECT_TRUE( | 
 |             json->GetDictionary("deviceDraft.commandDefs", &commandDefs)); | 
 |         EXPECT_FALSE(commandDefs->empty()); | 
 |  | 
 |         auto expected = R"({ | 
 |             'base': { | 
 |               'reboot': { | 
 |                 'parameters': { | 
 |                   'delay': { | 
 |                     'minimum': 10, | 
 |                     'type': 'integer' | 
 |                   } | 
 |                 }, | 
 |                 'minimalRole': 'user' | 
 |               } | 
 |             }, | 
 |             'robot': { | 
 |               '_jump': { | 
 |                 'parameters': { | 
 |                   '_height': { | 
 |                     'type': 'integer' | 
 |                   } | 
 |                 }, | 
 |                 'minimalRole': 'user' | 
 |               } | 
 |             } | 
 |           })"; | 
 |         EXPECT_JSON_EQ(expected, *commandDefs); | 
 |  | 
 |         base::DictionaryValue json_resp; | 
 |         json_resp.SetString("id", test_data::kClaimTicketId); | 
 |         json_resp.SetString("kind", "weave#registrationTicket"); | 
 |         json_resp.SetString("oauthClientId", test_data::kClientId); | 
 |         base::DictionaryValue* device_draft = nullptr; | 
 |         EXPECT_TRUE(json->GetDictionary("deviceDraft", &device_draft)); | 
 |         device_draft = device_draft->DeepCopy(); | 
 |         device_draft->SetString("id", test_data::kDeviceId); | 
 |         device_draft->SetString("kind", "weave#device"); | 
 |         json_resp.Set("deviceDraft", device_draft); | 
 |  | 
 |         callback.Run(ReplyWithJson(200, json_resp), nullptr); | 
 |       }))); | 
 |  | 
 |   EXPECT_CALL(http_client_, | 
 |               SendRequest(HttpClient::Method::kPost, | 
 |                           ticket_url + "/finalize?key=" + test_data::kApiKey, | 
 |                           HttpClient::Headers{}, _, _)) | 
 |       .WillOnce(WithArgs<4>( | 
 |           Invoke([](const HttpClient::SendRequestCallback& callback) { | 
 |             base::DictionaryValue json; | 
 |             json.SetString("id", test_data::kClaimTicketId); | 
 |             json.SetString("kind", "weave#registrationTicket"); | 
 |             json.SetString("oauthClientId", test_data::kClientId); | 
 |             json.SetString("userEmail", "user@email.com"); | 
 |             json.SetString("deviceDraft.id", test_data::kDeviceId); | 
 |             json.SetString("deviceDraft.kind", "weave#device"); | 
 |             json.SetString("deviceDraft.channel.supportedType", "xmpp"); | 
 |             json.SetString("robotAccountEmail", test_data::kRobotAccountEmail); | 
 |             json.SetString("robotAccountAuthorizationCode", | 
 |                            test_data::kRobotAccountAuthCode); | 
 |             callback.Run(ReplyWithJson(200, json), nullptr); | 
 |           }))); | 
 |  | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"), | 
 |                   HttpClient::Headers{GetFormHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         EXPECT_EQ("authorization_code", GetFormField(data, "grant_type")); | 
 |         EXPECT_EQ(test_data::kRobotAccountAuthCode, GetFormField(data, "code")); | 
 |         EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id")); | 
 |         EXPECT_EQ(test_data::kClientSecret, | 
 |                   GetFormField(data, "client_secret")); | 
 |         EXPECT_EQ("oob", GetFormField(data, "redirect_uri")); | 
 |  | 
 |         base::DictionaryValue json; | 
 |         json.SetString("access_token", test_data::kAccessToken); | 
 |         json.SetString("token_type", "Bearer"); | 
 |         json.SetString("refresh_token", test_data::kRefreshToken); | 
 |         json.SetInteger("expires_in", 3600); | 
 |  | 
 |         callback.Run(ReplyWithJson(200, json), nullptr); | 
 |       }))); | 
 |  | 
 |   bool done = false; | 
 |   dev_reg_->RegisterDevice( | 
 |       test_data::kClaimTicketId, base::Bind([this, &done](ErrorPtr error) { | 
 |         EXPECT_FALSE(error); | 
 |         done = true; | 
 |         task_runner_.Break(); | 
 |         EXPECT_EQ(GcdState::kConnecting, GetGcdState()); | 
 |  | 
 |         // Validate the device info saved to storage... | 
 |         EXPECT_EQ(test_data::kDeviceId, dev_reg_->GetSettings().cloud_id); | 
 |         EXPECT_EQ(test_data::kRefreshToken, | 
 |                   dev_reg_->GetSettings().refresh_token); | 
 |         EXPECT_EQ(test_data::kRobotAccountEmail, | 
 |                   dev_reg_->GetSettings().robot_account); | 
 |       })); | 
 |   task_runner_.Run(); | 
 |   EXPECT_TRUE(done); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, OOBRegistrationStatus) { | 
 |   // After we've been initialized, we should be either offline or | 
 |   // unregistered, depending on whether or not we've found credentials. | 
 |   EXPECT_EQ(GcdState::kUnconfigured, GetGcdState()); | 
 |   // Put some credentials into our state, make sure we call that offline. | 
 |   ReloadSettings(); | 
 |   EXPECT_EQ(GcdState::kConnecting, GetGcdState()); | 
 | } | 
 |  | 
 | class DeviceRegistrationInfoUpdateCommandTest | 
 |     : public DeviceRegistrationInfoTest { | 
 |  protected: | 
 |   void SetUp() override { | 
 |     DeviceRegistrationInfoTest::SetUp(); | 
 |  | 
 |     ReloadSettings(); | 
 |     SetAccessToken(); | 
 |  | 
 |     auto json_cmds = CreateDictionaryValue(R"({ | 
 |       'robot': { | 
 |         '_jump': { | 
 |           'parameters': {'_height': 'integer'}, | 
 |           'progress': {'progress': 'integer'}, | 
 |           'results': {'status': 'string'}, | 
 |           'minimalRole': 'user' | 
 |         } | 
 |       } | 
 |     })"); | 
 |     EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, nullptr)); | 
 |  | 
 |     command_url_ = dev_reg_->GetServiceURL("commands/1234"); | 
 |  | 
 |     auto commands_json = CreateValue(R"([{ | 
 |       'name':'robot._jump', | 
 |       'id':'1234', | 
 |       'parameters': {'_height': 100}, | 
 |       'minimalRole': 'user' | 
 |     }])"); | 
 |     ASSERT_NE(nullptr, commands_json.get()); | 
 |     const base::ListValue* command_list = nullptr; | 
 |     ASSERT_TRUE(commands_json->GetAsList(&command_list)); | 
 |     PublishCommands(*command_list); | 
 |     command_ = command_manager_->FindCommand("1234"); | 
 |     ASSERT_NE(nullptr, command_); | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     task_runner_.RunOnce(); | 
 |     DeviceRegistrationInfoTest::TearDown(); | 
 |   } | 
 |  | 
 |   Command* command_{nullptr}; | 
 |   std::string command_url_; | 
 | }; | 
 |  | 
 | TEST_F(DeviceRegistrationInfoUpdateCommandTest, SetProgress) { | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPatch, command_url_, | 
 |                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         EXPECT_JSON_EQ((R"({"state":"inProgress","progress":{"progress":18}})"), | 
 |                        *CreateDictionaryValue(data)); | 
 |         base::DictionaryValue json; | 
 |         callback.Run(ReplyWithJson(200, json), nullptr); | 
 |       }))); | 
 |   EXPECT_TRUE(command_->SetProgress(*CreateDictionaryValue("{'progress':18}"), | 
 |                                     nullptr)); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoUpdateCommandTest, Complete) { | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPatch, command_url_, | 
 |                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>( | 
 |           Invoke([](const std::string& data, | 
 |                     const HttpClient::SendRequestCallback& callback) { | 
 |             EXPECT_JSON_EQ(R"({"state":"done", "results":{"status":"Ok"}})", | 
 |                            *CreateDictionaryValue(data)); | 
 |             base::DictionaryValue json; | 
 |             callback.Run(ReplyWithJson(200, json), nullptr); | 
 |           }))); | 
 |   EXPECT_TRUE( | 
 |       command_->Complete(*CreateDictionaryValue("{'status': 'Ok'}"), nullptr)); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoUpdateCommandTest, Cancel) { | 
 |   EXPECT_CALL( | 
 |       http_client_, | 
 |       SendRequest(HttpClient::Method::kPatch, command_url_, | 
 |                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _)) | 
 |       .WillOnce(WithArgs<3, 4>(Invoke([]( | 
 |           const std::string& data, | 
 |           const HttpClient::SendRequestCallback& callback) { | 
 |         EXPECT_JSON_EQ(R"({"state":"cancelled"})", | 
 |                        *CreateDictionaryValue(data)); | 
 |         base::DictionaryValue json; | 
 |         callback.Run(ReplyWithJson(200, json), nullptr); | 
 |       }))); | 
 |   EXPECT_TRUE(command_->Cancel(nullptr)); | 
 | } | 
 |  | 
 | }  // namespace weave |