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
GetFormField(const std::string & data,const std::string & name)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
ReplyWithJson(int status_code,const base::Value & json)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
GetAuthHeader()110 std::pair<std::string, std::string> GetAuthHeader() {
111 return {http::kAuthorization,
112 std::string("Bearer ") + test_data::kAccessToken};
113 }
114
GetJsonHeader()115 std::pair<std::string, std::string> GetJsonHeader() {
116 return {http::kContentType, http::kJsonUtf8};
117 }
118
GetFormHeader()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:
SetUp()127 void SetUp() override {
128 EXPECT_CALL(clock_, Now())
129 .WillRepeatedly(Return(base::Time::FromTimeT(1450000000)));
130 ReloadDefaults();
131 }
132
ReloadDefaults()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
ReloadSettings(bool registered=true)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
PublishCommands(const base::ListValue & commands)174 void PublishCommands(const base::ListValue& commands) {
175 dev_reg_->PublishCommands(commands, nullptr);
176 }
177
RefreshAccessToken(ErrorPtr * error) const178 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
SetAccessToken()191 void SetAccessToken() { dev_reg_->access_token_ = test_data::kAccessToken; }
192
GetGcdState() const193 GcdState GetGcdState() const { return dev_reg_->GetGcdState(); }
194
HaveRegistrationCredentials() const195 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
TEST_F(DeviceRegistrationInfoTest,GetServiceURL)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
TEST_F(DeviceRegistrationInfoTest,GetOAuthURL)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
TEST_F(DeviceRegistrationInfoTest,HaveRegistrationCredentials)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
TEST_F(DeviceRegistrationInfoTest,CheckAuthenticationFailure)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
TEST_F(DeviceRegistrationInfoTest,CheckDeregistration)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
TEST_F(DeviceRegistrationInfoTest,GetDeviceInfo)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
TEST_F(DeviceRegistrationInfoTest,RegisterDevice)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@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
TEST_F(DeviceRegistrationInfoTest,ReRegisterDevice)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
TEST_F(DeviceRegistrationInfoTest,OOBRegistrationStatus)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:
SetUp()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
TearDown()639 void TearDown() override {
640 task_runner_.RunOnce();
641 DeviceRegistrationInfoTest::TearDown();
642 }
643
644 Command* command_{nullptr};
645 std::string command_url_;
646 };
647
TEST_F(DeviceRegistrationInfoUpdateCommandTest,SetProgress)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
TEST_F(DeviceRegistrationInfoUpdateCommandTest,Complete)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
TEST_F(DeviceRegistrationInfoUpdateCommandTest,Cancel)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