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