blob: cc519d298e086f056529707e1f5cdbcc97bced9b [file] [log] [blame]
Vitaly Buka4615e0d2015-10-14 15:35:12 -07001// Copyright 2015 The Weave Authors. All rights reserved.
Alex Vakulenko8e34d392014-04-29 11:02:56 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Stefan Sauer2d16dfa2015-09-25 17:08:35 +02005#include "src/device_registration_info.h"
Alex Deymof6cbe322014-11-10 19:55:35 -08006
Alex Vakulenko8e34d392014-04-29 11:02:56 -07007#include <base/json/json_reader.h>
Alex Vakulenko9e2f8cd2015-04-07 16:28:09 -07008#include <base/json/json_writer.h>
Alex Vakulenko8e34d392014-04-29 11:02:56 -07009#include <base/values.h>
10#include <gtest/gtest.h>
Vitaly Bukaff1d1862015-10-07 20:40:36 -070011#include <weave/provider/test/fake_task_runner.h>
Vitaly Buka1e363672015-09-25 14:01:16 -070012#include <weave/provider/test/mock_config_store.h>
13#include <weave/provider/test/mock_http_client.h>
Alex Vakulenko8a05beb2015-11-24 17:13:20 -080014#include <weave/test/unittest_utils.h>
Alex Vakulenko8e34d392014-04-29 11:02:56 -070015
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020016#include "src/bind_lambda.h"
Alex Vakulenkod91d6252015-12-05 17:14:39 -080017#include "src/component_manager_impl.h"
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020018#include "src/http_constants.h"
Vitaly Buka72d8d162015-12-09 18:39:36 -080019#include "src/privet/auth_manager.h"
20#include "src/test/mock_clock.h"
Alex Vakulenko8e34d392014-04-29 11:02:56 -070021
Vitaly Buka6b61e572015-08-05 23:16:13 -070022using testing::_;
Vitaly Buka10206182015-08-05 11:17:43 -070023using testing::AtLeast;
Vitaly Buka72d8d162015-12-09 18:39:36 -080024using testing::HasSubstr;
Vitaly Buka10206182015-08-05 11:17:43 -070025using testing::Invoke;
26using testing::InvokeWithoutArgs;
27using testing::Mock;
Vitaly Buka6b61e572015-08-05 23:16:13 -070028using testing::Return;
Vitaly Buka10206182015-08-05 11:17:43 -070029using testing::ReturnRef;
30using testing::ReturnRefOfCopy;
Vitaly Bukac11a17d2015-08-15 10:36:10 -070031using testing::SaveArg;
Vitaly Buka6b61e572015-08-05 23:16:13 -070032using testing::StrictMock;
Vitaly Buka10206182015-08-05 11:17:43 -070033using testing::WithArgs;
Vitaly Buka6b61e572015-08-05 23:16:13 -070034
Vitaly Bukab6f015a2015-07-09 14:59:23 -070035namespace weave {
Vitaly Buka32005de2015-05-01 12:33:31 -070036
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -070037using test::CreateDictionaryValue;
38using test::CreateValue;
Vitaly Buka1e363672015-09-25 14:01:16 -070039using provider::test::MockHttpClient;
40using provider::test::MockHttpClientResponse;
41using provider::HttpClient;
Alex Vakulenkocca20932014-08-20 17:35:12 -070042
Alex Vakulenko8e34d392014-04-29 11:02:56 -070043namespace {
Alex Vakulenko8e34d392014-04-29 11:02:56 -070044
45namespace test_data {
46
Vitaly Bukaa647c852015-07-06 14:51:01 -070047const char kServiceURL[] = "http://gcd.server.com/";
48const char kOAuthURL[] = "http://oauth.server.com/";
49const char kApiKey[] = "GOadRdTf9FERf0k4w6EFOof56fUJ3kFDdFL3d7f";
50const char kClientId[] =
51 "123543821385-sfjkjshdkjhfk234sdfsdfkskd"
52 "fkjh7f.apps.googleusercontent.com";
53const char kClientSecret[] = "5sdGdGlfolGlrFKfdFlgP6FG";
Vitaly Buka6a9d0e22015-12-06 17:49:11 -080054const char kCloudId[] = "4a7ea2d1-b331-1e1f-b206-e863c7635196";
Vitaly Buka72d8d162015-12-09 18:39:36 -080055const char kDeviceId[] = "f6885e46-b432-42d7-86a5-d759bfb61f62";
Vitaly Bukaa647c852015-07-06 14:51:01 -070056const char kClaimTicketId[] = "RTcUE";
57const char kAccessToken[] =
58 "ya29.1.AADtN_V-dLUM-sVZ0qVjG9Dxm5NgdS9J"
59 "Mx_JLUqhC9bED_YFjzHZtYt65ZzXCS35NMAeaVZ"
60 "Dei530-w0yE2urpQ";
61const char kRefreshToken[] =
62 "1/zQmxR6PKNvhcxf9SjXUrCjcmCrcqRKXctc6cp"
63 "1nI-GQ";
64const char kRobotAccountAuthCode[] =
65 "4/Mf_ujEhPejVhOq-OxW9F5cSOnWzx."
66 "YgciVjTYGscRshQV0ieZDAqiTIjMigI";
67const char kRobotAccountEmail[] =
68 "6ed0b3f54f9bd619b942f4ad2441c252@"
69 "clouddevices.gserviceaccount.com";
Vitaly Buka72d8d162015-12-09 18:39:36 -080070const char kAuthInfo[] = R"({
71 "certFingerprint":
72 "FQY6BEINDjw3FgsmYChRWgMzMhc4TC8uG0UUUFhdDz0=",
73 "clientToken": "UBPkqttkiWt5VWgICLP0eHuCQgECRgMaVm0+gA==",
74 "localId": "f6885e46-b432-42d7-86a5-d759bfb61f62"
75})";
Vitaly Buka10206182015-08-05 11:17:43 -070076
Alex Vakulenko8e34d392014-04-29 11:02:56 -070077} // namespace test_data
78
Vitaly Buka10206182015-08-05 11:17:43 -070079std::string GetFormField(const std::string& data, const std::string& name) {
80 EXPECT_FALSE(data.empty());
Vitaly Buka7d556392015-08-13 20:06:48 -070081 for (const auto& i : WebParamsDecode(data)) {
Vitaly Buka10206182015-08-05 11:17:43 -070082 if (i.first == name)
83 return i.second;
Alex Vakulenko8e34d392014-04-29 11:02:56 -070084 }
Vitaly Buka10206182015-08-05 11:17:43 -070085 return {};
Alex Vakulenko8e34d392014-04-29 11:02:56 -070086}
87
Vitaly Buka17280372015-10-10 17:11:47 -070088std::unique_ptr<HttpClient::Response> ReplyWithJson(int status_code,
89 const base::Value& json) {
Vitaly Buka10206182015-08-05 11:17:43 -070090 std::string text;
91 base::JSONWriter::WriteWithOptions(
92 json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &text);
93
Vitaly Buka17280372015-10-10 17:11:47 -070094 std::unique_ptr<MockHttpClientResponse> response{
95 new StrictMock<MockHttpClientResponse>};
Vitaly Buka10206182015-08-05 11:17:43 -070096 EXPECT_CALL(*response, GetStatusCode())
97 .Times(AtLeast(1))
98 .WillRepeatedly(Return(status_code));
99 EXPECT_CALL(*response, GetContentType())
100 .Times(AtLeast(1))
Vitaly Buka815b6032015-08-06 11:06:25 -0700101 .WillRepeatedly(Return(http::kJsonUtf8));
Vitaly Buka10206182015-08-05 11:17:43 -0700102 EXPECT_CALL(*response, GetData())
103 .Times(AtLeast(1))
Vitaly Buka4774df22015-10-09 12:36:22 -0700104 .WillRepeatedly(Return(text));
Vitaly Buka17280372015-10-10 17:11:47 -0700105 return std::move(response);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500106}
107
Vitaly Buka10206182015-08-05 11:17:43 -0700108std::pair<std::string, std::string> GetAuthHeader() {
Vitaly Buka815b6032015-08-06 11:06:25 -0700109 return {http::kAuthorization,
110 std::string("Bearer ") + test_data::kAccessToken};
111}
112
113std::pair<std::string, std::string> GetJsonHeader() {
114 return {http::kContentType, http::kJsonUtf8};
115}
116
117std::pair<std::string, std::string> GetFormHeader() {
118 return {http::kContentType, http::kWwwFormUrlEncoded};
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700119}
120
121} // anonymous namespace
122
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700123class DeviceRegistrationInfoTest : public ::testing::Test {
124 protected:
Alex Vakulenko5a9e7182014-08-11 15:59:58 -0700125 void SetUp() override {
Vitaly Buka72d8d162015-12-09 18:39:36 -0800126 EXPECT_CALL(clock_, Now())
127 .WillRepeatedly(Return(base::Time::FromTimeT(1450000000)));
Vitaly Buka666b43e2015-12-08 16:35:47 -0800128 dev_reg_.reset(new DeviceRegistrationInfo{&config_, &component_manager_,
129 &task_runner_, &http_client_,
Vitaly Buka72d8d162015-12-09 18:39:36 -0800130 nullptr, &auth_});
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700131
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700132 ReloadDefaults();
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700133 }
134
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700135 void ReloadDefaults() {
136 EXPECT_CALL(config_store_, LoadDefaults(_))
137 .WillOnce(Invoke([](Settings* settings) {
138 settings->client_id = test_data::kClientId;
139 settings->client_secret = test_data::kClientSecret;
140 settings->api_key = test_data::kApiKey;
Vitaly Bukaa05eadb2015-09-29 16:38:24 -0700141 settings->oem_name = "Coffee Pot Maker";
142 settings->model_name = "Pot v1";
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700143 settings->name = "Coffee Pot";
144 settings->description = "Easy to clean";
145 settings->location = "Kitchen";
Vitaly Bukab624bc42015-09-29 19:13:55 -0700146 settings->local_anonymous_access_role = AuthScope::kViewer;
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700147 settings->model_id = "AAAAA";
148 settings->oauth_url = test_data::kOAuthURL;
149 settings->service_url = test_data::kServiceURL;
150 return true;
151 }));
Vitaly Buka666b43e2015-12-08 16:35:47 -0800152 config_.Load();
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700153 dev_reg_->Start();
154 }
155
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700156 void ReloadSettings() {
157 base::DictionaryValue dict;
158 dict.SetString("refresh_token", test_data::kRefreshToken);
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800159 dict.SetString("cloud_id", test_data::kCloudId);
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700160 dict.SetString("robot_account", test_data::kRobotAccountEmail);
Vitaly Buka72d8d162015-12-09 18:39:36 -0800161 dict.SetString("device_id", test_data::kDeviceId);
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700162 std::string json_string;
163 base::JSONWriter::WriteWithOptions(
164 dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
165 EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(json_string));
166 ReloadDefaults();
167 }
168
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700169 void PublishCommands(const base::ListValue& commands) {
Vitaly Buka74763422015-10-11 00:39:52 -0700170 dev_reg_->PublishCommands(commands, nullptr);
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700171 }
172
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700173 bool RefreshAccessToken(ErrorPtr* error) const {
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700174 bool succeeded = false;
Vitaly Buka74763422015-10-11 00:39:52 -0700175 auto callback = [&succeeded, &error](ErrorPtr in_error) {
176 if (error) {
Vitaly Bukaf7f52d42015-10-10 22:43:55 -0700177 *error = std::move(in_error);
Vitaly Buka74763422015-10-11 00:39:52 -0700178 return;
179 }
180 succeeded = true;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700181 };
Vitaly Buka74763422015-10-11 00:39:52 -0700182 dev_reg_->RefreshAccessToken(base::Bind(callback));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700183 return succeeded;
184 }
185
Vitaly Bukaa647c852015-07-06 14:51:01 -0700186 void SetAccessToken() { dev_reg_->access_token_ = test_data::kAccessToken; }
Vitaly Bukaee7a3af2015-05-14 16:57:23 -0700187
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700188 GcdState GetGcdState() const { return dev_reg_->GetGcdState(); }
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700189
Vitaly Buka672634b2015-11-20 09:49:30 -0800190 bool HaveRegistrationCredentials() const {
191 return dev_reg_->HaveRegistrationCredentials();
192 }
193
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700194 provider::test::FakeTaskRunner task_runner_;
Vitaly Buka1e363672015-09-25 14:01:16 -0700195 provider::test::MockConfigStore config_store_;
Vitaly Buka10206182015-08-05 11:17:43 -0700196 StrictMock<MockHttpClient> http_client_;
Alex Vakulenko45109442014-07-29 11:07:10 -0700197 base::DictionaryValue data_;
Vitaly Buka666b43e2015-12-08 16:35:47 -0800198 Config config_{&config_store_};
Vitaly Buka72d8d162015-12-09 18:39:36 -0800199 test::MockClock clock_;
200 privet::AuthManager auth_{
201 {68, 52, 36, 95, 74, 89, 25, 2, 31, 5, 65, 87, 64, 32, 17, 26, 8, 73, 57,
202 16, 33, 82, 71, 10, 72, 62, 45, 1, 77, 97, 70, 24},
203 {21, 6, 58, 4, 66, 13, 14, 60, 55, 22, 11, 38, 96, 40, 81, 90, 3, 51, 50,
204 23, 56, 76, 47, 46, 27, 69, 20, 80, 88, 93, 15, 61},
205 &clock_};
Alex Vakulenko45109442014-07-29 11:07:10 -0700206 std::unique_ptr<DeviceRegistrationInfo> dev_reg_;
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800207 ComponentManagerImpl component_manager_;
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700208};
209
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700210TEST_F(DeviceRegistrationInfoTest, GetServiceURL) {
Alex Vakulenko45109442014-07-29 11:07:10 -0700211 EXPECT_EQ(test_data::kServiceURL, dev_reg_->GetServiceURL());
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700212 std::string url = test_data::kServiceURL;
213 url += "registrationTickets";
Alex Vakulenko45109442014-07-29 11:07:10 -0700214 EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets"));
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700215 url += "?key=";
216 url += test_data::kApiKey;
Vitaly Bukaa647c852015-07-06 14:51:01 -0700217 EXPECT_EQ(url, dev_reg_->GetServiceURL("registrationTickets",
218 {{"key", test_data::kApiKey}}));
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700219 url += "&restart=true";
Vitaly Bukaa647c852015-07-06 14:51:01 -0700220 EXPECT_EQ(url, dev_reg_->GetServiceURL(
221 "registrationTickets",
222 {
223 {"key", test_data::kApiKey}, {"restart", "true"},
224 }));
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700225}
226
227TEST_F(DeviceRegistrationInfoTest, GetOAuthURL) {
Alex Vakulenko45109442014-07-29 11:07:10 -0700228 EXPECT_EQ(test_data::kOAuthURL, dev_reg_->GetOAuthURL());
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700229 std::string url = test_data::kOAuthURL;
Vitaly Buka88272d52015-11-17 17:04:01 -0800230 url += "auth?redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&";
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700231 url += "response_type=code&";
232 url += "client_id=";
233 url += test_data::kClientId;
Vitaly Bukaa647c852015-07-06 14:51:01 -0700234 EXPECT_EQ(url, dev_reg_->GetOAuthURL(
Vitaly Bukac27390d2015-11-19 14:42:35 -0800235 "auth", {{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
236 {"response_type", "code"},
237 {"client_id", test_data::kClientId}}));
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700238}
239
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700240TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) {
Vitaly Buka672634b2015-11-20 09:49:30 -0800241 EXPECT_FALSE(HaveRegistrationCredentials());
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700242 ReloadSettings();
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700243
Vitaly Buka1a42e142015-10-10 18:15:15 -0700244 EXPECT_CALL(
245 http_client_,
246 SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
Vitaly Buka74763422015-10-11 00:39:52 -0700247 HttpClient::Headers{GetFormHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700248 .WillOnce(WithArgs<3, 4>(Invoke([](
249 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700250 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka10206182015-08-05 11:17:43 -0700251 EXPECT_EQ("refresh_token", GetFormField(data, "grant_type"));
252 EXPECT_EQ(test_data::kRefreshToken,
253 GetFormField(data, "refresh_token"));
254 EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
255 EXPECT_EQ(test_data::kClientSecret,
256 GetFormField(data, "client_secret"));
257
258 base::DictionaryValue json;
259 json.SetString("access_token", test_data::kAccessToken);
260 json.SetInteger("expires_in", 3600);
261
Vitaly Buka74763422015-10-11 00:39:52 -0700262 callback.Run(ReplyWithJson(200, json), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700263 })));
264
Vitaly Buka72d8d162015-12-09 18:39:36 -0800265 EXPECT_CALL(
266 http_client_,
267 SendRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"),
268 HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
269 .WillOnce(WithArgs<3, 4>(
270 Invoke([](const std::string& data,
271 const HttpClient::SendRequestCallback& callback) {
272 EXPECT_JSON_EQ(test_data::kAuthInfo, *CreateDictionaryValue(data));
273 base::DictionaryValue json;
274 callback.Run(ReplyWithJson(200, json), nullptr);
275 })));
276
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700277 EXPECT_TRUE(RefreshAccessToken(nullptr));
Vitaly Buka672634b2015-11-20 09:49:30 -0800278 EXPECT_TRUE(HaveRegistrationCredentials());
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700279}
280
Nathan Bullock24d189f2015-02-26 13:09:18 -0500281TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700282 ReloadSettings();
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700283 EXPECT_EQ(GcdState::kConnecting, GetGcdState());
Nathan Bullock24d189f2015-02-26 13:09:18 -0500284
Vitaly Buka1a42e142015-10-10 18:15:15 -0700285 EXPECT_CALL(
286 http_client_,
287 SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
Vitaly Buka74763422015-10-11 00:39:52 -0700288 HttpClient::Headers{GetFormHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700289 .WillOnce(WithArgs<3, 4>(Invoke([](
290 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700291 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka10206182015-08-05 11:17:43 -0700292 EXPECT_EQ("refresh_token", GetFormField(data, "grant_type"));
293 EXPECT_EQ(test_data::kRefreshToken,
294 GetFormField(data, "refresh_token"));
295 EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
296 EXPECT_EQ(test_data::kClientSecret,
297 GetFormField(data, "client_secret"));
298
299 base::DictionaryValue json;
300 json.SetString("error", "unable_to_authenticate");
Vitaly Buka74763422015-10-11 00:39:52 -0700301 callback.Run(ReplyWithJson(400, json), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700302 })));
303
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700304 ErrorPtr error;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700305 EXPECT_FALSE(RefreshAccessToken(&error));
Vitaly Buka32005de2015-05-01 12:33:31 -0700306 EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "unable_to_authenticate"));
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700307 EXPECT_EQ(GcdState::kConnecting, GetGcdState());
Nathan Bullock24d189f2015-02-26 13:09:18 -0500308}
309
310TEST_F(DeviceRegistrationInfoTest, CheckDeregistration) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700311 ReloadSettings();
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700312 EXPECT_EQ(GcdState::kConnecting, GetGcdState());
Nathan Bullock24d189f2015-02-26 13:09:18 -0500313
Vitaly Buka1a42e142015-10-10 18:15:15 -0700314 EXPECT_CALL(
315 http_client_,
316 SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
Vitaly Buka74763422015-10-11 00:39:52 -0700317 HttpClient::Headers{GetFormHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700318 .WillOnce(WithArgs<3, 4>(Invoke([](
319 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700320 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka10206182015-08-05 11:17:43 -0700321 EXPECT_EQ("refresh_token", GetFormField(data, "grant_type"));
322 EXPECT_EQ(test_data::kRefreshToken,
323 GetFormField(data, "refresh_token"));
324 EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
325 EXPECT_EQ(test_data::kClientSecret,
326 GetFormField(data, "client_secret"));
327
328 base::DictionaryValue json;
329 json.SetString("error", "invalid_grant");
Vitaly Buka74763422015-10-11 00:39:52 -0700330 callback.Run(ReplyWithJson(400, json), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700331 })));
332
Vitaly Buka0801a1f2015-08-14 10:03:46 -0700333 ErrorPtr error;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700334 EXPECT_FALSE(RefreshAccessToken(&error));
Vitaly Buka32005de2015-05-01 12:33:31 -0700335 EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant"));
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700336 EXPECT_EQ(GcdState::kInvalidCredentials, GetGcdState());
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800337 EXPECT_EQ(test_data::kCloudId, dev_reg_->GetSettings().cloud_id);
Nathan Bullock24d189f2015-02-26 13:09:18 -0500338}
339
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700340TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700341 ReloadSettings();
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700342 SetAccessToken();
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700343
Vitaly Buka74763422015-10-11 00:39:52 -0700344 EXPECT_CALL(
345 http_client_,
346 SendRequest(HttpClient::Method::kGet, dev_reg_->GetDeviceURL(),
347 HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700348 .WillOnce(WithArgs<3, 4>(
349 Invoke([](const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700350 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka17280372015-10-10 17:11:47 -0700351 base::DictionaryValue json;
352 json.SetString("channel.supportedType", "xmpp");
353 json.SetString("deviceKind", "vendor");
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800354 json.SetString("id", test_data::kCloudId);
Vitaly Buka88272d52015-11-17 17:04:01 -0800355 json.SetString("kind", "weave#device");
Vitaly Buka74763422015-10-11 00:39:52 -0700356 callback.Run(ReplyWithJson(200, json), nullptr);
Vitaly Buka17280372015-10-10 17:11:47 -0700357 })));
Vitaly Buka10206182015-08-05 11:17:43 -0700358
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700359 bool succeeded = false;
Vitaly Buka74763422015-10-11 00:39:52 -0700360 auto callback = [&succeeded, this](const base::DictionaryValue& info,
361 ErrorPtr error) {
362 EXPECT_FALSE(error);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700363 std::string id;
364 EXPECT_TRUE(info.GetString("id", &id));
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800365 EXPECT_EQ(test_data::kCloudId, id);
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700366 succeeded = true;
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700367 };
Vitaly Buka74763422015-10-11 00:39:52 -0700368 dev_reg_->GetDeviceInfo(base::Bind(callback));
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700369 EXPECT_TRUE(succeeded);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700370}
371
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400372TEST_F(DeviceRegistrationInfoTest, RegisterDevice) {
Vitaly Buka72d8d162015-12-09 18:39:36 -0800373 ReloadSettings();
374
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800375 auto json_traits = CreateDictionaryValue(R"({
Alex Vakulenko45109442014-07-29 11:07:10 -0700376 'base': {
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800377 'commands': {
378 'reboot': {
379 'parameters': {'delay': {'minimum': 10, 'type': 'integer'}},
380 'minimalRole': 'user'
381 }
382 },
383 'state': {
384 'firmwareVersion': {'type': 'string'}
Alex Vakulenko45109442014-07-29 11:07:10 -0700385 }
386 },
387 'robot': {
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800388 'commands': {
389 '_jump': {
390 'parameters': {'_height': {'type': 'integer'}},
391 'minimalRole': 'user'
392 }
Alex Vakulenko45109442014-07-29 11:07:10 -0700393 }
394 }
395 })");
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800396 EXPECT_TRUE(component_manager_.LoadTraits(*json_traits, nullptr));
397 EXPECT_TRUE(component_manager_.AddComponent("", "comp", {"base", "robot"},
398 nullptr));
399 base::StringValue ver{"1.0"};
400 EXPECT_TRUE(component_manager_.SetStateProperty(
401 "comp", "base.firmwareVersion", ver, nullptr));
Alex Vakulenko45109442014-07-29 11:07:10 -0700402
Vitaly Buka10206182015-08-05 11:17:43 -0700403 std::string ticket_url = dev_reg_->GetServiceURL("registrationTickets/") +
404 test_data::kClaimTicketId;
Vitaly Buka1a42e142015-10-10 18:15:15 -0700405 EXPECT_CALL(http_client_,
406 SendRequest(HttpClient::Method::kPatch,
407 ticket_url + "?key=" + test_data::kApiKey,
Vitaly Buka74763422015-10-11 00:39:52 -0700408 HttpClient::Headers{GetJsonHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700409 .WillOnce(WithArgs<3, 4>(Invoke([](
410 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700411 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -0700412 auto json = test::CreateDictionaryValue(data);
Vitaly Buka10206182015-08-05 11:17:43 -0700413 EXPECT_NE(nullptr, json.get());
414 std::string value;
415 EXPECT_TRUE(json->GetString("id", &value));
416 EXPECT_EQ(test_data::kClaimTicketId, value);
417 EXPECT_TRUE(
418 json->GetString("deviceDraft.channel.supportedType", &value));
419 EXPECT_EQ("pull", value);
420 EXPECT_TRUE(json->GetString("oauthClientId", &value));
421 EXPECT_EQ(test_data::kClientId, value);
Vitaly Buka10206182015-08-05 11:17:43 -0700422 EXPECT_TRUE(json->GetString("deviceDraft.description", &value));
423 EXPECT_EQ("Easy to clean", value);
424 EXPECT_TRUE(json->GetString("deviceDraft.location", &value));
425 EXPECT_EQ("Kitchen", value);
426 EXPECT_TRUE(json->GetString("deviceDraft.modelManifestId", &value));
427 EXPECT_EQ("AAAAA", value);
428 EXPECT_TRUE(json->GetString("deviceDraft.name", &value));
429 EXPECT_EQ("Coffee Pot", value);
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800430 base::DictionaryValue* dict = nullptr;
431 EXPECT_TRUE(json->GetDictionary("deviceDraft.commandDefs", &dict));
432 auto expectedCommandDefs = R"({
Vitaly Buka10206182015-08-05 11:17:43 -0700433 'base': {
434 'reboot': {
435 'parameters': {
436 'delay': {
437 'minimum': 10,
438 'type': 'integer'
439 }
440 },
441 'minimalRole': 'user'
442 }
443 },
444 'robot': {
445 '_jump': {
446 'parameters': {
447 '_height': {
448 'type': 'integer'
449 }
450 },
451 'minimalRole': 'user'
452 }
453 }
454 })";
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800455 EXPECT_JSON_EQ(expectedCommandDefs, *dict);
456
457 EXPECT_TRUE(json->GetDictionary("deviceDraft.state", &dict));
458 auto expectedState = R"({
459 'base': {
460 'firmwareVersion': '1.0'
461 }
462 })";
463 EXPECT_JSON_EQ(expectedState, *dict);
464
465 EXPECT_TRUE(json->GetDictionary("deviceDraft.traits", &dict));
466 auto expectedTraits = R"({
467 'base': {
468 'commands': {
469 'reboot': {
470 'parameters': {'delay': {'minimum': 10, 'type': 'integer'}},
471 'minimalRole': 'user'
472 }
473 },
474 'state': {
475 'firmwareVersion': {'type': 'string'}
476 }
477 },
478 'robot': {
479 'commands': {
480 '_jump': {
481 'parameters': {'_height': {'type': 'integer'}},
482 'minimalRole': 'user'
483 }
484 }
485 }
486 })";
487 EXPECT_JSON_EQ(expectedTraits, *dict);
488
489 EXPECT_TRUE(json->GetDictionary("deviceDraft.components", &dict));
490 auto expectedComponents = R"({
491 'comp': {
492 'traits': ['base', 'robot'],
493 'state': {
494 'base': { 'firmwareVersion': '1.0' }
495 }
496 }
497 })";
498 EXPECT_JSON_EQ(expectedComponents, *dict);
Vitaly Buka10206182015-08-05 11:17:43 -0700499
500 base::DictionaryValue json_resp;
501 json_resp.SetString("id", test_data::kClaimTicketId);
Vitaly Buka88272d52015-11-17 17:04:01 -0800502 json_resp.SetString("kind", "weave#registrationTicket");
Vitaly Buka10206182015-08-05 11:17:43 -0700503 json_resp.SetString("oauthClientId", test_data::kClientId);
504 base::DictionaryValue* device_draft = nullptr;
505 EXPECT_TRUE(json->GetDictionary("deviceDraft", &device_draft));
506 device_draft = device_draft->DeepCopy();
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800507 device_draft->SetString("id", test_data::kCloudId);
Vitaly Buka88272d52015-11-17 17:04:01 -0800508 device_draft->SetString("kind", "weave#device");
Vitaly Buka10206182015-08-05 11:17:43 -0700509 json_resp.Set("deviceDraft", device_draft);
510
Vitaly Buka74763422015-10-11 00:39:52 -0700511 callback.Run(ReplyWithJson(200, json_resp), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700512 })));
513
514 EXPECT_CALL(http_client_,
Vitaly Buka1a42e142015-10-10 18:15:15 -0700515 SendRequest(HttpClient::Method::kPost,
Vitaly Buka17280372015-10-10 17:11:47 -0700516 ticket_url + "/finalize?key=" + test_data::kApiKey,
Vitaly Buka74763422015-10-11 00:39:52 -0700517 HttpClient::Headers{}, _, _))
518 .WillOnce(WithArgs<4>(
519 Invoke([](const HttpClient::SendRequestCallback& callback) {
Vitaly Buka17280372015-10-10 17:11:47 -0700520 base::DictionaryValue json;
521 json.SetString("id", test_data::kClaimTicketId);
Vitaly Buka88272d52015-11-17 17:04:01 -0800522 json.SetString("kind", "weave#registrationTicket");
Vitaly Buka17280372015-10-10 17:11:47 -0700523 json.SetString("oauthClientId", test_data::kClientId);
524 json.SetString("userEmail", "user@email.com");
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800525 json.SetString("deviceDraft.id", test_data::kCloudId);
Vitaly Buka88272d52015-11-17 17:04:01 -0800526 json.SetString("deviceDraft.kind", "weave#device");
Vitaly Buka17280372015-10-10 17:11:47 -0700527 json.SetString("deviceDraft.channel.supportedType", "xmpp");
528 json.SetString("robotAccountEmail", test_data::kRobotAccountEmail);
529 json.SetString("robotAccountAuthorizationCode",
530 test_data::kRobotAccountAuthCode);
Vitaly Buka74763422015-10-11 00:39:52 -0700531 callback.Run(ReplyWithJson(200, json), nullptr);
Vitaly Buka17280372015-10-10 17:11:47 -0700532 })));
Vitaly Buka10206182015-08-05 11:17:43 -0700533
Vitaly Buka1a42e142015-10-10 18:15:15 -0700534 EXPECT_CALL(
535 http_client_,
536 SendRequest(HttpClient::Method::kPost, dev_reg_->GetOAuthURL("token"),
Vitaly Buka74763422015-10-11 00:39:52 -0700537 HttpClient::Headers{GetFormHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700538 .WillOnce(WithArgs<3, 4>(Invoke([](
539 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700540 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka10206182015-08-05 11:17:43 -0700541 EXPECT_EQ("authorization_code", GetFormField(data, "grant_type"));
542 EXPECT_EQ(test_data::kRobotAccountAuthCode, GetFormField(data, "code"));
543 EXPECT_EQ(test_data::kClientId, GetFormField(data, "client_id"));
544 EXPECT_EQ(test_data::kClientSecret,
545 GetFormField(data, "client_secret"));
Vitaly Buka10206182015-08-05 11:17:43 -0700546 EXPECT_EQ("oob", GetFormField(data, "redirect_uri"));
Vitaly Buka10206182015-08-05 11:17:43 -0700547
548 base::DictionaryValue json;
549 json.SetString("access_token", test_data::kAccessToken);
550 json.SetString("token_type", "Bearer");
551 json.SetString("refresh_token", test_data::kRefreshToken);
552 json.SetInteger("expires_in", 3600);
553
Vitaly Buka74763422015-10-11 00:39:52 -0700554 callback.Run(ReplyWithJson(200, json), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700555 })));
556
Vitaly Buka72d8d162015-12-09 18:39:36 -0800557 EXPECT_CALL(
558 http_client_,
559 SendRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"),
560 HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
561 .WillOnce(WithArgs<3, 4>(
562 Invoke([](const std::string& data,
563 const HttpClient::SendRequestCallback& callback) {
564 EXPECT_JSON_EQ(test_data::kAuthInfo, *CreateDictionaryValue(data));
565 base::DictionaryValue json;
566 callback.Run(ReplyWithJson(200, json), nullptr);
567 })));
568
Vitaly Buka12870bd2015-10-08 23:49:39 -0700569 bool done = false;
570 dev_reg_->RegisterDevice(
Vitaly Buka74763422015-10-11 00:39:52 -0700571 test_data::kClaimTicketId, base::Bind([this, &done](ErrorPtr error) {
572 EXPECT_FALSE(error);
Vitaly Buka12870bd2015-10-08 23:49:39 -0700573 done = true;
574 task_runner_.Break();
575 EXPECT_EQ(GcdState::kConnecting, GetGcdState());
Anton Muhinbeb1c5b2014-10-16 18:59:57 +0400576
Vitaly Buka12870bd2015-10-08 23:49:39 -0700577 // Validate the device info saved to storage...
Vitaly Buka6a9d0e22015-12-06 17:49:11 -0800578 EXPECT_EQ(test_data::kCloudId, dev_reg_->GetSettings().cloud_id);
Vitaly Buka12870bd2015-10-08 23:49:39 -0700579 EXPECT_EQ(test_data::kRefreshToken,
580 dev_reg_->GetSettings().refresh_token);
581 EXPECT_EQ(test_data::kRobotAccountEmail,
582 dev_reg_->GetSettings().robot_account);
Vitaly Buka74763422015-10-11 00:39:52 -0700583 }));
Vitaly Buka12870bd2015-10-08 23:49:39 -0700584 task_runner_.Run();
585 EXPECT_TRUE(done);
Alex Vakulenko8e34d392014-04-29 11:02:56 -0700586}
587
Christopher Wileyc900e482015-02-15 15:42:04 -0800588TEST_F(DeviceRegistrationInfoTest, OOBRegistrationStatus) {
Vitaly Buka10206182015-08-05 11:17:43 -0700589 // After we've been initialized, we should be either offline or
590 // unregistered, depending on whether or not we've found credentials.
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700591 EXPECT_EQ(GcdState::kUnconfigured, GetGcdState());
Christopher Wileyc900e482015-02-15 15:42:04 -0800592 // Put some credentials into our state, make sure we call that offline.
Vitaly Bukac11a17d2015-08-15 10:36:10 -0700593 ReloadSettings();
Vitaly Bukac3c6dab2015-10-01 19:41:02 -0700594 EXPECT_EQ(GcdState::kConnecting, GetGcdState());
Christopher Wileyc900e482015-02-15 15:42:04 -0800595}
596
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700597class DeviceRegistrationInfoUpdateCommandTest
598 : public DeviceRegistrationInfoTest {
599 protected:
600 void SetUp() override {
601 DeviceRegistrationInfoTest::SetUp();
Alex Vakulenko266b2b12015-06-19 11:01:42 -0700602
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700603 ReloadSettings();
604 SetAccessToken();
605
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800606 auto json_traits = CreateDictionaryValue(R"({
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700607 'robot': {
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800608 'commands': {
609 '_jump': {
610 'parameters': {'_height': 'integer'},
611 'progress': {'progress': 'integer'},
612 'results': {'status': 'string'},
613 'minimalRole': 'user'
614 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700615 }
Alex Vakulenko9e2f8cd2015-04-07 16:28:09 -0700616 }
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700617 })");
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800618 EXPECT_TRUE(component_manager_.LoadTraits(*json_traits, nullptr));
619 EXPECT_TRUE(component_manager_.AddComponent("", "comp", {"robot"},
620 nullptr));
Alex Vakulenko9e2f8cd2015-04-07 16:28:09 -0700621
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700622 command_url_ = dev_reg_->GetServiceURL("commands/1234");
Alex Vakulenko808e2d82015-04-08 15:45:56 -0700623
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700624 auto commands_json = CreateValue(R"([{
625 'name':'robot._jump',
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800626 'component': 'comp',
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700627 '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);
Alex Vakulenkod91d6252015-12-05 17:14:39 -0800635 command_ = component_manager_.FindCommand("1234");
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700636 ASSERT_NE(nullptr, command_);
637 }
Alex Vakulenko9e2f8cd2015-04-07 16:28:09 -0700638
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700639 void TearDown() override {
640 task_runner_.RunOnce();
641 DeviceRegistrationInfoTest::TearDown();
642 }
643
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700644 Command* command_{nullptr};
645 std::string command_url_;
646};
647
648TEST_F(DeviceRegistrationInfoUpdateCommandTest, SetProgress) {
Vitaly Buka74763422015-10-11 00:39:52 -0700649 EXPECT_CALL(
650 http_client_,
651 SendRequest(HttpClient::Method::kPatch, command_url_,
652 HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700653 .WillOnce(WithArgs<3, 4>(Invoke([](
654 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700655 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka17280372015-10-10 17:11:47 -0700656 EXPECT_JSON_EQ((R"({"state":"inProgress","progress":{"progress":18}})"),
Vitaly Buka10206182015-08-05 11:17:43 -0700657 *CreateDictionaryValue(data));
658 base::DictionaryValue json;
Vitaly Buka74763422015-10-11 00:39:52 -0700659 callback.Run(ReplyWithJson(200, json), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700660 })));
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700661 EXPECT_TRUE(command_->SetProgress(*CreateDictionaryValue("{'progress':18}"),
662 nullptr));
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700663}
Alex Vakulenko808e2d82015-04-08 15:45:56 -0700664
Vitaly Buka2f548972015-10-08 19:34:49 -0700665TEST_F(DeviceRegistrationInfoUpdateCommandTest, Complete) {
Vitaly Buka74763422015-10-11 00:39:52 -0700666 EXPECT_CALL(
667 http_client_,
668 SendRequest(HttpClient::Method::kPatch, command_url_,
669 HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
Vitaly Bukaf0a3b222015-10-16 01:49:36 -0700670 .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 })));
Vitaly Buka2f548972015-10-08 19:34:49 -0700678 EXPECT_TRUE(
679 command_->Complete(*CreateDictionaryValue("{'status': 'Ok'}"), nullptr));
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700680}
681
682TEST_F(DeviceRegistrationInfoUpdateCommandTest, Cancel) {
Vitaly Buka74763422015-10-11 00:39:52 -0700683 EXPECT_CALL(
684 http_client_,
685 SendRequest(HttpClient::Method::kPatch, command_url_,
686 HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
Vitaly Buka17280372015-10-10 17:11:47 -0700687 .WillOnce(WithArgs<3, 4>(Invoke([](
688 const std::string& data,
Vitaly Buka74763422015-10-11 00:39:52 -0700689 const HttpClient::SendRequestCallback& callback) {
Vitaly Buka10206182015-08-05 11:17:43 -0700690 EXPECT_JSON_EQ(R"({"state":"cancelled"})",
691 *CreateDictionaryValue(data));
692 base::DictionaryValue json;
Vitaly Buka74763422015-10-11 00:39:52 -0700693 callback.Run(ReplyWithJson(200, json), nullptr);
Vitaly Buka10206182015-08-05 11:17:43 -0700694 })));
Vitaly Buka47a1f6f2015-10-07 18:09:57 -0700695 EXPECT_TRUE(command_->Cancel(nullptr));
Alex Vakulenko9e2f8cd2015-04-07 16:28:09 -0700696}
697
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700698} // namespace weave