blob: ef1b6ccaa1a07ee48774de9c5d5b1bece574742b [file]
// Copyright 2026 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_metadata.h"
#include <memory>
#include "base/time/time.h"
#include "components/sync/base/client_tag_hash.h"
#include "components/sync/base/deletion_origin.h"
#include "components/sync/base/time.h"
#include "components/sync/engine/commit_and_get_updates_types.h"
#include "components/sync/protocol/entity_metadata.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
sync_pb::EntityMetadata CreateValidMetadataProto() {
sync_pb::EntityMetadata metadata;
metadata.set_client_tag_hash("hash");
metadata.set_creation_time(TimeToProtoTime(base::Time::Now()));
metadata.set_sequence_number(1);
metadata.set_acked_sequence_number(0);
return metadata;
}
TEST(ProcessorEntityMetadataTest, ShouldBeValidForValidMetadata) {
EXPECT_TRUE(ProcessorEntityMetadata::IsValid(CreateValidMetadataProto()));
}
TEST(ProcessorEntityMetadataTest, ShouldBeInvalidIfClientTagHashIsEmpty) {
sync_pb::EntityMetadata metadata = CreateValidMetadataProto();
metadata.clear_client_tag_hash();
EXPECT_FALSE(ProcessorEntityMetadata::IsValid(metadata));
}
TEST(ProcessorEntityMetadataTest, ShouldBeInvalidIfCreationTimeIsMissing) {
sync_pb::EntityMetadata metadata = CreateValidMetadataProto();
metadata.clear_creation_time();
EXPECT_FALSE(ProcessorEntityMetadata::IsValid(metadata));
}
TEST(ProcessorEntityMetadataTest,
ShouldBeInvalidIfSequenceNumberIsLessThanAcked) {
sync_pb::EntityMetadata metadata = CreateValidMetadataProto();
metadata.set_sequence_number(0);
metadata.set_acked_sequence_number(1);
EXPECT_FALSE(ProcessorEntityMetadata::IsValid(metadata));
}
TEST(ProcessorEntityMetadataTest,
ShouldBeUnsyncedAfterIncrementSequenceNumber) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), "server_id", base::Time::Now());
ASSERT_FALSE(metadata.IsUnsynced());
metadata.IncrementSequenceNumber();
EXPECT_TRUE(metadata.IsUnsynced());
// Test serialization.
std::unique_ptr<ProcessorEntityMetadata> metadata_restored =
ProcessorEntityMetadata::FromProto(metadata.proto());
ASSERT_NE(metadata_restored, nullptr);
EXPECT_TRUE(metadata_restored->IsUnsynced());
}
TEST(ProcessorEntityMetadataTest, ShouldNotBeUnsyncedAfterCommitResponse) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), "server_id", base::Time::Now());
metadata.IncrementSequenceNumber();
ASSERT_TRUE(metadata.IsUnsynced());
CommitResponseData response;
response.id = "server_id";
response.client_tag_hash = ClientTagHash::FromHashed("hash");
response.sequence_number = 1;
response.response_version = 1;
metadata.RecordCommitResponse(response);
EXPECT_FALSE(metadata.IsUnsynced());
// Test serialization.
std::unique_ptr<ProcessorEntityMetadata> metadata_restored =
ProcessorEntityMetadata::FromProto(metadata.proto());
ASSERT_NE(metadata_restored, nullptr);
EXPECT_FALSE(metadata_restored->IsUnsynced());
}
TEST(ProcessorEntityMetadataTest,
ShouldBeUnsyncedLocalCreationAfterLocalUpdate) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), /*server_id=*/"", base::Time::Now());
ASSERT_FALSE(metadata.IsUnsyncedLocalCreation());
metadata.IncrementSequenceNumber();
EXPECT_TRUE(metadata.IsUnsyncedLocalCreation());
// Test serialization.
std::unique_ptr<ProcessorEntityMetadata> metadata_restored =
ProcessorEntityMetadata::FromProto(metadata.proto());
ASSERT_NE(metadata_restored, nullptr);
EXPECT_TRUE(metadata_restored->IsUnsyncedLocalCreation());
}
TEST(ProcessorEntityMetadataTest,
ShouldNotBeUnsyncedLocalCreationAfterCommitResponse) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), /*server_id=*/"", base::Time::Now());
metadata.IncrementSequenceNumber();
ASSERT_TRUE(metadata.IsUnsyncedLocalCreation());
// Simulate commit response.
CommitResponseData response;
response.id = "server_id";
response.client_tag_hash = ClientTagHash::FromHashed("hash");
response.sequence_number = 1;
response.response_version = 12345;
metadata.RecordCommitResponse(response);
EXPECT_FALSE(metadata.IsUnsyncedLocalCreation());
// Test serialization.
std::unique_ptr<ProcessorEntityMetadata> metadata_restored =
ProcessorEntityMetadata::FromProto(metadata.proto());
ASSERT_NE(metadata_restored, nullptr);
EXPECT_FALSE(metadata_restored->IsUnsyncedLocalCreation());
}
TEST(ProcessorEntityMetadataTest,
ShouldUpdateBaseSpecificsHashOnFirstIncrement) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), "server_id", base::Time::Now());
UpdateResponseData update;
update.entity.id = "server_id";
update.entity.specifics.mutable_preference()->set_name("name");
update.response_version = 1;
metadata.RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{},
/*unique_position=*/std::nullopt);
CommitResponseData response1;
response1.id = "server_id";
response1.sequence_number = metadata.proto().sequence_number();
response1.response_version = 1;
response1.specifics_hash = metadata.proto().specifics_hash();
metadata.RecordCommitResponse(response1);
const std::string initial_hash = metadata.proto().specifics_hash();
ASSERT_FALSE(initial_hash.empty());
// First local update (becomes unsynced, base_specifics_hash set to
// initial_hash).
EntityData local_data;
local_data.specifics.mutable_preference()->set_name("new_name");
metadata.RecordLocalUpdate(local_data, /*trimmed_specifics=*/{},
/*unique_position=*/std::nullopt);
EXPECT_EQ(metadata.proto().base_specifics_hash(), initial_hash);
}
TEST(ProcessorEntityMetadataTest,
ShouldNotUpdateBaseSpecificsHashOnSubsequentIncrements) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), "server_id", base::Time::Now());
UpdateResponseData update;
update.entity.id = "server_id";
update.entity.specifics.mutable_preference()->set_name("name1");
update.response_version = 1;
metadata.RecordAcceptedRemoteUpdate(update, /*trimmed_specifics=*/{},
/*unique_position=*/std::nullopt);
CommitResponseData response2;
response2.id = "server_id";
response2.sequence_number = metadata.proto().sequence_number();
response2.response_version = 1;
response2.specifics_hash = metadata.proto().specifics_hash();
metadata.RecordCommitResponse(response2);
const std::string initial_hash = metadata.proto().specifics_hash();
ASSERT_FALSE(initial_hash.empty());
// First local update (becomes unsynced).
EntityData local_data1;
local_data1.specifics.mutable_preference()->set_name("name2");
metadata.RecordLocalUpdate(local_data1, /*trimmed_specifics=*/{},
/*unique_position=*/std::nullopt);
ASSERT_EQ(metadata.proto().base_specifics_hash(), initial_hash);
// Second local update (already unsynced).
EntityData local_data2;
local_data2.specifics.mutable_preference()->set_name("name3");
metadata.RecordLocalUpdate(local_data2, /*trimmed_specifics=*/{},
/*unique_position=*/std::nullopt);
EXPECT_EQ(metadata.proto().base_specifics_hash(), initial_hash);
}
TEST(ProcessorEntityMetadataTest, ShouldRecordLocalDeletion) {
ProcessorEntityMetadata metadata = ProcessorEntityMetadata::CreateNew(
ClientTagHash::FromHashed("hash"), "server_id", base::Time::Now());
ASSERT_FALSE(metadata.IsDeleted());
metadata.RecordLocalDeletion(DeletionOrigin::Unspecified());
EXPECT_TRUE(metadata.IsDeleted());
// Test serialization.
std::unique_ptr<ProcessorEntityMetadata> metadata_restored =
ProcessorEntityMetadata::FromProto(metadata.proto());
ASSERT_NE(metadata_restored, nullptr);
EXPECT_TRUE(metadata_restored->IsDeleted());
}
} // namespace
} // namespace syncer