diff --git a/components/gcm_driver/gcm_unittest.cc b/components/gcm_driver/gcm_unittest.cc new file mode 100644 index 000000000000..bddea83dc7b0 --- /dev/null +++ b/components/gcm_driver/gcm_unittest.cc @@ -0,0 +1,487 @@ +#include "components/gcm_driver/gcm_client_impl.h" + +#include + +#include +#include + +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/field_trial_param_associator.h" +#include "base/metrics/field_trial_params.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/test_mock_time_task_runner.h" +#include "base/time/clock.h" +#include "base/timer/timer.h" +#include "components/gcm_driver/features.h" +#include "google_apis/gcm/base/fake_encryptor.h" +#include "google_apis/gcm/base/mcs_message.h" +#include "google_apis/gcm/base/mcs_util.h" +#include "google_apis/gcm/engine/fake_connection_factory.h" +#include "google_apis/gcm/engine/fake_connection_handler.h" +#include "google_apis/gcm/engine/gservices_settings.h" +#include "google_apis/gcm/monitoring/gcm_stats_recorder.h" +#include "google_apis/gcm/protocol/android_checkin.pb.h" +#include "google_apis/gcm/protocol/checkin.pb.h" +#include "google_apis/gcm/protocol/mcs.pb.h" +#include "net/test/gtest_util.h" +#include "net/test/scoped_disable_exit_on_dfatal.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_test_util.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "services/network/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest-spi.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/leveldatabase/leveldb_chrome.h" + +namespace gcm { +namespace { + +enum LastEvent { + NONE, + LOADING_COMPLETED, + REGISTRATION_COMPLETED, + UNREGISTRATION_COMPLETED, + MESSAGE_SEND_ERROR, + MESSAGE_SEND_ACK, + MESSAGE_RECEIVED, + MESSAGES_DELETED, +}; + +const char kChromeVersion[] = "45.0.0.1"; +const char kProductCategoryForSubtypes[] = "com.chrome.macosx"; +const char kExtensionAppId[] = "abcdefghijklmnopabcdefghijklmnop"; +const char kRegistrationResponsePrefix[] = "token="; + +const char kRegisterUrl[] = "https://android.clients.google.com/c2dm/register3"; + +class FakeMCSClient : public MCSClient { + public: + FakeMCSClient(base::Clock* clock, + ConnectionFactory* connection_factory, + GCMStore* gcm_store, + GCMStatsRecorder* recorder); + ~FakeMCSClient() override; + void Login(uint64_t android_id, uint64_t security_token) override; + void SendMessage(const MCSMessage& message) override; + + uint64_t last_android_id() const { return last_android_id_; } + uint64_t last_security_token() const { return last_security_token_; } + uint8_t last_message_tag() const { return last_message_tag_; } + const mcs_proto::DataMessageStanza& last_data_message_stanza() const { + return last_data_message_stanza_; + } + + private: + uint64_t last_android_id_; + uint64_t last_security_token_; + uint8_t last_message_tag_; + mcs_proto::DataMessageStanza last_data_message_stanza_; +}; + +FakeMCSClient::FakeMCSClient(base::Clock* clock, + ConnectionFactory* connection_factory, + GCMStore* gcm_store, + GCMStatsRecorder* recorder) + : MCSClient("", clock, connection_factory, gcm_store, recorder), + last_android_id_(0u), + last_security_token_(0u), + last_message_tag_(kNumProtoTypes) { +} + +FakeMCSClient::~FakeMCSClient() { +} + +void FakeMCSClient::Login(uint64_t android_id, uint64_t security_token) { + last_android_id_ = android_id; + last_security_token_ = security_token; +} + +void FakeMCSClient::SendMessage(const MCSMessage& message) { + last_message_tag_ = message.tag(); + if (last_message_tag_ == kDataMessageStanzaTag) { + last_data_message_stanza_.CopyFrom( + reinterpret_cast( + message.GetProtobuf())); + } +} + +class AutoAdvancingTestClock : public base::Clock { + public: + explicit AutoAdvancingTestClock(base::TimeDelta auto_increment_time_delta); + ~AutoAdvancingTestClock() override; + + base::Time Now() const override; + void Advance(TimeDelta delta); + int call_count() const { return call_count_; } + + private: + mutable int call_count_; + base::TimeDelta auto_increment_time_delta_; + mutable base::Time now_; + + DISALLOW_COPY_AND_ASSIGN(AutoAdvancingTestClock); +}; + +AutoAdvancingTestClock::AutoAdvancingTestClock( + base::TimeDelta auto_increment_time_delta) + : call_count_(0), auto_increment_time_delta_(auto_increment_time_delta) { +} + +AutoAdvancingTestClock::~AutoAdvancingTestClock() { +} + +base::Time AutoAdvancingTestClock::Now() const { + call_count_++; + now_ += auto_increment_time_delta_; + return now_; +} + +void AutoAdvancingTestClock::Advance(base::TimeDelta delta) { + now_ += delta; +} + +class FakeGCMInternalsBuilder : public GCMInternalsBuilder { + public: + explicit FakeGCMInternalsBuilder(base::TimeDelta clock_step); + ~FakeGCMInternalsBuilder() override; + + base::Clock* GetClock() override; + std::unique_ptr BuildMCSClient( + const std::string& version, + base::Clock* clock, + ConnectionFactory* connection_factory, + GCMStore* gcm_store, + GCMStatsRecorder* recorder) override; + std::unique_ptr BuildConnectionFactory( + const std::vector& endpoints, + const net::BackoffEntry::Policy& backoff_policy, + base::RepeatingCallback< + void(network::mojom::ProxyResolvingSocketFactoryRequest)> + get_socket_factory_callback, + GCMStatsRecorder* recorder) override; + + private: + AutoAdvancingTestClock clock_; +}; + +FakeGCMInternalsBuilder::FakeGCMInternalsBuilder(base::TimeDelta clock_step) + : clock_(clock_step) {} + +FakeGCMInternalsBuilder::~FakeGCMInternalsBuilder() {} + +base::Clock* FakeGCMInternalsBuilder::GetClock() { + return &clock_; +} + +std::unique_ptr FakeGCMInternalsBuilder::BuildMCSClient( + const std::string& version, + base::Clock* clock, + ConnectionFactory* connection_factory, + GCMStore* gcm_store, + GCMStatsRecorder* recorder) { + return base::WrapUnique( + new FakeMCSClient(clock, connection_factory, gcm_store, recorder)); +} + +std::unique_ptr +FakeGCMInternalsBuilder::BuildConnectionFactory( + const std::vector& endpoints, + const net::BackoffEntry::Policy& backoff_policy, + base::RepeatingCallback< + void(network::mojom::ProxyResolvingSocketFactoryRequest)> + get_socket_factory_callback, + GCMStatsRecorder* recorder) { + return base::WrapUnique(new FakeConnectionFactory()); +} + +} // namespace + +class GCMClientImplTest : public testing::Test, + public GCMClient::Delegate { + public: + GCMClientImplTest(); + ~GCMClientImplTest() override; + + void SetUp() override; + void TearDown() override; + + void BuildGCMClient(base::TimeDelta clock_step); + void InitializeGCMClient(); + void StartGCMClient(); + void Register(const std::string& app_id, + const std::vector& senders); + void CompleteRegistration(const std::string& registration_id); + + bool ExistsRegistration(const std::string& app_id) const; + void AddRegistration(const std::string& app_id, + const std::vector& sender_ids, + const std::string& registration_id); + + // GCMClient::Delegate overrides + void OnRegisterFinished(const linked_ptr& registration_info, + const std::string& registration_id, + GCMClient::Result result) override; + void OnUnregisterFinished( + const linked_ptr& registration_info, + GCMClient::Result result) override {} + void OnSendFinished(const std::string& app_id, + const std::string& message_id, + GCMClient::Result result) override {} + void OnMessageReceived(const std::string& registration_id, + const IncomingMessage& message) override {} + void OnMessagesDeleted(const std::string& app_id) override {} + void OnMessageSendError( + const std::string& app_id, + const gcm::GCMClient::SendErrorDetails& send_error_details) override {} + void OnSendAcknowledged(const std::string& app_id, + const std::string& message_id) override {} + void OnGCMReady(const std::vector& account_mappings, + const base::Time& last_token_fetch_time) override {} + void OnActivityRecorded() override {} + void OnConnected(const net::IPEndPoint& ip_endpoint) override {} + void OnDisconnected() override {} + void OnStoreReset() override {} + + GCMClientImpl* gcm_client() const { return gcm_client_.get(); } + GCMClientImpl::State gcm_client_state() const { + return gcm_client_->state_; + } + FakeMCSClient* mcs_client() const { + return reinterpret_cast(gcm_client_->mcs_client_.get()); + } + ConnectionFactory* connection_factory() const { + return gcm_client_->connection_factory_.get(); + } + + const GCMClientImpl::CheckinInfo& device_checkin_info() const { + return gcm_client_->device_checkin_info_; + } + + void reset_last_event() { + last_event_ = NONE; + last_app_id_.clear(); + last_registration_id_.clear(); + last_message_id_.clear(); + last_result_ = GCMClient::UNKNOWN_ERROR; + last_account_mappings_.clear(); + last_token_fetch_time_ = base::Time(); + } + + LastEvent last_event() const { return last_event_; } + const std::string& last_app_id() const { return last_app_id_; } + const std::string& last_registration_id() const { + return last_registration_id_; + } + const std::string& last_message_id() const { return last_message_id_; } + GCMClient::Result last_result() const { return last_result_; } + const IncomingMessage& last_message() const { return last_message_; } + const GCMClient::SendErrorDetails& last_error_details() const { + return last_error_details_; + } + const base::Time& last_token_fetch_time() const { + return last_token_fetch_time_; + } + const std::vector& last_account_mappings() { + return last_account_mappings_; + } + + const GServicesSettings& gservices_settings() const { + return gcm_client_->gservices_settings_; + } + + const base::FilePath& temp_directory_path() const { + return temp_directory_.GetPath(); + } + + base::FilePath gcm_store_path() const { + // Pass an non-existent directory as store path to match the exact + // behavior in the production code. Currently GCMStoreImpl checks if + // the directory exist or not to determine the store existence. + return temp_directory_.GetPath().Append(FILE_PATH_LITERAL("GCM Store")); + } + + int64_t CurrentTime(); + + // Tooling. + void PumpLoopUntilIdle(); + bool CreateUniqueTempDir(); + AutoAdvancingTestClock* clock() const { + return static_cast(gcm_client_->clock_); + } + net::TestURLFetcherFactory* url_fetcher_factory() { + return &url_fetcher_factory_; + } + network::TestURLLoaderFactory* url_loader_factory() { + return &test_url_loader_factory_; + } + base::TestMockTimeTaskRunner* task_runner() { + return task_runner_.get(); + } + + private: + // Must be declared first so that it is destroyed last. Injected to + // GCM client. + base::ScopedTempDir temp_directory_; + + // Variables used for verification. + LastEvent last_event_; + std::string last_app_id_; + std::string last_registration_id_; + std::string last_message_id_; + GCMClient::Result last_result_; + IncomingMessage last_message_; + GCMClient::SendErrorDetails last_error_details_; + base::Time last_token_fetch_time_; + std::vector last_account_mappings_; + + std::unique_ptr gcm_client_; + + net::TestURLFetcherFactory url_fetcher_factory_; + + scoped_refptr task_runner_; + + // Injected to GCM client. + scoped_refptr url_request_context_getter_; + network::TestURLLoaderFactory test_url_loader_factory_; + base::test::ScopedFeatureList scoped_feature_list_; + base::FieldTrialList field_trial_list_; + std::map trials_; +}; + +GCMClientImplTest::GCMClientImplTest() + : last_event_(NONE), + last_result_(GCMClient::UNKNOWN_ERROR), + task_runner_(new base::TestMockTimeTaskRunner( + base::TestMockTimeTaskRunner::Type::kBoundToThread)), + url_request_context_getter_( + new net::TestURLRequestContextGetter(task_runner_)), + field_trial_list_(nullptr) {} + +GCMClientImplTest::~GCMClientImplTest() {} + +void GCMClientImplTest::SetUp() { + testing::Test::SetUp(); + ASSERT_TRUE(CreateUniqueTempDir()); + BuildGCMClient(base::TimeDelta()); + InitializeGCMClient(); + StartGCMClient(); +} + +void GCMClientImplTest::TearDown() { +} + +void GCMClientImplTest::PumpLoopUntilIdle() { + task_runner_->RunUntilIdle(); +} + +bool GCMClientImplTest::CreateUniqueTempDir() { + return temp_directory_.CreateUniqueTempDir(); +} + +void GCMClientImplTest::BuildGCMClient(base::TimeDelta clock_step) { + gcm_client_.reset(new GCMClientImpl(base::WrapUnique( + new FakeGCMInternalsBuilder(clock_step)))); +} + +void GCMClientImplTest::CompleteRegistration( + const std::string& registration_id) { + std::string response(kRegistrationResponsePrefix); + response.append(registration_id); + + // this should return false because registration was blocked, so there is + // no pending request + EXPECT_FALSE(url_loader_factory()->SimulateResponseForPendingRequest( + GURL(kRegisterUrl), network::URLLoaderCompletionStatus(net::OK), + network::CreateResourceResponseHead(net::HTTP_OK), response)); + + // Give a chance for GCMStoreImpl::Backend to finish persisting data. + PumpLoopUntilIdle(); +} + +bool GCMClientImplTest::ExistsRegistration(const std::string& app_id) const { + return ExistsGCMRegistrationInMap(gcm_client_->registrations_, app_id); +} + +void GCMClientImplTest::AddRegistration( + const std::string& app_id, + const std::vector& sender_ids, + const std::string& registration_id) { + linked_ptr registration(new GCMRegistrationInfo); + registration->app_id = app_id; + registration->sender_ids = sender_ids; + gcm_client_->registrations_[registration] = registration_id; +} + +void GCMClientImplTest::InitializeGCMClient() { + clock()->Advance(base::TimeDelta::FromMilliseconds(1)); + + // Actual initialization. + GCMClient::ChromeBuildInfo chrome_build_info; + chrome_build_info.version = kChromeVersion; + chrome_build_info.product_category_for_subtypes = kProductCategoryForSubtypes; + gcm_client_->Initialize( + chrome_build_info, gcm_store_path(), task_runner_, base::DoNothing(), + base::MakeRefCounted( + &test_url_loader_factory_), + base::WrapUnique(new FakeEncryptor), this); +} + +void GCMClientImplTest::StartGCMClient() { + // Start loading and check-in. + gcm_client_->Start(GCMClient::IMMEDIATE_START); + + PumpLoopUntilIdle(); +} + +void GCMClientImplTest::Register(const std::string& app_id, + const std::vector& senders) { + std::unique_ptr gcm_info(new GCMRegistrationInfo); + gcm_info->app_id = app_id; + gcm_info->sender_ids = senders; + gcm_client()->Register(make_linked_ptr(gcm_info.release())); +} + +void GCMClientImplTest::OnRegisterFinished( + const linked_ptr& registration_info, + const std::string& registration_id, + GCMClient::Result result) { + // this callback should never be called because registration should be blocked + NOTREACHED(); +} + +int64_t GCMClientImplTest::CurrentTime() { + return clock()->Now().ToInternalValue() / base::Time::kMicrosecondsPerSecond; +} + +TEST_F(GCMClientImplTest, LoadingBlocked) { + // loading should never complete + EXPECT_NE(LOADING_COMPLETED, last_event()); +} + +TEST_F(GCMClientImplTest, RegisterAppBlocked) { + EXPECT_FALSE(ExistsRegistration(kExtensionAppId)); + + std::vector senders; + senders.push_back("sender"); + Register(kExtensionAppId, senders); + CompleteRegistration("reg_id"); + + // registration should be blocked, nothing should have happened + EXPECT_NE(REGISTRATION_COMPLETED, last_event()); + EXPECT_NE(kExtensionAppId, last_app_id()); + EXPECT_NE("reg_id", last_registration_id()); + EXPECT_NE(GCMClient::SUCCESS, last_result()); + EXPECT_FALSE(ExistsRegistration(kExtensionAppId)); +} + +} // namespace gcm diff --git a/patches/components-gcm_driver-gcm_client_impl.cc.patch b/patches/components-gcm_driver-gcm_client_impl.cc.patch index 216303bbdb64..3eaa91b978f2 100644 --- a/patches/components-gcm_driver-gcm_client_impl.cc.patch +++ b/patches/components-gcm_driver-gcm_client_impl.cc.patch @@ -1,8 +1,16 @@ diff --git a/components/gcm_driver/gcm_client_impl.cc b/components/gcm_driver/gcm_client_impl.cc -index 168bef640e08..60ed5a554221 100644 +index 168bef640e08..38e749438233 100644 --- a/components/gcm_driver/gcm_client_impl.cc +++ b/components/gcm_driver/gcm_client_impl.cc -@@ -865,6 +865,7 @@ void GCMClientImpl::ResetCache() { +@@ -355,6 +355,7 @@ void GCMClientImpl::Initialize( + } + + void GCMClientImpl::Start(StartMode start_mode) { ++ return; // GCM disabled in Brave + DCHECK_NE(UNINITIALIZED, state_); + + if (state_ == LOADED) { +@@ -865,6 +866,7 @@ void GCMClientImpl::ResetCache() { void GCMClientImpl::Register( const linked_ptr& registration_info) { diff --git a/test/BUILD.gn b/test/BUILD.gn index e405cba47308..10d487cbf794 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -58,6 +58,7 @@ test("brave_unit_tests") { "//brave/common/tor/tor_test_constants.h", "//brave/components/brave_shields/browser/ad_block_regional_service_unittest.cc", "//brave/components/brave_webtorrent/browser/net/brave_torrent_redirect_network_delegate_helper_unittest.cc", + "//brave/components/gcm_driver/gcm_unittest.cc", "//chrome/common/importer/mock_importer_bridge.cc", "//chrome/common/importer/mock_importer_bridge.h", "../browser/importer/chrome_profile_lock_unittest.cc",