blob: 0c045928047fc42a76a445e146ad58d8c6aad5ce [file] [log] [blame]
Vitaly Buka4615e0d2015-10-14 15:35:12 -07001// Copyright 2015 The Weave Authors. All rights reserved.
Alex Vakulenkobe4254b2015-06-26 11:34:03 -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/commands/cloud_command_proxy.h"
Alex Vakulenkobe4254b2015-06-26 11:34:03 -07006
7#include <memory>
8#include <queue>
9
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070010#include <gmock/gmock.h>
11#include <gtest/gtest.h>
Vitaly Buka727f3e62015-09-25 17:33:43 -070012#include <weave/provider/test/fake_task_runner.h>
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070013
Stefan Sauer2d16dfa2015-09-25 17:08:35 +020014#include "src/commands/command_dictionary.h"
15#include "src/commands/command_instance.h"
16#include "src/commands/unittest_utils.h"
17#include "src/states/mock_state_change_queue_interface.h"
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070018
Vitaly Bukaff1d1862015-10-07 20:40:36 -070019using testing::_;
20using testing::DoAll;
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070021using testing::Invoke;
22using testing::Return;
23using testing::ReturnPointee;
Vitaly Bukaff1d1862015-10-07 20:40:36 -070024using testing::SaveArg;
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070025
Vitaly Bukab6f015a2015-07-09 14:59:23 -070026namespace weave {
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070027
Vitaly Buka0f6b2ec2015-08-20 15:35:19 -070028using test::CreateDictionaryValue;
29using test::CreateValue;
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070030
31namespace {
32
33const char kCmdID[] = "abcd";
34
35MATCHER_P(MatchJson, str, "") {
36 return arg.Equals(CreateValue(str).get());
37}
38
39class MockCloudCommandUpdateInterface : public CloudCommandUpdateInterface {
40 public:
Vitaly Buka74763422015-10-11 00:39:52 -070041 MOCK_METHOD3(UpdateCommand,
Vitaly Bukaa647c852015-07-06 14:51:01 -070042 void(const std::string&,
43 const base::DictionaryValue&,
Vitaly Buka74763422015-10-11 00:39:52 -070044 const DoneCallback&));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070045};
46
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070047// Test back-off entry that uses the test clock.
Vitaly Buka0f80f7c2015-08-13 00:57:25 -070048class TestBackoffEntry : public BackoffEntry {
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070049 public:
50 TestBackoffEntry(const Policy* const policy, base::Clock* clock)
Vitaly Buka0f80f7c2015-08-13 00:57:25 -070051 : BackoffEntry{policy}, clock_{clock} {
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070052 creation_time_ = clock->Now();
53 }
54
55 private:
Vitaly Buka0f80f7c2015-08-13 00:57:25 -070056 // Override from BackoffEntry to use the custom test clock for
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070057 // the backoff calculations.
58 base::TimeTicks ImplGetTimeNow() const override {
59 return base::TimeTicks::FromInternalValue(clock_->Now().ToInternalValue());
60 }
61
62 base::Clock* clock_;
63 base::Time creation_time_;
64};
65
66class CloudCommandProxyTest : public ::testing::Test {
67 protected:
68 void SetUp() override {
69 // Set up the test StateChangeQueue.
70 auto callback = [this](
71 const base::Callback<void(StateChangeQueueInterface::UpdateID)>& call) {
72 return callbacks_.Add(call).release();
73 };
74 EXPECT_CALL(state_change_queue_, MockAddOnStateUpdatedCallback(_))
75 .WillRepeatedly(Invoke(callback));
76 EXPECT_CALL(state_change_queue_, GetLastStateChangeId())
77 .WillRepeatedly(testing::ReturnPointee(&current_state_update_id_));
78
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070079 // Set up the command schema.
80 auto json = CreateDictionaryValue(R"({
81 'calc': {
82 'add': {
83 'parameters': {
84 'value1': 'integer',
85 'value2': 'integer'
86 },
87 'progress': {
88 'status' : 'string'
89 },
90 'results': {
91 'sum' : 'integer'
92 }
93 }
94 }
95 })");
96 CHECK(json.get());
Vitaly Buka453c4dd2015-10-04 18:01:50 -070097 CHECK(command_dictionary_.LoadCommands(*json, nullptr, nullptr))
Alex Vakulenkobe4254b2015-06-26 11:34:03 -070098 << "Failed to parse test command dictionary";
99
100 CreateCommandInstance();
101 }
102
103 void CreateCommandInstance() {
104 auto command_json = CreateDictionaryValue(R"({
105 'name': 'calc.add',
106 'id': 'abcd',
107 'parameters': {
108 'value1': 10,
109 'value2': 20
110 }
111 })");
112 CHECK(command_json.get());
113
Vitaly Buka15f59092015-07-24 16:54:32 -0700114 command_instance_ =
Vitaly Buka0209da42015-10-08 00:07:18 -0700115 CommandInstance::FromJson(command_json.get(), Command::Origin::kCloud,
Vitaly Buka15f59092015-07-24 16:54:32 -0700116 command_dictionary_, nullptr, nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700117 CHECK(command_instance_.get());
118
119 // Backoff - start at 1s and double with each backoff attempt and no jitter.
Vitaly Buka0f80f7c2015-08-13 00:57:25 -0700120 static const BackoffEntry::Policy policy{0, 1000, 2.0, 0.0,
121 20000, -1, false};
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700122 std::unique_ptr<TestBackoffEntry> backoff{
Vitaly Buka823fdda2015-08-13 00:33:00 -0700123 new TestBackoffEntry{&policy, task_runner_.GetClock()}};
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700124
125 // Finally construct the CloudCommandProxy we are going to test here.
Vitaly Bukaf9630fb2015-08-12 21:15:40 -0700126 std::unique_ptr<CloudCommandProxy> proxy{new CloudCommandProxy{
127 command_instance_.get(), &cloud_updater_, &state_change_queue_,
128 std::move(backoff), &task_runner_}};
Vitaly Buka157b16a2015-07-31 16:20:48 -0700129 // CloudCommandProxy::CloudCommandProxy() subscribe itself to weave::Command
130 // notifications. When weave::Command is being destroyed it sends
131 // ::OnCommandDestroyed() and CloudCommandProxy deletes itself.
132 proxy.release();
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700133 }
134
135 StateChangeQueueInterface::UpdateID current_state_update_id_{0};
136 base::CallbackList<void(StateChangeQueueInterface::UpdateID)> callbacks_;
137 testing::StrictMock<MockCloudCommandUpdateInterface> cloud_updater_;
138 testing::StrictMock<MockStateChangeQueueInterface> state_change_queue_;
Vitaly Buka727f3e62015-09-25 17:33:43 -0700139 testing::StrictMock<provider::test::FakeTaskRunner> task_runner_;
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700140 std::queue<base::Closure> task_queue_;
141 CommandDictionary command_dictionary_;
142 std::unique_ptr<CommandInstance> command_instance_;
143};
144
145} // anonymous namespace
146
147TEST_F(CloudCommandProxyTest, ImmediateUpdate) {
148 const char expected[] = "{'state':'done'}";
Vitaly Buka74763422015-10-11 00:39:52 -0700149 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Vitaly Buka2f548972015-10-08 19:34:49 -0700150 command_instance_->Complete({}, nullptr);
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700151 task_runner_.RunOnce();
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700152}
153
154TEST_F(CloudCommandProxyTest, DelayedUpdate) {
155 // Simulate that the current device state has changed.
156 current_state_update_id_ = 20;
157 // No command update is expected here.
Vitaly Buka2f548972015-10-08 19:34:49 -0700158 command_instance_->Complete({}, nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700159 // Still no command update here...
160 callbacks_.Notify(19);
161 // Now we should get the update...
162 const char expected[] = "{'state':'done'}";
Vitaly Buka74763422015-10-11 00:39:52 -0700163 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700164 callbacks_.Notify(20);
165}
166
167TEST_F(CloudCommandProxyTest, InFlightRequest) {
168 // SetProgress causes two consecutive updates:
169 // state=inProgress
170 // progress={...}
171 // The first state update is sent immediately, the second should be delayed.
Vitaly Buka74763422015-10-11 00:39:52 -0700172 DoneCallback callback;
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700173 EXPECT_CALL(
174 cloud_updater_,
175 UpdateCommand(
176 kCmdID,
Vitaly Buka74763422015-10-11 00:39:52 -0700177 MatchJson("{'state':'inProgress', 'progress':{'status':'ready'}}"),
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700178 _))
Vitaly Buka74763422015-10-11 00:39:52 -0700179 .WillOnce(SaveArg<2>(&callback));
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700180 EXPECT_TRUE(command_instance_->SetProgress(
181 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700182
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700183 task_runner_.RunOnce();
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700184}
185
186TEST_F(CloudCommandProxyTest, CombineMultiple) {
187 // Simulate that the current device state has changed.
188 current_state_update_id_ = 20;
189 // SetProgress causes two consecutive updates:
190 // state=inProgress
191 // progress={...}
192 // Both updates will be held until device state is updated.
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700193 EXPECT_TRUE(command_instance_->SetProgress(
194 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700195
196 // Now simulate the device state updated. Both updates should come in one
197 // request.
198 const char expected[] = R"({
199 'progress': {'status':'ready'},
200 'state':'inProgress'
201 })";
Vitaly Buka74763422015-10-11 00:39:52 -0700202 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700203 callbacks_.Notify(20);
204}
205
206TEST_F(CloudCommandProxyTest, RetryFailed) {
Vitaly Buka74763422015-10-11 00:39:52 -0700207 DoneCallback callback;
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700208
209 const char expect[] =
210 "{'state':'inProgress', 'progress': {'status': 'ready'}}";
Vitaly Buka74763422015-10-11 00:39:52 -0700211 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect), _))
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700212 .Times(3)
Vitaly Buka74763422015-10-11 00:39:52 -0700213 .WillRepeatedly(SaveArg<2>(&callback));
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700214 auto started = task_runner_.GetClock()->Now();
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700215 EXPECT_TRUE(command_instance_->SetProgress(
216 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700217 task_runner_.Run();
Vitaly Buka74763422015-10-11 00:39:52 -0700218 ErrorPtr error;
219 Error::AddTo(&error, FROM_HERE, "TEST", "TEST", "TEST");
220 callback.Run(error->Clone());
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700221 task_runner_.Run();
222 EXPECT_GE(task_runner_.GetClock()->Now() - started,
223 base::TimeDelta::FromSecondsD(0.9));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700224
Vitaly Buka74763422015-10-11 00:39:52 -0700225 callback.Run(error->Clone());
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700226 task_runner_.Run();
227 EXPECT_GE(task_runner_.GetClock()->Now() - started,
228 base::TimeDelta::FromSecondsD(2.9));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700229
Vitaly Buka74763422015-10-11 00:39:52 -0700230 callback.Run(nullptr);
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700231 task_runner_.Run();
232 EXPECT_GE(task_runner_.GetClock()->Now() - started,
233 base::TimeDelta::FromSecondsD(2.9));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700234}
235
236TEST_F(CloudCommandProxyTest, GateOnStateUpdates) {
237 current_state_update_id_ = 20;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700238 EXPECT_TRUE(command_instance_->SetProgress(
239 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700240 current_state_update_id_ = 21;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700241 EXPECT_TRUE(command_instance_->SetProgress(
242 *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700243 current_state_update_id_ = 22;
Vitaly Buka2f548972015-10-08 19:34:49 -0700244 command_instance_->Complete({}, nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700245
246 // Device state #20 updated.
Vitaly Buka74763422015-10-11 00:39:52 -0700247 DoneCallback callback;
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700248 const char expect1[] = R"({
249 'progress': {'status':'ready'},
250 'state':'inProgress'
251 })";
Vitaly Buka74763422015-10-11 00:39:52 -0700252 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect1), _))
253 .WillOnce(SaveArg<2>(&callback));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700254 callbacks_.Notify(20);
Vitaly Buka74763422015-10-11 00:39:52 -0700255 callback.Run(nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700256
257 // Device state #21 updated.
258 const char expect2[] = "{'progress': {'status':'busy'}}";
Vitaly Buka74763422015-10-11 00:39:52 -0700259 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect2), _))
260 .WillOnce(SaveArg<2>(&callback));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700261 callbacks_.Notify(21);
262
263 // Device state #22 updated. Nothing happens here since the previous command
264 // update request hasn't completed yet.
265 callbacks_.Notify(22);
266
267 // Now the command update is complete, send out the patch that happened after
268 // the state #22 was updated.
269 const char expect3[] = "{'state': 'done'}";
Vitaly Buka74763422015-10-11 00:39:52 -0700270 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect3), _))
271 .WillOnce(SaveArg<2>(&callback));
272 callback.Run(nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700273}
274
275TEST_F(CloudCommandProxyTest, CombineSomeStates) {
276 current_state_update_id_ = 20;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700277 EXPECT_TRUE(command_instance_->SetProgress(
278 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700279 current_state_update_id_ = 21;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700280 EXPECT_TRUE(command_instance_->SetProgress(
281 *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700282 current_state_update_id_ = 22;
Vitaly Buka2f548972015-10-08 19:34:49 -0700283 command_instance_->Complete({}, nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700284
285 // Device state 20-21 updated.
Vitaly Buka74763422015-10-11 00:39:52 -0700286 DoneCallback callback;
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700287 const char expect1[] = R"({
288 'progress': {'status':'busy'},
289 'state':'inProgress'
290 })";
Vitaly Buka74763422015-10-11 00:39:52 -0700291 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect1), _))
292 .WillOnce(SaveArg<2>(&callback));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700293 callbacks_.Notify(21);
Vitaly Buka74763422015-10-11 00:39:52 -0700294 callback.Run(nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700295
296 // Device state #22 updated.
297 const char expect2[] = "{'state': 'done'}";
Vitaly Buka74763422015-10-11 00:39:52 -0700298 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect2), _))
299 .WillOnce(SaveArg<2>(&callback));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700300 callbacks_.Notify(22);
Vitaly Buka74763422015-10-11 00:39:52 -0700301 callback.Run(nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700302}
303
304TEST_F(CloudCommandProxyTest, CombineAllStates) {
305 current_state_update_id_ = 20;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700306 EXPECT_TRUE(command_instance_->SetProgress(
307 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700308 current_state_update_id_ = 21;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700309 EXPECT_TRUE(command_instance_->SetProgress(
310 *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700311 current_state_update_id_ = 22;
Vitaly Buka2f548972015-10-08 19:34:49 -0700312 command_instance_->Complete({}, nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700313
314 // Device state 30 updated.
315 const char expected[] = R"({
316 'progress': {'status':'busy'},
317 'state':'done'
318 })";
Vitaly Buka74763422015-10-11 00:39:52 -0700319 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700320 callbacks_.Notify(30);
321}
322
323TEST_F(CloudCommandProxyTest, CoalesceUpdates) {
324 current_state_update_id_ = 20;
Vitaly Buka4f4e2282015-07-23 17:50:07 -0700325 EXPECT_TRUE(command_instance_->SetProgress(
326 *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
327 EXPECT_TRUE(command_instance_->SetProgress(
328 *CreateDictionaryValue("{'status': 'busy'}"), nullptr));
329 EXPECT_TRUE(command_instance_->SetProgress(
330 *CreateDictionaryValue("{'status': 'finished'}"), nullptr));
Vitaly Buka2f548972015-10-08 19:34:49 -0700331 EXPECT_TRUE(command_instance_->Complete(*CreateDictionaryValue("{'sum': 30}"),
332 nullptr));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700333
334 const char expected[] = R"({
335 'progress': {'status':'finished'},
336 'results': {'sum':30},
337 'state':'done'
338 })";
Vitaly Buka74763422015-10-11 00:39:52 -0700339 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700340 callbacks_.Notify(30);
341}
342
343TEST_F(CloudCommandProxyTest, EmptyStateChangeQueue) {
344 // Assume the device state update queue was empty and was at update ID 20.
345 current_state_update_id_ = 20;
346
347 // Recreate the command instance and proxy with the new state change queue.
348 CreateCommandInstance();
349
350 // Empty queue will immediately call back with the state change notification.
351 callbacks_.Notify(20);
352
353 // As soon as we change the command, the update to the server should be sent.
354 const char expected[] = "{'state':'done'}";
Vitaly Buka74763422015-10-11 00:39:52 -0700355 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Vitaly Buka2f548972015-10-08 19:34:49 -0700356 command_instance_->Complete({}, nullptr);
Vitaly Bukaff1d1862015-10-07 20:40:36 -0700357 task_runner_.RunOnce();
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700358}
359
360TEST_F(CloudCommandProxyTest, NonEmptyStateChangeQueue) {
361 // Assume the device state update queue was NOT empty when the command
362 // instance was created.
363 current_state_update_id_ = 20;
364
365 // Recreate the command instance and proxy with the new state change queue.
366 CreateCommandInstance();
367
368 // No command updates right now.
Vitaly Buka2f548972015-10-08 19:34:49 -0700369 command_instance_->Complete({}, nullptr);
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700370
371 // Only when the state #20 is published we should update the command
372 const char expected[] = "{'state':'done'}";
Vitaly Buka74763422015-10-11 00:39:52 -0700373 EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _));
Alex Vakulenkobe4254b2015-06-26 11:34:03 -0700374 callbacks_.Notify(20);
375}
376
Vitaly Bukab6f015a2015-07-09 14:59:23 -0700377} // namespace weave