Post CloudCommandProxy::SendCommandUpdate to accumulate more changes

Single message loop usually updates several properties of command.
Accumulating them we can reduce number of requests to server.

Change-Id: I57f6ea7e15192c6c1a22dafd66b3add62aee846e
Reviewed-on: https://weave-review.googlesource.com/1263
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/libweave/src/commands/cloud_command_proxy.cc b/libweave/src/commands/cloud_command_proxy.cc
index 931e3e4..9d7a9d1 100644
--- a/libweave/src/commands/cloud_command_proxy.cc
+++ b/libweave/src/commands/cloud_command_proxy.cc
@@ -88,7 +88,12 @@
     }
   }
   // Send out an update request to the server, if needed.
-  SendCommandUpdate();
+
+  // Post to accumulate more changes during the current message loop task run.
+  task_runner_->PostDelayedTask(
+      FROM_HERE, base::Bind(&CloudCommandProxy::SendCommandUpdate,
+                            backoff_weak_ptr_factory_.GetWeakPtr()),
+      {});
 }
 
 void CloudCommandProxy::SendCommandUpdate() {
diff --git a/libweave/src/commands/cloud_command_proxy_unittest.cc b/libweave/src/commands/cloud_command_proxy_unittest.cc
index dbff905..f91e26c 100644
--- a/libweave/src/commands/cloud_command_proxy_unittest.cc
+++ b/libweave/src/commands/cloud_command_proxy_unittest.cc
@@ -16,11 +16,12 @@
 #include "src/commands/unittest_utils.h"
 #include "src/states/mock_state_change_queue_interface.h"
 
-using testing::SaveArg;
+using testing::_;
+using testing::DoAll;
 using testing::Invoke;
 using testing::Return;
 using testing::ReturnPointee;
-using testing::_;
+using testing::SaveArg;
 
 namespace weave {
 
@@ -148,6 +149,7 @@
   const char expected[] = "{'state':'done'}";
   EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _, _));
   command_instance_->SetResults({}, nullptr);
+  task_runner_.RunOnce();
 }
 
 TEST_F(CloudCommandProxyTest, DelayedUpdate) {
@@ -169,17 +171,17 @@
   //    progress={...}
   // The first state update is sent immediately, the second should be delayed.
   base::Closure on_success;
-  EXPECT_CALL(cloud_updater_,
-              UpdateCommand(kCmdID, MatchJson("{'state':'inProgress'}"), _, _))
+  EXPECT_CALL(
+      cloud_updater_,
+      UpdateCommand(
+          kCmdID,
+          MatchJson("{'state':'inProgress', 'progress':{'status':'ready'}}"), _,
+          _))
       .WillOnce(SaveArg<2>(&on_success));
   EXPECT_TRUE(command_instance_->SetProgress(
       *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
 
-  // Now simulate the first request completing.
-  // The second request should be sent now.
-  const char expected[] = "{'progress':{'status':'ready'}}";
-  EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _, _));
-  on_success.Run();
+  task_runner_.RunOnce();
 }
 
 TEST_F(CloudCommandProxyTest, CombineMultiple) {
@@ -203,40 +205,32 @@
 }
 
 TEST_F(CloudCommandProxyTest, RetryFailed) {
+  base::Closure on_success;
   base::Closure on_error;
-  const char expect1[] = "{'state':'inProgress'}";
-  EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect1), _, _))
-      .WillOnce(SaveArg<3>(&on_error));
+
+  const char expect[] =
+      "{'state':'inProgress', 'progress': {'status': 'ready'}}";
+  EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect), _, _))
+      .Times(3)
+      .WillRepeatedly(DoAll(SaveArg<2>(&on_success), SaveArg<3>(&on_error)));
+  auto started = task_runner_.GetClock()->Now();
   EXPECT_TRUE(command_instance_->SetProgress(
       *CreateDictionaryValue("{'status': 'ready'}"), nullptr));
-
-  // Now pretend the first command update request has failed.
-  // We should retry with both state and progress fields updated this time,
-  // after the initial backoff (which should be 1s in our case).
-  base::TimeDelta expected_delay = base::TimeDelta::FromSeconds(1);
+  task_runner_.Run();
   on_error.Run();
+  task_runner_.Run();
+  EXPECT_GE(task_runner_.GetClock()->Now() - started,
+            base::TimeDelta::FromSecondsD(0.9));
 
-  // Execute the delayed request. But pretend that it failed too.
-  const char expect2[] = R"({
-    'progress': {'status':'ready'},
-    'state':'inProgress'
-  })";
-  EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect2), _, _))
-      .WillOnce(SaveArg<3>(&on_error));
-  task_runner_.RunOnce();
-
-  // Now backoff should be 2 seconds.
-  expected_delay = base::TimeDelta::FromSeconds(2);
   on_error.Run();
+  task_runner_.Run();
+  EXPECT_GE(task_runner_.GetClock()->Now() - started,
+            base::TimeDelta::FromSecondsD(2.9));
 
-  // Retry the task.
-  base::Closure on_success;
-  EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expect2), _, _))
-      .WillOnce(SaveArg<2>(&on_success));
-  task_runner_.RunOnce();
-
-  // Pretend it succeeds this time.
   on_success.Run();
+  task_runner_.Run();
+  EXPECT_GE(task_runner_.GetClock()->Now() - started,
+            base::TimeDelta::FromSecondsD(2.9));
 }
 
 TEST_F(CloudCommandProxyTest, GateOnStateUpdates) {
@@ -361,6 +355,7 @@
   const char expected[] = "{'state':'done'}";
   EXPECT_CALL(cloud_updater_, UpdateCommand(kCmdID, MatchJson(expected), _, _));
   command_instance_->SetResults({}, nullptr);
+  task_runner_.RunOnce();
 }
 
 TEST_F(CloudCommandProxyTest, NonEmptyStateChangeQueue) {
diff --git a/libweave/src/device_registration_info.cc b/libweave/src/device_registration_info.cc
index c83b7b6..0e3d64f 100644
--- a/libweave/src/device_registration_info.cc
+++ b/libweave/src/device_registration_info.cc
@@ -438,8 +438,8 @@
   LOG(INFO) << "Starting notification channel";
 
   // If no TaskRunner assume we're in test.
-  if (!task_runner_) {
-    LOG(INFO) << "No TaskRunner, not starting notification channel";
+  if (!network_) {
+    LOG(INFO) << "No Network, not starting notification channel";
     return;
   }
 
diff --git a/libweave/src/device_registration_info_unittest.cc b/libweave/src/device_registration_info_unittest.cc
index 71c9832..84ee33a 100644
--- a/libweave/src/device_registration_info_unittest.cc
+++ b/libweave/src/device_registration_info_unittest.cc
@@ -8,6 +8,7 @@
 #include <base/json/json_writer.h>
 #include <base/values.h>
 #include <gtest/gtest.h>
+#include <weave/provider/test/fake_task_runner.h>
 #include <weave/provider/test/mock_config_store.h>
 #include <weave/provider/test/mock_http_client.h>
 
@@ -123,7 +124,7 @@
     std::unique_ptr<Config> config{new Config{&config_store_}};
     config_ = config.get();
     dev_reg_.reset(new DeviceRegistrationInfo{command_manager_, state_manager_,
-                                              std::move(config), nullptr,
+                                              std::move(config), &task_runner_,
                                               &http_client_, nullptr});
 
     ReloadDefaults();
@@ -182,6 +183,7 @@
 
   GcdState GetGcdState() const { return dev_reg_->GetGcdState(); }
 
+  provider::test::FakeTaskRunner task_runner_;
   provider::test::MockConfigStore config_store_;
   StrictMock<MockHttpClient> http_client_;
   base::DictionaryValue data_;
@@ -539,6 +541,11 @@
     ASSERT_NE(nullptr, command_);
   }
 
+  void TearDown() override {
+    task_runner_.RunOnce();
+    DeviceRegistrationInfoTest::TearDown();
+  }
+
   Command* command_{nullptr};
   std::string command_url_;
 };
@@ -549,20 +556,13 @@
                   http::kPatch, command_url_,
                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
       .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
-        EXPECT_JSON_EQ(R"({"state":"inProgress"})",
-                       *CreateDictionaryValue(data));
-        base::DictionaryValue json;
-        return ReplyWithJson(200, json);
-      })))
-      .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
-        EXPECT_JSON_EQ(R"({"progress":{"progress":18}})",
+        EXPECT_JSON_EQ(R"({"state":"inProgress", "progress":{"progress":18}})",
                        *CreateDictionaryValue(data));
         base::DictionaryValue json;
         return ReplyWithJson(200, json);
       })));
   EXPECT_TRUE(command_->SetProgress(*CreateDictionaryValue("{'progress':18}"),
                                     nullptr));
-  Mock::VerifyAndClearExpectations(&http_client_);
 }
 
 TEST_F(DeviceRegistrationInfoUpdateCommandTest, SetResults) {
@@ -571,19 +571,13 @@
                   http::kPatch, command_url_,
                   HttpClient::Headers{GetAuthHeader(), GetJsonHeader()}, _, _))
       .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
-        EXPECT_JSON_EQ(R"({"results":{"status":"Ok"}})",
+        EXPECT_JSON_EQ(R"({"state":"done", "results":{"status":"Ok"}})",
                        *CreateDictionaryValue(data));
         base::DictionaryValue json;
         return ReplyWithJson(200, json);
-      })))
-      .WillOnce(WithArgs<3>(Invoke([](const std::string& data) {
-        EXPECT_JSON_EQ(R"({"state":"done"})", *CreateDictionaryValue(data));
-        base::DictionaryValue json;
-        return ReplyWithJson(200, json);
       })));
   EXPECT_TRUE(command_->SetResults(*CreateDictionaryValue("{'status': 'Ok'}"),
                                    nullptr));
-  Mock::VerifyAndClearExpectations(&http_client_);
 }
 
 TEST_F(DeviceRegistrationInfoUpdateCommandTest, Cancel) {
@@ -598,7 +592,6 @@
         return ReplyWithJson(200, json);
       })));
   EXPECT_TRUE(command_->Cancel(nullptr));
-  Mock::VerifyAndClearExpectations(&http_client_);
 }
 
 }  // namespace weave