| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/browser_sync/sync_internals_message_handler.h" |
| |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/gmock_move_support.h" |
| #include "base/test/task_environment.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/identity_test_environment.h" |
| #include "components/sync/model/type_entities_count.h" |
| #include "components/sync/service/sync_internals_util.h" |
| #include "components/sync/service/sync_service.h" |
| #include "components/sync/test/mock_sync_invalidations_service.h" |
| #include "components/sync/test/mock_sync_service.h" |
| #include "components/sync_user_events/fake_user_event_service.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using syncer::sync_ui_util::kGetAllNodes; |
| using syncer::sync_ui_util::kOnAboutInfoUpdated; |
| using syncer::sync_ui_util::kOnEntityCountsUpdated; |
| using syncer::sync_ui_util::kRequestDataAndRegisterForUpdates; |
| using syncer::sync_ui_util::kRequestStart; |
| using syncer::sync_ui_util::kWriteUserEvent; |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::Return; |
| |
| namespace browser_sync { |
| namespace { |
| |
| const char kChannel[] = "canary"; |
| |
| // Matches a given base::ValueView against `dict`. |
| MATCHER_P(ValueViewMatchesDict, dict, "") { |
| base::Value value = arg.ToValue(); |
| return value.is_dict() && value.GetDict() == dict; |
| } |
| |
| class MockDelegate : public SyncInternalsMessageHandler::Delegate { |
| public: |
| MockDelegate() = default; |
| ~MockDelegate() override = default; |
| |
| MOCK_METHOD(void, |
| SendEventToPage, |
| (std::string_view, base::span<const base::ValueView>), |
| (override)); |
| MOCK_METHOD(void, |
| ResolvePageCallback, |
| (const base::ValueView, const base::ValueView), |
| (override)); |
| }; |
| |
| class SyncInternalsMessageHandlerTest : public testing::Test { |
| public: |
| SyncInternalsMessageHandlerTest() { |
| ON_CALL(mock_sync_service_, GetEntityCountsForDebugging) |
| .WillByDefault(base::test::RunCallback<0>( |
| syncer::TypeEntitiesCount(syncer::PASSWORDS))); |
| } |
| |
| SyncInternalsMessageHandlerTest(const SyncInternalsMessageHandlerTest&) = |
| delete; |
| SyncInternalsMessageHandlerTest& operator=( |
| const SyncInternalsMessageHandlerTest&) = delete; |
| |
| ~SyncInternalsMessageHandlerTest() override = default; |
| |
| MockDelegate* mock_delegate() { return &mock_delegate_; } |
| |
| signin::IdentityTestEnvironment* identity_test_environment() { |
| return &identity_test_environment_; |
| } |
| |
| syncer::MockSyncService* mock_sync_service() { return &mock_sync_service_; } |
| |
| syncer::MockSyncInvalidationsService* mock_sync_invalidations_service() { |
| return &mock_sync_invalidations_service_; |
| } |
| |
| syncer::FakeUserEventService* fake_user_event_service() { |
| return &fake_user_event_service_; |
| } |
| |
| SyncInternalsMessageHandler* handler() { return handler_.get(); } |
| |
| int get_about_sync_data_call_count() const { |
| return get_about_sync_data_dall_count_; |
| } |
| |
| void ResetHandler() { handler_.reset(); } |
| |
| // Fake return value for sync_ui_util::ConstructAboutInformation(). |
| const base::Value::Dict kAboutInformation = |
| base::Value::Dict().Set("some_sync_state", "some_value"); |
| |
| private: |
| // Returns copies of the same constant dictionary, |kAboutInformation|. |
| base::Value::Dict ConstructFakeAboutInformation(syncer::SyncService* service, |
| const std::string& channel) { |
| ++get_about_sync_data_dall_count_; |
| return kAboutInformation.Clone(); |
| } |
| |
| // SingleThreadTaskEnvironment is needed for IdentityTestEnvironment. |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| MockDelegate mock_delegate_; |
| signin::IdentityTestEnvironment identity_test_environment_; |
| syncer::MockSyncService mock_sync_service_; |
| syncer::MockSyncInvalidationsService mock_sync_invalidations_service_; |
| syncer::FakeUserEventService fake_user_event_service_; |
| std::unique_ptr<SyncInternalsMessageHandler> handler_ = |
| std::make_unique<SyncInternalsMessageHandler>( |
| &mock_delegate_, |
| base::BindRepeating( |
| &SyncInternalsMessageHandlerTest::ConstructFakeAboutInformation, |
| base::Unretained(this)), |
| identity_test_environment_.identity_manager(), |
| &mock_sync_service_, |
| &mock_sync_invalidations_service_, |
| &fake_user_event_service_, |
| kChannel); |
| int get_about_sync_data_dall_count_ = 0; |
| }; |
| |
| TEST_F(SyncInternalsMessageHandlerTest, AddRemoveObservers) { |
| EXPECT_CALL(*mock_sync_service(), AddObserver); |
| EXPECT_CALL(*mock_sync_service(), RemoveObserver).Times(0); |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kRequestDataAndRegisterForUpdates) |
| .Run(base::Value::List()); |
| testing::Mock::VerifyAndClearExpectations(mock_sync_service()); |
| |
| EXPECT_CALL(*mock_sync_service(), AddObserver).Times(0); |
| EXPECT_CALL(*mock_sync_service(), RemoveObserver); |
| ResetHandler(); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, AddRemoveObserversDisableMessages) { |
| EXPECT_CALL(*mock_sync_service(), AddObserver); |
| EXPECT_CALL(*mock_sync_service(), RemoveObserver).Times(0); |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kRequestDataAndRegisterForUpdates) |
| .Run(base::Value::List()); |
| testing::Mock::VerifyAndClearExpectations(mock_sync_service()); |
| |
| EXPECT_CALL(*mock_sync_service(), AddObserver).Times(0); |
| EXPECT_CALL(*mock_sync_service(), RemoveObserver); |
| handler()->DisableMessagesToPage(); |
| testing::Mock::VerifyAndClearExpectations(mock_sync_service()); |
| |
| // Deregistration should not repeat, no counts should increase. |
| EXPECT_CALL(*mock_sync_service(), AddObserver).Times(0); |
| EXPECT_CALL(*mock_sync_service(), RemoveObserver).Times(0); |
| ResetHandler(); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, AddRemoveObserversSyncDisabled) { |
| // Simulate completely disabling sync by flag or other mechanism. |
| auto handler = std::make_unique<SyncInternalsMessageHandler>( |
| mock_delegate(), |
| base::BindLambdaForTesting([&](syncer::SyncService*, const std::string&) { |
| return kAboutInformation.Clone(); |
| }), |
| identity_test_environment()->identity_manager(), |
| /*sync_service=*/nullptr, mock_sync_invalidations_service(), |
| fake_user_event_service(), kChannel); |
| handler->GetMessageHandlerMap() |
| .at(kRequestDataAndRegisterForUpdates) |
| .Run(base::Value::List()); |
| handler->DisableMessagesToPage(); |
| // Cannot verify observer methods on sync services were not called, because |
| // there is no sync service. Rather, we're just making sure the handler hasn't |
| // performed any invalid operations when the sync service is missing. |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, HandleGetAllNodes) { |
| base::OnceCallback<void(base::Value::List)> get_all_nodes_callback; |
| ON_CALL(*mock_sync_service(), GetAllNodesForDebugging) |
| .WillByDefault(MoveArg<0>(&get_all_nodes_callback)); |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kGetAllNodes) |
| .Run(base::Value::List().Append("getAllNodes_0")); |
| EXPECT_CALL(*mock_delegate(), ResolvePageCallback); |
| std::move(get_all_nodes_callback).Run(base::Value::List()); |
| testing::Mock::VerifyAndClearExpectations(mock_delegate()); |
| |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kGetAllNodes) |
| .Run(base::Value::List().Append("getAllNodes_1")); |
| // This breaks the weak ref the callback is hanging onto. Which results in |
| // the call count not incrementing. |
| handler()->DisableMessagesToPage(); |
| EXPECT_CALL(*mock_delegate(), ResolvePageCallback).Times(0); |
| std::move(get_all_nodes_callback).Run(base::Value::List()); |
| testing::Mock::VerifyAndClearExpectations(mock_delegate()); |
| |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kGetAllNodes) |
| .Run(base::Value::List().Append("getAllNodes_2")); |
| EXPECT_CALL(*mock_delegate(), ResolvePageCallback); |
| std::move(get_all_nodes_callback).Run(base::Value::List()); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, SendAboutInfo) { |
| EXPECT_CALL( |
| *mock_delegate(), |
| SendEventToPage(kOnAboutInfoUpdated, ElementsAre(ValueViewMatchesDict( |
| std::cref(kAboutInformation))))); |
| EXPECT_CALL(*mock_delegate(), SendEventToPage(kOnEntityCountsUpdated, _)); |
| static_cast<syncer::SyncServiceObserver*>(handler())->OnStateChanged( |
| mock_sync_service()); |
| EXPECT_EQ(1, get_about_sync_data_call_count()); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, WriteUserEvent) { |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kWriteUserEvent) |
| .Run(base::Value::List().Append("1000000000000000000").Append("-1")); |
| |
| ASSERT_EQ(1u, fake_user_event_service()->GetRecordedUserEvents().size()); |
| const sync_pb::UserEventSpecifics& event = |
| *fake_user_event_service()->GetRecordedUserEvents().begin(); |
| EXPECT_EQ(sync_pb::UserEventSpecifics::kTestEvent, event.event_case()); |
| EXPECT_EQ(1000000000000000000, event.event_time_usec()); |
| EXPECT_EQ(-1, event.navigation_id()); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, WriteUserEventBadParse) { |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kWriteUserEvent) |
| .Run(base::Value::List().Append("123abc").Append("abcde")); |
| |
| ASSERT_EQ(1u, fake_user_event_service()->GetRecordedUserEvents().size()); |
| const sync_pb::UserEventSpecifics& event = |
| *fake_user_event_service()->GetRecordedUserEvents().begin(); |
| EXPECT_EQ(sync_pb::UserEventSpecifics::kTestEvent, event.event_case()); |
| EXPECT_EQ(0, event.event_time_usec()); |
| EXPECT_EQ(0, event.navigation_id()); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, WriteUserEventBlank) { |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kWriteUserEvent) |
| .Run(base::Value::List().Append("").Append("")); |
| |
| ASSERT_EQ(1u, fake_user_event_service()->GetRecordedUserEvents().size()); |
| const sync_pb::UserEventSpecifics& event = |
| *fake_user_event_service()->GetRecordedUserEvents().begin(); |
| EXPECT_EQ(sync_pb::UserEventSpecifics::kTestEvent, event.event_case()); |
| EXPECT_TRUE(event.has_event_time_usec()); |
| EXPECT_EQ(0, event.event_time_usec()); |
| // Should not have a navigation_id because that means something different to |
| // the UserEvents logic. |
| EXPECT_FALSE(event.has_navigation_id()); |
| } |
| |
| TEST_F(SyncInternalsMessageHandlerTest, WriteUserEventZero) { |
| handler() |
| ->GetMessageHandlerMap() |
| .at(kWriteUserEvent) |
| .Run(base::Value::List().Append("0").Append("0")); |
| |
| ASSERT_EQ(1u, fake_user_event_service()->GetRecordedUserEvents().size()); |
| const sync_pb::UserEventSpecifics& event = |
| *fake_user_event_service()->GetRecordedUserEvents().begin(); |
| EXPECT_EQ(sync_pb::UserEventSpecifics::kTestEvent, event.event_case()); |
| EXPECT_TRUE(event.has_event_time_usec()); |
| EXPECT_EQ(0, event.event_time_usec()); |
| // Should have a navigation_id, even though the value is 0. |
| EXPECT_TRUE(event.has_navigation_id()); |
| EXPECT_EQ(0, event.navigation_id()); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| TEST_F(SyncInternalsMessageHandlerTest, RequestStart) { |
| identity_test_environment()->MakePrimaryAccountAvailable( |
| "foo@gmail.com", signin::ConsentLevel::kSignin); |
| EXPECT_CALL(*mock_sync_service()->GetMockUserSettings(), |
| SetInitialSyncFeatureSetupComplete); |
| |
| handler()->GetMessageHandlerMap().at(kRequestStart).Run(base::Value::List()); |
| |
| CoreAccountInfo account_info = |
| identity_test_environment()->identity_manager()->GetPrimaryAccountInfo( |
| signin::ConsentLevel::kSync); |
| EXPECT_FALSE(account_info.IsEmpty()); |
| EXPECT_EQ(account_info.email, "foo@gmail.com"); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| } // namespace |
| } // namespace browser_sync |