Home | History | Annotate | Download | only in src
      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 "src/device_registration_info.h"
      6 
      7 #include <base/json/json_reader.h>
      8 #include <base/json/json_writer.h>
      9 #include <base/values.h>
     10 #include <gtest/gtest.h>
     11 #include <weave/provider/test/fake_task_runner.h>
     12 #include <weave/provider/test/mock_config_store.h>
     13 #include <weave/provider/test/mock_http_client.h>
     14 #include <weave/test/unittest_utils.h>
     15 
     16 #include "src/bind_lambda.h"
     17 #include "src/component_manager_impl.h"
     18 #include "src/http_constants.h"
     19 #include "src/privet/auth_manager.h"
     20 #include "src/test/mock_clock.h"
     21 
     22 using testing::_;
     23 using testing::AtLeast;
     24 using testing::HasSubstr;
     25 using testing::Invoke;
     26 using testing::InvokeWithoutArgs;
     27 using testing::Mock;
     28 using testing::Return;
     29 using testing::ReturnRef;
     30 using testing::ReturnRefOfCopy;
     31 using testing::SaveArg;
     32 using testing::StrictMock;
     33 using testing::WithArgs;
     34 
     35 namespace weave {
     36 
     37 using test::CreateDictionaryValue;
     38 using test::CreateValue;
     39 using provider::test::MockHttpClient;
     40 using provider::test::MockHttpClientResponse;
     41 using provider::HttpClient;
     42 
     43 namespace {
     44 
     45 namespace test_data {
     46 
     47 const char kXmppEndpoint[] = "xmpp.server.com:1234";
     48 const char kServiceURL[] = "http://gcd.server.com/";
     49 const char kOAuthURL[] = "http://oauth.server.com/";
     50 const char kApiKey[] = "GOadRdTf9FERf0k4w6EFOof56fUJ3kFDdFL3d7f";
     51 const char kClientId[] =
     52     "123543821385-sfjkjshdkjhfk234sdfsdfkskd"
     53     "fkjh7f.apps.googleusercontent.com";
     54 const char kClientSecret[] = "5sdGdGlfolGlrFKfdFlgP6FG";
     55 const char kCloudId[] = "4a7ea2d1-b331-1e1f-b206-e863c7635196";
     56 const char kDeviceId[] = "f6885e46-b432-42d7-86a5-d759bfb61f62";
     57 const char kClaimTicketId[] = "RTcUE";
     58 const char kAccessToken[] =
     59     "ya29.1.AADtN_V-dLUM-sVZ0qVjG9Dxm5NgdS9J"
     60     "Mx_JLUqhC9bED_YFjzHZtYt65ZzXCS35NMAeaVZ"
     61     "Dei530-w0yE2urpQ";
     62 const char kRefreshToken[] =
     63     "1/zQmxR6PKNvhcxf9SjXUrCjcmCrcqRKXctc6cp"
     64     "1nI-GQ";
     65 const char kRobotAccountAuthCode[] =
     66     "4/Mf_ujEhPejVhOq-OxW9F5cSOnWzx."
     67     "YgciVjTYGscRshQV0ieZDAqiTIjMigI";
     68 const char kRobotAccountEmail[] =
     69     "6ed0b3f54f9bd619b942f4ad2441c252@"
     70     "clouddevices.gserviceaccount.com";
     71 const char kAuthInfo[] = R"({
     72   "localAuthInfo": {
     73     "certFingerprint":
     74     "FQY6BEINDjw3FgsmYChRWgMzMhc4TC8uG0UUUFhdDz0=",
     75     "localId": "f6885e46-b432-42d7-86a5-d759bfb61f62"
     76   }
     77 })";
     78 
     79 }  // namespace test_data
     80 
     81 std::string GetFormField(const std::string& data, const std::string& name) {
     82   EXPECT_FALSE(data.empty());
     83   for (const auto& i : WebParamsDecode(data)) {
     84     if (i.first == name)
     85       return i.second;
     86   }
     87   return {};
     88 }
     89 
     90 std::unique_ptr<HttpClient::Response> ReplyWithJson(int status_code,
     91                                                     const base::Value& json) {
     92   std::string text;
     93   base::JSONWriter::WriteWithOptions(
     94       json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &text);
     95 
     96   std::unique_ptr<MockHttpClientResponse> response{
     97       new StrictMock<MockHttpClientResponse>};
     98   EXPECT_CALL(*response, GetStatusCode())
     99       .Times(AtLeast(1))
    100       .WillRepeatedly(Return(status_code));
    101   EXPECT_CALL(*response, GetContentType())
    102       .Times(AtLeast(1))
    103       .WillRepeatedly(Return(http::kJsonUtf8));
    104   EXPECT_CALL(*response, GetData())
    105       .Times(AtLeast(1))
    106       .WillRepeatedly(Return(text));
    107   return std::move(response);
    108 }
    109 
    110 std::pair<std::string, std::string> GetAuthHeader() {
    111   return {http::kAuthorization,
    112           std::string("Bearer ") + test_data::kAccessToken};
    113 }
    114 
    115 std::pair<std::string, std::string> GetJsonHeader() {
    116   return {http::kContentType, http::kJsonUtf8};
    117 }
    118 
    119 std::pair<std::string, std::string> GetFormHeader() {
    120   return {http::kContentType, http::kWwwFormUrlEncoded};
    121 }
    122 
    123 }  // anonymous namespace
    124 
    125 class DeviceRegistrationInfoTest : public ::testing::Test {
    126  protected:
    127   void SetUp() override {
    128     EXPECT_CALL(clock_, Now())
    129         .WillRepeatedly(Return(base::Time::FromTimeT(1450000000)));
    130     ReloadDefaults();
    131   }
    132 
    133   void ReloadDefaults() {
    134     EXPECT_CALL(config_store_, LoadDefaults(_))
    135         .WillOnce(Invoke([](Settings* settings) {
    136           settings->client_id = test_data::kClientId;
    137           settings->client_secret = test_data::kClientSecret;
    138           settings->api_key = test_data::kApiKey;
    139           settings->oem_name = "Coffee Pot Maker";
    140           settings->model_name = "Pot v1";
    141           settings->name = "Coffee Pot";
    142           settings->description = "Easy to clean";
    143           settings->location = "Kitchen";
    144           settings->local_anonymous_access_role = AuthScope::kViewer;
    145           settings->model_id = "AAAAA";
    146           settings->oauth_url = test_data::kOAuthURL;
    147           settings->service_url = test_data::kServiceURL;
    148           settings->xmpp_endpoint = test_data::kXmppEndpoint;
    149           return true;
    150         }));
    151     config_.reset(new Config{&config_store_});
    152     dev_reg_.reset(new DeviceRegistrationInfo{
    153         config_.get(), &component_manager_, &task_runner_, &http_client_,
    154         nullptr, &auth_});
    155     dev_reg_->Start();
    156   }
    157 
    158   void ReloadSettings(bool registered = true) {
    159     base::DictionaryValue dict;
    160     dict.SetInteger("version", 1);
    161     if (registered) {
    162       dict.SetString("refresh_token", test_data::kRefreshToken);
    163       dict.SetString("cloud_id", test_data::kCloudId);
    164       dict.SetString("robot_account", test_data::kRobotAccountEmail);
    165     }
    166     dict.SetString("device_id", test_data::kDeviceId);
    167     std::string json_string;
    168     base::JSONWriter::WriteWithOptions(
    169         dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
    170     EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(json_string));
    171     ReloadDefaults();
    172   }
    173 
    174   void PublishCommands(const base::ListValue& commands) {
    175     dev_reg_->PublishCommands(commands, nullptr);
    176   }
    177 
    178   bool RefreshAccessToken(ErrorPtr* error) const {
    179     bool succeeded = false;
    180     auto callback = [&succeeded, &error](ErrorPtr in_error) {
    181       if (error) {
    182         *error = std::move(in_error);
    183         return;
    184       }
    185       succeeded = true;
    186     };
    187     dev_reg_->RefreshAccessToken(base::Bind(callback));
    188     return succeeded;
    189   }
    190 
    191   void SetAccessToken() { dev_reg_->access_token_ = test_data::kAccessToken; }
    192 
    193   GcdState GetGcdState() const { return dev_reg_->GetGcdState(); }
    194 
    195   bool HaveRegistrationCredentials() const {
    196     return dev_reg_->HaveRegistrationCredentials();
    197   }
    198 
    199   provider::test::FakeTaskRunner task_runner_;
    200   provider::test::MockConfigStore config_store_;
    201   StrictMock<MockHttpClient> http_client_;
    202   base::DictionaryValue data_;
    203   std::unique_ptr<Config> config_;
    204   test::MockClock clock_;
    205   privet::AuthManager auth_{
    206       {68, 52, 36, 95, 74, 89, 25, 2, 31, 5, 65, 87, 64, 32, 17, 26, 8, 73, 57,
    207        16, 33, 82, 71, 10, 72, 62, 45, 1, 77, 97, 70, 24},
    208       {21, 6, 58, 4, 66, 13, 14, 60, 55, 22, 11, 38, 96, 40, 81, 90, 3, 51, 50,
    209        23, 56, 76, 47, 46, 27, 69, 20, 80, 88, 93, 15, 61},
    210       {},
    211       &clock_};
    212   std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
    213   ComponentManagerImpl component_manager_{&task_runner_};
    214 };
    215 
    216 TEST_F(DeviceRegistrationInfoTest, GetServiceURL) {
    217   EXPECT_EQ(test_data::kServiceURL, dev_reg_->GetServiceURL());
    218   std::string url = test_data::kServiceURL;
    219   url += "registrationTickets";
    220   EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets"));
    221   url += "?key=";
    222   url += test_data::kApiKey;
    223   EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets",
    224                                          {{"key", test_data::kApiKey}}));
    225   url += "&restart=true";
    226   EXPECT_EQ(url, dev_reg_->GetServiceURL(
    227                      "registrationTickets",
    228                      {
    229                          {"key", test_data::kApiKey}, {"restart", "true"},
    230                      }));
    231 }
    232 
    233 TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) {
    234   EXPECT_EQ(test_data::kOAuthURL, dev_reg_->GetOAuthURL());
    235   std::string url = test_data::kOAuthURL;
    236   url += "auth?redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&";
    237   url += "response_type=code&";
    238   url += "client_id=";
    239   url += test_data::kClientId;
    240   EXPECT_EQ(url, dev_reg_->GetOAuthURL(
    241                      "auth", {{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
    242                               {"response_type", "code"},
    243                               {"client_id", test_data::kClientId}}));
    244 }
    245 
    246 TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) {
    247   EXPECT_FALSE(HaveRegistrationCredentials());
    248   ReloadSettings();
    249 
    250   EXPECT_CALL(
    251       http_client_,
    252       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
    253                   HttpClient::Headers{GetFormHeader()}, _, _))
    254       .WillOnce(WithArgs<3, 4>(
    255           Invoke([](const std::string& data,
    256                     const HttpClient::SendRequestCallback& callback) {
    257             EXPECT_EQ("refresh_token", GetFormField(data, "grant_type"));
    258             EXPECT_EQ(test_data::kRefreshToken,
    259                       GetFormField(data, "refresh_token"));
    260             EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
    261             EXPECT_EQ(test_data::kClientSecret,
    262                       GetFormField(data, "client_secret"));
    263 
    264             base::DictionaryValue json;
    265             json.SetString("access_token", test_data::kAccessToken);
    266             json.SetInteger("expires_in", 3600);
    267 
    268             callback.Run(ReplyWithJson(200, json), nullptr);
    269           })));
    270 
    271   EXPECT_CALL(
    272       http_client_,
    273       SendRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"),
    274                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
    275       .WillOnce(WithArgs<3, 4>(
    276           Invoke([](const std::string& data,
    277                     const HttpClient::SendRequestCallback& callback) {
    278             auto dict = CreateDictionaryValue(data);
    279             EXPECT_TRUE(dict->Remove("localAuthInfo.clientToken", nullptr));
    280             EXPECT_JSON_EQ(test_data::kAuthInfo, *dict);
    281             base::DictionaryValue json;
    282             callback.Run(ReplyWithJson(200, json), nullptr);
    283           })));
    284 
    285   EXPECT_TRUE(RefreshAccessToken(nullptr));
    286   EXPECT_TRUE(HaveRegistrationCredentials());
    287 }
    288 
    289 TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
    290   ReloadSettings();
    291   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
    292 
    293   EXPECT_CALL(
    294       http_client_,
    295       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
    296                   HttpClient::Headers{GetFormHeader()}, _, _))
    297       .WillOnce(WithArgs<3, 4>(
    298           Invoke([](const std::string& data,
    299                     const HttpClient::SendRequestCallback& callback) {
    300             EXPECT_EQ("refresh_token", GetFormField(data, "grant_type"));
    301             EXPECT_EQ(test_data::kRefreshToken,
    302                       GetFormField(data, "refresh_token"));
    303             EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
    304             EXPECT_EQ(test_data::kClientSecret,
    305                       GetFormField(data, "client_secret"));
    306 
    307             base::DictionaryValue json;
    308             json.SetString("error", "unable_to_authenticate");
    309             callback.Run(ReplyWithJson(400, json), nullptr);
    310           })));
    311 
    312   ErrorPtr error;
    313   EXPECT_FALSE(RefreshAccessToken(&error));
    314   EXPECT_TRUE(error->HasError("unable_to_authenticate"));
    315   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
    316 }
    317 
    318 TEST_F(DeviceRegistrationInfoTest, CheckDeregistration) {
    319   ReloadSettings();
    320   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
    321 
    322   EXPECT_CALL(
    323       http_client_,
    324       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
    325                   HttpClient::Headers{GetFormHeader()}, _, _))
    326       .WillOnce(WithArgs<3, 4>(
    327           Invoke([](const std::string& data,
    328                     const HttpClient::SendRequestCallback& callback) {
    329             EXPECT_EQ("refresh_token", GetFormField(data, "grant_type"));
    330             EXPECT_EQ(test_data::kRefreshToken,
    331                       GetFormField(data, "refresh_token"));
    332             EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
    333             EXPECT_EQ(test_data::kClientSecret,
    334                       GetFormField(data, "client_secret"));
    335 
    336             base::DictionaryValue json;
    337             json.SetString("error", "invalid_grant");
    338             callback.Run(ReplyWithJson(400, json), nullptr);
    339           })));
    340 
    341   ErrorPtr error;
    342   EXPECT_FALSE(RefreshAccessToken(&error));
    343   EXPECT_TRUE(error->HasError("invalid_grant"));
    344   EXPECT_EQ(GcdState::kInvalidCredentials, GetGcdState());
    345   EXPECT_EQ(test_data::kCloudId, dev_reg_->GetSettings().cloud_id);
    346 }
    347 
    348 TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
    349   ReloadSettings();
    350   SetAccessToken();
    351 
    352   EXPECT_CALL(
    353       http_client_,
    354       SendRequest(HttpClient::Method::kGet, dev_reg_->GetDeviceURL(),
    355                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
    356       .WillOnce(WithArgs<3, 4>(
    357           Invoke([](const std::string& data,
    358                     const HttpClient::SendRequestCallback& callback) {
    359             base::DictionaryValue json;
    360             json.SetString("channel.supportedType", "xmpp");
    361             json.SetString("deviceKind", "vendor");
    362             json.SetString("id", test_data::kCloudId);
    363             json.SetString("kind", "weave#device");
    364             callback.Run(ReplyWithJson(200, json), nullptr);
    365           })));
    366 
    367   bool succeeded = false;
    368   auto callback = [&succeeded, this](const base::DictionaryValue& info,
    369                                      ErrorPtr error) {
    370     EXPECT_FALSE(error);
    371     std::string id;
    372     EXPECT_TRUE(info.GetString("id", &id));
    373     EXPECT_EQ(test_data::kCloudId, id);
    374     succeeded = true;
    375   };
    376   dev_reg_->GetDeviceInfo(base::Bind(callback));
    377   EXPECT_TRUE(succeeded);
    378 }
    379 
    380 TEST_F(DeviceRegistrationInfoTest, RegisterDevice) {
    381   ReloadSettings(false);
    382 
    383   auto json_traits = CreateDictionaryValue(R"({
    384     'base': {
    385       'commands': {
    386         'reboot': {
    387           'parameters': {'delay': {'minimum': 10, 'type': 'integer'}},
    388           'minimalRole': 'user'
    389         }
    390       },
    391       'state': {
    392         'firmwareVersion': {'type': 'string'}
    393       }
    394     },
    395     'robot': {
    396       'commands': {
    397         '_jump': {
    398           'parameters': {'_height': {'type': 'integer'}},
    399           'minimalRole': 'user'
    400         }
    401       }
    402     }
    403   })");
    404   EXPECT_TRUE(component_manager_.LoadTraits(*json_traits, nullptr));
    405   EXPECT_TRUE(
    406       component_manager_.AddComponent("", "comp", {"base", "robot"}, nullptr));
    407   base::StringValue ver{"1.0"};
    408   EXPECT_TRUE(component_manager_.SetStateProperty(
    409       "comp", "base.firmwareVersion", ver, nullptr));
    410 
    411   std::string ticket_url = dev_reg_->GetServiceURL("registrationTickets/") +
    412                            test_data::kClaimTicketId;
    413   EXPECT_CALL(http_client_,
    414               SendRequest(HttpClient::Method::kPatch,
    415                           ticket_url + "?key=" + test_data::kApiKey,
    416                           HttpClient::Headers{GetJsonHeader()}, _, _))
    417       .WillOnce(WithArgs<3, 4>(
    418           Invoke([](const std::string& data,
    419                     const HttpClient::SendRequestCallback& callback) {
    420             auto json = test::CreateDictionaryValue(data);
    421             EXPECT_NE(nullptr, json.get());
    422             std::string value;
    423             EXPECT_TRUE(json->GetString("id", &value));
    424             EXPECT_EQ(test_data::kClaimTicketId, value);
    425             EXPECT_TRUE(
    426                 json->GetString("deviceDraft.channel.supportedType", &value));
    427             EXPECT_EQ("pull", value);
    428             EXPECT_TRUE(json->GetString("oauthClientId", &value));
    429             EXPECT_EQ(test_data::kClientId, value);
    430             EXPECT_TRUE(json->GetString("deviceDraft.description", &value));
    431             EXPECT_EQ("Easy to clean", value);
    432             EXPECT_TRUE(json->GetString("deviceDraft.location", &value));
    433             EXPECT_EQ("Kitchen", value);
    434             EXPECT_TRUE(json->GetString("deviceDraft.modelManifestId", &value));
    435             EXPECT_EQ("AAAAA", value);
    436             EXPECT_TRUE(json->GetString("deviceDraft.name", &value));
    437             EXPECT_EQ("Coffee Pot", value);
    438             base::DictionaryValue* dict = nullptr;
    439             EXPECT_FALSE(json->GetDictionary("deviceDraft.commandDefs", &dict));
    440             EXPECT_FALSE(json->GetDictionary("deviceDraft.state", &dict));
    441             EXPECT_TRUE(json->GetDictionary("deviceDraft.traits", &dict));
    442             auto expectedTraits = R"({
    443               'base': {
    444                 'commands': {
    445                   'reboot': {
    446                     'parameters': {'delay': {'minimum': 10, 'type': 'integer'}},
    447                     'minimalRole': 'user'
    448                   }
    449                 },
    450                 'state': {
    451                   'firmwareVersion': {'type': 'string'}
    452                 }
    453               },
    454               'robot': {
    455                 'commands': {
    456                   '_jump': {
    457                     'parameters': {'_height': {'type': 'integer'}},
    458                     'minimalRole': 'user'
    459                   }
    460                 }
    461               }
    462             })";
    463             EXPECT_JSON_EQ(expectedTraits, *dict);
    464 
    465             EXPECT_TRUE(json->GetDictionary("deviceDraft.components", &dict));
    466             auto expectedComponents = R"({
    467               'comp': {
    468                 'traits': ['base', 'robot'],
    469                 'state': {
    470                   'base': { 'firmwareVersion': '1.0' }
    471                 }
    472               }
    473             })";
    474             EXPECT_JSON_EQ(expectedComponents, *dict);
    475 
    476             base::DictionaryValue json_resp;
    477             json_resp.SetString("id", test_data::kClaimTicketId);
    478             json_resp.SetString("kind", "weave#registrationTicket");
    479             json_resp.SetString("oauthClientId", test_data::kClientId);
    480             base::DictionaryValue* device_draft = nullptr;
    481             EXPECT_TRUE(json->GetDictionary("deviceDraft", &device_draft));
    482             device_draft = device_draft->DeepCopy();
    483             device_draft->SetString("id", test_data::kCloudId);
    484             device_draft->SetString("kind", "weave#device");
    485             json_resp.Set("deviceDraft", device_draft);
    486 
    487             callback.Run(ReplyWithJson(200, json_resp), nullptr);
    488           })));
    489 
    490   EXPECT_CALL(http_client_,
    491               SendRequest(HttpClient::Method::kPost,
    492                           ticket_url + "/finalize?key=" + test_data::kApiKey,
    493                           HttpClient::Headers{}, _, _))
    494       .WillOnce(WithArgs<4>(
    495           Invoke([](const HttpClient::SendRequestCallback& callback) {
    496             base::DictionaryValue json;
    497             json.SetString("id", test_data::kClaimTicketId);
    498             json.SetString("kind", "weave#registrationTicket");
    499             json.SetString("oauthClientId", test_data::kClientId);
    500             json.SetString("userEmail", "user (at) email.com");
    501             json.SetString("deviceDraft.id", test_data::kCloudId);
    502             json.SetString("deviceDraft.kind", "weave#device");
    503             json.SetString("deviceDraft.channel.supportedType", "xmpp");
    504             json.SetString("robotAccountEmail", test_data::kRobotAccountEmail);
    505             json.SetString("robotAccountAuthorizationCode",
    506                            test_data::kRobotAccountAuthCode);
    507             callback.Run(ReplyWithJson(200, json), nullptr);
    508           })));
    509 
    510   EXPECT_CALL(
    511       http_client_,
    512       SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
    513                   HttpClient::Headers{GetFormHeader()}, _, _))
    514       .WillOnce(WithArgs<3, 4>(Invoke([](
    515           const std::string& data,
    516           const HttpClient::SendRequestCallback& callback) {
    517         EXPECT_EQ("authorization_code", GetFormField(data, "grant_type"));
    518         EXPECT_EQ(test_data::kRobotAccountAuthCode, GetFormField(data, "code"));
    519         EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
    520         EXPECT_EQ(test_data::kClientSecret,
    521                   GetFormField(data, "client_secret"));
    522         EXPECT_EQ("oob", GetFormField(data, "redirect_uri"));
    523 
    524         base::DictionaryValue json;
    525         json.SetString("access_token", test_data::kAccessToken);
    526         json.SetString("token_type", "Bearer");
    527         json.SetString("refresh_token", test_data::kRefreshToken);
    528         json.SetInteger("expires_in", 3600);
    529 
    530         callback.Run(ReplyWithJson(200, json), nullptr);
    531       })));
    532 
    533   EXPECT_CALL(
    534       http_client_,
    535       SendRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"),
    536                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
    537       .WillOnce(WithArgs<3, 4>(
    538           Invoke([](const std::string& data,
    539                     const HttpClient::SendRequestCallback& callback) {
    540             auto dict = CreateDictionaryValue(data);
    541             EXPECT_TRUE(dict->Remove("localAuthInfo.clientToken", nullptr));
    542             EXPECT_JSON_EQ(test_data::kAuthInfo, *dict);
    543             base::DictionaryValue json;
    544             callback.Run(ReplyWithJson(200, json), nullptr);
    545           })));
    546 
    547   bool done = false;
    548   dev_reg_->RegisterDevice(
    549       test_data::kClaimTicketId, base::Bind([this, &done](ErrorPtr error) {
    550         EXPECT_FALSE(error);
    551         done = true;
    552         task_runner_.Break();
    553         EXPECT_EQ(GcdState::kConnecting, GetGcdState());
    554 
    555         // Validate the device info saved to storage...
    556         EXPECT_EQ(test_data::kCloudId, dev_reg_->GetSettings().cloud_id);
    557         EXPECT_EQ(test_data::kRefreshToken,
    558                   dev_reg_->GetSettings().refresh_token);
    559         EXPECT_EQ(test_data::kRobotAccountEmail,
    560                   dev_reg_->GetSettings().robot_account);
    561       }));
    562   task_runner_.Run();
    563   EXPECT_TRUE(done);
    564 }
    565 
    566 TEST_F(DeviceRegistrationInfoTest, ReRegisterDevice) {
    567   ReloadSettings();
    568 
    569   bool done = false;
    570   dev_reg_->RegisterDevice(
    571       test_data::kClaimTicketId, base::Bind([this, &done](ErrorPtr error) {
    572         EXPECT_TRUE(error->HasError("already_registered"));
    573         done = true;
    574         task_runner_.Break();
    575         EXPECT_EQ(GcdState::kConnecting, GetGcdState());
    576 
    577         // Validate the device info saved to storage...
    578         EXPECT_EQ(test_data::kCloudId, dev_reg_->GetSettings().cloud_id);
    579         EXPECT_EQ(test_data::kRefreshToken,
    580                   dev_reg_->GetSettings().refresh_token);
    581         EXPECT_EQ(test_data::kRobotAccountEmail,
    582                   dev_reg_->GetSettings().robot_account);
    583       }));
    584   task_runner_.Run();
    585   EXPECT_TRUE(done);
    586 }
    587 
    588 TEST_F(DeviceRegistrationInfoTest, OOBRegistrationStatus) {
    589   // After we've been initialized, we should be either offline or
    590   // unregistered, depending on whether or not we've found credentials.
    591   EXPECT_EQ(GcdState::kUnconfigured, GetGcdState());
    592   // Put some credentials into our state, make sure we call that offline.
    593   ReloadSettings();
    594   EXPECT_EQ(GcdState::kConnecting, GetGcdState());
    595 }
    596 
    597 class DeviceRegistrationInfoUpdateCommandTest
    598     : public DeviceRegistrationInfoTest {
    599  protected:
    600   void SetUp() override {
    601     DeviceRegistrationInfoTest::SetUp();
    602 
    603     ReloadSettings();
    604     SetAccessToken();
    605 
    606     auto json_traits = CreateDictionaryValue(R"({
    607       'robot': {
    608         'commands': {
    609           '_jump': {
    610             'parameters': {'_height': 'integer'},
    611             'progress': {'progress': 'integer'},
    612             'results': {'status': 'string'},
    613             'minimalRole': 'user'
    614           }
    615         }
    616       }
    617     })");
    618     EXPECT_TRUE(component_manager_.LoadTraits(*json_traits, nullptr));
    619     EXPECT_TRUE(
    620         component_manager_.AddComponent("", "comp", {"robot"}, nullptr));
    621 
    622     command_url_ = dev_reg_->GetServiceURL("commands/1234");
    623 
    624     auto commands_json = CreateValue(R"([{
    625       'name':'robot._jump',
    626       'component': 'comp',
    627       'id':'1234',
    628       'parameters': {'_height': 100},
    629       'minimalRole': 'user'
    630     }])");
    631     ASSERT_NE(nullptr, commands_json.get());
    632     const base::ListValue* command_list = nullptr;
    633     ASSERT_TRUE(commands_json->GetAsList(&command_list));
    634     PublishCommands(*command_list);
    635     command_ = component_manager_.FindCommand("1234");
    636     ASSERT_NE(nullptr, command_);
    637   }
    638 
    639   void TearDown() override {
    640     task_runner_.RunOnce();
    641     DeviceRegistrationInfoTest::TearDown();
    642   }
    643 
    644   Command* command_{nullptr};
    645   std::string command_url_;
    646 };
    647 
    648 TEST_F(DeviceRegistrationInfoUpdateCommandTest, SetProgress) {
    649   EXPECT_CALL(
    650       http_client_,
    651       SendRequest(HttpClient::Method::kPatch, command_url_,
    652                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
    653       .WillOnce(WithArgs<3, 4>(Invoke([](
    654           const std::string& data,
    655           const HttpClient::SendRequestCallback& callback) {
    656         EXPECT_JSON_EQ((R"({"state":"inProgress","progress":{"progress":18}})"),
    657                        *CreateDictionaryValue(data));
    658         base::DictionaryValue json;
    659         callback.Run(ReplyWithJson(200, json), nullptr);
    660       })));
    661   EXPECT_TRUE(command_->SetProgress(*CreateDictionaryValue("{'progress':18}"),
    662                                     nullptr));
    663 }
    664 
    665 TEST_F(DeviceRegistrationInfoUpdateCommandTest, Complete) {
    666   EXPECT_CALL(
    667       http_client_,
    668       SendRequest(HttpClient::Method::kPatch, command_url_,
    669                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
    670       .WillOnce(WithArgs<3, 4>(
    671           Invoke([](const std::string& data,
    672                     const HttpClient::SendRequestCallback& callback) {
    673             EXPECT_JSON_EQ(R"({"state":"done", "results":{"status":"Ok"}})",
    674                            *CreateDictionaryValue(data));
    675             base::DictionaryValue json;
    676             callback.Run(ReplyWithJson(200, json), nullptr);
    677           })));
    678   EXPECT_TRUE(
    679       command_->Complete(*CreateDictionaryValue("{'status': 'Ok'}"), nullptr));
    680 }
    681 
    682 TEST_F(DeviceRegistrationInfoUpdateCommandTest, Cancel) {
    683   EXPECT_CALL(
    684       http_client_,
    685       SendRequest(HttpClient::Method::kPatch, command_url_,
    686                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
    687       .WillOnce(WithArgs<3, 4>(
    688           Invoke([](const std::string& data,
    689                     const HttpClient::SendRequestCallback& callback) {
    690             EXPECT_JSON_EQ(R"({"state":"cancelled"})",
    691                            *CreateDictionaryValue(data));
    692             base::DictionaryValue json;
    693             callback.Run(ReplyWithJson(200, json), nullptr);
    694           })));
    695   EXPECT_TRUE(command_->Cancel(nullptr));
    696 }
    697 
    698 }  // namespace weave
    699