blob: 6c6b0a1f7e4394aac7d9e20c6d0846ef55dc5087 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/model_impl/client_tag_based_remote_update_handler.h"
#include <utility>
#include "components/sync/model/fake_model_type_sync_bridge.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mock_model_type_change_processor.h"
#include "components/sync/model_impl/processor_entity_tracker.h"
#include "components/sync/test/engine/mock_model_type_processor.h"
#include "components/sync/test/engine/mock_model_type_worker.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
const char kKey1[] = "key1";
const char kKey2[] = "key2";
const char kValue1[] = "value1";
sync_pb::ModelTypeState GenerateModelTypeState() {
sync_pb::ModelTypeState model_type_state;
model_type_state.set_initial_sync_done(true);
return model_type_state;
}
class ClientTagBasedRemoteUpdateHandlerTest : public ::testing::Test {
public:
ClientTagBasedRemoteUpdateHandlerTest()
: processor_entity_tracker_(GenerateModelTypeState(),
EntityMetadataMap()),
model_type_sync_bridge_(change_processor_.CreateForwardingProcessor()),
remote_update_handler_(PREFERENCES,
&model_type_sync_bridge_,
&processor_entity_tracker_),
worker_(GenerateModelTypeState(), &model_type_processor_) {}
~ClientTagBasedRemoteUpdateHandlerTest() override = default;
void ProcessSingleUpdate(const sync_pb::ModelTypeState& model_type_state,
UpdateResponseData update) {
UpdateResponseDataList updates;
updates.push_back(std::move(update));
remote_update_handler_.ProcessIncrementalUpdate(model_type_state,
std::move(updates));
}
void ProcessSingleUpdate(UpdateResponseData update) {
ProcessSingleUpdate(GenerateModelTypeState(), std::move(update));
}
UpdateResponseData GenerateUpdate(const std::string& key,
const std::string& value) {
const ClientTagHash client_tag_hash =
FakeModelTypeSyncBridge::TagHashFromKey(key);
return GenerateUpdate(client_tag_hash, key, value);
}
UpdateResponseData GenerateUpdate(const ClientTagHash& client_tag_hash,
const std::string& key,
const std::string& value) {
return worker()->GenerateUpdateData(
client_tag_hash,
FakeModelTypeSyncBridge::GenerateSpecifics(key, value));
}
UpdateResponseData GenerateUpdate(const std::string& key,
const std::string& value,
int64_t version_offset) {
const ClientTagHash client_tag_hash =
FakeModelTypeSyncBridge::TagHashFromKey(key);
const sync_pb::ModelTypeState model_type_state = GenerateModelTypeState();
const sync_pb::EntitySpecifics specifics =
FakeModelTypeSyncBridge::GenerateSpecifics(key, value);
return worker()->GenerateUpdateData(client_tag_hash, specifics,
version_offset,
model_type_state.encryption_key_name());
}
size_t ProcessorEntityCount() const {
return processor_entity_tracker_.GetAllEntitiesIncludingTombstones().size();
}
FakeModelTypeSyncBridge* bridge() { return &model_type_sync_bridge_; }
ClientTagBasedRemoteUpdateHandler* remote_update_handler() {
return &remote_update_handler_;
}
FakeModelTypeSyncBridge::Store* db() { return bridge()->mutable_db(); }
ProcessorEntityTracker* entity_tracker() {
return &processor_entity_tracker_;
}
testing::NiceMock<MockModelTypeChangeProcessor>* change_processor() {
return &change_processor_;
}
MockModelTypeWorker* worker() { return &worker_; }
private:
testing::NiceMock<MockModelTypeChangeProcessor> change_processor_;
ProcessorEntityTracker processor_entity_tracker_;
FakeModelTypeSyncBridge model_type_sync_bridge_;
ClientTagBasedRemoteUpdateHandler remote_update_handler_;
testing::NiceMock<MockModelTypeProcessor> model_type_processor_;
MockModelTypeWorker worker_;
};
// Thoroughly tests the data generated by a server item creation.
TEST_F(ClientTagBasedRemoteUpdateHandlerTest, ShouldProcessRemoteCreation) {
ProcessSingleUpdate(GenerateUpdate(kKey1, kValue1));
EXPECT_EQ(1u, db()->data_count());
EXPECT_EQ(1u, db()->metadata_count());
const EntityData& data = db()->GetData(kKey1);
EXPECT_FALSE(data.id.empty());
EXPECT_EQ(kKey1, data.specifics.preference().name());
EXPECT_EQ(kValue1, data.specifics.preference().value());
EXPECT_FALSE(data.creation_time.is_null());
EXPECT_FALSE(data.modification_time.is_null());
EXPECT_EQ(kKey1, data.name);
EXPECT_FALSE(data.is_deleted());
const sync_pb::EntityMetadata& metadata = db()->GetMetadata(kKey1);
EXPECT_TRUE(metadata.has_client_tag_hash());
EXPECT_TRUE(metadata.has_server_id());
EXPECT_FALSE(metadata.is_deleted());
EXPECT_EQ(0, metadata.sequence_number());
EXPECT_EQ(0, metadata.acked_sequence_number());
EXPECT_EQ(1, metadata.server_version());
EXPECT_TRUE(metadata.has_creation_time());
EXPECT_TRUE(metadata.has_modification_time());
EXPECT_TRUE(metadata.has_specifics_hash());
}
TEST_F(ClientTagBasedRemoteUpdateHandlerTest,
ShouldIgnoreRemoteUpdatesForRootNodes) {
ASSERT_EQ(0U, ProcessorEntityCount());
ProcessSingleUpdate(
worker()->GenerateTypeRootUpdateData(ModelType::SESSIONS));
// Root node update should be filtered out.
EXPECT_EQ(0U, db()->data_count());
EXPECT_EQ(0U, db()->metadata_count());
EXPECT_EQ(0U, ProcessorEntityCount());
}
TEST_F(ClientTagBasedRemoteUpdateHandlerTest,
ShouldIgnoreRemoteUpdatesWithUnexpectedClientTagHash) {
ASSERT_EQ(0U, ProcessorEntityCount());
ProcessSingleUpdate(GenerateUpdate(
FakeModelTypeSyncBridge::TagHashFromKey(kKey2), kKey1, kValue1));
EXPECT_EQ(0U, db()->data_count());
EXPECT_EQ(0U, db()->metadata_count());
EXPECT_EQ(0U, ProcessorEntityCount());
}
// Thoroughly tests the data generated by a server item creation.
TEST_F(ClientTagBasedRemoteUpdateHandlerTest, ShouldProcessRemoteUpdate) {
ProcessSingleUpdate(GenerateUpdate(kKey1, kValue1));
ASSERT_EQ(1U, ProcessorEntityCount());
ASSERT_EQ(1U, db()->data_change_count());
ASSERT_EQ(1U, db()->metadata_change_count());
// Redundant update from server doesn't write data but updates metadata.
ProcessSingleUpdate(GenerateUpdate(kKey1, kValue1));
EXPECT_EQ(1U, db()->data_change_count());
EXPECT_EQ(2U, db()->metadata_change_count());
// A reflection (update already received) is ignored completely.
ProcessSingleUpdate(GenerateUpdate(kKey1, kValue1, /*version_offset=*/0));
EXPECT_EQ(1U, db()->data_change_count());
EXPECT_EQ(2U, db()->metadata_change_count());
}
TEST_F(ClientTagBasedRemoteUpdateHandlerTest, ShouldProcessRemoteDeletion) {
ProcessSingleUpdate(GenerateUpdate(kKey1, kValue1));
ASSERT_EQ(1U, ProcessorEntityCount());
ASSERT_EQ(1U, db()->data_change_count());
ASSERT_EQ(1U, db()->metadata_change_count());
ProcessSingleUpdate(worker()->GenerateTombstoneUpdateData(
FakeModelTypeSyncBridge::TagHashFromKey(kKey1)));
// Delete from server should clear the data and all the metadata.
EXPECT_EQ(0U, db()->data_count());
EXPECT_EQ(0U, db()->metadata_count());
EXPECT_EQ(0U, ProcessorEntityCount());
}
// Deletes an item we've never seen before. Should have no effect and not crash.
TEST_F(ClientTagBasedRemoteUpdateHandlerTest,
ShouldIgnoreRemoteDeletionOfUnknownEntity) {
ASSERT_EQ(0U, ProcessorEntityCount());
ProcessSingleUpdate(worker()->GenerateTombstoneUpdateData(
FakeModelTypeSyncBridge::TagHashFromKey(kKey1)));
EXPECT_EQ(0U, db()->data_count());
EXPECT_EQ(0U, db()->metadata_count());
EXPECT_EQ(0U, ProcessorEntityCount());
}
TEST_F(ClientTagBasedRemoteUpdateHandlerTest,
ShouldNotTreatMatchingChangesAsConflict) {
UpdateResponseData update = GenerateUpdate(kKey1, kValue1);
sync_pb::EntitySpecifics specifics = update.entity.specifics;
ProcessSingleUpdate(std::move(update));
ASSERT_EQ(1U, ProcessorEntityCount());
ASSERT_EQ(1U, db()->data_change_count());
ASSERT_EQ(1U, db()->metadata_change_count());
ASSERT_EQ(1U, db()->GetMetadata(kKey1).server_version());
// Mark local entity as changed.
entity_tracker()->IncrementSequenceNumberForAllExcept({});
ASSERT_TRUE(entity_tracker()->HasLocalChanges());
update = GenerateUpdate(kKey1, kValue1);
// Make sure to have the same specifics.
update.entity.specifics = specifics;
// Changes match doesn't call ResolveConflict.
ProcessSingleUpdate(std::move(update));
EXPECT_EQ(1U, db()->data_change_count());
EXPECT_EQ(2U, db()->GetMetadata(kKey1).server_version());
EXPECT_EQ(1U, ProcessorEntityCount());
EXPECT_FALSE(entity_tracker()->HasLocalChanges());
}
// Test for the case from crbug.com/1046309. Tests that there is no redundant
// deletion when processing remote deletion with different encryption key.
TEST_F(ClientTagBasedRemoteUpdateHandlerTest,
ShouldNotIssueDeletionUponRemoteDeletion) {
const std::string kTestEncryptionKeyName = "TestEncryptionKey";
const std::string kDifferentEncryptionKeyName = "DifferentEncryptionKey";
const ClientTagHash kClientTagHash =
FakeModelTypeSyncBridge::TagHashFromKey(kKey1);
sync_pb::ModelTypeState model_type_state = GenerateModelTypeState();
model_type_state.set_encryption_key_name(kTestEncryptionKeyName);
ProcessSingleUpdate(GenerateUpdate(kClientTagHash, kKey1, kValue1));
// Generate a remote deletion with a different encryption key.
model_type_state.set_encryption_key_name(kDifferentEncryptionKeyName);
ProcessSingleUpdate(model_type_state,
worker()->GenerateTombstoneUpdateData(kClientTagHash));
EXPECT_EQ(0u, ProcessorEntityCount());
}
} // namespace
} // namespace syncer