| // Copyright 2014 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/model/processor_entity.h" |
| |
| #include <utility> |
| |
| #include "base/hash/hash.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/protobuf_matchers.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "components/sync/base/client_tag_hash.h" |
| #include "components/sync/base/collaboration_id.h" |
| #include "components/sync/base/data_type.h" |
| #include "components/sync/base/deletion_origin.h" |
| #include "components/sync/base/features.h" |
| #include "components/sync/base/time.h" |
| #include "components/sync/base/unique_position.h" |
| #include "components/sync/engine/commit_and_get_updates_types.h" |
| #include "components/sync/protocol/collaboration_metadata.h" |
| #include "components/sync/protocol/entity_metadata.pb.h" |
| #include "components/sync/protocol/entity_specifics.pb.h" |
| #include "components/sync/protocol/unique_position.pb.h" |
| #include "components/version_info/version_info.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace syncer { |
| |
| namespace { |
| |
| using base::test::EqualsProto; |
| using testing::Not; |
| |
| const char kKey[] = "key"; |
| const ClientTagHash kHash = ClientTagHash::FromHashed("hash"); |
| const char kId[] = "id"; |
| const char kName[] = "name"; |
| const char kValue1[] = "value1"; |
| const char kValue2[] = "value2"; |
| const char kValue3[] = "value3"; |
| |
| sync_pb::EntitySpecifics GenerateSpecifics(const std::string& name, |
| const std::string& value) { |
| sync_pb::EntitySpecifics specifics; |
| specifics.mutable_preference()->set_name(name); |
| specifics.mutable_preference()->set_value(value); |
| return specifics; |
| } |
| |
| std::unique_ptr<EntityData> GenerateEntityData(const ClientTagHash& hash, |
| const std::string& name, |
| const std::string& value) { |
| std::unique_ptr<EntityData> entity_data(new EntityData()); |
| entity_data->client_tag_hash = hash; |
| entity_data->specifics = GenerateSpecifics(name, value); |
| entity_data->name = name; |
| return entity_data; |
| } |
| |
| sync_pb::EntitySpecifics GenerateSharedTabGroupDataSpecifics( |
| const std::string& guid) { |
| sync_pb::EntitySpecifics specifics; |
| specifics.mutable_shared_tab_group_data()->set_guid(guid); |
| return specifics; |
| } |
| |
| std::unique_ptr<EntityData> GenerateSharedTabGroupDataEntityData( |
| const ClientTagHash& hash, |
| const std::string& guid, |
| CollaborationMetadata collaboration_metadata) { |
| std::unique_ptr<EntityData> entity_data(new EntityData()); |
| entity_data->client_tag_hash = hash; |
| entity_data->specifics = GenerateSharedTabGroupDataSpecifics(guid); |
| entity_data->collaboration_metadata = std::move(collaboration_metadata); |
| return entity_data; |
| } |
| |
| UpdateResponseData GenerateSharedTabGroupDataUpdate( |
| const ProcessorEntity& entity, |
| const ClientTagHash& hash, |
| const std::string& server_id, |
| const std::string& guid, |
| const base::Time& mtime, |
| int64_t version, |
| CollaborationMetadata collaboration_metadata) { |
| std::unique_ptr<EntityData> data = GenerateSharedTabGroupDataEntityData( |
| hash, guid, std::move(collaboration_metadata)); |
| data->id = server_id; |
| data->modification_time = mtime; |
| UpdateResponseData update; |
| update.entity = std::move(*data); |
| update.response_version = version; |
| return update; |
| } |
| |
| UpdateResponseData GenerateUpdate(const ProcessorEntity& entity, |
| const ClientTagHash& hash, |
| const std::string& id, |
| const std::string& name, |
| const std::string& value, |
| const base::Time& mtime, |
| int64_t version) { |
| std::unique_ptr<EntityData> data = GenerateEntityData(hash, name, value); |
| data->id = id; |
| data->modification_time = mtime; |
| UpdateResponseData update; |
| update.entity = std::move(*data); |
| update.response_version = version; |
| return update; |
| } |
| |
| UpdateResponseData GenerateTombstone(const ProcessorEntity& entity, |
| const ClientTagHash& hash, |
| const std::string& id, |
| const std::string& name, |
| const base::Time& mtime, |
| int64_t version) { |
| std::unique_ptr<EntityData> data = std::make_unique<EntityData>(); |
| data->client_tag_hash = hash; |
| data->name = name; |
| data->id = id; |
| data->modification_time = mtime; |
| UpdateResponseData update; |
| update.entity = std::move(*data); |
| update.response_version = version; |
| return update; |
| } |
| |
| CommitResponseData GenerateAckData(const CommitRequestData& request, |
| const std::string id, |
| int64_t version) { |
| CommitResponseData response; |
| response.id = id; |
| response.client_tag_hash = request.entity->client_tag_hash; |
| response.sequence_number = request.sequence_number; |
| response.response_version = version; |
| response.specifics_hash = request.specifics_hash; |
| return response; |
| } |
| |
| sync_pb::UniquePosition GenerateUniquePosition(const ClientTagHash& hash) { |
| return UniquePosition::InitialPosition(UniquePosition::GenerateSuffix(hash)) |
| .ToProto(); |
| } |
| |
| } // namespace |
| |
| // Some simple sanity tests for the ProcessorEntity. |
| // |
| // A lot of the more complicated sync logic is implemented in the |
| // ClientTagBasedDataTypeProcessor that owns the ProcessorEntity. We |
| // can't unit test it here. |
| // |
| // Instead, we focus on simple tests to make sure that variables are getting |
| // properly intialized and flags properly set. Anything more complicated would |
| // be a redundant and incomplete version of the ClientTagBasedDataTypeProcessor |
| // tests. |
| class ProcessorEntityTest : public ::testing::Test { |
| public: |
| ProcessorEntityTest() : ctime_(base::Time::Now() - base::Seconds(1)) {} |
| |
| std::unique_ptr<ProcessorEntity> CreateNew() { |
| return ProcessorEntity::CreateNew(kKey, kHash, "", ctime_); |
| } |
| |
| std::unique_ptr<ProcessorEntity> CreateNewWithEmptyStorageKey() { |
| return ProcessorEntity::CreateNew("", kHash, "", ctime_); |
| } |
| |
| std::unique_ptr<ProcessorEntity> CreateSynced() { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| UpdateResponseData update = |
| GenerateUpdate(*entity, kHash, kId, kName, kValue1, ctime_, 1); |
| entity->RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| DCHECK(!entity->IsUnsynced()); |
| return entity; |
| } |
| |
| std::unique_ptr<ProcessorEntity> CreateSyncedWithUniquePosition() { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| UpdateResponseData update = |
| GenerateUpdate(*entity, kHash, kId, kName, kValue1, ctime_, 1); |
| entity->RecordAcceptedRemoteUpdate( |
| update, /*trimmed_specifics=*/{}, |
| /*unique_position=*/GenerateUniquePosition(kHash)); |
| CHECK(!entity->IsUnsynced()); |
| return entity; |
| } |
| |
| std::unique_ptr<ProcessorEntity> RestoreFromMetadata( |
| sync_pb::EntityMetadata entity_metadata) { |
| return ProcessorEntity::CreateFromMetadata(kKey, |
| std::move(entity_metadata)); |
| } |
| |
| const base::Time ctime_; |
| }; |
| |
| // Test the state of the default new entity. |
| TEST_F(ProcessorEntityTest, DefaultEntity) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| |
| EXPECT_EQ(kKey, entity->storage_key()); |
| EXPECT_EQ(kHash.value(), entity->metadata().client_tag_hash()); |
| EXPECT_EQ("", entity->metadata().server_id()); |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| EXPECT_EQ(0, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(kUncommittedVersion, entity->metadata().server_version()); |
| EXPECT_EQ(TimeToProtoTime(ctime_), entity->metadata().creation_time()); |
| EXPECT_EQ(0, entity->metadata().modification_time()); |
| EXPECT_TRUE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->IsVersionAlreadyKnown(1)); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| // Test creating and commiting a new local item. |
| TEST_F(ProcessorEntityTest, NewLocalItem) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| EXPECT_EQ("", entity->metadata().server_id()); |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(kUncommittedVersion, entity->metadata().server_version()); |
| EXPECT_NE(0, entity->metadata().modification_time()); |
| EXPECT_FALSE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| EXPECT_FALSE(entity->metadata().has_unique_position()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_TRUE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->IsVersionAlreadyKnown(1)); |
| EXPECT_TRUE(entity->HasCommitData()); |
| |
| EXPECT_EQ(kValue1, |
| entity->GetCommitDataForTesting().specifics.preference().value()); |
| |
| // Generate a commit request. The metadata should not change. |
| const sync_pb::EntityMetadata metadata_v1 = entity->metadata(); |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_EQ(metadata_v1.SerializeAsString(), |
| entity->metadata().SerializeAsString()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->IsVersionAlreadyKnown(1)); |
| |
| const EntityData& data = *request.entity; |
| EXPECT_EQ("", data.id); |
| EXPECT_EQ(kHash, data.client_tag_hash); |
| EXPECT_EQ(kName, data.name); |
| EXPECT_EQ(kValue1, data.specifics.preference().value()); |
| EXPECT_EQ(TimeToProtoTime(ctime_), TimeToProtoTime(data.creation_time)); |
| EXPECT_EQ(entity->metadata().modification_time(), |
| TimeToProtoTime(data.modification_time)); |
| EXPECT_FALSE(data.is_deleted()); |
| EXPECT_EQ(1, request.sequence_number); |
| EXPECT_EQ(kUncommittedVersion, request.base_version); |
| EXPECT_EQ(entity->metadata().specifics_hash(), request.specifics_hash); |
| |
| // Ack the commit. |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 1), false); |
| |
| EXPECT_EQ(kId, entity->metadata().server_id()); |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(1, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(1, entity->metadata().server_version()); |
| EXPECT_EQ(metadata_v1.creation_time(), entity->metadata().creation_time()); |
| EXPECT_EQ(metadata_v1.modification_time(), |
| entity->metadata().modification_time()); |
| EXPECT_FALSE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| EXPECT_FALSE(entity->metadata().has_unique_position()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_TRUE(entity->IsVersionAlreadyKnown(1)); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldStoreUniquePositionForNewLocalItem) { |
| const sync_pb::UniquePosition unique_position = GenerateUniquePosition(kHash); |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, unique_position); |
| EXPECT_TRUE(entity->metadata().has_unique_position()); |
| EXPECT_THAT(entity->metadata().unique_position(), |
| EqualsProto(unique_position)); |
| |
| // Generate a commit request. The metadata should not change. |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_THAT(entity->metadata().unique_position(), |
| EqualsProto(unique_position)); |
| |
| // Ack the commit. |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 1), false); |
| EXPECT_THAT(entity->metadata().unique_position(), |
| EqualsProto(unique_position)); |
| } |
| |
| // Test handling of invalid server version. |
| TEST_F(ProcessorEntityTest, |
| ShouldIgnoreCommitResponseWithInvalidServerVersion) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| CommitRequestData request; |
| |
| // Ack the commit - set current version to 2. |
| entity->InitializeCommitRequestData(&request); |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 2), false); |
| |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| ASSERT_EQ(2, entity->metadata().server_version()); |
| ASSERT_EQ(2, entity->metadata().sequence_number()); |
| ASSERT_EQ(1, entity->metadata().acked_sequence_number()); |
| |
| // Ack the commit - try server version 1. |
| entity->InitializeCommitRequestData(&request); |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 1), false); |
| // no update as the server responds with an older version. |
| EXPECT_EQ(2, entity->metadata().server_version()); |
| } |
| |
| // Test state for a newly synced server item. |
| TEST_F(ProcessorEntityTest, NewServerItem) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| |
| const base::Time mtime = base::Time::Now(); |
| UpdateResponseData update = |
| GenerateUpdate(*entity, kHash, kId, kName, kValue1, mtime, 10); |
| entity->RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| EXPECT_EQ(kId, entity->metadata().server_id()); |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| EXPECT_EQ(0, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(10, entity->metadata().server_version()); |
| EXPECT_EQ(TimeToProtoTime(mtime), entity->metadata().modification_time()); |
| EXPECT_FALSE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_TRUE(entity->IsVersionAlreadyKnown(9)); |
| EXPECT_TRUE(entity->IsVersionAlreadyKnown(10)); |
| EXPECT_FALSE(entity->IsVersionAlreadyKnown(11)); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldStoreUniquePositionForNewServerItem) { |
| const sync_pb::UniquePosition unique_position = GenerateUniquePosition(kHash); |
| |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| UpdateResponseData update = GenerateUpdate( |
| *entity, kHash, kId, kName, kValue1, /*mtime=*/base::Time::Now(), 10); |
| entity->RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{}, |
| unique_position); |
| |
| EXPECT_THAT(entity->metadata().unique_position(), |
| EqualsProto(unique_position)); |
| } |
| |
| // Test creating an entity for new server item with empty storage key, applying |
| // update and updating storage key. |
| TEST_F(ProcessorEntityTest, NewServerItem_EmptyStorageKey) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNewWithEmptyStorageKey(); |
| |
| EXPECT_EQ("", entity->storage_key()); |
| |
| const base::Time mtime = base::Time::Now(); |
| UpdateResponseData update = |
| GenerateUpdate(*entity, kHash, kId, kName, kValue1, mtime, 10); |
| entity->RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| entity->SetStorageKey(kKey); |
| EXPECT_EQ(kKey, entity->storage_key()); |
| } |
| |
| // Test state for a tombstone received for a previously unknown item. |
| TEST_F(ProcessorEntityTest, NewServerTombstone) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| |
| const base::Time mtime = base::Time::Now(); |
| UpdateResponseData tombstone = |
| GenerateTombstone(*entity, kHash, kId, kName, mtime, 1); |
| entity->RecordAcceptedRemoteUpdate(tombstone, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| EXPECT_EQ(kId, entity->metadata().server_id()); |
| EXPECT_TRUE(entity->metadata().is_deleted()); |
| EXPECT_EQ(0, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(1, entity->metadata().server_version()); |
| EXPECT_EQ(TimeToProtoTime(mtime), entity->metadata().modification_time()); |
| EXPECT_TRUE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_TRUE(entity->CanClearMetadata()); |
| EXPECT_TRUE(entity->IsVersionAlreadyKnown(1)); |
| EXPECT_FALSE(entity->IsVersionAlreadyKnown(2)); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| // Apply a deletion update to a synced item. |
| TEST_F(ProcessorEntityTest, ServerTombstone) { |
| // Start with a non-deleted state with version 1. |
| std::unique_ptr<ProcessorEntity> entity = CreateSynced(); |
| // A deletion update one version later. |
| const base::Time mtime = base::Time::Now(); |
| UpdateResponseData tombstone = |
| GenerateTombstone(*entity, kHash, kId, kName, mtime, 2); |
| entity->RecordAcceptedRemoteUpdate(tombstone, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| EXPECT_TRUE(entity->metadata().is_deleted()); |
| EXPECT_EQ(0, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(2, entity->metadata().server_version()); |
| EXPECT_EQ(TimeToProtoTime(mtime), entity->metadata().modification_time()); |
| EXPECT_TRUE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_TRUE(entity->CanClearMetadata()); |
| EXPECT_TRUE(entity->IsVersionAlreadyKnown(2)); |
| EXPECT_FALSE(entity->IsVersionAlreadyKnown(3)); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldResetUniquePositionOnServerUpdate) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSyncedWithUniquePosition(); |
| ASSERT_TRUE(entity->metadata().has_unique_position()); |
| |
| UpdateResponseData update = GenerateUpdate( |
| *entity, kHash, kId, kName, kValue1, /*mtime=*/base::Time::Now(), 10); |
| entity->RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| EXPECT_FALSE(entity->metadata().has_unique_position()); |
| } |
| |
| // Test a local change of a synced item. |
| TEST_F(ProcessorEntityTest, LocalChange) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSynced(); |
| const int64_t mtime_v0 = entity->metadata().modification_time(); |
| const std::string specifics_hash_v0 = entity->metadata().specifics_hash(); |
| |
| // Make a local change with different specifics. |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| const int64_t mtime_v1 = entity->metadata().modification_time(); |
| const std::string specifics_hash_v1 = entity->metadata().specifics_hash(); |
| |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(1, entity->metadata().server_version()); |
| EXPECT_LT(mtime_v0, mtime_v1); |
| EXPECT_NE(specifics_hash_v0, specifics_hash_v1); |
| EXPECT_EQ(specifics_hash_v0, entity->metadata().base_specifics_hash()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_TRUE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_TRUE(entity->HasCommitData()); |
| |
| // Make a commit. |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| |
| EXPECT_EQ(kId, request.entity->id); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| |
| // Ack the commit. |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 2), false); |
| |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(1, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(2, entity->metadata().server_version()); |
| EXPECT_EQ(mtime_v1, entity->metadata().modification_time()); |
| EXPECT_EQ(specifics_hash_v1, entity->metadata().specifics_hash()); |
| EXPECT_EQ("", entity->metadata().base_specifics_hash()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldStoreNewUniquePositionOnLocalUpdate) { |
| const sync_pb::UniquePosition new_unique_position = |
| GenerateUniquePosition(ClientTagHash::FromHashed("new_hash")); |
| std::unique_ptr<ProcessorEntity> entity = CreateSyncedWithUniquePosition(); |
| ASSERT_TRUE(entity->metadata().has_unique_position()); |
| ASSERT_THAT(entity->metadata().unique_position(), |
| Not(EqualsProto(new_unique_position))); |
| |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, new_unique_position); |
| EXPECT_THAT(entity->metadata().unique_position(), |
| EqualsProto(new_unique_position)); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldResetUniquePositionOnLocalUpdate) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSyncedWithUniquePosition(); |
| ASSERT_TRUE(entity->metadata().has_unique_position()); |
| |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| EXPECT_FALSE(entity->metadata().has_unique_position()); |
| } |
| |
| // Test a local deletion of a synced item. |
| TEST_F(ProcessorEntityTest, LocalDeletion) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSynced(); |
| const int64_t mtime = entity->metadata().modification_time(); |
| const std::string specifics_hash = entity->metadata().specifics_hash(); |
| |
| // Make a local delete. |
| entity->RecordLocalDeletion(DeletionOrigin::Unspecified()); |
| |
| EXPECT_TRUE(entity->metadata().is_deleted()); |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(1, entity->metadata().server_version()); |
| EXPECT_LT(mtime, entity->metadata().modification_time()); |
| EXPECT_TRUE(entity->metadata().specifics_hash().empty()); |
| EXPECT_EQ(specifics_hash, entity->metadata().base_specifics_hash()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_TRUE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->HasCommitData()); |
| |
| // Generate a commit request. The metadata should not change. |
| const sync_pb::EntityMetadata metadata_v1 = entity->metadata(); |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_EQ(metadata_v1.SerializeAsString(), |
| entity->metadata().SerializeAsString()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->HasCommitData()); |
| |
| const EntityData& data = *request.entity; |
| EXPECT_EQ(kId, data.id); |
| EXPECT_EQ(kHash, data.client_tag_hash); |
| EXPECT_EQ("", data.name); |
| EXPECT_EQ(TimeToProtoTime(ctime_), TimeToProtoTime(data.creation_time)); |
| EXPECT_EQ(entity->metadata().modification_time(), |
| TimeToProtoTime(data.modification_time)); |
| EXPECT_TRUE(data.is_deleted()); |
| EXPECT_EQ(1, request.sequence_number); |
| EXPECT_EQ(1, request.base_version); |
| EXPECT_EQ(entity->metadata().specifics_hash(), request.specifics_hash); |
| EXPECT_FALSE(entity->metadata().has_deletion_origin()); |
| |
| // Ack the deletion. |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 2), false); |
| |
| EXPECT_TRUE(entity->metadata().is_deleted()); |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(1, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(2, entity->metadata().server_version()); |
| EXPECT_EQ(metadata_v1.modification_time(), |
| entity->metadata().modification_time()); |
| EXPECT_TRUE(entity->metadata().specifics_hash().empty()); |
| EXPECT_TRUE(entity->metadata().base_specifics_hash().empty()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_TRUE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldResetUniquePositionOnLocalDeletion) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSyncedWithUniquePosition(); |
| ASSERT_TRUE(entity->metadata().has_unique_position()); |
| |
| entity->RecordLocalDeletion(DeletionOrigin::Unspecified()); |
| |
| EXPECT_FALSE(entity->metadata().has_unique_position()); |
| } |
| |
| TEST_F(ProcessorEntityTest, LocalDeletionWithSpecifiedOrigin) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSynced(); |
| const std::string specifics_hash = entity->metadata().specifics_hash(); |
| const base::Location location = FROM_HERE; |
| |
| // Make a local delete. |
| entity->RecordLocalDeletion(DeletionOrigin::FromLocation(location)); |
| |
| ASSERT_TRUE(entity->metadata().is_deleted()); |
| ASSERT_TRUE(entity->IsUnsynced()); |
| ASSERT_TRUE(entity->RequiresCommitRequest()); |
| |
| // Generate a commit request. The metadata should not change. |
| const sync_pb::EntityMetadata metadata_v1 = entity->metadata(); |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_EQ(metadata_v1.SerializeAsString(), |
| entity->metadata().SerializeAsString()); |
| |
| EXPECT_TRUE(entity->metadata().has_deletion_origin()); |
| EXPECT_EQ(location.line_number(), |
| entity->metadata().deletion_origin().file_line_number()); |
| EXPECT_EQ(base::PersistentHash(location.file_name()), |
| entity->metadata().deletion_origin().file_name_hash()); |
| EXPECT_TRUE(entity->metadata().deletion_origin().has_chromium_version()); |
| } |
| |
| // Test a local deletion followed by an undeletion (creation). |
| TEST_F(ProcessorEntityTest, LocalUndeletion) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSynced(); |
| const std::string specifics_hash = entity->metadata().specifics_hash(); |
| |
| entity->RecordLocalDeletion(DeletionOrigin::FromLocation(FROM_HERE)); |
| ASSERT_TRUE(entity->metadata().is_deleted()); |
| ASSERT_TRUE(entity->IsUnsynced()); |
| ASSERT_EQ(1, entity->metadata().sequence_number()); |
| |
| // Undelete the entity with different specifics. |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| const std::string specifics_hash_v1 = entity->metadata().specifics_hash(); |
| ASSERT_NE(specifics_hash_v1, specifics_hash); |
| |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| EXPECT_EQ(2, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(1, entity->metadata().server_version()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_TRUE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_TRUE(entity->HasCommitData()); |
| |
| // Make a commit. |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| |
| EXPECT_EQ(kId, request.entity->id); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| |
| // Ack the commit. |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 2), false); |
| |
| EXPECT_EQ(2, entity->metadata().sequence_number()); |
| EXPECT_EQ(2, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(2, entity->metadata().server_version()); |
| EXPECT_EQ(specifics_hash_v1, entity->metadata().specifics_hash()); |
| EXPECT_EQ("", entity->metadata().base_specifics_hash()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldStoreUniquePositionForLocalUndeletion) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSyncedWithUniquePosition(); |
| entity->RecordLocalDeletion(DeletionOrigin::Unspecified()); |
| ASSERT_FALSE(entity->metadata().has_unique_position()); |
| |
| // Undelete the entity with unique position. |
| const sync_pb::UniquePosition unique_position = |
| GenerateUniquePosition(ClientTagHash::FromHashed("new_hash")); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, unique_position); |
| |
| EXPECT_THAT(entity->metadata().unique_position(), |
| EqualsProto(unique_position)); |
| } |
| |
| // Test that hashes and sequence numbers are handled correctly for the "commit |
| // commit, ack ack" case. |
| TEST_F(ProcessorEntityTest, LocalChangesInterleaved) { |
| std::unique_ptr<ProcessorEntity> entity = CreateSynced(); |
| const std::string specifics_hash_v0 = entity->metadata().specifics_hash(); |
| |
| // Make the first change. |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| const std::string specifics_hash_v1 = entity->metadata().specifics_hash(); |
| |
| EXPECT_EQ(1, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_NE(specifics_hash_v0, specifics_hash_v1); |
| EXPECT_EQ(specifics_hash_v0, entity->metadata().base_specifics_hash()); |
| |
| // Request the first commit. |
| CommitRequestData request_v1; |
| entity->InitializeCommitRequestData(&request_v1); |
| |
| // Make the second change. |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue3), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| const std::string specifics_hash_v2 = entity->metadata().specifics_hash(); |
| |
| EXPECT_EQ(2, entity->metadata().sequence_number()); |
| EXPECT_EQ(0, entity->metadata().acked_sequence_number()); |
| EXPECT_NE(specifics_hash_v1, specifics_hash_v2); |
| EXPECT_EQ(specifics_hash_v0, entity->metadata().base_specifics_hash()); |
| |
| // Request the second commit. |
| CommitRequestData request_v2; |
| entity->InitializeCommitRequestData(&request_v2); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| |
| // Ack the first commit. |
| entity->ReceiveCommitResponse(GenerateAckData(request_v1, kId, 2), false); |
| |
| EXPECT_EQ(2, entity->metadata().sequence_number()); |
| EXPECT_EQ(1, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(2, entity->metadata().server_version()); |
| EXPECT_EQ(specifics_hash_v2, entity->metadata().specifics_hash()); |
| EXPECT_EQ(specifics_hash_v1, entity->metadata().base_specifics_hash()); |
| |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| // Commit data has been moved already to the request. |
| EXPECT_FALSE(entity->HasCommitData()); |
| |
| // Ack the second commit. |
| entity->ReceiveCommitResponse(GenerateAckData(request_v2, kId, 3), false); |
| |
| EXPECT_EQ(2, entity->metadata().sequence_number()); |
| EXPECT_EQ(2, entity->metadata().acked_sequence_number()); |
| EXPECT_EQ(3, entity->metadata().server_version()); |
| EXPECT_EQ(specifics_hash_v2, entity->metadata().specifics_hash()); |
| EXPECT_EQ("", entity->metadata().base_specifics_hash()); |
| |
| EXPECT_FALSE(entity->IsUnsynced()); |
| EXPECT_FALSE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_FALSE(entity->CanClearMetadata()); |
| EXPECT_FALSE(entity->HasCommitData()); |
| } |
| |
| // Tests that updating entity id with commit response while next local change is |
| // pending correctly updates that change's id and version. |
| TEST_F(ProcessorEntityTest, NewLocalChangeUpdatedId) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| // Create new local change. Make sure initial id is empty. |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_TRUE(request.entity->id.empty()); |
| |
| // Before receiving commit response make local modification to the entity. |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue2), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| entity->ReceiveCommitResponse(GenerateAckData(request, kId, 1), false); |
| |
| // Receiving commit response with valid id should update |
| // ProcessorEntity. Consecutive commit requests should include updated |
| // id. |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_EQ(kId, request.entity->id); |
| EXPECT_EQ(1, request.base_version); |
| } |
| |
| // Tests that entity restored after restart accepts specifics that don't match |
| // the ones passed originally to RecordLocalUpdate. |
| TEST_F(ProcessorEntityTest, RestoredLocalChangeWithUpdatedSpecifics) { |
| // Create new entity and preserver its metadata. |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| sync_pb::EntityMetadata entity_metadata = entity->metadata(); |
| |
| // Restore entity from metadata and emulate bridge passing different specifics |
| // to SetCommitData. |
| entity = RestoreFromMetadata(std::move(entity_metadata)); |
| std::unique_ptr<EntityData> entity_data = |
| GenerateEntityData(kHash, kName, kValue2); |
| entity->SetCommitData(std::move(entity_data)); |
| |
| // No verification is necessary. SetCommitData shouldn't DCHECK. |
| } |
| |
| // Tests the scenario where a local creation conflicts with a remote deletion, |
| // where usually (and in this test) local wins. In this case, the remote update |
| // should be ignored but the server IDs should be updated. |
| TEST_F(ProcessorEntityTest, LocalCreationConflictsWithServerTombstone) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| ASSERT_TRUE(entity->IsUnsynced()); |
| ASSERT_TRUE(entity->RequiresCommitRequest()); |
| ASSERT_FALSE(entity->RequiresCommitData()); |
| ASSERT_TRUE(entity->HasCommitData()); |
| ASSERT_FALSE(entity->metadata().is_deleted()); |
| ASSERT_TRUE(entity->metadata().server_id().empty()); |
| |
| // Before anything gets committed, we receive a remote tombstone, but local |
| // would usually win so the remote update is ignored. |
| UpdateResponseData tombstone = |
| GenerateTombstone(*entity, kHash, kId, kName, base::Time::Now(), 2); |
| entity->RecordIgnoredRemoteUpdate(tombstone); |
| |
| EXPECT_EQ(kId, entity->metadata().server_id()); |
| EXPECT_TRUE(entity->IsUnsynced()); |
| EXPECT_TRUE(entity->RequiresCommitRequest()); |
| EXPECT_FALSE(entity->RequiresCommitData()); |
| EXPECT_TRUE(entity->HasCommitData()); |
| EXPECT_FALSE(entity->metadata().is_deleted()); |
| |
| // Generate a commit request. The server ID should have been reused from the |
| // otherwise ignored update. |
| const sync_pb::EntityMetadata metadata_v1 = entity->metadata(); |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| EXPECT_EQ(kId, request.entity->id); |
| } |
| |
| TEST_F(ProcessorEntityTest, UpdatesSpecificsCacheOnRemoteUpdates) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| const base::Time mtime = base::Time::Now(); |
| UpdateResponseData update = |
| GenerateUpdate(*entity, kHash, kId, kName, kValue1, mtime, 10); |
| sync_pb::EntitySpecifics specifics_for_caching = |
| GenerateSpecifics(kName, kValue2); |
| entity->RecordAcceptedRemoteUpdate(update, specifics_for_caching, |
| /*unique_position=*/std::nullopt); |
| EXPECT_EQ( |
| specifics_for_caching.SerializeAsString(), |
| entity->metadata().possibly_trimmed_base_specifics().SerializeAsString()); |
| } |
| |
| TEST_F(ProcessorEntityTest, UpdatesSpecificsCacheOnLocalUpdates) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| sync_pb::EntitySpecifics specifics_for_caching = |
| GenerateSpecifics(kName, kValue2); |
| entity->RecordLocalUpdate(GenerateEntityData(kHash, kName, kValue1), |
| specifics_for_caching, |
| /*unique_position=*/std::nullopt); |
| EXPECT_EQ( |
| specifics_for_caching.SerializeAsString(), |
| entity->metadata().possibly_trimmed_base_specifics().SerializeAsString()); |
| } |
| |
| TEST_F(ProcessorEntityTest, LocalDeletionRecordsVersionInfo) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalDeletion(DeletionOrigin::FromLocation(FROM_HERE)); |
| std::string expected_version = std::string(version_info::GetVersionNumber()); |
| EXPECT_EQ(expected_version, entity->metadata().deleted_by_version()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldCreateAndCommitNewLocalSharedItem) { |
| const GaiaId kCreatorUserId("creator_user_id"); |
| const GaiaId kUpdaterUserId("updater_user_id"); |
| |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate( |
| GenerateSharedTabGroupDataEntityData( |
| kHash, "guid", |
| CollaborationMetadata::ForLocalChange( |
| /*changed_by=*/kCreatorUserId, CollaborationId("collaboration"))), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| EXPECT_EQ("", entity->metadata().server_id()); |
| EXPECT_EQ(kUncommittedVersion, entity->metadata().server_version()); |
| EXPECT_EQ("collaboration", |
| entity->metadata().collaboration().collaboration_id()); |
| EXPECT_EQ(kCreatorUserId.ToString(), entity->metadata() |
| .collaboration() |
| .last_update_attribution() |
| .obfuscated_gaia_id()); |
| |
| // The same user ID is used as a creator for the first time. |
| EXPECT_EQ(kCreatorUserId.ToString(), entity->metadata() |
| .collaboration() |
| .creation_attribution() |
| .obfuscated_gaia_id()); |
| EXPECT_TRUE(entity->IsUnsynced()); |
| |
| // Generate a commit request. |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| const EntityData& data = *request.entity; |
| ASSERT_TRUE(data.collaboration_metadata.has_value()); |
| EXPECT_EQ(CollaborationId("collaboration"), |
| data.collaboration_metadata->collaboration_id()); |
| |
| // Verify that creator is not updated on the next local update. |
| entity->RecordLocalUpdate( |
| GenerateSharedTabGroupDataEntityData( |
| kHash, "guid", |
| CollaborationMetadata::ForLocalChange( |
| /*changed_by=*/kUpdaterUserId, CollaborationId("collaboration"))), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| EXPECT_EQ(kUpdaterUserId.ToString(), entity->metadata() |
| .collaboration() |
| .last_update_attribution() |
| .obfuscated_gaia_id()); |
| EXPECT_EQ(kCreatorUserId.ToString(), entity->metadata() |
| .collaboration() |
| .creation_attribution() |
| .obfuscated_gaia_id()); |
| EXPECT_NE(entity->metadata() |
| .collaboration() |
| .last_update_attribution() |
| .obfuscated_gaia_id(), |
| entity->metadata() |
| .collaboration() |
| .creation_attribution() |
| .obfuscated_gaia_id()); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldCreateNewRemoteSharedItem) { |
| const std::string kCreatorUserId = "creator_user_id"; |
| const std::string kUpdaterUserId = "updater_user_id"; |
| |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| const base::Time mtime = base::Time::Now(); |
| |
| sync_pb::SyncEntity::CollaborationMetadata collaboration_remote_proto; |
| collaboration_remote_proto.set_collaboration_id("collaboration"); |
| collaboration_remote_proto.mutable_creation_attribution() |
| ->set_obfuscated_gaia_id(kCreatorUserId); |
| collaboration_remote_proto.mutable_last_update_attribution() |
| ->set_obfuscated_gaia_id(kUpdaterUserId); |
| |
| UpdateResponseData update = GenerateSharedTabGroupDataUpdate( |
| *entity, kHash, kId, "guid", mtime, /*version=*/10, |
| CollaborationMetadata::FromRemoteProto(collaboration_remote_proto)); |
| entity->RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| |
| EXPECT_EQ(kId, entity->metadata().server_id()); |
| EXPECT_EQ("collaboration", |
| entity->metadata().collaboration().collaboration_id()); |
| EXPECT_EQ(entity->metadata() |
| .collaboration() |
| .creation_attribution() |
| .obfuscated_gaia_id(), |
| kCreatorUserId); |
| EXPECT_EQ(entity->metadata() |
| .collaboration() |
| .last_update_attribution() |
| .obfuscated_gaia_id(), |
| kUpdaterUserId); |
| } |
| |
| TEST_F(ProcessorEntityTest, ShouldPopulateCollaborationForTombstones) { |
| std::unique_ptr<ProcessorEntity> entity = CreateNew(); |
| entity->RecordLocalUpdate( |
| GenerateSharedTabGroupDataEntityData( |
| kHash, "guid", |
| CollaborationMetadata::ForLocalChange( |
| /*changed_by=*/GaiaId(), CollaborationId("collaboration"))), |
| /*trimmed_specifics=*/{}, |
| /*unique_position=*/std::nullopt); |
| entity->RecordLocalDeletion(DeletionOrigin::Unspecified()); |
| |
| CommitRequestData request; |
| entity->InitializeCommitRequestData(&request); |
| |
| ASSERT_TRUE(request.entity->collaboration_metadata.has_value()); |
| EXPECT_EQ(request.entity->collaboration_metadata->collaboration_id(), |
| CollaborationId("collaboration")); |
| } |
| |
| } // namespace syncer |