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