| // Copyright 2017 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/sync_bookmarks/bookmark_data_type_processor.h" |
| |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_move_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "base/uuid.h" |
| #include "components/bookmarks/test/test_bookmark_client.h" |
| #include "components/bookmarks/test/test_matchers.h" |
| #include "components/favicon/core/test/mock_favicon_service.h" |
| #include "components/sync/base/client_tag_hash.h" |
| #include "components/sync/base/features.h" |
| #include "components/sync/base/unique_position.h" |
| #include "components/sync/engine/data_type_activation_response.h" |
| #include "components/sync/model/data_type_activation_request.h" |
| #include "components/sync/model/type_entities_count.h" |
| #include "components/sync/protocol/bookmark_model_metadata.pb.h" |
| #include "components/sync/protocol/bookmark_specifics.pb.h" |
| #include "components/sync/protocol/data_type_state.pb.h" |
| #include "components/sync/test/mock_commit_queue.h" |
| #include "components/sync_bookmarks/bookmark_model_view.h" |
| #include "components/sync_bookmarks/switches.h" |
| #include "components/sync_bookmarks/synced_bookmark_tracker_entity.h" |
| #include "components/sync_bookmarks/test_bookmark_model_view.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/models/tree_node_iterator.h" |
| |
| namespace sync_bookmarks { |
| |
| namespace { |
| |
| using base::ASCIIToUTF16; |
| using bookmarks::test::IsUrlBookmark; |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::Eq; |
| using testing::IsEmpty; |
| using testing::IsNull; |
| using testing::NiceMock; |
| using testing::NotNull; |
| using testing::Pointer; |
| using testing::Property; |
| using testing::SizeIs; |
| using testing::UnorderedElementsAre; |
| |
| const char kBookmarkBarTag[] = "bookmark_bar"; |
| const char kOtherBookmarksTag[] = "other_bookmarks"; |
| const char kMobileBookmarksTag[] = "synced_bookmarks"; |
| const char kBookmarkBarId[] = "bookmark_bar_id"; |
| const char kOtherBookmarksId[] = "other_bookmarks_id"; |
| const char kMobileBookmarksId[] = "mobile_bookmarks_id"; |
| const char kBookmarksRootId[] = "root_id"; |
| const char kCacheGuid[] = "generated_id"; |
| const char kPersistentDataTypeConfigurationTimeMetricName[] = |
| "Sync.DataTypeConfigurationTime.Persistent.BOOKMARK"; |
| |
| struct BookmarkInfo { |
| std::string server_id; |
| std::string title; |
| std::string url; // empty for folders. |
| std::string parent_id; |
| std::string server_tag; |
| }; |
| |
| MATCHER_P(CommitRequestDataMatchesGuid, uuid, "") { |
| const syncer::CommitRequestData* data = arg.get(); |
| return data != nullptr && data->entity != nullptr && |
| data->entity->specifics.bookmark().guid() == uuid.AsLowercaseString(); |
| } |
| |
| MATCHER_P(TrackedEntityCorrespondsToBookmarkNode, bookmark_node, "") { |
| const SyncedBookmarkTrackerEntity* entity = arg; |
| return entity->bookmark_node() == bookmark_node; |
| } |
| |
| syncer::UpdateResponseData CreateUpdateResponseData( |
| const BookmarkInfo& bookmark_info, |
| const syncer::UniquePosition& unique_position, |
| int response_version, |
| const base::Uuid& uuid) { |
| syncer::EntityData data; |
| data.id = bookmark_info.server_id; |
| data.legacy_parent_id = bookmark_info.parent_id; |
| data.server_defined_unique_tag = bookmark_info.server_tag; |
| data.originator_client_item_id = uuid.AsLowercaseString(); |
| |
| sync_pb::BookmarkSpecifics* bookmark_specifics = |
| data.specifics.mutable_bookmark(); |
| bookmark_specifics->set_guid(uuid.AsLowercaseString()); |
| bookmark_specifics->set_legacy_canonicalized_title(bookmark_info.title); |
| bookmark_specifics->set_full_title(bookmark_info.title); |
| *bookmark_specifics->mutable_unique_position() = unique_position.ToProto(); |
| |
| if (bookmark_info.url.empty()) { |
| bookmark_specifics->set_type(sync_pb::BookmarkSpecifics::FOLDER); |
| } else { |
| bookmark_specifics->set_type(sync_pb::BookmarkSpecifics::URL); |
| bookmark_specifics->set_url(bookmark_info.url); |
| } |
| |
| syncer::UpdateResponseData response_data; |
| response_data.entity = std::move(data); |
| response_data.response_version = response_version; |
| return response_data; |
| } |
| |
| syncer::UpdateResponseData CreateUpdateResponseData( |
| const BookmarkInfo& bookmark_info, |
| const syncer::UniquePosition& unique_position, |
| int response_version) { |
| return CreateUpdateResponseData(bookmark_info, unique_position, |
| response_version, |
| base::Uuid::GenerateRandomV4()); |
| } |
| |
| sync_pb::DataTypeState CreateDataTypeState() { |
| sync_pb::DataTypeState data_type_state; |
| data_type_state.set_cache_guid(kCacheGuid); |
| data_type_state.set_initial_sync_state( |
| sync_pb::DataTypeState_InitialSyncState_INITIAL_SYNC_DONE); |
| return data_type_state; |
| } |
| |
| // |node| must not be nullptr. |
| sync_pb::BookmarkMetadata CreateNodeMetadata( |
| const bookmarks::BookmarkNode* node, |
| const std::string& server_id, |
| const syncer::UniquePosition& unique_position = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix())) { |
| sync_pb::BookmarkMetadata bookmark_metadata; |
| bookmark_metadata.set_id(node->id()); |
| bookmark_metadata.mutable_metadata()->set_server_id(server_id); |
| bookmark_metadata.mutable_metadata()->set_client_tag_hash( |
| syncer::ClientTagHash::FromUnhashed(syncer::BOOKMARKS, |
| node->uuid().AsLowercaseString()) |
| .value()); |
| *bookmark_metadata.mutable_metadata()->mutable_unique_position() = |
| unique_position.ToProto(); |
| // Required by SyncedBookmarkTracker during validation of local metadata. |
| if (!node->is_folder()) { |
| bookmark_metadata.mutable_metadata()->set_bookmark_favicon_hash(123); |
| } |
| return bookmark_metadata; |
| } |
| |
| // Same as above but marks the node as unsynced (pending commit). |node| must |
| // not be nullptr. |
| sync_pb::BookmarkMetadata CreateUnsyncedNodeMetadata( |
| const bookmarks::BookmarkNode* node, |
| const std::string& server_id) { |
| sync_pb::BookmarkMetadata bookmark_metadata = |
| CreateNodeMetadata(node, server_id); |
| // Mark the entity as unsynced. |
| bookmark_metadata.mutable_metadata()->set_sequence_number(2); |
| bookmark_metadata.mutable_metadata()->set_acked_sequence_number(1); |
| return bookmark_metadata; |
| } |
| |
| sync_pb::BookmarkModelMetadata CreateMetadataForPermanentNodes( |
| const BookmarkModelView* bookmark_model) { |
| sync_pb::BookmarkModelMetadata model_metadata; |
| *model_metadata.mutable_data_type_state() = CreateDataTypeState(); |
| |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmark_model->bookmark_bar_node(), |
| /*server_id=*/kBookmarkBarId); |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmark_model->mobile_node(), |
| /*server_id=*/kMobileBookmarksId); |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmark_model->other_node(), |
| /*server_id=*/kOtherBookmarksId); |
| |
| return model_metadata; |
| } |
| |
| syncer::UpdateResponseDataList CreateUpdateResponseDataListForPermanentNodes() { |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates; |
| // Add update for the permanent folders. |
| updates.push_back( |
| CreateUpdateResponseData({kBookmarkBarId, std::string(), std::string(), |
| kBookmarksRootId, kBookmarkBarTag}, |
| kRandomPosition, /*response_version=*/0)); |
| updates.push_back( |
| CreateUpdateResponseData({kOtherBookmarksId, std::string(), std::string(), |
| kBookmarksRootId, kOtherBookmarksTag}, |
| kRandomPosition, /*response_version=*/0)); |
| updates.push_back(CreateUpdateResponseData( |
| {kMobileBookmarksId, std::string(), std::string(), kBookmarksRootId, |
| kMobileBookmarksTag}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| return updates; |
| } |
| |
| void AssertState(const BookmarkDataTypeProcessor* processor, |
| const std::vector<BookmarkInfo>& bookmarks) { |
| const SyncedBookmarkTracker* tracker = processor->GetTrackerForTest(); |
| ASSERT_THAT(tracker, NotNull()); |
| |
| // Make sure the tracker contains all bookmarks in |bookmarks| + the |
| // 3 permanent nodes. |
| ASSERT_THAT(tracker->TrackedEntitiesCountForTest(), Eq(bookmarks.size() + 3)); |
| |
| for (BookmarkInfo bookmark : bookmarks) { |
| const SyncedBookmarkTrackerEntity* entity = |
| tracker->GetEntityForSyncId(bookmark.server_id); |
| ASSERT_THAT(entity, NotNull()); |
| const bookmarks::BookmarkNode* node = entity->bookmark_node(); |
| ASSERT_THAT(node->GetTitle(), Eq(ASCIIToUTF16(bookmark.title))); |
| ASSERT_THAT(node->url(), Eq(GURL(bookmark.url))); |
| const SyncedBookmarkTrackerEntity* parent_entity = |
| tracker->GetEntityForSyncId(bookmark.parent_id); |
| ASSERT_THAT(node->parent(), Eq(parent_entity->bookmark_node())); |
| } |
| } |
| |
| class ProxyCommitQueue : public syncer::CommitQueue { |
| public: |
| explicit ProxyCommitQueue(CommitQueue* commit_queue) |
| : commit_queue_(commit_queue) { |
| DCHECK(commit_queue_); |
| } |
| |
| void NudgeForCommit() override { commit_queue_->NudgeForCommit(); } |
| |
| private: |
| raw_ptr<CommitQueue> commit_queue_ = nullptr; |
| }; |
| |
| class BookmarkDataTypeProcessorTest : public testing::Test { |
| public: |
| BookmarkDataTypeProcessorTest() |
| : bookmark_model_(std::make_unique<TestBookmarkModelView>()), |
| processor_(std::make_unique<BookmarkDataTypeProcessor>( |
| syncer::WipeModelUponSyncDisabledBehavior::kNever)) { |
| processor_->SetFaviconService(&favicon_service_); |
| } |
| |
| // Initialized the processor with bookmarks from the existing model and always |
| // initializes permanent folders. |
| void SimulateModelReadyToSyncWithInitialSyncDone() { |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model_.get()); |
| DCHECK_EQ(model_metadata.data_type_state().initial_sync_state(), |
| sync_pb::DataTypeState_InitialSyncState_INITIAL_SYNC_DONE); |
| |
| // By default, set that bookmarks are reuploaded to avoid reupload logic. |
| model_metadata.set_bookmarks_hierarchy_fields_reuploaded(true); |
| |
| // Rely on the order of iterating over the tree: left child is always |
| // handled before the current one. In this case increasing unique position |
| // will always represent the right order. |
| syncer::UniquePosition next_unique_position = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| ui::TreeNodeIterator<const bookmarks::BookmarkNode> iterator( |
| bookmark_model_->root_node()); |
| while (iterator.has_next()) { |
| const bookmarks::BookmarkNode* node = iterator.Next(); |
| if (node->is_permanent_node()) { |
| // Permanent nodes have been added explicitly. |
| continue; |
| } |
| |
| *model_metadata.add_bookmarks_metadata() = CreateNodeMetadata( |
| node, /*server_id=*/"id_" + node->uuid().AsLowercaseString(), |
| next_unique_position); |
| next_unique_position = syncer::UniquePosition::After( |
| next_unique_position, syncer::UniquePosition::RandomSuffix()); |
| } |
| processor_->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure_.Get(), |
| bookmark_model_.get()); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| } |
| |
| void SimulateModelReadyToSyncWithoutLocalMetadata() { |
| processor_->ModelReadyToSync( |
| /*metadata_str=*/std::string(), schedule_save_closure_.Get(), |
| bookmark_model_.get()); |
| } |
| |
| base::test::TestFuture<std::unique_ptr<syncer::DataTypeActivationResponse>> |
| SimulateOnSyncStartingNoWait(const std::string& cache_guid = kCacheGuid) { |
| syncer::DataTypeActivationRequest request; |
| request.cache_guid = cache_guid; |
| request.error_handler = error_handler_.Get(); |
| base::test::TestFuture<std::unique_ptr<syncer::DataTypeActivationResponse>> |
| response; |
| processor_->OnSyncStarting(request, response.GetCallback()); |
| return response; |
| } |
| |
| void SimulateOnSyncStarting(const std::string& cache_guid = kCacheGuid) { |
| std::ignore = SimulateOnSyncStartingNoWait(cache_guid).Wait(); |
| } |
| |
| void SimulateConnectSync() { |
| processor_->ConnectSync( |
| std::make_unique<ProxyCommitQueue>(&mock_commit_queue_)); |
| } |
| |
| // Simulate browser restart. |
| void ResetDataTypeProcessor( |
| syncer::WipeModelUponSyncDisabledBehavior |
| wipe_model_upon_sync_disabled_behavior = |
| syncer::WipeModelUponSyncDisabledBehavior::kNever) { |
| processor_ = std::make_unique<BookmarkDataTypeProcessor>( |
| wipe_model_upon_sync_disabled_behavior); |
| processor_->SetFaviconService(&favicon_service_); |
| } |
| |
| void DestroyBookmarkModel() { bookmark_model_.reset(); } |
| |
| TestBookmarkModelView* bookmark_model() { return bookmark_model_.get(); } |
| bookmarks::TestBookmarkClient* bookmark_client() { |
| return bookmark_model_->underlying_client(); |
| } |
| favicon::FaviconService* favicon_service() { return &favicon_service_; } |
| syncer::MockCommitQueue* mock_commit_queue() { return &mock_commit_queue_; } |
| BookmarkDataTypeProcessor* processor() { return processor_.get(); } |
| base::MockCallback<base::RepeatingClosure>* schedule_save_closure() { |
| return &schedule_save_closure_; |
| } |
| |
| syncer::CommitRequestDataList GetLocalChangesFromProcessor( |
| size_t max_entries) { |
| base::MockOnceCallback<void(syncer::CommitRequestDataList&&)> callback; |
| syncer::CommitRequestDataList local_changes; |
| // Destruction of the mock upon return will verify that Run() was indeed |
| // invoked. |
| EXPECT_CALL(callback, Run).WillOnce(MoveArg(&local_changes)); |
| processor_->GetLocalChanges(max_entries, callback.Get()); |
| return local_changes; |
| } |
| |
| base::MockRepeatingCallback<void(const syncer::ModelError&)>* |
| error_handler() { |
| return &error_handler_; |
| } |
| |
| sync_pb::DataTypeState::Invalidation BuildInvalidation( |
| int64_t version, |
| const std::string& payload) { |
| sync_pb::DataTypeState::Invalidation inv; |
| inv.set_version(version); |
| inv.set_hint(payload); |
| return inv; |
| } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| |
| private: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| NiceMock<base::MockCallback<base::RepeatingClosure>> schedule_save_closure_; |
| NiceMock<base::MockRepeatingCallback<void(const syncer::ModelError&)>> |
| error_handler_; |
| NiceMock<favicon::MockFaviconService> favicon_service_; |
| NiceMock<syncer::MockCommitQueue> mock_commit_queue_; |
| std::unique_ptr<TestBookmarkModelView> bookmark_model_; |
| // `processor_` might hold a raw_ptr to `bookmark_model_`. It should be |
| // destroyed first to avoid holding a briefly dangling pointer. |
| std::unique_ptr<BookmarkDataTypeProcessor> processor_; |
| }; |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldDoInitialMergeWithZeroBookmarks) { |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| base::HistogramTester histogram_tester; |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| EXPECT_THAT(bookmark_model()->bookmark_bar_node()->children(), IsEmpty()); |
| |
| histogram_tester.ExpectTotalCount( |
| kPersistentDataTypeConfigurationTimeMetricName, |
| /*count=*/1); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldDoInitialMergeWithOneBookmark) { |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Add one regular bookmark. |
| updates.push_back(CreateUpdateResponseData( |
| {"id1", "title1", "http://foo.com", kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()), |
| /*response_version=*/0)); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| base::HistogramTester histogram_tester; |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| EXPECT_THAT(bookmark_model()->bookmark_bar_node()->children(), SizeIs(1)); |
| |
| histogram_tester.ExpectTotalCount( |
| kPersistentDataTypeConfigurationTimeMetricName, |
| /*count=*/1); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldFailInitialMergeIfServerPermanentNodeMissing) { |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Remove one of the permanent nodes. |
| updates.pop_back(); |
| |
| // Add one regular bookmark. |
| updates.push_back(CreateUpdateResponseData( |
| {"id1", "title1", "http://foo.com", kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()), |
| /*response_version=*/0)); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| |
| // Expect failure when doing initial merge. |
| EXPECT_CALL(*error_handler(), Run); |
| |
| base::HistogramTester histogram_tester; |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| |
| // Not an actual requirement but it documents current behavior. |
| EXPECT_THAT(bookmark_model()->bookmark_bar_node()->children(), SizeIs(1)); |
| |
| histogram_tester.ExpectTotalCount( |
| kPersistentDataTypeConfigurationTimeMetricName, |
| /*count=*/0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorForLegacyClientsWhenExceedingLimitInInitialSync) { |
| sync_pb::BookmarkModelMetadata model_metadata; |
| model_metadata.set_last_initial_merge_remote_updates_exceeded_limit(true); |
| |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // The processor should have stored the error state and be ready to report it |
| // upon OnSyncStarting(). |
| sync_pb::BookmarkModelMetadata new_model_metadata; |
| new_model_metadata.ParseFromString(processor()->EncodeSyncMetadata()); |
| EXPECT_FALSE(new_model_metadata |
| .has_last_initial_merge_remote_updates_exceeded_limit()); |
| EXPECT_TRUE( |
| new_model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| const base::Time timestamp = base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds( |
| new_model_metadata |
| .initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros())); |
| const base::Time now = base::Time::Now(); |
| EXPECT_LE(now - base::Days(30), timestamp); |
| EXPECT_LE(timestamp, now - base::Days(23)); |
| |
| EXPECT_CALL(*error_handler(), |
| Run(Property( |
| &syncer::ModelError::type, |
| Eq(syncer::ModelError::Type:: |
| kBookmarksRemoteCountExceededLimitLastInitialMerge)))); |
| SimulateOnSyncStartingNoWait(); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldFailInitialMergeAndAvoidPartialDataIfServerPermanentNodeMissing) { |
| ResetDataTypeProcessor(syncer::WipeModelUponSyncDisabledBehavior::kAlways); |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Remove one of the permanent nodes. |
| updates.pop_back(); |
| |
| // Add one regular bookmark. |
| updates.push_back(CreateUpdateResponseData( |
| {"id1", "title1", "http://foo.com", kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()), |
| /*response_version=*/0)); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| |
| // Expect failure when doing initial merge. |
| EXPECT_CALL(*error_handler(), Run); |
| |
| base::HistogramTester histogram_tester; |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| |
| // Avoid exposing part of the tree to the user. When using |
| // `syncer::WipeModelUponSyncDisabledBehavior::kAlways`, reverting to the |
| // pre-merge state means clearing all data. |
| EXPECT_THAT(bookmark_model()->bookmark_bar_node()->children(), IsEmpty()); |
| |
| histogram_tester.ExpectTotalCount( |
| kPersistentDataTypeConfigurationTimeMetricName, |
| /*count=*/0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldUpdateModelAfterRemoteCreation) { |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId = "node_id"; |
| const std::string kTitle = "title"; |
| const std::string kUrl = "http://www.url.com"; |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates; |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| ASSERT_TRUE(bookmark_bar->children().empty()); |
| |
| base::HistogramTester histogram_tester; |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| |
| ASSERT_THAT(bookmark_bar->children().front().get(), NotNull()); |
| EXPECT_THAT(bookmark_bar->children().front()->GetTitle(), |
| Eq(ASCIIToUTF16(kTitle))); |
| EXPECT_THAT(bookmark_bar->children().front()->url(), Eq(GURL(kUrl))); |
| |
| // Incremental updates to not contribute to Sync.DataTypeConfigurationTime. |
| histogram_tester.ExpectTotalCount( |
| kPersistentDataTypeConfigurationTimeMetricName, |
| /*count=*/0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldUpdateModelAfterRemoteUpdate) { |
| const std::string kTitle = "title"; |
| const GURL kUrl("http://www.url.com"); |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL( |
| bookmark_bar, /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl); |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| |
| const SyncedBookmarkTrackerEntity* entity = |
| processor()->GetTrackerForTest()->GetEntityForBookmarkNode(bookmark_node); |
| ASSERT_THAT(entity, NotNull()); |
| |
| // Process an update for the same bookmark. |
| const std::string kNewTitle = "new-title"; |
| const std::string kNewUrl = "http://www.new-url.com"; |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateUpdateResponseData( |
| {entity->metadata().server_id(), kNewTitle, kNewUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/1, bookmark_node->uuid())); |
| |
| base::HistogramTester histogram_tester; |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| |
| // Check if the bookmark has been updated properly. |
| EXPECT_THAT(bookmark_bar->children().front().get(), Eq(bookmark_node)); |
| EXPECT_THAT(bookmark_node->GetTitle(), Eq(ASCIIToUTF16(kNewTitle))); |
| EXPECT_THAT(bookmark_node->url(), Eq(GURL(kNewUrl))); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldApplyGcDirective) { |
| const std::string kTitle = "title"; |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| |
| // Entity 1: Synced, will be deleted (not present in the update below). |
| const GURL kUrl1("https://www.url1.com"); |
| const bookmarks::BookmarkNode* node1 = bookmark_model()->AddURL( |
| bookmark_bar, /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl1); |
| // Entity 2: Synced, will be updated with a new value. |
| const GURL kUrl2("https://www.url2.com"); |
| const bookmarks::BookmarkNode* node2 = bookmark_model()->AddURL( |
| bookmark_bar, /*index=*/1, base::UTF8ToUTF16(kTitle), kUrl2); |
| // Entity 3: Synced, will be updated with the same value. |
| const GURL kUrl3("https://www.url3.com"); |
| const bookmarks::BookmarkNode* node3 = bookmark_model()->AddURL( |
| bookmark_bar, /*index=*/2, base::UTF8ToUTF16(kTitle), kUrl3); |
| // Entity 4: Unsynced, will be kept even though not present in the update. |
| const GURL kUrl4("https://www.url4.com"); |
| const bookmarks::BookmarkNode* node4 = bookmark_model()->AddURL( |
| bookmark_bar, /*index=*/3, std::u16string(), kUrl4); |
| // Entity 5: Does not exist locally yet (remote creation). |
| const GURL kUrl5("https://www.url5.com"); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| // Make a local change to node 4, so it's unsynced. |
| bookmark_model()->SetTitle(node4, base::UTF8ToUTF16(kTitle)); |
| |
| const SyncedBookmarkTrackerEntity* entity1 = |
| processor()->GetTrackerForTest()->GetEntityForBookmarkNode(node1); |
| ASSERT_THAT(entity1, NotNull()); |
| ASSERT_FALSE(entity1->IsUnsynced()); |
| |
| const SyncedBookmarkTrackerEntity* entity2 = |
| processor()->GetTrackerForTest()->GetEntityForBookmarkNode(node2); |
| ASSERT_THAT(entity2, NotNull()); |
| ASSERT_FALSE(entity2->IsUnsynced()); |
| |
| const SyncedBookmarkTrackerEntity* entity3 = |
| processor()->GetTrackerForTest()->GetEntityForBookmarkNode(node3); |
| ASSERT_THAT(entity3, NotNull()); |
| ASSERT_FALSE(entity3->IsUnsynced()); |
| |
| const SyncedBookmarkTrackerEntity* entity4 = |
| processor()->GetTrackerForTest()->GetEntityForBookmarkNode(node4); |
| ASSERT_THAT(entity4, NotNull()); |
| // This one is unsynced! |
| ASSERT_TRUE(entity4->IsUnsynced()); |
| |
| // Process an update with a "clear all" GC directive. |
| const std::string kNewTitle2 = "new-title2"; |
| syncer::UpdateResponseDataList updates; |
| // Entity 1: Not present in the update (remote deletion). |
| // Entity 2: Remote update (new title). |
| updates.push_back(CreateUpdateResponseData( |
| {entity2->metadata().server_id(), kNewTitle2, kUrl2.spec(), |
| kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::FromProto(entity2->metadata().unique_position()), |
| /*response_version=*/1, node2->uuid())); |
| // Entity 3: No-op remote update. |
| updates.push_back(CreateUpdateResponseData( |
| {entity3->metadata().server_id(), kTitle, kUrl3.spec(), kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::FromProto(entity3->metadata().unique_position()), |
| /*response_version=*/1, node3->uuid())); |
| // Entity 4: Not present (remote deletion) but has local changes. |
| // Entity 5: Remote creation. |
| const base::Uuid kUuid5 = base::Uuid::GenerateRandomV4(); |
| updates.push_back(CreateUpdateResponseData( |
| {"id5", kTitle, kUrl5.spec(), kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::After(syncer::UniquePosition::FromProto( |
| entity4->metadata().unique_position()), |
| syncer::UniquePosition::RandomSuffix()), |
| /*response_version=*/1, kUuid5)); |
| |
| sync_pb::GarbageCollectionDirective garbage_collection_directive; |
| garbage_collection_directive.set_version_watermark(1); |
| |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| garbage_collection_directive); |
| |
| // Node 1 should have been deleted, and the new node 5 should've been added. |
| EXPECT_THAT(bookmark_bar->children(), |
| ElementsAre(IsUrlBookmark(_, kUrl2), IsUrlBookmark(_, kUrl3), |
| IsUrlBookmark(_, kUrl4), IsUrlBookmark(_, kUrl5))); |
| |
| // Node 4 should still be unsynced. |
| const std::vector<const SyncedBookmarkTrackerEntity*> unsynced_entities = |
| processor()->GetTrackerForTest()->GetEntitiesWithLocalChanges(); |
| ASSERT_THAT( |
| unsynced_entities, |
| UnorderedElementsAre(TrackedEntityCorrespondsToBookmarkNode(node4))); |
| } |
| |
| TEST_F( |
| BookmarkDataTypeProcessorTest, |
| ShouldScheduleSaveAfterRemoteUpdateWithOnlyMetadataChangeAndReflections) { |
| const std::string kTitle = "title"; |
| const GURL kUrl("http://www.url.com"); |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmark_node = bookmark_model()->AddURL( |
| bookmark_bar, /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl); |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| |
| const SyncedBookmarkTrackerEntity* entity = |
| processor()->GetTrackerForTest()->GetEntityForBookmarkNode(bookmark_node); |
| ASSERT_THAT(entity, NotNull()); |
| |
| // Process an update for the same bookmark with the same data. |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(CreateUpdateResponseData( |
| {entity->metadata().server_id(), kTitle, kUrl.spec(), kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/1, bookmark_node->uuid())); |
| updates[0].response_version++; |
| |
| EXPECT_CALL(*schedule_save_closure(), Run()); |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldDecodeSyncMetadata) { |
| const std::string kNodeId = "node_id1"; |
| const std::string kTitle = "title1"; |
| const std::string kUrl = "http://www.url1.com"; |
| |
| std::vector<BookmarkInfo> bookmarks = { |
| {kNodeId, kTitle, kUrl, kBookmarkBarId, /*server_tag=*/std::string()}}; |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmarknode = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl)); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| |
| // Add an entry for the bookmark node. |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmarknode, kNodeId); |
| |
| // Create a new processor and init it with the metadata str. |
| BookmarkDataTypeProcessor new_processor( |
| syncer::WipeModelUponSyncDisabledBehavior::kNever); |
| |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| new_processor.ModelReadyToSync(metadata_str, base::DoNothing(), |
| bookmark_model()); |
| |
| AssertState(&new_processor, bookmarks); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldDecodeEncodedSyncMetadata) { |
| const std::string kNodeId1 = "node_id1"; |
| const std::string kTitle1 = "title1"; |
| const GURL kUrl1("http://www.url1.com"); |
| |
| const std::string kNodeId2 = "node_id2"; |
| const std::string kTitle2 = "title2"; |
| const GURL kUrl2("http://www.url2.com"); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| bookmark_model()->AddURL(bookmark_bar, /*index=*/0, |
| base::UTF8ToUTF16(kTitle1), kUrl1); |
| bookmark_model()->AddURL(bookmark_bar, /*index=*/1, |
| base::UTF8ToUTF16(kTitle2), kUrl2); |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| |
| // Create a new processor and init it with the same metadata str. |
| BookmarkDataTypeProcessor new_processor( |
| syncer::WipeModelUponSyncDisabledBehavior::kNever); |
| new_processor.ModelReadyToSync(processor()->EncodeSyncMetadata(), |
| base::DoNothing(), bookmark_model()); |
| |
| new_processor.GetTrackerForTest()->CheckAllNodesTracked(bookmark_model()); |
| |
| // Make sure shutdown doesn't crash. |
| DestroyBookmarkModel(); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| EXPECT_FALSE(new_processor.IsConnectedForTest()); |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| EXPECT_TRUE(new_processor.IsTrackingMetadata()); |
| } |
| |
| // Test suite with kSyncResetBookmarksInitialMergeLimitExceededError enabled. |
| class BookmarkDataTypeProcessorWithResetErrorFeatureEnabledTest |
| : public BookmarkDataTypeProcessorTest { |
| private: |
| base::test::ScopedFeatureList features_{ |
| syncer::kSyncResetBookmarksInitialMergeLimitExceededError}; |
| }; |
| |
| // Test suite with kSyncResetBookmarksInitialMergeLimitExceededError disabled. |
| class BookmarkDataTypeProcessorWithResetErrorFeatureDisabledTest |
| : public BookmarkDataTypeProcessorTest { |
| public: |
| BookmarkDataTypeProcessorWithResetErrorFeatureDisabledTest() { |
| features_.InitAndDisableFeature( |
| syncer::kSyncResetBookmarksInitialMergeLimitExceededError); |
| } |
| |
| private: |
| base::test::ScopedFeatureList features_; |
| }; |
| |
| TEST_F(BookmarkDataTypeProcessorWithResetErrorFeatureDisabledTest, |
| ShouldNotResetExceededLimitErrorWithTimestamp) { |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| model_metadata |
| .set_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros( |
| (base::Time::Now() - base::Days(31)) |
| .ToDeltaSinceWindowsEpoch() |
| .InMicroseconds()); |
| |
| EXPECT_CALL(*schedule_save_closure(), Run()).Times(0); |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| |
| EXPECT_FALSE(processor()->GetTrackerForTest()); |
| std::string new_metadata_str = processor()->EncodeSyncMetadata(); |
| sync_pb::BookmarkModelMetadata new_model_metadata; |
| new_model_metadata.ParseFromString(new_metadata_str); |
| EXPECT_FALSE( |
| new_model_metadata.last_initial_merge_remote_updates_exceeded_limit()); |
| EXPECT_TRUE( |
| new_model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorWithResetErrorFeatureEnabledTest, |
| ShouldSetTimestampForExceededLimitErrorForMigratingUsers) { |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| model_metadata.set_last_initial_merge_remote_updates_exceeded_limit(true); |
| // Note: The timestamp field is not set, to simulate migrating users. |
| |
| EXPECT_CALL(*schedule_save_closure(), Run()); |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| |
| // The error is not reset right away, so no tracker. |
| EXPECT_FALSE(processor()->GetTrackerForTest()); |
| std::string new_metadata_str = processor()->EncodeSyncMetadata(); |
| sync_pb::BookmarkModelMetadata new_model_metadata; |
| new_model_metadata.ParseFromString(new_metadata_str); |
| EXPECT_TRUE( |
| new_model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| |
| // The new timestamp should be between 23 and 30 days ago. |
| const base::Time limit_set_time = |
| base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds( |
| new_model_metadata |
| .initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros())); |
| |
| EXPECT_FALSE(limit_set_time.is_null()); |
| EXPECT_FALSE(new_model_metadata |
| .has_last_initial_merge_remote_updates_exceeded_limit()); |
| const base::TimeDelta time_since_limit_set = |
| base::Time::Now() - limit_set_time; |
| EXPECT_GE(time_since_limit_set, base::Days(23)); |
| EXPECT_LE(time_since_limit_set, base::Days(30)); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorWithResetErrorFeatureEnabledTest, |
| ShouldResetExceededLimitErrorAfter30Days) { |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| model_metadata |
| .set_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros( |
| (base::Time::Now() - base::Days(31)) |
| .ToDeltaSinceWindowsEpoch() |
| .InMicroseconds()); |
| |
| EXPECT_CALL(*schedule_save_closure(), Run()); |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| |
| // After resetting the error, the metadata should be valid for creating a |
| // tracker. |
| EXPECT_TRUE(processor()->GetTrackerForTest()); |
| std::string new_metadata_str = processor()->EncodeSyncMetadata(); |
| sync_pb::BookmarkModelMetadata new_model_metadata; |
| new_model_metadata.ParseFromString(new_metadata_str); |
| EXPECT_FALSE(new_model_metadata |
| .has_last_initial_merge_remote_updates_exceeded_limit()); |
| EXPECT_FALSE( |
| new_model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorWithResetErrorFeatureEnabledTest, |
| ShouldNotResetExceededLimitErrorWithin30Days) { |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| model_metadata |
| .set_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros( |
| (base::Time::Now() - base::Days(29)) |
| .ToDeltaSinceWindowsEpoch() |
| .InMicroseconds()); |
| |
| EXPECT_CALL(*schedule_save_closure(), Run()).Times(0); |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| |
| EXPECT_FALSE(processor()->GetTrackerForTest()); |
| std::string new_metadata_str = processor()->EncodeSyncMetadata(); |
| sync_pb::BookmarkModelMetadata new_model_metadata; |
| new_model_metadata.ParseFromString(new_metadata_str); |
| EXPECT_TRUE( |
| new_model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorWhenExceededLimitTimestampIsSet) { |
| sync_pb::BookmarkModelMetadata model_metadata; |
| model_metadata.mutable_data_type_state()->set_initial_sync_state( |
| sync_pb::DataTypeState_InitialSyncState_INITIAL_SYNC_DONE); |
| model_metadata |
| .set_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros( |
| base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds()); |
| |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| |
| // The tracker should not be created because of the error. |
| EXPECT_FALSE(processor()->GetTrackerForTest()); |
| |
| // Now simulate sync starting. |
| EXPECT_CALL(*error_handler(), |
| Run(testing::Property( |
| &syncer::ModelError::type, |
| syncer::ModelError::Type:: |
| kBookmarksRemoteCountExceededLimitLastInitialMerge))); |
| |
| SimulateOnSyncStartingNoWait(); |
| |
| // The tracker should still not exist. |
| EXPECT_FALSE(processor()->GetTrackerForTest()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldDecodeEmptyMetadata) { |
| // No save should be scheduled. |
| EXPECT_CALL(*schedule_save_closure(), Run()).Times(0); |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldIgnoreNonEmptyMetadataWhileSyncNotDone) { |
| sync_pb::BookmarkModelMetadata model_metadata; |
| model_metadata.mutable_data_type_state()->set_initial_sync_state( |
| sync_pb::DataTypeState_InitialSyncState_INITIAL_SYNC_STATE_UNSPECIFIED); |
| // Add entries to the metadata. |
| sync_pb::BookmarkMetadata* bookmark_metadata = |
| model_metadata.add_bookmarks_metadata(); |
| bookmark_metadata->set_id(bookmark_model()->bookmark_bar_node()->id()); |
| bookmark_metadata->mutable_metadata()->set_server_id(kBookmarkBarId); |
| |
| // Create a new processor and init it with the metadata str. |
| BookmarkDataTypeProcessor new_processor( |
| syncer::WipeModelUponSyncDisabledBehavior::kNever); |
| |
| // A save should be scheduled. |
| NiceMock<base::MockCallback<base::RepeatingClosure>> |
| new_schedule_save_closure; |
| EXPECT_CALL(new_schedule_save_closure, Run()); |
| |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| new_processor.ModelReadyToSync(metadata_str, new_schedule_save_closure.Get(), |
| bookmark_model()); |
| // Metadata are corrupted, so no tracker should have been created. |
| EXPECT_FALSE(new_processor.IsTrackingMetadata()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldIgnoreMetadataNotMatchingTheModel) { |
| sync_pb::BookmarkModelMetadata model_metadata; |
| model_metadata.mutable_data_type_state()->set_initial_sync_state( |
| sync_pb::DataTypeState_InitialSyncState_INITIAL_SYNC_DONE); |
| // Add entries for only the bookmark bar. However, the TestBookmarkClient will |
| // create all the 3 permanent nodes. |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmark_model()->bookmark_bar_node(), |
| /*server_id=*/kBookmarkBarId); |
| |
| // Create a new processor and init it with the metadata str. |
| BookmarkDataTypeProcessor new_processor( |
| syncer::WipeModelUponSyncDisabledBehavior::kNever); |
| |
| // A save should be scheduled. |
| NiceMock<base::MockCallback<base::RepeatingClosure>> |
| new_schedule_save_closure; |
| EXPECT_CALL(new_schedule_save_closure, Run()); |
| |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| new_processor.ModelReadyToSync(metadata_str, new_schedule_save_closure.Get(), |
| bookmark_model()); |
| |
| // Metadata are corrupted, so no tracker should have been created. |
| EXPECT_FALSE(new_processor.IsTrackingMetadata()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldIgnoreMetadataIfCacheGuidMismatch) { |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| SimulateOnSyncStarting("unexpected_cache_guid"); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldIgnoreMetadataIfCacheGuidMismatchUponEarlySyncStartup) { |
| base::test::TestFuture<std::unique_ptr<syncer::DataTypeActivationResponse>> |
| start_response = SimulateOnSyncStartingNoWait("unexpected_cache_guid"); |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| std::ignore = start_response.Wait(); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| } |
| |
| // Verifies that the data type state stored in the tracker gets |
| // updated upon handling remote updates by assigning a new encryption |
| // key name. |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldUpdateDataTypeStateUponHandlingRemoteUpdates) { |
| // Initialize the process to make sure the tracker has been created. |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| const SyncedBookmarkTracker* tracker = processor()->GetTrackerForTest(); |
| // The encryption key name should be empty. |
| ASSERT_TRUE(tracker->data_type_state().encryption_key_name().empty()); |
| |
| // Build a data type state with an encryption key name. |
| const std::string kEncryptionKeyName = "new_encryption_key_name"; |
| sync_pb::DataTypeState data_type_state(CreateDataTypeState()); |
| data_type_state.set_encryption_key_name(kEncryptionKeyName); |
| |
| // Push empty updates list to the processor together with the updated model |
| // type state. |
| syncer::UpdateResponseDataList empty_updates_list; |
| processor()->OnUpdateReceived(data_type_state, std::move(empty_updates_list), |
| /*gc_directive=*/std::nullopt); |
| |
| // The data type state inside the tracker should have been updated, and |
| // carries the new encryption key name. |
| EXPECT_THAT(tracker->data_type_state().encryption_key_name(), |
| Eq(kEncryptionKeyName)); |
| } |
| |
| // Verifies that the data type state stored in the tracker gets |
| // updated upon handling remote updates by replacing new pending invalidations. |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldUpdateDataTypeStateUponHandlingInvalidations) { |
| // Initialize the process to make sure the tracker has been created. |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| const SyncedBookmarkTracker* tracker = processor()->GetTrackerForTest(); |
| |
| // Build invalidations. |
| sync_pb::DataTypeState::Invalidation inv_1 = |
| BuildInvalidation(1, "bm_hint_1"); |
| sync_pb::DataTypeState::Invalidation inv_2 = |
| BuildInvalidation(2, "bm_hint_2"); |
| EXPECT_CALL(*schedule_save_closure(), Run()); |
| |
| processor()->StorePendingInvalidations({inv_1, inv_2}); |
| |
| // The data type state inside the tracker should have been updated, and |
| // carries the new invalidations. |
| EXPECT_EQ(2, tracker->data_type_state().invalidations_size()); |
| |
| EXPECT_EQ(inv_1.hint(), tracker->data_type_state().invalidations(0).hint()); |
| EXPECT_EQ(inv_1.version(), |
| tracker->data_type_state().invalidations(0).version()); |
| |
| EXPECT_EQ(inv_2.hint(), tracker->data_type_state().invalidations(1).hint()); |
| EXPECT_EQ(inv_2.version(), |
| tracker->data_type_state().invalidations(1).version()); |
| } |
| |
| // This tests that when the encryption key changes, but the received entities |
| // are already encrypted with the up-to-date encryption key, no recommit is |
| // needed. |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldNotRecommitEntitiesWhenEncryptionIsUpToDate) { |
| // Initialize the process to make sure the tracker has been created. |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| const SyncedBookmarkTracker* tracker = processor()->GetTrackerForTest(); |
| // The encryption key name should be empty. |
| ASSERT_TRUE(tracker->data_type_state().encryption_key_name().empty()); |
| |
| // Build a data type state with an encryption key name. |
| const std::string kEncryptionKeyName = "new_encryption_key_name"; |
| sync_pb::DataTypeState data_type_state(CreateDataTypeState()); |
| data_type_state.set_encryption_key_name(kEncryptionKeyName); |
| |
| // Push an update that is encrypted with the new encryption key. |
| const std::string kNodeId = "node_id"; |
| syncer::UpdateResponseData response_data = CreateUpdateResponseData( |
| {kNodeId, "title", "http://www.url.com", /*parent_id=*/kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()), |
| /*response_version=*/0); |
| response_data.encryption_key_name = kEncryptionKeyName; |
| |
| EXPECT_CALL(*mock_commit_queue(), NudgeForCommit()).Times(0); |
| syncer::UpdateResponseDataList updates; |
| updates.push_back(std::move(response_data)); |
| processor()->OnUpdateReceived(data_type_state, std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| |
| // The bookmarks shouldn't be marked for committing. |
| ASSERT_THAT(tracker->GetEntityForSyncId(kNodeId), NotNull()); |
| EXPECT_THAT(tracker->GetEntityForSyncId(kNodeId)->IsUnsynced(), Eq(false)); |
| } |
| |
| // Verifies that the processor doesn't crash if sync is stopped before receiving |
| // remote updates or tracking metadata. |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldStopBeforeReceivingRemoteUpdates) { |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| processor()->OnSyncStopping(syncer::CLEAR_METADATA); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldStopAfterReceivingRemoteUpdates) { |
| // Initialize the process to make sure the tracker has been created. |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| processor()->OnSyncStopping(syncer::CLEAR_METADATA); |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportNoCountersWhenModelIsNotLoaded) { |
| base::test::TestFuture<std::unique_ptr<syncer::DataTypeActivationResponse>> |
| start_response = SimulateOnSyncStartingNoWait(); |
| |
| // Process any pending tasks, in case that would incorrectly lead to |
| // completion of the start procedure. |
| RunUntilIdle(); |
| |
| ASSERT_FALSE(start_response.IsReady()); |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| syncer::TypeEntitiesCount count(syncer::BOOKMARKS); |
| // Assign an arbitrary non-zero number of entities to be able to check that |
| // actually a 0 has been written to it later. |
| count.non_tombstone_entities = 1000; |
| processor()->GetTypeEntitiesCountForDebugging(base::BindLambdaForTesting( |
| [&](const syncer::TypeEntitiesCount& returned_count) { |
| count = returned_count; |
| })); |
| EXPECT_EQ(0, count.non_tombstone_entities); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldNotCommitEntitiesWithoutLoadedFavicons) { |
| const std::string kNodeId = "node_id1"; |
| const std::string kTitle = "title1"; |
| const std::string kUrl = "http://www.url1.com"; |
| const std::string kIconUrl = "http://www.url1.com/favicon"; |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* node = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl)); |
| |
| // Add an entry for the bookmark node that is unsynced. |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| *model_metadata.add_bookmarks_metadata() = |
| CreateUnsyncedNodeMetadata(node, kNodeId); |
| |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| SimulateOnSyncStarting(); |
| |
| ASSERT_FALSE(bookmark_client()->HasFaviconLoadTasks()); |
| EXPECT_THAT(GetLocalChangesFromProcessor(/*max_entries=*/10), IsEmpty()); |
| EXPECT_TRUE(node->is_favicon_loading()); |
| |
| bookmark_client()->SimulateFaviconLoaded(GURL(kUrl), GURL(kIconUrl), |
| gfx::Image()); |
| ASSERT_TRUE(node->is_favicon_loaded()); |
| EXPECT_THAT(GetLocalChangesFromProcessor(/*max_entries=*/10), |
| ElementsAre(CommitRequestDataMatchesGuid(node->uuid()))); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldCommitEntitiesWhileOtherFaviconsLoading) { |
| const std::string kNodeId1 = "node_id1"; |
| const std::string kNodeId2 = "node_id2"; |
| const std::string kTitle = "title"; |
| const std::string kUrl1 = "http://www.url1.com"; |
| const std::string kUrl2 = "http://www.url2.com"; |
| const std::string kIconUrl = "http://www.url.com/favicon"; |
| |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* node1 = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl1)); |
| const bookmarks::BookmarkNode* node2 = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/1, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl2)); |
| |
| // Add entries for the two bookmark nodes and mark them as unsynced. |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| *model_metadata.add_bookmarks_metadata() = |
| CreateUnsyncedNodeMetadata(node1, kNodeId1); |
| *model_metadata.add_bookmarks_metadata() = |
| CreateUnsyncedNodeMetadata(node2, kNodeId2); |
| |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| SimulateOnSyncStarting(); |
| |
| // The goal of this test is to mimic the case where one bookmark (the first |
| // one listed by SyncedBookmarkTracker::GetEntitiesWithLocalChanges()) has no |
| // loaded favicon, while the second one does. The precise order is not known |
| // in advance (in the current implementation, it depends on the iteration |
| // order for raw pointers in an unordered_set) which means the test needs to |
| // pass for both cases. |
| const std::vector<const SyncedBookmarkTrackerEntity*> unsynced_entities = |
| processor()->GetTrackerForTest()->GetEntitiesWithLocalChanges(); |
| ASSERT_THAT( |
| unsynced_entities, |
| UnorderedElementsAre(TrackedEntityCorrespondsToBookmarkNode(node1), |
| TrackedEntityCorrespondsToBookmarkNode(node2))); |
| |
| // Force a favicon load for the second listed entity, but leave the first |
| // without loaded favicon. |
| bookmark_model()->GetFavicon(unsynced_entities[1]->bookmark_node()); |
| bookmark_client()->SimulateFaviconLoaded( |
| unsynced_entities[1]->bookmark_node()->url(), GURL(kIconUrl), |
| gfx::Image()); |
| ASSERT_TRUE(unsynced_entities[1]->bookmark_node()->is_favicon_loaded()); |
| ASSERT_FALSE(unsynced_entities[0]->bookmark_node()->is_favicon_loaded()); |
| ASSERT_FALSE(unsynced_entities[0]->bookmark_node()->is_favicon_loading()); |
| |
| EXPECT_THAT(GetLocalChangesFromProcessor(/*max_entries=*/1), |
| ElementsAre(CommitRequestDataMatchesGuid( |
| unsynced_entities[1]->bookmark_node()->uuid()))); |
| |
| // |unsynced_entities[0]| has been excluded from the result above because the |
| // favicon isn't loaded, but the loading process should have started now (see |
| // BookmarkLocalChangesBuilder::BuildCommitRequests()). |
| EXPECT_TRUE(unsynced_entities[0]->bookmark_node()->is_favicon_loading()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldReuploadLegacyBookmarksOnStart) { |
| const std::string kTitle = "title"; |
| const GURL kUrl("http://www.url.com"); |
| |
| const bookmarks::BookmarkNode* node = |
| bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), |
| /*index=*/0, base::UTF8ToUTF16(kTitle), kUrl); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| ASSERT_THAT(processor()->GetTrackerForTest()->GetEntityForBookmarkNode(node), |
| NotNull()); |
| const std::string server_id = processor() |
| ->GetTrackerForTest() |
| ->GetEntityForBookmarkNode(node) |
| ->metadata() |
| .server_id(); |
| |
| sync_pb::BookmarkModelMetadata model_metadata = |
| processor()->GetTrackerForTest()->BuildBookmarkModelMetadata(); |
| model_metadata.clear_bookmarks_hierarchy_fields_reuploaded(); |
| ASSERT_FALSE(processor()->GetTrackerForTest()->HasLocalChanges()); |
| |
| // Simulate browser restart, enable sync reupload and initialize the processor |
| // again. |
| ResetDataTypeProcessor(); |
| |
| base::test::ScopedFeatureList features; |
| features.InitAndEnableFeature(switches::kSyncReuploadBookmarks); |
| |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| processor()->ModelReadyToSync(metadata_str, base::DoNothing(), |
| bookmark_model()); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| const SyncedBookmarkTrackerEntity* entity = |
| processor()->GetTrackerForTest()->GetEntityForSyncId(server_id); |
| ASSERT_THAT(entity, NotNull()); |
| |
| // Entity should be synced before until first update is received. |
| ASSERT_FALSE(entity->IsUnsynced()); |
| ASSERT_FALSE(processor() |
| ->GetTrackerForTest() |
| ->BuildBookmarkModelMetadata() |
| .bookmarks_hierarchy_fields_reuploaded()); |
| |
| // Synchronize with the server and get any updates. |
| EXPECT_CALL(*mock_commit_queue(), NudgeForCommit()); |
| processor()->OnUpdateReceived(CreateDataTypeState(), |
| syncer::UpdateResponseDataList(), |
| /*gc_directive=*/std::nullopt); |
| |
| // Check that all entities are unsynced now and metadata is marked as |
| // reuploaded. |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_TRUE(processor() |
| ->GetTrackerForTest() |
| ->BuildBookmarkModelMetadata() |
| .bookmarks_hierarchy_fields_reuploaded()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfIncrementalLocalCreationCrossesMaxCountLimit) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect failure when adding new bookmark. |
| EXPECT_CALL(*error_handler(), Run); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const std::string kNodeId = "node_id1"; |
| const std::string kTitle = "title1"; |
| const std::string kUrl = "http://www.url1.com"; |
| const std::string kIconUrl = "http://www.url1.com/favicon"; |
| |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| // Add a new bookmark to exceed the limit. |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl)); |
| |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| // Expect tracking to still be enabled. |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| } |
| |
| TEST_F( |
| BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfBookmarksCountExceedsLimitOnStartupWhenMetadataMatchesModel) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect error twice. First, when new bookmark is added. Next after restart. |
| EXPECT_CALL(*error_handler(), Run).Times(2); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const std::string kNodeId = "node_id1"; |
| const std::string kTitle = "title1"; |
| const std::string kUrl = "http://www.url1.com"; |
| const std::string kIconUrl = "http://www.url1.com/favicon"; |
| |
| // Add a new bookmark to exceed the limit. |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmarknode = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl)); |
| |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| |
| // Add an entry for the bookmark node. |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmarknode, kNodeId); |
| |
| // Save metadata for init after restart. |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| |
| // Simulate browser restart. |
| ResetDataTypeProcessor(); |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| processor()->ModelReadyToSync(metadata_str, schedule_save_closure()->Get(), |
| bookmark_model()); |
| // Metadata matches model, so tracker should be not null. |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| |
| // Should invoke error_handler::Run and schedule_save_closure::Run. This |
| // requires using SimulateOnSyncStartingNoWait() because the operation never |
| // completes successfully. |
| SimulateOnSyncStartingNoWait(); |
| |
| // Expect tracking to still be enabled. |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| } |
| |
| TEST_F( |
| BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfBookmarksCountExceedsLimitOnStartupWhenMetadataDoesNotMatchModel) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect error twice. First, when new bookmark is added. Next after restart. |
| EXPECT_CALL(*error_handler(), Run).Times(2); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const std::string kNodeId = "node_id1"; |
| const std::string kTitle = "title1"; |
| const std::string kUrl = "http://www.url1.com"; |
| const std::string kIconUrl = "http://www.url1.com/favicon"; |
| |
| // Add a new bookmark to exceed the limit. |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle), |
| GURL(kUrl)); |
| |
| // Simulate browser restart. |
| ResetDataTypeProcessor(); |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| |
| // Metadata does not match model, so tracker should be null. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Should invoke error_handler::Run and schedule_save_closure::Run. This |
| // requires using SimulateOnSyncStartingNoWait() because the operation never |
| // completes successfully. |
| SimulateOnSyncStartingNoWait(); |
| } |
| |
| TEST_F( |
| BookmarkDataTypeProcessorTest, |
| BookmarkModelShouldWorkNormallyEvenAfterSyncReportedErrorDueToMaxLimitCrossed) { |
| // Ensure that bookmarks model works normally even after sync reports error |
| // when max count limit is crossed. |
| |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const std::string kNodeId1 = "node_id1"; |
| const std::string kTitle1 = "title1"; |
| const std::string kUrl1 = "http://www.url1.com"; |
| |
| // Add a new bookmark to exceed the limit. |
| const bookmarks::BookmarkNode* bookmark_bar_node = |
| bookmark_model()->bookmark_bar_node(); |
| const bookmarks::BookmarkNode* bookmarknode1 = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle1), |
| GURL(kUrl1)); |
| |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| |
| // Add an entry for the bookmark node. |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmarknode1, kNodeId1); |
| |
| // Add another bookmark. |
| const std::string kNodeId2 = "node_id2"; |
| const std::string kTitle2 = "title2"; |
| const std::string kUrl2 = "http://www.url2.com"; |
| |
| const bookmarks::BookmarkNode* bookmarknode2 = bookmark_model()->AddURL( |
| /*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle2), |
| GURL(kUrl2)); |
| |
| // Add an entry for the bookmark node. |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmarknode2, kNodeId2); |
| |
| // Save metadata for init after restart. |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| |
| // Simulate browser restart. |
| ResetDataTypeProcessor(); |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| processor()->ModelReadyToSync(metadata_str, base::DoNothing(), |
| bookmark_model()); |
| // Should lead to error_handler::Run. |
| SimulateOnSyncStartingNoWait(); |
| |
| // The second bookmark should have been added anyway. |
| EXPECT_EQ(bookmark_model()->bookmark_bar_node()->children().size(), 2u); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfBookmarksCountExceedsLimitAfterInitialUpdate) { |
| // Set a limit of 4 bookmarks: 3 permanent nodes and 1 additional node which |
| // is different from the remote. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(4); |
| |
| const std::string kTitle1 = "title1"; |
| const std::string kUrl1 = "http://www.url1.com"; |
| |
| // Set up a preexisting bookmark under other node. |
| const bookmarks::BookmarkNode* other_node = bookmark_model()->other_node(); |
| bookmark_model()->AddURL( |
| /*parent=*/other_node, /*index=*/0, base::UTF8ToUTF16(kTitle1), |
| GURL(kUrl1)); |
| |
| // Expect failure after initial update is merged. |
| bool error_reported = false; |
| EXPECT_CALL(*error_handler(), Run).Times(1).WillRepeatedly([&]() { |
| error_reported = true; |
| }); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId2 = "node_id2"; |
| const std::string kTitle2 = "title2"; |
| const std::string kUrl2 = "http://www.url2.com"; |
| |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId2, kTitle2, kUrl2, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| |
| // Ensures that OnInitialUpdateReceived will be called. |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE(bookmark_bar->children().empty()); |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| |
| ASSERT_FALSE(error_reported); |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| EXPECT_TRUE(error_reported); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| // New bookmark gets added though. Note that this is as per the current |
| // behaviour but is not a requirement. |
| EXPECT_FALSE(bookmark_bar->children().empty()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfBookmarksCountExceedsLimitAfterIncrementalUpdate) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect failure after initial update is merged. |
| bool error_reported = false; |
| EXPECT_CALL(*error_handler(), Run).Times(1).WillRepeatedly([&]() { |
| error_reported = true; |
| }); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates; |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId = "node_id"; |
| const std::string kTitle = "title"; |
| const std::string kUrl = "http://www.url.com"; |
| |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| |
| // Ensures that path for incremental updates will be called. |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE(bookmark_bar->children().empty()); |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| |
| ASSERT_FALSE(error_reported); |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| EXPECT_TRUE(error_reported); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| // New bookmark gets added though. Note that this is as per the current |
| // behaviour but is not a requirement. |
| EXPECT_FALSE(bookmark_bar->children().empty()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfInitialUpdatesCrossMaxCountLimit) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect failure when initial update of count 4 is received. |
| bool error_reported = false; |
| EXPECT_CALL(*error_handler(), Run).Times(1).WillRepeatedly([&]() { |
| error_reported = true; |
| }); |
| EXPECT_CALL(*mock_commit_queue(), NudgeForCommit()).Times(0); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Entry for the root folder. The server may or may not send a root node, but |
| // the current implementation still handles it. |
| updates.push_back(CreateUpdateResponseData( |
| {kBookmarksRootId, std::string(), std::string(), std::string(), |
| syncer::DataTypeToProtocolRootTag(syncer::BOOKMARKS)}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId = "node_id"; |
| const std::string kTitle = "title"; |
| const std::string kUrl = "http://www.url.com"; |
| |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| const bookmarks::BookmarkNode* bookmark_bar = |
| bookmark_model()->bookmark_bar_node(); |
| |
| // Ensures that OnInitialUpdateReceived will be called. |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE(bookmark_bar->children().empty()); |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| |
| ASSERT_FALSE(error_reported); |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| EXPECT_TRUE(error_reported); |
| EXPECT_FALSE(processor()->IsConnectedForTest()); |
| // Tracker should remain null and bookmark model unchanged. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| EXPECT_TRUE(bookmark_bar->children().empty()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldSaveRemoteUpdatesCountExceedingLimitResultDuringInitialMerge) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Entry for the root folder. The server may or may not send a root node, but |
| // the current implementation still handles it. |
| updates.push_back(CreateUpdateResponseData( |
| {kBookmarksRootId, std::string(), std::string(), std::string(), |
| syncer::DataTypeToProtocolRootTag(syncer::BOOKMARKS)}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId = "node_id"; |
| const std::string kTitle = "title"; |
| const std::string kUrl = "http://www.url.com"; |
| |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Ensures that OnInitialUpdateReceived will be called. |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE(processor()->IsConnectedForTest()); |
| |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_FALSE(processor()->IsConnectedForTest()); |
| |
| // Metadata should contain the relevant field. |
| sync_pb::BookmarkModelMetadata model_metadata; |
| std::string metadata_str = processor()->EncodeSyncMetadata(); |
| ASSERT_FALSE(metadata_str.empty()); |
| ASSERT_TRUE(model_metadata.ParseFromString(metadata_str)); |
| EXPECT_TRUE( |
| model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldReportErrorIfRemoteBookmarksCountExceededLimitOnLastTry) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect failure when initial update of count 4 is received. |
| bool error_reported = false; |
| EXPECT_CALL(*error_handler(), Run).Times(2).WillRepeatedly([&]() { |
| error_reported = true; |
| }); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| SimulateConnectSync(); |
| |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Entry for the root folder. The server may or may not send a root node, but |
| // the current implementation still handles it. |
| updates.push_back(CreateUpdateResponseData( |
| {kBookmarksRootId, std::string(), std::string(), std::string(), |
| syncer::DataTypeToProtocolRootTag(syncer::BOOKMARKS)}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId = "node_id"; |
| const std::string kTitle = "title"; |
| const std::string kUrl = "http://www.url.com"; |
| |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Ensures that OnInitialUpdateReceived will be called. |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| ASSERT_FALSE(error_reported); |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| ASSERT_TRUE(error_reported); |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_FALSE(processor()->IsConnectedForTest()); |
| |
| sync_pb::BookmarkModelMetadata model_metadata; |
| std::string metadata_str = processor()->EncodeSyncMetadata(); |
| ASSERT_FALSE(metadata_str.empty()); |
| ASSERT_TRUE(model_metadata.ParseFromString(metadata_str)); |
| ASSERT_TRUE( |
| model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| |
| ResetDataTypeProcessor(); |
| // Expect failure. |
| error_reported = false; |
| processor()->ModelReadyToSync(metadata_str, schedule_save_closure()->Get(), |
| bookmark_model()); |
| SimulateOnSyncStartingNoWait(); |
| |
| EXPECT_TRUE(error_reported); |
| // Tracker would not be initialised. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Metadata remains unchanged on this failure. |
| metadata_str = processor()->EncodeSyncMetadata(); |
| ASSERT_FALSE(metadata_str.empty()); |
| ASSERT_TRUE(model_metadata.ParseFromString(metadata_str)); |
| EXPECT_TRUE( |
| model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldPersistRemoteBookmarksCountExceedingLimitAcrossBrowserRestarts) { |
| // Set a limit of 3 bookmarks, i.e. limit it to the 3 permanent nodes. |
| processor()->SetMaxBookmarksTillSyncEnabledForTest(3); |
| |
| // Expect failure when initial update of count 4 is received. |
| bool error_reported = false; |
| EXPECT_CALL(*error_handler(), Run).Times(3).WillRepeatedly([&]() { |
| error_reported = true; |
| }); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| SimulateOnSyncStarting(); |
| |
| const syncer::UniquePosition kRandomPosition = |
| syncer::UniquePosition::InitialPosition( |
| syncer::UniquePosition::RandomSuffix()); |
| |
| syncer::UpdateResponseDataList updates = |
| CreateUpdateResponseDataListForPermanentNodes(); |
| |
| // Entry for the root folder. The server may or may not send a root node, but |
| // the current implementation still handles it. |
| updates.push_back(CreateUpdateResponseData( |
| {kBookmarksRootId, std::string(), std::string(), std::string(), |
| syncer::DataTypeToProtocolRootTag(syncer::BOOKMARKS)}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Add update for another node under the bookmarks bar. |
| const std::string kNodeId = "node_id"; |
| const std::string kTitle = "title"; |
| const std::string kUrl = "http://www.url.com"; |
| |
| updates.push_back( |
| CreateUpdateResponseData({kNodeId, kTitle, kUrl, kBookmarkBarId, |
| /*server_tag=*/std::string()}, |
| kRandomPosition, /*response_version=*/0)); |
| |
| // Ensures that OnInitialUpdateReceived will be called. |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| ASSERT_FALSE(error_reported); |
| processor()->OnUpdateReceived(CreateDataTypeState(), std::move(updates), |
| /*gc_directive=*/std::nullopt); |
| ASSERT_TRUE(error_reported); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| sync_pb::BookmarkModelMetadata model_metadata; |
| std::string metadata_str = processor()->EncodeSyncMetadata(); |
| ASSERT_FALSE(metadata_str.empty()); |
| ASSERT_TRUE(model_metadata.ParseFromString(metadata_str)); |
| ASSERT_TRUE( |
| model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| |
| // Simulate browser restart. |
| ResetDataTypeProcessor(); |
| // Expect failure. |
| error_reported = false; |
| processor()->ModelReadyToSync(metadata_str, schedule_save_closure()->Get(), |
| bookmark_model()); |
| SimulateOnSyncStartingNoWait(); |
| EXPECT_TRUE(error_reported); |
| // Tracker would not be initialised. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Metadata remains unchanged on this failure. |
| metadata_str = processor()->EncodeSyncMetadata(); |
| ASSERT_FALSE(metadata_str.empty()); |
| ASSERT_TRUE(model_metadata.ParseFromString(metadata_str)); |
| ASSERT_TRUE( |
| model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| |
| // Simulate browser restart again. |
| ResetDataTypeProcessor(); |
| // Expect failure. |
| error_reported = false; |
| processor()->ModelReadyToSync(metadata_str, schedule_save_closure()->Get(), |
| bookmark_model()); |
| SimulateOnSyncStartingNoWait(); |
| EXPECT_TRUE(error_reported); |
| // Tracker would not be initialised. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Metadata remains unchanged on this failure as well. |
| metadata_str = processor()->EncodeSyncMetadata(); |
| ASSERT_FALSE(metadata_str.empty()); |
| ASSERT_TRUE(model_metadata.ParseFromString(metadata_str)); |
| EXPECT_TRUE( |
| model_metadata |
| .has_initial_merge_remote_updates_exceeded_limit_timestamp_windows_epoch_micros()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldClearMetadataIfStopped) { |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| processor()->OnSyncStopping(syncer::KEEP_METADATA); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Expect saving empty metadata upon call to ClearMetadataIfStopped(). |
| EXPECT_CALL(*schedule_save_closure(), Run); |
| |
| processor()->ClearMetadataIfStopped(); |
| // Should clear the tracker even if already stopped. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| // Expect an entry to the histogram. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 1); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 1); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldClearMetadataIfStoppedUponModelReadyToSync) { |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Expect no call to save metadata before ModelReadyToSync(). |
| EXPECT_CALL(*schedule_save_closure(), Run).Times(0); |
| // Call ClearMetadataIfStopped() before ModelReadyToSync(). This should set |
| // the flag for a pending clearing of metadata. |
| processor()->ClearMetadataIfStopped(); |
| // Nothing recorded to the histograms yet. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.DelayedClear", 0); |
| |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| // Expect saving empty metadata from ModelReadyToSync() while processing the |
| // pending clearing of metadata. |
| EXPECT_CALL(*schedule_save_closure(), Run); |
| // ModelReadyToSync() should take into account the pending metadata clearing |
| // flag and clear the metadata. |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| // Tracker should have not been set. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| // Expect recording of the delayed clear. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 1); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.DelayedClear", 1); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldNotClearMetadataIfNotStopped) { |
| // Initialize and start the processor with some metadata. |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| |
| base::HistogramTester histogram_tester; |
| |
| processor()->ClearMetadataIfStopped(); |
| |
| // Should NOT have cleared the metadata since the processor is not stopped. |
| EXPECT_TRUE(processor()->IsTrackingMetadata()); |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldNotClearMetadataIfStoppedIfPreviouslyStoppedWithClearMetadata) { |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| processor()->OnSyncStopping(syncer::CLEAR_METADATA); |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Expect no call to save metadata upon ClearMetadataIfStopped(). |
| EXPECT_CALL(*schedule_save_closure(), Run).Times(0); |
| |
| base::HistogramTester histogram_tester; |
| |
| processor()->ClearMetadataIfStopped(); |
| // Expect no entry to the histogram. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldWipeBookmarksRepeatedlyIfStoppedWithClearMetadata) { |
| ResetDataTypeProcessor(syncer::WipeModelUponSyncDisabledBehavior::kAlways); |
| |
| const GURL kUrl("http://www.example.com"); |
| bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), /*index=*/0, |
| u"foo", kUrl); |
| const bookmarks::BookmarkNode* folder = bookmark_model()->AddFolder( |
| bookmark_model()->mobile_node(), /*index=*/0, u"folder"); |
| bookmark_model()->AddURL(folder, /*index=*/0, u"bar", kUrl); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| ASSERT_TRUE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| |
| processor()->OnSyncStopping(syncer::CLEAR_METADATA); |
| EXPECT_FALSE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| |
| // If the process is repeated, the result should be the same (bookmarks |
| // deleted once again). This requires doing initial sync again. |
| SimulateOnSyncStarting(); |
| processor()->OnUpdateReceived(CreateDataTypeState(), |
| CreateUpdateResponseDataListForPermanentNodes(), |
| /*gc_directive=*/std::nullopt); |
| bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), /*index=*/0, |
| u"foo", kUrl); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| |
| processor()->OnSyncStopping(syncer::CLEAR_METADATA); |
| EXPECT_FALSE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldNotWipeBookmarksIfStoppedWithKeepMetadata) { |
| ResetDataTypeProcessor(syncer::WipeModelUponSyncDisabledBehavior::kAlways); |
| |
| const GURL kUrl("http://www.example.com"); |
| const bookmarks::BookmarkNode* node = bookmark_model()->AddURL( |
| bookmark_model()->mobile_node(), /*index=*/0, u"foo", kUrl); |
| |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| SimulateOnSyncStarting(); |
| |
| processor()->OnSyncStopping(syncer::KEEP_METADATA); |
| EXPECT_THAT(bookmark_model()->mobile_node()->children(), |
| ElementsAre(Pointer(Eq(node)))); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldNotClearMetadataIfStoppedWithoutMetadataInitially) { |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Call ClearMetadataIfStopped() without a prior call to OnSyncStopping(). |
| processor()->ClearMetadataIfStopped(); |
| |
| // Expect no call to save metadata upon ClearMetadataIfStopped(). |
| EXPECT_CALL(*schedule_save_closure(), Run).Times(0); |
| // Expect no entry to the histogram. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldNotClearMetadataIfStoppedUponModelReadyToSyncWithoutMetadata) { |
| base::HistogramTester histogram_tester; |
| |
| // Expect no call to save metadata. |
| EXPECT_CALL(*schedule_save_closure(), Run).Times(0); |
| // Call ClearMetadataIfStopped() before ModelReadyToSync(). This should set |
| // the flag for a pending clearing of metadata. |
| processor()->ClearMetadataIfStopped(); |
| |
| SimulateModelReadyToSyncWithoutLocalMetadata(); |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Nothing recorded to the histograms. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.DelayedClear", 0); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldWipeBookmarksIfMetadataClearedWhileStopped) { |
| ResetDataTypeProcessor(syncer::WipeModelUponSyncDisabledBehavior::kAlways); |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| processor()->OnSyncStopping(syncer::KEEP_METADATA); |
| |
| bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), /*index=*/0, |
| u"foo", GURL("http://www.example.com")); |
| |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Expect saving empty metadata upon call to ClearMetadataIfStopped(). |
| EXPECT_CALL(*schedule_save_closure(), Run); |
| |
| processor()->ClearMetadataIfStopped(); |
| // Should clear the tracker even if already stopped. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| // Expect an entry to the histogram. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 1); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 1); |
| |
| // Local bookmarks should have been deleted. |
| EXPECT_FALSE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| } |
| |
| TEST_F( |
| BookmarkDataTypeProcessorTest, |
| ShouldNotTrackMetadataIfMetadataClearedWhileStoppedUponModelReadyToSync) { |
| ResetDataTypeProcessor(syncer::WipeModelUponSyncDisabledBehavior::kAlways); |
| |
| base::HistogramTester histogram_tester; |
| |
| // Expect no call to save metadata before ModelReadyToSync(). |
| EXPECT_CALL(*schedule_save_closure(), Run).Times(0); |
| // Call ClearMetadataIfStopped() before ModelReadyToSync(). This should set |
| // the flag for a pending clearing of metadata. |
| processor()->ClearMetadataIfStopped(); |
| // Nothing recorded to the histograms yet. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.DelayedClear", 0); |
| |
| // Mimic some bookmarks being loaded as part of startup. |
| const bookmarks::BookmarkNode* bookmarknode = bookmark_model()->AddURL( |
| bookmark_model()->bookmark_bar_node(), /*index=*/0, u"foo", |
| GURL("http://www.example.com")); |
| |
| ASSERT_FALSE(processor()->IsTrackingMetadata()); |
| ASSERT_TRUE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| |
| sync_pb::BookmarkModelMetadata model_metadata = |
| CreateMetadataForPermanentNodes(bookmark_model()); |
| *model_metadata.add_bookmarks_metadata() = |
| CreateNodeMetadata(bookmarknode, "node_id1"); |
| |
| // Expect saving empty metadata from ModelReadyToSync() while processing the |
| // pending clearing of metadata. |
| EXPECT_CALL(*schedule_save_closure(), Run); |
| // ModelReadyToSync() should take into account the pending metadata clearing |
| // flag and clear the metadata. |
| processor()->ModelReadyToSync(model_metadata.SerializeAsString(), |
| schedule_save_closure()->Get(), |
| bookmark_model()); |
| // Tracker should have not been set. |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| // Expect recording of the delayed clear. |
| histogram_tester.ExpectTotalCount("Sync.ClearMetadataWhileStopped", 1); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.ImmediateClear", 0); |
| histogram_tester.ExpectTotalCount( |
| "Sync.ClearMetadataWhileStopped.DelayedClear", 1); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, ShouldWipeBookmarksIfCacheGuidMismatch) { |
| ResetDataTypeProcessor(syncer::WipeModelUponSyncDisabledBehavior::kAlways); |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| ASSERT_TRUE(processor()->IsTrackingMetadata()); |
| bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), /*index=*/0, |
| u"foo", GURL("http://www.example.com")); |
| |
| ASSERT_TRUE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| |
| SimulateOnSyncStarting("unexpected_cache_guid"); |
| |
| EXPECT_FALSE(processor()->IsTrackingMetadata()); |
| |
| // Local bookmarks should have been deleted. |
| EXPECT_FALSE( |
| bookmark_model()->underlying_model()->HasUserCreatedBookmarksOrFolders()); |
| } |
| |
| TEST_F(BookmarkDataTypeProcessorTest, |
| ShouldRecordNumUnsyncedEntitiesOnModelReady) { |
| { |
| base::HistogramTester histogram_tester; |
| SimulateModelReadyToSyncWithInitialSyncDone(); |
| // There are no local unsynced entities. |
| histogram_tester.ExpectUniqueSample( |
| "Sync.DataTypeNumUnsyncedEntitiesOnModelReady.BOOKMARK", /*sample=*/0, |
| /*expected_bucket_count=*/1); |
| } |
| |
| // Add a bookmark, but don't sync it. |
| bookmark_model()->AddURL(bookmark_model()->bookmark_bar_node(), |
| /*index=*/0, u"Title", GURL("http://www.url.com")); |
| sync_pb::BookmarkModelMetadata model_metadata = |
| processor()->GetTrackerForTest()->BuildBookmarkModelMetadata(); |
| |
| // Simulate the browser restart. |
| ResetDataTypeProcessor(); |
| |
| { |
| base::HistogramTester histogram_tester; |
| |
| std::string metadata_str; |
| model_metadata.SerializeToString(&metadata_str); |
| processor()->ModelReadyToSync(metadata_str, base::DoNothing(), |
| bookmark_model()); |
| |
| // Bookmark added above is unsynced. |
| histogram_tester.ExpectUniqueSample( |
| "Sync.DataTypeNumUnsyncedEntitiesOnModelReady.BOOKMARK", /*sample=*/1, |
| /*expected_bucket_count=*/1); |
| } |
| } |
| |
| } // namespace |
| |
| } // namespace sync_bookmarks |