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