|  | // 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; | 
|  |  | 
|  | constexpr char kKey[] = "key"; | 
|  | constexpr char kId[] = "id"; | 
|  | constexpr char kName[] = "name"; | 
|  | constexpr char kValue1[] = "value1"; | 
|  | constexpr char kValue2[] = "value2"; | 
|  | constexpr 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, hash(), "", ctime_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ProcessorEntity> CreateNewWithEmptyStorageKey() { | 
|  | return ProcessorEntity::CreateNew("", hash(), "", ctime_); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ProcessorEntity> CreateSynced() { | 
|  | std::unique_ptr<ProcessorEntity> entity = CreateNew(); | 
|  | UpdateResponseData update = | 
|  | GenerateUpdate(*entity, hash(), 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, hash(), kId, kName, kValue1, ctime_, 1); | 
|  | entity->RecordAcceptedRemoteUpdate( | 
|  | update, /*trimmed_specifics=*/{}, | 
|  | /*unique_position=*/GenerateUniquePosition(hash())); | 
|  | CHECK(!entity->IsUnsynced()); | 
|  | return entity; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ProcessorEntity> RestoreFromMetadata( | 
|  | sync_pb::EntityMetadata entity_metadata) { | 
|  | return ProcessorEntity::CreateFromMetadata(kKey, | 
|  | std::move(entity_metadata)); | 
|  | } | 
|  | const ClientTagHash& hash() const { return hash_; } | 
|  | base::Time ctime() const { return ctime_; } | 
|  |  | 
|  | private: | 
|  | const base::Time ctime_; | 
|  | const ClientTagHash hash_{ClientTagHash::FromHashed("hash")}; | 
|  | }; | 
|  |  | 
|  | // 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(hash().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(hash(), 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(hash(), 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(hash()); | 
|  | std::unique_ptr<ProcessorEntity> entity = CreateNew(); | 
|  | entity->RecordLocalUpdate(GenerateEntityData(hash(), 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(hash(), 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(hash(), 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, hash(), 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(hash()); | 
|  |  | 
|  | std::unique_ptr<ProcessorEntity> entity = CreateNew(); | 
|  | UpdateResponseData update = GenerateUpdate( | 
|  | *entity, hash(), 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, hash(), 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, hash(), 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, hash(), 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, hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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(hash(), 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, hash(), 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, hash(), 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(hash(), 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( | 
|  | hash(), "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( | 
|  | hash(), "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, hash(), 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( | 
|  | hash(), "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 |