| // Copyright 2014 The Chromium OS 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 "buffet/device_registration_info.h" | 
 |  | 
 | #include <base/json/json_reader.h> | 
 | #include <base/json/json_writer.h> | 
 | #include <base/values.h> | 
 | #include <chromeos/bind_lambda.h> | 
 | #include <chromeos/http/http_request.h> | 
 | #include <chromeos/http/http_transport_fake.h> | 
 | #include <chromeos/key_value_store.h> | 
 | #include <chromeos/mime_utils.h> | 
 | #include <gtest/gtest.h> | 
 |  | 
 | #include "buffet/commands/command_manager.h" | 
 | #include "buffet/commands/unittest_utils.h" | 
 | #include "buffet/device_registration_storage_keys.h" | 
 | #include "buffet/states/mock_state_change_queue_interface.h" | 
 | #include "buffet/states/state_manager.h" | 
 | #include "buffet/storage_impls.h" | 
 |  | 
 | namespace buffet { | 
 |  | 
 | using chromeos::http::request_header::kAuthorization; | 
 | using chromeos::http::fake::ServerRequest; | 
 | using chromeos::http::fake::ServerResponse; | 
 |  | 
 | 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"; | 
 | const char kUserAccountAuthCode[]  = "2/sd_GD1TGFKpJOLJ34-0g5fK0fflp.GlT" | 
 |                                      "I0F5g7hNtFgj5HFGOf8FlGK9eflO"; | 
 | const char kUserAccessToken[]      = "sd56.4.FGDjG_F-gFGF-dFG6gGOG9Dxm5NgdS9" | 
 |                                      "JMx_JLUqhC9bED_YFjLKjlkjLKJlkjLKjlKJea" | 
 |                                      "VZDei530-w0yE2urpQ"; | 
 | const char kUserRefreshToken[]     = "1/zQLKjlKJlkLkLKjLkjLKjLkjLjLkjl0ftc6" | 
 |                                      "cp1nI-GQ"; | 
 |  | 
 | }  // namespace test_data | 
 |  | 
 | // Add the test device registration information. | 
 | void SetDefaultDeviceRegistration(base::DictionaryValue* data) { | 
 |   data->SetString(storage_keys::kRefreshToken, test_data::kRefreshToken); | 
 |   data->SetString(storage_keys::kDeviceId, test_data::kDeviceId); | 
 |   data->SetString(storage_keys::kRobotAccount, test_data::kRobotAccountEmail); | 
 | } | 
 |  | 
 | void OAuth2Handler(const ServerRequest& request, ServerResponse* response) { | 
 |   base::DictionaryValue json; | 
 |   if (request.GetFormField("grant_type") == "refresh_token") { | 
 |     // Refresh device access token. | 
 |     EXPECT_EQ(test_data::kRefreshToken, request.GetFormField("refresh_token")); | 
 |     EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id")); | 
 |     EXPECT_EQ(test_data::kClientSecret, request.GetFormField("client_secret")); | 
 |     json.SetString("access_token", test_data::kAccessToken); | 
 |   } else if (request.GetFormField("grant_type") == "authorization_code") { | 
 |     // Obtain access token. | 
 |     std::string code = request.GetFormField("code"); | 
 |     if (code == test_data::kUserAccountAuthCode) { | 
 |       // Get user access token. | 
 |       EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id")); | 
 |       EXPECT_EQ(test_data::kClientSecret, | 
 |                 request.GetFormField("client_secret")); | 
 |       EXPECT_EQ("urn:ietf:wg:oauth:2.0:oob", | 
 |                 request.GetFormField("redirect_uri")); | 
 |       json.SetString("access_token", test_data::kUserAccessToken); | 
 |       json.SetString("token_type", "Bearer"); | 
 |       json.SetString("refresh_token", test_data::kUserRefreshToken); | 
 |     } else if (code == test_data::kRobotAccountAuthCode) { | 
 |       // Get device access token. | 
 |       EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id")); | 
 |       EXPECT_EQ(test_data::kClientSecret, | 
 |                 request.GetFormField("client_secret")); | 
 |       EXPECT_EQ("oob", request.GetFormField("redirect_uri")); | 
 |       EXPECT_EQ("https://www.googleapis.com/auth/clouddevices", | 
 |                 request.GetFormField("scope")); | 
 |       json.SetString("access_token", test_data::kAccessToken); | 
 |       json.SetString("token_type", "Bearer"); | 
 |       json.SetString("refresh_token", test_data::kRefreshToken); | 
 |     } else { | 
 |       FAIL() << "Unexpected authorization code"; | 
 |     } | 
 |   } else { | 
 |     FAIL() << "Unexpected grant type"; | 
 |   } | 
 |   json.SetInteger("expires_in", 3600); | 
 |   response->ReplyJson(chromeos::http::status_code::Ok, &json); | 
 | } | 
 |  | 
 | void OAuth2HandlerFail(const ServerRequest& request, | 
 |                        ServerResponse* response) { | 
 |   base::DictionaryValue json; | 
 |   EXPECT_EQ("refresh_token", request.GetFormField("grant_type")); | 
 |   EXPECT_EQ(test_data::kRefreshToken, request.GetFormField("refresh_token")); | 
 |   EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id")); | 
 |   EXPECT_EQ(test_data::kClientSecret, request.GetFormField("client_secret")); | 
 |   json.SetString("error", "unable_to_authenticate"); | 
 |   response->ReplyJson(chromeos::http::status_code::BadRequest, &json); | 
 | } | 
 |  | 
 | void OAuth2HandlerDeregister(const ServerRequest& request, | 
 |                              ServerResponse* response) { | 
 |   base::DictionaryValue json; | 
 |   EXPECT_EQ("refresh_token", request.GetFormField("grant_type")); | 
 |   EXPECT_EQ(test_data::kRefreshToken, request.GetFormField("refresh_token")); | 
 |   EXPECT_EQ(test_data::kClientId, request.GetFormField("client_id")); | 
 |   EXPECT_EQ(test_data::kClientSecret, request.GetFormField("client_secret")); | 
 |   json.SetString("error", "invalid_grant"); | 
 |   response->ReplyJson(chromeos::http::status_code::BadRequest, &json); | 
 | } | 
 |  | 
 | void DeviceInfoHandler(const ServerRequest& request, ServerResponse* response) { | 
 |   std::string auth = "Bearer "; | 
 |   auth += test_data::kAccessToken; | 
 |   EXPECT_EQ(auth, | 
 |             request.GetHeader(chromeos::http::request_header::kAuthorization)); | 
 |   response->ReplyJson(chromeos::http::status_code::Ok, { | 
 |     {"channel.supportedType", "xmpp"}, | 
 |     {"deviceKind", "vendor"}, | 
 |     {"id", test_data::kDeviceId}, | 
 |     {"kind", "clouddevices#device"}, | 
 |   }); | 
 | } | 
 |  | 
 | void FinalizeTicketHandler(const ServerRequest& request, | 
 |                            ServerResponse* response) { | 
 |   EXPECT_EQ(test_data::kApiKey, request.GetFormField("key")); | 
 |   EXPECT_TRUE(request.GetData().empty()); | 
 |  | 
 |   response->ReplyJson(chromeos::http::status_code::Ok, { | 
 |     {"id", test_data::kClaimTicketId}, | 
 |     {"kind", "clouddevices#registrationTicket"}, | 
 |     {"oauthClientId", test_data::kClientId}, | 
 |     {"userEmail", "user@email.com"}, | 
 |     {"deviceDraft.id", test_data::kDeviceId}, | 
 |     {"deviceDraft.kind", "clouddevices#device"}, | 
 |     {"deviceDraft.channel.supportedType", "xmpp"}, | 
 |     {"robotAccountEmail", test_data::kRobotAccountEmail}, | 
 |     {"robotAccountAuthorizationCode", test_data::kRobotAccountAuthCode}, | 
 |   }); | 
 | } | 
 |  | 
 | }  // anonymous namespace | 
 |  | 
 | // This is a helper class that allows the unit tests to access private | 
 | // methods and data since TestHelper is declared as a friend to | 
 | // DeviceRegistrationInfo. | 
 | class DeviceRegistrationInfo::TestHelper { | 
 |  public: | 
 |   static bool Save(DeviceRegistrationInfo* info) { | 
 |     return info->Save(); | 
 |   } | 
 |  | 
 |   static void PublishCommands(DeviceRegistrationInfo* info, | 
 |                               const base::ListValue& commands) { | 
 |     return info->PublishCommands(commands); | 
 |   } | 
 |  | 
 |   static bool CheckRegistration(DeviceRegistrationInfo* info, | 
 |                                 chromeos::ErrorPtr* error) { | 
 |     return info->CheckRegistration(error); | 
 |   } | 
 | }; | 
 |  | 
 | class DeviceRegistrationInfoTest : public ::testing::Test { | 
 |  protected: | 
 |   void SetUp() override { | 
 |     storage_ = std::make_shared<MemStorage>(); | 
 |     storage_->Save(&data_); | 
 |     transport_ = std::make_shared<chromeos::http::fake::Transport>(); | 
 |     command_manager_ = std::make_shared<CommandManager>(); | 
 |     state_manager_ = std::make_shared<StateManager>(&mock_state_change_queue_); | 
 |     chromeos::KeyValueStore config_store; | 
 |     config_store.SetString("client_id", test_data::kClientId); | 
 |     config_store.SetString("client_secret", test_data::kClientSecret); | 
 |     config_store.SetString("api_key", test_data::kApiKey); | 
 |     config_store.SetString("device_kind",  "vendor"); | 
 |     config_store.SetString("name",  "Coffee Pot"); | 
 |     config_store.SetString("description", "Easy to clean"); | 
 |     config_store.SetString("location", "Kitchen"); | 
 |     config_store.SetString("anonymous_access_role", "viewer"); | 
 |     config_store.SetString("model_id", "AAAAA"); | 
 |     config_store.SetString("oauth_url", test_data::kOAuthURL); | 
 |     config_store.SetString("service_url", test_data::kServiceURL); | 
 |     std::unique_ptr<BuffetConfig> config{new BuffetConfig}; | 
 |     config->Load(config_store); | 
 |     dev_reg_ = std::unique_ptr<DeviceRegistrationInfo>( | 
 |         new DeviceRegistrationInfo(command_manager_, state_manager_, | 
 |                                    std::move(config), | 
 |                                    transport_, storage_, | 
 |                                    true, | 
 |                                    nullptr)); | 
 |   } | 
 |  | 
 |   base::DictionaryValue data_; | 
 |   std::shared_ptr<MemStorage> storage_; | 
 |   std::shared_ptr<chromeos::http::fake::Transport> transport_; | 
 |   std::unique_ptr<DeviceRegistrationInfo> dev_reg_; | 
 |   std::shared_ptr<CommandManager> command_manager_; | 
 |   testing::NiceMock<MockStateChangeQueueInterface> mock_state_change_queue_; | 
 |   std::shared_ptr<StateManager> state_manager_; | 
 | }; | 
 |  | 
 | //////////////////////////////////////////////////////////////////////////////// | 
 | TEST_F(DeviceRegistrationInfoTest, GetServiceURL) { | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   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, VerifySave) { | 
 |   base::DictionaryValue data; | 
 |   data.SetString(storage_keys::kRefreshToken, "d"); | 
 |   data.SetString(storage_keys::kDeviceId, "e"); | 
 |   data.SetString(storage_keys::kRobotAccount, "h"); | 
 |   data.SetString(storage_keys::kName, "k"); | 
 |   data.SetString(storage_keys::kDescription, "l"); | 
 |   data.SetString(storage_keys::kLocation, "m"); | 
 |   data.SetString(storage_keys::kAnonymousAccessRole, "user"); | 
 |  | 
 |   storage_->Save(&data); | 
 |  | 
 |   // This test isn't really trying to test Load, it is just the easiest | 
 |   // way to initialize the properties in dev_reg_. | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |  | 
 |   // Clear the storage to get a clean test. | 
 |   base::DictionaryValue empty; | 
 |   storage_->Save(&empty); | 
 |   EXPECT_TRUE(DeviceRegistrationInfo::TestHelper::Save(dev_reg_.get())); | 
 |   EXPECT_TRUE(storage_->Load()->Equals(&data)); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) { | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   EXPECT_EQ(test_data::kOAuthURL, dev_reg_->GetOAuthURL()); | 
 |   std::string url = test_data::kOAuthURL; | 
 |   url += "auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fclouddevices&"; | 
 |   url += "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", { | 
 |     {"scope", "https://www.googleapis.com/auth/clouddevices"}, | 
 |     {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"}, | 
 |     {"response_type", "code"}, | 
 |     {"client_id", test_data::kClientId} | 
 |   })); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, CheckRegistration) { | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   EXPECT_FALSE(DeviceRegistrationInfo::TestHelper::CheckRegistration( | 
 |       dev_reg_.get(), nullptr)); | 
 |   EXPECT_EQ(0, transport_->GetRequestCount()); | 
 |  | 
 |   SetDefaultDeviceRegistration(&data_); | 
 |   storage_->Save(&data_); | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetOAuthURL("token"), | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(OAuth2Handler)); | 
 |   transport_->ResetRequestCount(); | 
 |   EXPECT_TRUE(DeviceRegistrationInfo::TestHelper::CheckRegistration( | 
 |       dev_reg_.get(), nullptr)); | 
 |   EXPECT_EQ(1, transport_->GetRequestCount()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) { | 
 |   SetDefaultDeviceRegistration(&data_); | 
 |   storage_->Save(&data_); | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus()); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetOAuthURL("token"), | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(OAuth2HandlerFail)); | 
 |   transport_->ResetRequestCount(); | 
 |   chromeos::ErrorPtr error; | 
 |   EXPECT_FALSE(DeviceRegistrationInfo::TestHelper::CheckRegistration( | 
 |       dev_reg_.get(), &error)); | 
 |   EXPECT_EQ(1, transport_->GetRequestCount()); | 
 |   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "unable_to_authenticate")); | 
 |   EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, CheckDeregistration) { | 
 |   SetDefaultDeviceRegistration(&data_); | 
 |   storage_->Save(&data_); | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus()); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetOAuthURL("token"), | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(OAuth2HandlerDeregister)); | 
 |   transport_->ResetRequestCount(); | 
 |   chromeos::ErrorPtr error; | 
 |   EXPECT_FALSE(DeviceRegistrationInfo::TestHelper::CheckRegistration( | 
 |       dev_reg_.get(), &error)); | 
 |   EXPECT_EQ(1, transport_->GetRequestCount()); | 
 |   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant")); | 
 |   EXPECT_EQ(RegistrationStatus::kInvalidCredentials, | 
 |             dev_reg_->GetRegistrationStatus()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) { | 
 |   SetDefaultDeviceRegistration(&data_); | 
 |   storage_->Save(&data_); | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetOAuthURL("token"), | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(OAuth2Handler)); | 
 |   transport_->AddHandler(dev_reg_->GetDeviceURL(), | 
 |                          chromeos::http::request_type::kGet, | 
 |                          base::Bind(DeviceInfoHandler)); | 
 |   transport_->ResetRequestCount(); | 
 |   auto device_info = dev_reg_->GetDeviceInfo(nullptr); | 
 |   EXPECT_EQ(2, transport_->GetRequestCount()); | 
 |   EXPECT_NE(nullptr, device_info.get()); | 
 |   base::DictionaryValue* dict = nullptr; | 
 |   EXPECT_TRUE(device_info->GetAsDictionary(&dict)); | 
 |   std::string id; | 
 |   EXPECT_TRUE(dict->GetString("id", &id)); | 
 |   EXPECT_EQ(test_data::kDeviceId, id); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, GetDeviceId) { | 
 |   SetDefaultDeviceRegistration(&data_); | 
 |   storage_->Save(&data_); | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetOAuthURL("token"), | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(OAuth2Handler)); | 
 |   transport_->AddHandler(dev_reg_->GetDeviceURL(), | 
 |                          chromeos::http::request_type::kGet, | 
 |                          base::Bind(DeviceInfoHandler)); | 
 |   EXPECT_EQ(test_data::kDeviceId, dev_reg_->GetDeviceId()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, RegisterDevice) { | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |  | 
 |   auto update_ticket = [](const ServerRequest& request, | 
 |                           ServerResponse* response) { | 
 |     EXPECT_EQ(test_data::kApiKey, request.GetFormField("key")); | 
 |     auto json = request.GetDataAsJson(); | 
 |     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("xmpp", value); | 
 |     EXPECT_TRUE(json->GetString("oauthClientId", &value)); | 
 |     EXPECT_EQ(test_data::kClientId, value); | 
 |     EXPECT_TRUE(json->GetString("deviceDraft.deviceKind", &value)); | 
 |     EXPECT_EQ("vendor", 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' | 
 |             } | 
 |           } | 
 |         } | 
 |       }, | 
 |       'robot': { | 
 |         '_jump': { | 
 |           'parameters': { | 
 |             '_height': { | 
 |               'type': 'integer' | 
 |             } | 
 |           } | 
 |         } | 
 |       } | 
 |     })"; | 
 |     EXPECT_JSON_EQ(expected, *commandDefs); | 
 |  | 
 |     base::DictionaryValue json_resp; | 
 |     json_resp.SetString("id", test_data::kClaimTicketId); | 
 |     json_resp.SetString("kind", "clouddevices#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", "clouddevices#device"); | 
 |     json_resp.Set("deviceDraft", device_draft); | 
 |  | 
 |     response->ReplyJson(chromeos::http::status_code::Ok, &json_resp); | 
 |   }; | 
 |  | 
 |   auto json_base = unittests::CreateDictionaryValue(R"({ | 
 |     'base': { | 
 |       'reboot': { | 
 |         'parameters': {'delay': 'integer'}, | 
 |         'results': {} | 
 |       }, | 
 |       'shutdown': { | 
 |         'parameters': {}, | 
 |         'results': {} | 
 |       } | 
 |     } | 
 |   })"); | 
 |   EXPECT_TRUE(command_manager_->LoadBaseCommands(*json_base, nullptr)); | 
 |   auto json_cmds = unittests::CreateDictionaryValue(R"({ | 
 |     'base': { | 
 |       'reboot': { | 
 |         'parameters': {'delay': {'minimum': 10}}, | 
 |         'results': {} | 
 |       } | 
 |     }, | 
 |     'robot': { | 
 |       '_jump': { | 
 |         'parameters': {'_height': 'integer'}, | 
 |         'results': {} | 
 |       } | 
 |     } | 
 |   })"); | 
 |   EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, "", nullptr)); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetServiceURL( | 
 |       std::string("registrationTickets/") + test_data::kClaimTicketId), | 
 |       chromeos::http::request_type::kPatch, | 
 |       base::Bind(update_ticket)); | 
 |   std::string ticket_url = | 
 |       dev_reg_->GetServiceURL("registrationTickets/" + | 
 |                              std::string(test_data::kClaimTicketId)); | 
 |   transport_->AddHandler(ticket_url + "/finalize", | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(FinalizeTicketHandler)); | 
 |  | 
 |   transport_->AddHandler(dev_reg_->GetOAuthURL("token"), | 
 |                          chromeos::http::request_type::kPost, | 
 |                          base::Bind(OAuth2Handler)); | 
 |   storage_->reset_save_count(); | 
 |  | 
 |   std::map<std::string, std::string> params; | 
 |   params["ticket_id"] = test_data::kClaimTicketId; | 
 |   std::string device_id = dev_reg_->RegisterDevice(params, nullptr); | 
 |  | 
 |   EXPECT_EQ(test_data::kDeviceId, device_id); | 
 |   EXPECT_GT(storage_->save_count(), 1);  // The state must have been saved. | 
 |   EXPECT_EQ(3, transport_->GetRequestCount()); | 
 |   EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus()); | 
 |  | 
 |   // Validate the device info saved to storage... | 
 |   auto storage_data = storage_->Load(); | 
 |   base::DictionaryValue* dict = nullptr; | 
 |   EXPECT_TRUE(storage_data->GetAsDictionary(&dict)); | 
 |   std::string value; | 
 |   EXPECT_TRUE(dict->GetString(storage_keys::kDeviceId, &value)); | 
 |   EXPECT_EQ(test_data::kDeviceId, value); | 
 |   EXPECT_TRUE(dict->GetString(storage_keys::kRefreshToken, &value)); | 
 |   EXPECT_EQ(test_data::kRefreshToken, value); | 
 |   EXPECT_TRUE(dict->GetString(storage_keys::kRobotAccount, &value)); | 
 |   EXPECT_EQ(test_data::kRobotAccountEmail, value); | 
 | } | 
 |  | 
 | 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_TRUE(dev_reg_->Load()); | 
 |   EXPECT_EQ(RegistrationStatus::kUnconfigured, | 
 |             dev_reg_->GetRegistrationStatus()); | 
 |   // Put some credentials into our state, make sure we call that offline. | 
 |   SetDefaultDeviceRegistration(&data_); | 
 |   storage_->Save(&data_); | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   EXPECT_EQ(RegistrationStatus::kConnecting, dev_reg_->GetRegistrationStatus()); | 
 | } | 
 |  | 
 | TEST_F(DeviceRegistrationInfoTest, UpdateCommand) { | 
 |   EXPECT_TRUE(dev_reg_->Load()); | 
 |   auto json_cmds = unittests::CreateDictionaryValue(R"({ | 
 |     'robot': { | 
 |       '_jump': { | 
 |         'parameters': {'_height': 'integer'}, | 
 |         'results': {'status': 'string'} | 
 |       } | 
 |     } | 
 |   })"); | 
 |   EXPECT_TRUE(command_manager_->LoadCommands(*json_cmds, "", nullptr)); | 
 |  | 
 |   const std::string command_url = dev_reg_->GetServiceURL("commands/1234"); | 
 |  | 
 |   auto commands_json = unittests::CreateValue(R"([{ | 
 |     'name':'robot._jump', | 
 |     'id':'1234', | 
 |     'parameters': {'_height': 100} | 
 |   }])"); | 
 |   ASSERT_NE(nullptr, commands_json.get()); | 
 |   const base::ListValue* command_list = nullptr; | 
 |   ASSERT_TRUE(commands_json->GetAsList(&command_list)); | 
 |   DeviceRegistrationInfo::TestHelper::PublishCommands(dev_reg_.get(), | 
 |                                                       *command_list); | 
 |   auto command = command_manager_->FindCommand("1234"); | 
 |   ASSERT_NE(nullptr, command); | 
 |   StringPropType string_type; | 
 |   native_types::Object results{ | 
 |     {"status", string_type.CreateValue(std::string{"Ok"}, nullptr)} | 
 |   }; | 
 |  | 
 |   // UpdateCommand when setting command results. | 
 |   auto update_command_results = [](const ServerRequest& request, | 
 |                                    ServerResponse* response) { | 
 |     EXPECT_EQ(R"({"results":{"status":"Ok"}})", | 
 |               request.GetDataAsNormalizedJsonString()); | 
 |     response->ReplyJson(chromeos::http::status_code::Ok, | 
 |                         chromeos::http::FormFieldList{}); | 
 |   }; | 
 |  | 
 |   transport_->AddHandler(command_url, | 
 |                          chromeos::http::request_type::kPatch, | 
 |                          base::Bind(update_command_results)); | 
 |  | 
 |   command->SetResults(results); | 
 |  | 
 |   // UpdateCommand when setting command progress. | 
 |   int count = 0;  // This will be called twice... | 
 |   auto update_command_progress = [&count](const ServerRequest& request, | 
 |                                           ServerResponse* response) { | 
 |     if (count++ == 0) { | 
 |       EXPECT_EQ(R"({"state":"inProgress"})", | 
 |                 request.GetDataAsNormalizedJsonString()); | 
 |     } else { | 
 |       EXPECT_EQ(R"({"progress":{"progress":18}})", | 
 |                 request.GetDataAsNormalizedJsonString()); | 
 |     } | 
 |     response->ReplyJson(chromeos::http::status_code::Ok, | 
 |                         chromeos::http::FormFieldList{}); | 
 |   }; | 
 |  | 
 |   transport_->AddHandler(command_url, | 
 |                          chromeos::http::request_type::kPatch, | 
 |                          base::Bind(update_command_progress)); | 
 |  | 
 |   native_types::Object progress{ | 
 |       {"progress", unittests::make_int_prop_value(18)}}; | 
 |   command->SetProgress(progress); | 
 |  | 
 |   // UpdateCommand when changing command status. | 
 |   auto update_command_state = [](const ServerRequest& request, | 
 |                                  ServerResponse* response) { | 
 |     EXPECT_EQ(R"({"state":"cancelled"})", | 
 |               request.GetDataAsNormalizedJsonString()); | 
 |     response->ReplyJson(chromeos::http::status_code::Ok, | 
 |                         chromeos::http::FormFieldList{}); | 
 |   }; | 
 |  | 
 |   transport_->AddHandler(command_url, | 
 |                          chromeos::http::request_type::kPatch, | 
 |                          base::Bind(update_command_state)); | 
 |  | 
 |   command->Cancel(); | 
 | } | 
 |  | 
 |  | 
 | }  // namespace buffet |