libweave: Test for libweave public API.

Only GCD registration is implemented.
Added more mocks of public interfaces.

BUG=brillo:1256
TEST=`FEATURES=test emerge-gizmo libweave buffet`

Change-Id: I70efbee318955e446c0c5c5490dc2d40b526683a
Reviewed-on: https://chromium-review.googlesource.com/294844
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/libweave/include/weave/error.h b/libweave/include/weave/error.h
index 344f56f..314f685 100644
--- a/libweave/include/weave/error.h
+++ b/libweave/include/weave/error.h
@@ -19,7 +19,7 @@
 
 using ErrorPtr = std::unique_ptr<Error>;
 
-class Error final {
+class LIBWEAVE_EXPORT Error final {
  public:
   ~Error() = default;
 
@@ -36,20 +36,19 @@
   // If |error| is not nullptr, creates another instance of Error class,
   // initializes it with specified arguments and adds it to the head of
   // the error chain pointed to by |error|.
-  LIBWEAVE_EXPORT static void AddTo(ErrorPtr* error,
-                                    const tracked_objects::Location& location,
-                                    const std::string& domain,
-                                    const std::string& code,
-                                    const std::string& message);
+  static void AddTo(ErrorPtr* error,
+                    const tracked_objects::Location& location,
+                    const std::string& domain,
+                    const std::string& code,
+                    const std::string& message);
   // Same as the Error::AddTo above, but allows to pass in a printf-like
   // format string and optional parameters to format the error message.
-  LIBWEAVE_EXPORT static void AddToPrintf(
-      ErrorPtr* error,
-      const tracked_objects::Location& location,
-      const std::string& domain,
-      const std::string& code,
-      const char* format,
-      ...) PRINTF_FORMAT(5, 6);
+  static void AddToPrintf(ErrorPtr* error,
+                          const tracked_objects::Location& location,
+                          const std::string& domain,
+                          const std::string& code,
+                          const char* format,
+                          ...) PRINTF_FORMAT(5, 6);
 
   // Clones error with all inner errors.
   ErrorPtr Clone() const;
diff --git a/libweave/include/weave/mock_config_store.h b/libweave/include/weave/mock_config_store.h
index 0d03cf2..e82bc66 100644
--- a/libweave/include/weave/mock_config_store.h
+++ b/libweave/include/weave/mock_config_store.h
@@ -12,15 +12,15 @@
 #include <gmock/gmock.h>
 #include <weave/config_store.h>
 
-using testing::_;
-using testing::Return;
-
 namespace weave {
 namespace unittests {
 
 class MockConfigStore : public ConfigStore {
  public:
   MockConfigStore() {
+    using testing::_;
+    using testing::Return;
+
     EXPECT_CALL(*this, LoadDefaults(_)).WillRepeatedly(Return(true));
     EXPECT_CALL(*this, LoadSettings()).WillRepeatedly(Return(""));
     EXPECT_CALL(*this, SaveSettings(_)).WillRepeatedly(Return());
diff --git a/libweave/include/weave/mock_http_server.h b/libweave/include/weave/mock_http_server.h
new file mode 100644
index 0000000..7cd8355
--- /dev/null
+++ b/libweave/include/weave/mock_http_server.h
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_INCLUDE_WEAVE_MOCK_HTTP_SERVER_H_
+#define LIBWEAVE_INCLUDE_WEAVE_MOCK_HTTP_SERVER_H_
+
+#include <weave/http_server.h>
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+
+namespace weave {
+namespace unittests {
+
+class MockHttpServer : public HttpServer {
+ public:
+  MOCK_METHOD1(AddOnStateChangedCallback, void(const OnStateChangedCallback&));
+  MOCK_METHOD2(AddRequestHandler,
+               void(const std::string&, const OnRequestCallback&));
+
+  MOCK_CONST_METHOD0(GetHttpPort, uint16_t());
+  MOCK_CONST_METHOD0(GetHttpsPort, uint16_t());
+  MOCK_CONST_METHOD0(GetHttpsCertificateFingerprint, std::vector<uint8_t>&());
+};
+
+}  // namespace unittests
+}  // namespace weave
+
+#endif  // LIBWEAVE_INCLUDE_WEAVE_MOCK_HTTP_SERVER_H_
diff --git a/libweave/include/weave/mock_mdns.h b/libweave/include/weave/mock_mdns.h
new file mode 100644
index 0000000..c6106c1
--- /dev/null
+++ b/libweave/include/weave/mock_mdns.h
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_INCLUDE_WEAVE_MOCK_MDNS_H_
+#define LIBWEAVE_INCLUDE_WEAVE_MOCK_MDNS_H_
+
+#include <weave/mdns.h>
+
+#include <map>
+#include <string>
+
+#include <gmock/gmock.h>
+
+namespace weave {
+namespace unittests {
+
+class MockMdns : public Mdns {
+ public:
+  MOCK_METHOD3(PublishService,
+               void(const std::string&,
+                    uint16_t,
+                    const std::map<std::string, std::string>&));
+  MOCK_METHOD1(StopPublishing, void(const std::string&));
+  MOCK_CONST_METHOD0(GetId, std::string());
+};
+
+}  // namespace unittests
+}  // namespace weave
+
+#endif  // LIBWEAVE_INCLUDE_WEAVE_MOCK_MDNS_H_
diff --git a/libweave/include/weave/mock_network.h b/libweave/include/weave/mock_network.h
new file mode 100644
index 0000000..2d20395
--- /dev/null
+++ b/libweave/include/weave/mock_network.h
@@ -0,0 +1,54 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_INCLUDE_WEAVE_MOCK_NETWORK_H_
+#define LIBWEAVE_INCLUDE_WEAVE_MOCK_NETWORK_H_
+
+#include <weave/network.h>
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+namespace weave {
+namespace unittests {
+
+class MockNetwork : public Network {
+ public:
+  MockNetwork() {}
+  ~MockNetwork() override = default;
+
+  MOCK_METHOD1(AddOnConnectionChangedCallback,
+               void(const OnConnectionChangedCallback&));
+  MOCK_METHOD4(ConnectToService,
+               bool(const std::string&,
+                    const std::string&,
+                    const base::Closure&,
+                    ErrorPtr*));
+  MOCK_CONST_METHOD0(GetConnectionState, NetworkState());
+  MOCK_METHOD1(EnableAccessPoint, void(const std::string&));
+  MOCK_METHOD0(DisableAccessPoint, void());
+
+  MOCK_METHOD2(MockOpenSocketBlocking, Stream*(const std::string&, uint16_t));
+  MOCK_METHOD2(MockCreateTlsStream, Stream*(Stream*, const std::string&));
+
+  std::unique_ptr<Stream> OpenSocketBlocking(const std::string& host,
+                                             uint16_t port) override {
+    return std::unique_ptr<Stream>{MockOpenSocketBlocking(host, port)};
+  }
+
+  void CreateTlsStream(
+      std::unique_ptr<Stream> socket,
+      const std::string& host,
+      const base::Callback<void(std::unique_ptr<Stream>)>& success_callback,
+      const base::Callback<void(const Error*)>& error_callback) override {
+    success_callback.Run(
+        std::unique_ptr<Stream>{MockCreateTlsStream(socket.get(), host)});
+  }
+};
+
+}  // namespace unittests
+}  // namespace weave
+
+#endif  // LIBWEAVE_INCLUDE_WEAVE_MOCK_NETWORK_H_
diff --git a/libweave/include/weave/mock_task_runner.h b/libweave/include/weave/mock_task_runner.h
index d0d06f0..65d9b79 100644
--- a/libweave/include/weave/mock_task_runner.h
+++ b/libweave/include/weave/mock_task_runner.h
@@ -20,45 +20,22 @@
 
 class MockTaskRunner : public TaskRunner {
  public:
-  MockTaskRunner() {
-    test_clock_.SetNow(base::Time::Now());
-    using testing::_;
-    using testing::Invoke;
-    using testing::AnyNumber;
-    ON_CALL(*this, PostDelayedTask(_, _, _))
-        .WillByDefault(Invoke(this, &MockTaskRunner::SaveTask));
-    EXPECT_CALL(*this, PostDelayedTask(_, _, _)).Times(AnyNumber());
-  }
-  ~MockTaskRunner() override = default;
+  MockTaskRunner();
+  ~MockTaskRunner() override;
 
   MOCK_METHOD3(PostDelayedTask,
                void(const tracked_objects::Location&,
                     const base::Closure&,
                     base::TimeDelta));
 
-  bool RunOnce() {
-    if (queue_.empty())
-      return false;
-    auto top = queue_.top();
-    queue_.pop();
-    test_clock_.SetNow(std::max(test_clock_.Now(), top.first.first));
-    top.second.Run();
-    return true;
-  }
-
-  void Run() {
-    while (RunOnce()) {
-    }
-  }
-
-  base::Clock* GetClock() { return &test_clock_; }
+  bool RunOnce();
+  void Run();
+  base::Clock* GetClock();
 
  private:
   void SaveTask(const tracked_objects::Location& from_here,
                 const base::Closure& task,
-                base::TimeDelta delay) {
-    queue_.emplace(std::make_pair(test_clock_.Now() + delay, ++counter_), task);
-  }
+                base::TimeDelta delay);
 
   using QueueItem = std::pair<std::pair<base::Time, size_t>, base::Closure>;
 
@@ -70,16 +47,8 @@
 
   size_t counter_{0};  // Keeps order of tasks with the same time.
 
-  class TestClock : public base::Clock {
-   public:
-    base::Time Now() override { return now_; }
-
-    void SetNow(base::Time now) { now_ = now; }
-
-   private:
-    base::Time now_;
-  };
-  TestClock test_clock_;
+  class TestClock;
+  std::unique_ptr<TestClock> test_clock_;
 
   std::priority_queue<QueueItem,
                       std::vector<QueueItem>,
diff --git a/libweave/include/weave/task_runner.h b/libweave/include/weave/task_runner.h
index 66b83ab..845d9f7 100644
--- a/libweave/include/weave/task_runner.h
+++ b/libweave/include/weave/task_runner.h
@@ -11,6 +11,7 @@
 
 #include <base/callback.h>
 #include <base/location.h>
+#include <base/time/time.h>
 
 namespace weave {
 
diff --git a/libweave/include/weave/unittest_utils.h b/libweave/include/weave/unittest_utils.h
index 3cfaeea..fe06ab8 100644
--- a/libweave/include/weave/unittest_utils.h
+++ b/libweave/include/weave/unittest_utils.h
@@ -19,6 +19,8 @@
 // are replaced with apostrophes.
 std::unique_ptr<base::Value> CreateValue(const std::string& json);
 
+std::string ValueToString(const base::Value& value);
+
 // Helper method to create a JSON dictionary object from a string.
 std::unique_ptr<base::DictionaryValue> CreateDictionaryValue(
     const std::string& json);
diff --git a/libweave/libweave.gyp b/libweave/libweave.gyp
index e17ac6a..5288b56 100644
--- a/libweave/libweave.gyp
+++ b/libweave/libweave.gyp
@@ -147,6 +147,39 @@
             '<@(base_unittests)',
           ],
         },
+        {
+          'target_name': 'libweave_exports_testrunner',
+          'type': 'executable',
+          'variables': {
+            'deps': [
+              'libchrome-<(libbase_ver)',
+            ],
+          },
+          'dependencies': [
+            'libweave-<(libbase_ver)',
+            'libweave-test-<(libbase_ver)',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            '<@(weave_exports_unittest_sources)',
+          ],
+        },
+        {
+          'target_name': 'libweave_base_exports_testrunner',
+          'type': 'executable',
+          'cflags': ['-Wno-format-nonliteral'],
+          'include_dirs': [
+            '../libweave/external',
+          ],
+          'dependencies': [
+            'libweave_base',
+            'libweave_base-test',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            '<@(weave_exports_unittest_sources)',
+          ],
+        },
       ],
     }],
   ],
diff --git a/libweave/libweave.gypi b/libweave/libweave.gypi
index 5543575..c0e752f 100644
--- a/libweave/libweave.gypi
+++ b/libweave/libweave.gypi
@@ -56,6 +56,7 @@
       'src/commands/mock_command.cc',
       'src/commands/unittest_utils.cc',
       'src/mock_http_client.cc',
+      'src/mock_task_runner.cc',
     ],
     'weave_unittest_sources': [
       'external/crypto/p224_spake_unittest.cc',
@@ -89,6 +90,10 @@
       'src/string_utils_unittest.cc',
       'src/weave_testrunner.cc',
     ],
+    'weave_exports_unittest_sources': [
+      'src/weave_unittest.cc',
+      'src/weave_testrunner.cc',
+    ],
     'base_sources': [
       'external/base/bind_helpers.cc',
       'external/base/callback_internal.cc',
diff --git a/libweave/src/commands/command_manager_unittest.cc b/libweave/src/commands/command_manager_unittest.cc
index 932414c..00ab9c8 100644
--- a/libweave/src/commands/command_manager_unittest.cc
+++ b/libweave/src/commands/command_manager_unittest.cc
@@ -13,6 +13,8 @@
 #include "libweave/src/bind_lambda.h"
 #include "libweave/src/commands/unittest_utils.h"
 
+using testing::Return;
+
 namespace weave {
 
 using unittests::CreateDictionaryValue;
diff --git a/libweave/src/commands/unittest_utils.cc b/libweave/src/commands/unittest_utils.cc
index 12a1c02..3148ffe 100644
--- a/libweave/src/commands/unittest_utils.cc
+++ b/libweave/src/commands/unittest_utils.cc
@@ -5,6 +5,7 @@
 #include "libweave/src/commands/unittest_utils.h"
 
 #include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
 #include <base/logging.h>
 
 namespace weave {
@@ -24,6 +25,13 @@
   return value;
 }
 
+std::string ValueToString(const base::Value& value) {
+  std::string json;
+  base::JSONWriter::WriteWithOptions(
+      value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+  return json;
+}
+
 std::unique_ptr<base::DictionaryValue> CreateDictionaryValue(
     const std::string& json) {
   std::unique_ptr<base::Value> value = CreateValue(json);
diff --git a/libweave/src/config_unittest.cc b/libweave/src/config_unittest.cc
index 794f5cb..7746fbd 100644
--- a/libweave/src/config_unittest.cc
+++ b/libweave/src/config_unittest.cc
@@ -15,6 +15,7 @@
 
 using testing::_;
 using testing::Invoke;
+using testing::Return;
 
 namespace weave {
 
diff --git a/libweave/src/device_registration_info.cc b/libweave/src/device_registration_info.cc
index 62d1ac9..cd62cac 100644
--- a/libweave/src/device_registration_info.cc
+++ b/libweave/src/device_registration_info.cc
@@ -523,6 +523,12 @@
 void DeviceRegistrationInfo::GetDeviceInfo(
     const CloudRequestCallback& success_callback,
     const CloudRequestErrorCallback& error_callback) {
+  ErrorPtr error;
+  if (!VerifyRegistrationCredentials(&error)) {
+    if (!error_callback.is_null())
+      error_callback.Run(error.get());
+    return;
+  }
   DoCloudRequest(http::kGet, GetDeviceURL(), nullptr, success_callback,
                  error_callback);
 }
diff --git a/libweave/src/mock_task_runner.cc b/libweave/src/mock_task_runner.cc
new file mode 100644
index 0000000..5052b94
--- /dev/null
+++ b/libweave/src/mock_task_runner.cc
@@ -0,0 +1,58 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <weave/mock_task_runner.h>
+
+using testing::_;
+using testing::Invoke;
+using testing::AnyNumber;
+
+namespace weave {
+namespace unittests {
+
+class MockTaskRunner::TestClock : public base::Clock {
+ public:
+  base::Time Now() override { return now_; }
+
+  void SetNow(base::Time now) { now_ = now; }
+
+ private:
+  base::Time now_{base::Time::Now()};
+};
+
+MockTaskRunner::MockTaskRunner() : test_clock_{new TestClock} {
+  ON_CALL(*this, PostDelayedTask(_, _, _))
+      .WillByDefault(Invoke(this, &MockTaskRunner::SaveTask));
+  EXPECT_CALL(*this, PostDelayedTask(_, _, _)).Times(AnyNumber());
+}
+
+MockTaskRunner::~MockTaskRunner() {}
+
+bool MockTaskRunner::RunOnce() {
+  if (queue_.empty())
+    return false;
+  auto top = queue_.top();
+  queue_.pop();
+  test_clock_->SetNow(std::max(test_clock_->Now(), top.first.first));
+  top.second.Run();
+  return true;
+}
+
+void MockTaskRunner::Run() {
+  while (RunOnce()) {
+  }
+}
+
+base::Clock* MockTaskRunner::GetClock() {
+  return test_clock_.get();
+}
+
+void MockTaskRunner::SaveTask(const tracked_objects::Location& from_here,
+                              const base::Closure& task,
+                              base::TimeDelta delay) {
+  queue_.emplace(std::make_pair(test_clock_->Now() + delay, ++counter_), task);
+}
+
+}  // namespace unittests
+}  // namespace weave
diff --git a/libweave/src/privet/publisher.cc b/libweave/src/privet/publisher.cc
index 4edb7bc..85576cf 100644
--- a/libweave/src/privet/publisher.cc
+++ b/libweave/src/privet/publisher.cc
@@ -84,10 +84,14 @@
   if (!cloud_->GetDescription().empty())
     txt_record.emplace("note", cloud_->GetDescription());
 
+  is_publishing_ = true;
   mdns_->PublishService(kPrivetServiceType, port, txt_record);
 }
 
 void Publisher::RemoveService() {
+  if (!is_publishing_)
+    return;
+  is_publishing_ = false;
   VLOG(1) << "Stopping service publishing.";
   mdns_->StopPublishing(kPrivetServiceType);
 }
diff --git a/libweave/src/privet/publisher.cc.rej b/libweave/src/privet/publisher.cc.rej
new file mode 100644
index 0000000..6b84196
--- /dev/null
+++ b/libweave/src/privet/publisher.cc.rej
@@ -0,0 +1,16 @@
+diff a/libweave/src/privet/publisher.cc b/libweave/src/privet/publisher.cc	(rejected hunks)
+@@ -82,10 +82,14 @@ void Publisher::ExposeService() {
+   if (!cloud_->GetDescription().empty())
+     txt_record.emplace("note", cloud_->GetDescription());
+ 
++  is_publishing_ = true;
+   mdns_->PublishService(kPrivetServiceId, port, txt_record);
+ }
+ 
+ void Publisher::RemoveService() {
++  if (!is_publishing_)
++    return;
++  is_publishing_ = false;
+   VLOG(1) << "Stopping service publishing.";
+   mdns_->StopPublishing(kPrivetServiceId);
+ }
diff --git a/libweave/src/privet/publisher.h b/libweave/src/privet/publisher.h
index ddedd05..71d38a8 100644
--- a/libweave/src/privet/publisher.h
+++ b/libweave/src/privet/publisher.h
@@ -41,6 +41,7 @@
   void ExposeService();
   void RemoveService();
 
+  bool is_publishing_{false};
   Mdns* mdns_{nullptr};
 
   const DeviceDelegate* device_{nullptr};
diff --git a/libweave/src/weave_unittest.cc b/libweave/src/weave_unittest.cc
new file mode 100644
index 0000000..f0bbfa8
--- /dev/null
+++ b/libweave/src/weave_unittest.cc
@@ -0,0 +1,357 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <weave/device.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <weave/mock_config_store.h>
+#include <weave/mock_http_client.h>
+#include <weave/mock_http_server.h>
+#include <weave/mock_mdns.h>
+#include <weave/mock_network.h>
+#include <weave/mock_task_runner.h>
+#include <weave/unittest_utils.h>
+
+#include "libweave/src/bind_lambda.h"
+
+using testing::_;
+using testing::AtMost;
+using testing::HasSubstr;
+using testing::Invoke;
+using testing::InvokeWithoutArgs;
+using testing::MatchesRegex;
+using testing::Mock;
+using testing::AtLeast;
+using testing::Return;
+using testing::ReturnRefOfCopy;
+using testing::StartsWith;
+using testing::StrictMock;
+using testing::WithArgs;
+
+namespace weave {
+
+using unittests::CreateDictionaryValue;
+using unittests::ValueToString;
+
+const char kCategory[] = "powerd";
+
+const char kBaseCommandDefs[] = R"({
+  "base": {
+    "reboot": {
+      "parameters": {"delay": "integer"},
+      "results": {}
+    },
+    "shutdown": {
+      "parameters": {},
+      "results": {}
+    }
+  }
+})";
+
+const char kCommandDefs[] = R"({
+  "base": {
+    "reboot": {},
+    "shutdown": {}
+  }
+})";
+
+const char kBaseStateDefs[] = R"({
+  "base": {
+    "firmwareVersion": "string",
+    "localDiscoveryEnabled": "boolean",
+    "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+    "localPairingEnabled": "boolean",
+    "network": {
+      "properties": {
+        "name": "string"
+      }
+    }
+  }
+})";
+
+const char kBaseStateDefaults[] = R"({
+  "base": {
+    "firmwareVersion": "",
+    "localDiscoveryEnabled": false,
+    "localAnonymousAccessMaxRole": "none",
+    "localPairingEnabled": false
+  }
+})";
+
+const char kDeviceResource[] = R"({
+  "kind": "clouddevices#device",
+  "id": "DEVICE_ID",
+  "channel": {
+    "supportedType": "pull"
+  },
+  "deviceKind": "vendor",
+  "modelManifestId": "ABCDE",
+  "systemName": "",
+  "name": "DEVICE_NAME",
+  "displayName": "",
+  "description": "Developer device",
+  "stateValidationEnabled": true,
+  "commandDefs":{
+    "base": {
+      "reboot": {
+        "minimalRole": "user",
+        "parameters": {"delay": "integer"},
+        "results": {}
+      },
+      "shutdown": {
+        "minimalRole": "user",
+        "parameters": {},
+        "results": {}
+      }
+    }
+  },
+  "state":{
+    "base":{
+      "firmwareVersion":"FIRMWARE_VERSION",
+      "localAnonymousAccessMaxRole":"viewer",
+      "localDiscoveryEnabled":true,
+      "localPairingEnabled":true,
+      "network":{
+      }
+    },
+    "power": {"battery_level":44}
+  }
+})";
+
+const char kRegistrationResponse[] = R"({
+  "kind": "clouddevices#registrationTicket",
+  "id": "TEST_ID",
+  "deviceId": "DEVICE_ID",
+  "oauthClientId": "CLIENT_ID",
+  "userEmail": "USER@gmail.com",
+  "creationTimeMs": "1440087183738",
+  "expirationTimeMs": "1440087423738"
+})";
+
+const char kRegistrationFinalResponse[] = R"({
+  "kind": "clouddevices#registrationTicket",
+  "id": "TEST_ID",
+  "deviceId": "DEVICE_ID",
+  "oauthClientId": "CLIENT_ID",
+  "userEmail": "USER@gmail.com",
+  "robotAccountEmail": "ROBO@gmail.com",
+  "robotAccountAuthorizationCode": "AUTH_CODE",
+  "creationTimeMs": "1440087183738",
+  "expirationTimeMs": "1440087423738"
+})";
+
+const char kAuthTokenResponse[] = R"({
+  "access_token" : "ACCESS_TOKEN",
+  "token_type" : "Bearer",
+  "expires_in" : 3599,
+  "refresh_token" : "REFRESH_TOKEN"
+})";
+
+const char kStateDefs[] = R"({"power": {"battery_level":"integer"}})";
+
+const char kStateDefaults[] = R"({"power": {"battery_level":44}})";
+
+class WeaveTest : public ::testing::Test {
+ protected:
+  void SetUp() override { device_ = weave::Device::Create(); }
+
+  void ExpectRequest(const std::string& method,
+                     const std::string& url,
+                     const std::string& json_response) {
+    EXPECT_CALL(http_client_, MockSendRequest(method, url, _, _, _))
+        .WillOnce(InvokeWithoutArgs([json_response]() {
+          unittests::MockHttpClientResponse* response =
+              new StrictMock<unittests::MockHttpClientResponse>;
+          EXPECT_CALL(*response, GetStatusCode())
+              .Times(AtLeast(1))
+              .WillRepeatedly(Return(200));
+          EXPECT_CALL(*response, GetContentType())
+              .Times(AtLeast(1))
+              .WillRepeatedly(Return("application/json; charset=utf-8"));
+          EXPECT_CALL(*response, GetData())
+              .Times(AtLeast(1))
+              .WillRepeatedly(ReturnRefOfCopy(json_response));
+          return response;
+        }));
+  }
+
+  void InitConfigStore() {
+    EXPECT_CALL(config_store_, LoadDefaults(_))
+        .WillOnce(Invoke([](weave::Settings* settings) {
+          settings->api_key = "API_KEY";
+          settings->client_secret = "CLIENT_SECRET";
+          settings->client_id = "CLIENT_ID";
+          settings->firmware_version = "FIRMWARE_VERSION";
+          settings->name = "DEVICE_NAME";
+          settings->model_id = "ABCDE";
+          return true;
+        }));
+    EXPECT_CALL(config_store_, SaveSettings("")).WillRepeatedly(Return());
+
+    EXPECT_CALL(config_store_, LoadBaseCommandDefs())
+        .WillOnce(Return(kBaseCommandDefs));
+
+    EXPECT_CALL(config_store_, LoadCommandDefs())
+        .WillOnce(Return(
+            std::map<std::string, std::string>{{kCategory, kCommandDefs}}));
+
+    EXPECT_CALL(config_store_, LoadBaseStateDefs())
+        .WillOnce(Return(kBaseStateDefs));
+
+    EXPECT_CALL(config_store_, LoadStateDefs())
+        .WillOnce(Return(
+            std::map<std::string, std::string>{{kCategory, kStateDefs}}));
+
+    EXPECT_CALL(config_store_, LoadBaseStateDefaults())
+        .WillOnce(Return(kBaseStateDefaults));
+
+    EXPECT_CALL(config_store_, LoadStateDefaults())
+        .WillOnce(Return(std::vector<std::string>{kStateDefaults}));
+  }
+
+  void InitNetwork() {
+    EXPECT_CALL(network_, AddOnConnectionChangedCallback(_))
+        .WillRepeatedly(Return());
+    EXPECT_CALL(network_, GetConnectionState())
+        .WillRepeatedly(Return(NetworkState::kOffline));
+    EXPECT_CALL(network_, EnableAccessPoint(MatchesRegex("DEVICE_NAME.*prv")))
+        .WillOnce(Return());
+  }
+
+  void InitMdns() {
+    EXPECT_CALL(mdns_, GetId()).WillRepeatedly(Return("TEST_ID"));
+    InitMdnsPublishing(false);
+    EXPECT_CALL(mdns_, StopPublishing("privet")).WillOnce(Return());
+  }
+
+  void InitMdnsPublishing(bool registered) {
+    std::map<std::string, std::string> txt{
+        {"id", "TEST_ID"},     {"flags", "DB"},  {"mmid", "ABCDE"},
+        {"services", "_base"}, {"txtvers", "3"}, {"ty", "DEVICE_NAME"}};
+    if (registered) {
+      txt["gcd_id"] = "DEVICE_ID";
+
+      // During registration device may announce itself twice:
+      // 1. with GCD ID but not connected (DB)
+      // 2. with GCD ID and connected (BB)
+      EXPECT_CALL(mdns_, PublishService("privet", 11, txt))
+          .Times(AtMost(1))
+          .WillOnce(Return());
+
+      txt["flags"] = "BB";
+    }
+
+    EXPECT_CALL(mdns_, PublishService("privet", 11, txt)).WillOnce(Return());
+  }
+
+  void InitHttpServer() {
+    EXPECT_CALL(http_server_, GetHttpPort()).WillRepeatedly(Return(11));
+    EXPECT_CALL(http_server_, GetHttpsPort()).WillRepeatedly(Return(12));
+    EXPECT_CALL(http_server_, GetHttpsCertificateFingerprint())
+        .WillRepeatedly(ReturnRefOfCopy(std::vector<uint8_t>{1, 2, 3}));
+    EXPECT_CALL(http_server_, AddRequestHandler(_, _))
+        .WillRepeatedly(Invoke([this](const std::string& path_prefix,
+                                      const HttpServer::OnRequestCallback& cb) {
+          http_server_request_cb_.push_back(cb);
+        }));
+    EXPECT_CALL(http_server_, AddOnStateChangedCallback(_))
+        .WillRepeatedly(
+            Invoke([this](const HttpServer::OnStateChangedCallback& cb) {
+              http_server_changed_cb_.push_back(cb);
+            }));
+  }
+
+  void StartDevice() {
+    InitConfigStore();
+    InitNetwork();
+    InitHttpServer();
+    InitMdns();
+
+    weave::Device::Options options;
+    options.xmpp_enabled = false;
+
+    device_->Start(options, &config_store_, &task_runner_, &http_client_,
+                   &network_, &mdns_, &http_server_);
+
+    cloud_ = device_->GetCloud();
+    ASSERT_TRUE(cloud_);
+
+    cloud_->GetDeviceInfo(
+        base::Bind(
+            [](const base::DictionaryValue& response) { ADD_FAILURE(); }),
+        base::Bind([](const Error* error) {
+          EXPECT_TRUE(error->HasError("gcd", "device_not_registered"));
+        }));
+
+    for (const auto& cb : http_server_changed_cb_) {
+      cb.Run(http_server_);
+    }
+
+    task_runner_.Run();
+  }
+
+  std::vector<HttpServer::OnStateChangedCallback> http_server_changed_cb_;
+  std::vector<HttpServer::OnRequestCallback> http_server_request_cb_;
+
+  StrictMock<unittests::MockConfigStore> config_store_;
+  StrictMock<unittests::MockTaskRunner> task_runner_;
+  StrictMock<unittests::MockHttpClient> http_client_;
+  StrictMock<unittests::MockNetwork> network_;
+  StrictMock<unittests::MockMdns> mdns_;
+  StrictMock<unittests::MockHttpServer> http_server_;
+
+  weave::Cloud* cloud_{nullptr};
+
+  std::unique_ptr<weave::Device> device_;
+};
+
+TEST_F(WeaveTest, Create) {
+  ASSERT_TRUE(device_.get());
+}
+
+TEST_F(WeaveTest, StartMinimal) {
+  weave::Device::Options options;
+  options.xmpp_enabled = false;
+  options.disable_privet = true;
+  options.disable_security = true;
+
+  InitConfigStore();
+  device_->Start(options, &config_store_, &task_runner_, &http_client_,
+                 &network_, nullptr, nullptr);
+}
+
+TEST_F(WeaveTest, Start) {
+  StartDevice();
+}
+
+TEST_F(WeaveTest, Register) {
+  StartDevice();
+
+  auto draft = CreateDictionaryValue(kDeviceResource);
+  auto response = CreateDictionaryValue(kRegistrationResponse);
+  response->Set("deviceDraft", draft->DeepCopy());
+  ExpectRequest(
+      "PATCH",
+      "https://www.googleapis.com/clouddevices/v1/registrationTickets/"
+      "TEST_ID?key=API_KEY",
+      ValueToString(*response));
+
+  response = CreateDictionaryValue(kRegistrationFinalResponse);
+  response->Set("deviceDraft", draft->DeepCopy());
+  ExpectRequest(
+      "POST",
+      "https://www.googleapis.com/clouddevices/v1/registrationTickets/"
+      "TEST_ID/finalize?key=API_KEY",
+      ValueToString(*response));
+
+  ExpectRequest("POST", "https://accounts.google.com/o/oauth2/token",
+                kAuthTokenResponse);
+
+  InitMdnsPublishing(true);
+
+  EXPECT_EQ("DEVICE_ID", cloud_->RegisterDevice("TEST_ID", nullptr));
+}
+
+}  // namespace weave