blob: ab1848e62698d6c0d9c5e6ff50ffa83ff39f58f2 [file] [log] [blame]
// Copyright 2015 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/device_info/device_info_service.h"
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "components/sync/api/data_batch.h"
#include "components/sync/api/data_type_error_handler_mock.h"
#include "components/sync/api/entity_data.h"
#include "components/sync/api/fake_model_type_change_processor.h"
#include "components/sync/api/metadata_batch.h"
#include "components/sync/api/model_type_store.h"
#include "components/sync/base/time.h"
#include "components/sync/core/test/model_type_store_test_util.h"
#include "components/sync/device_info/local_device_info_provider_mock.h"
#include "components/sync/protocol/data_type_state.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_driver_v2 {
using base::Time;
using base::TimeDelta;
using syncer::SyncError;
using syncer_v2::DataBatch;
using syncer_v2::EntityChange;
using syncer_v2::EntityChangeList;
using syncer_v2::EntityData;
using syncer_v2::EntityDataMap;
using syncer_v2::EntityDataPtr;
using syncer_v2::MetadataBatch;
using syncer_v2::MetadataChangeList;
using syncer_v2::ModelTypeChangeProcessor;
using syncer_v2::ModelTypeService;
using syncer_v2::ModelTypeStore;
using syncer_v2::ModelTypeStoreTestUtil;
using syncer_v2::KeyAndData;
using sync_driver::DeviceInfo;
using sync_driver::DeviceInfoTracker;
using sync_driver::LocalDeviceInfoProviderMock;
using sync_pb::DataTypeState;
using sync_pb::DeviceInfoSpecifics;
using sync_pb::EntitySpecifics;
using DeviceInfoList = std::vector<std::unique_ptr<DeviceInfo>>;
using StorageKeyList = ModelTypeService::StorageKeyList;
using RecordList = ModelTypeStore::RecordList;
using Result = ModelTypeStore::Result;
using StartCallback = ModelTypeChangeProcessor::StartCallback;
using WriteBatch = ModelTypeStore::WriteBatch;
namespace {
std::unique_ptr<DeviceInfo> CreateDeviceInfo() {
return base::MakeUnique<DeviceInfo>(
"guid_1", "client_1", "Chromium 10k", "Chrome 10k",
sync_pb::SyncEnums_DeviceType_TYPE_LINUX, "device_id");
}
void AssertResultIsSuccess(Result result) {
ASSERT_EQ(Result::SUCCESS, result);
}
void AssertEqual(const DeviceInfoSpecifics& s1, const DeviceInfoSpecifics& s2) {
ASSERT_EQ(s1.cache_guid(), s2.cache_guid());
ASSERT_EQ(s1.client_name(), s2.client_name());
ASSERT_EQ(s1.device_type(), s2.device_type());
ASSERT_EQ(s1.sync_user_agent(), s2.sync_user_agent());
ASSERT_EQ(s1.chrome_version(), s2.chrome_version());
ASSERT_EQ(s1.signin_scoped_device_id(), s2.signin_scoped_device_id());
}
void AssertEqual(const DeviceInfoSpecifics& specifics,
const DeviceInfo& model) {
ASSERT_EQ(specifics.cache_guid(), model.guid());
ASSERT_EQ(specifics.client_name(), model.client_name());
ASSERT_EQ(specifics.device_type(), model.device_type());
ASSERT_EQ(specifics.sync_user_agent(), model.sync_user_agent());
ASSERT_EQ(specifics.chrome_version(), model.chrome_version());
ASSERT_EQ(specifics.signin_scoped_device_id(),
model.signin_scoped_device_id());
}
void AssertExpectedFromDataBatch(
std::map<std::string, DeviceInfoSpecifics> expected,
SyncError error,
std::unique_ptr<DataBatch> batch) {
ASSERT_FALSE(error.IsSet());
while (batch->HasNext()) {
const KeyAndData& pair = batch->Next();
std::map<std::string, DeviceInfoSpecifics>::iterator iter =
expected.find(pair.first);
ASSERT_NE(iter, expected.end());
AssertEqual(iter->second, pair.second->specifics.device_info());
// Removing allows us to verify we don't see the same item multiple times,
// and that we saw everything we expected.
expected.erase(iter);
}
ASSERT_TRUE(expected.empty());
}
// Creates an EntityData/EntityDataPtr around a copy of the given specifics.
EntityDataPtr SpecificsToEntity(const DeviceInfoSpecifics& specifics) {
EntityData data;
// These tests do not care about the tag hash, but EntityData and friends
// cannot differentiate between the default EntityData object if the hash
// is unset, which causes pass/copy operations to no-op and things start to
// break, so we throw in a junk value and forget about it.
data.client_tag_hash = "junk";
*data.specifics.mutable_device_info() = specifics;
return data.PassToPtr();
}
std::string CacheGuidToTag(const std::string& guid) {
return "DeviceInfo_" + guid;
}
// Helper method to reduce duplicated code between tests. Wraps the given
// specifics object in an EntityData and EntityChange of type ACTION_ADD, and
// pushes them onto the given change list. The corresponding guid of the data
// is returned, which happens to be the storage key.
std::string PushBackEntityChangeAdd(const DeviceInfoSpecifics& specifics,
EntityChangeList* changes) {
EntityDataPtr ptr = SpecificsToEntity(specifics);
changes->push_back(EntityChange::CreateAdd(specifics.cache_guid(), ptr));
return specifics.cache_guid();
}
// Instead of actually processing anything, simply accumulates all instructions
// in members that can then be accessed. TODO(skym): If this ends up being
// useful for other model type unittests it should be moved out to a shared
// location.
class RecordingModelTypeChangeProcessor
: public syncer_v2::FakeModelTypeChangeProcessor {
public:
RecordingModelTypeChangeProcessor() {}
~RecordingModelTypeChangeProcessor() override {}
void Put(const std::string& storage_key,
std::unique_ptr<EntityData> entity_data,
MetadataChangeList* metadata_changes) override {
put_map_.insert(std::make_pair(storage_key, std::move(entity_data)));
}
void Delete(const std::string& storage_key,
MetadataChangeList* metadata_changes) override {
delete_set_.insert(storage_key);
}
void OnMetadataLoaded(syncer::SyncError error,
std::unique_ptr<MetadataBatch> batch) override {
std::swap(metadata_, batch);
}
const std::map<std::string, std::unique_ptr<EntityData>>& put_map() const {
return put_map_;
}
const std::set<std::string>& delete_set() const { return delete_set_; }
const MetadataBatch* metadata() const { return metadata_.get(); }
private:
std::map<std::string, std::unique_ptr<EntityData>> put_map_;
std::set<std::string> delete_set_;
std::unique_ptr<MetadataBatch> metadata_;
};
} // namespace
class DeviceInfoServiceTest : public testing::Test,
public DeviceInfoTracker::Observer {
protected:
DeviceInfoServiceTest()
: change_count_(0),
store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()),
local_device_(new LocalDeviceInfoProviderMock()) {
local_device_->Initialize(CreateDeviceInfo());
}
~DeviceInfoServiceTest() override {
// Some tests may never initialize the service.
if (service_)
service_->RemoveObserver(this);
// Force all remaining (store) tasks to execute so we don't leak memory.
base::RunLoop().RunUntilIdle();
}
void OnDeviceInfoChange() override { change_count_++; }
std::unique_ptr<ModelTypeChangeProcessor> CreateModelTypeChangeProcessor(
syncer::ModelType type,
ModelTypeService* service) {
processor_ = new RecordingModelTypeChangeProcessor();
return base::WrapUnique(processor_);
}
// Initialized the service based on the current local device and store. Can
// only be called once per run, as it passes |store_|.
void InitializeService() {
ASSERT_TRUE(store_);
service_.reset(new DeviceInfoService(
local_device_.get(),
base::Bind(&ModelTypeStoreTestUtil::MoveStoreToCallback,
base::Passed(&store_)),
base::Bind(&DeviceInfoServiceTest::CreateModelTypeChangeProcessor,
base::Unretained(this))));
service_->AddObserver(this);
}
void OnSyncStarting() {
service()->OnSyncStarting(
base::MakeUnique<syncer::DataTypeErrorHandlerMock>(), StartCallback());
}
// Creates the service and runs any outstanding tasks. This will typically
// cause all initialization callbacks between the sevice and store to fire.
void InitializeAndPump() {
InitializeService();
base::RunLoop().RunUntilIdle();
}
// Creates the service, runs any outstanding tasks, and then indicates to the
// service that sync wants to start and forces the processor to be created.
void InitializeAndPumpAndStart() {
InitializeAndPump();
OnSyncStarting();
ASSERT_TRUE(processor_);
}
// Generates a specifics object with slightly differing values. Will generate
// the same values on each run of a test because a simple counter is used to
// vary field values.
DeviceInfoSpecifics GenerateTestSpecifics() {
int label = ++generated_count_;
DeviceInfoSpecifics specifics;
specifics.set_cache_guid(base::StringPrintf("cache guid %d", label));
specifics.set_client_name(base::StringPrintf("client name %d", label));
specifics.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_LINUX);
specifics.set_sync_user_agent(
base::StringPrintf("sync user agent %d", label));
specifics.set_chrome_version(
base::StringPrintf("chrome version %d", label));
specifics.set_signin_scoped_device_id(
base::StringPrintf("signin scoped device id %d", label));
return specifics;
}
std::unique_ptr<DeviceInfoSpecifics> CopyToSpecifics(const DeviceInfo& info) {
return DeviceInfoService::CopyToSpecifics(info);
}
// Override to allow specific cache guids.
DeviceInfoSpecifics GenerateTestSpecifics(const std::string& guid) {
DeviceInfoSpecifics specifics(GenerateTestSpecifics());
specifics.set_cache_guid(guid);
return specifics;
}
// Allows access to the store before that will ultimately be used to
// initialize the service.
ModelTypeStore* store() {
EXPECT_TRUE(store_);
return store_.get();
}
// Get the number of times the service notifies observers of changes.
int change_count() { return change_count_; }
// Allows overriding the provider before the service is initialized.
void set_local_device(std::unique_ptr<LocalDeviceInfoProviderMock> provider) {
ASSERT_FALSE(service_);
std::swap(local_device_, provider);
}
LocalDeviceInfoProviderMock* local_device() { return local_device_.get(); }
// Allows access to the service after InitializeService() is called.
DeviceInfoService* service() {
EXPECT_TRUE(service_);
return service_.get();
}
RecordingModelTypeChangeProcessor* processor() {
EXPECT_TRUE(processor_);
return processor_;
}
// Should only be called after the service has been initialized. Will first
// recover the service's store, so another can be initialized later, and then
// deletes the service.
void PumpAndShutdown() {
ASSERT_TRUE(service_);
base::RunLoop().RunUntilIdle();
std::swap(store_, service_->store_);
service_->RemoveObserver(this);
service_.reset();
}
void RestartService() {
PumpAndShutdown();
InitializeAndPump();
}
Time GetLastUpdateTime(const DeviceInfoSpecifics& specifics) {
return DeviceInfoService::GetLastUpdateTime(specifics);
}
private:
int change_count_;
// In memory model type store needs a MessageLoop.
base::MessageLoop message_loop_;
// Holds the store while the service is not initialized.
std::unique_ptr<ModelTypeStore> store_;
std::unique_ptr<LocalDeviceInfoProviderMock> local_device_;
// Not initialized immediately (upon test's constructor). This allows each
// test case to modify the dependencies the service will be constructed with.
std::unique_ptr<DeviceInfoService> service_;
// A non-owning pointer to the processor given to the service. Will be nullptr
// before being given to the service, to make ownership easier.
RecordingModelTypeChangeProcessor* processor_ = nullptr;
// A monotonically increasing label for generated specifics objects with data
// that is slightly different from eachother.
int generated_count_ = 0;
};
namespace {
TEST_F(DeviceInfoServiceTest, EmptyDataReconciliation) {
InitializeAndPump();
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
OnSyncStarting();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
ASSERT_TRUE(
local_device()->GetLocalDeviceInfo()->Equals(*all_device_info[0]));
}
TEST_F(DeviceInfoServiceTest, EmptyDataReconciliationSlowLoad) {
InitializeService();
OnSyncStarting();
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
base::RunLoop().RunUntilIdle();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
ASSERT_TRUE(
local_device()->GetLocalDeviceInfo()->Equals(*all_device_info[0]));
}
TEST_F(DeviceInfoServiceTest, LocalProviderSubscription) {
set_local_device(base::MakeUnique<LocalDeviceInfoProviderMock>());
InitializeAndPumpAndStart();
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
local_device()->Initialize(CreateDeviceInfo());
base::RunLoop().RunUntilIdle();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
ASSERT_TRUE(
local_device()->GetLocalDeviceInfo()->Equals(*all_device_info[0]));
}
// Metadata shouldn't be loaded before the provider is initialized.
TEST_F(DeviceInfoServiceTest, LocalProviderInitRace) {
set_local_device(base::WrapUnique(new LocalDeviceInfoProviderMock()));
InitializeAndPump();
OnSyncStarting();
EXPECT_FALSE(processor()->metadata());
ASSERT_EQ(0u, service()->GetAllDeviceInfo().size());
local_device()->Initialize(CreateDeviceInfo());
base::RunLoop().RunUntilIdle();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
ASSERT_TRUE(
local_device()->GetLocalDeviceInfo()->Equals(*all_device_info[0]));
EXPECT_TRUE(processor()->metadata());
}
TEST_F(DeviceInfoServiceTest, GetClientTagNormal) {
InitializeService();
const std::string guid = "abc";
EntitySpecifics entity_specifics;
entity_specifics.mutable_device_info()->set_cache_guid(guid);
EntityData entity_data;
entity_data.specifics = entity_specifics;
EXPECT_EQ(CacheGuidToTag(guid), service()->GetClientTag(entity_data));
}
TEST_F(DeviceInfoServiceTest, GetClientTagEmpty) {
InitializeService();
EntitySpecifics entity_specifics;
entity_specifics.mutable_device_info();
EntityData entity_data;
entity_data.specifics = entity_specifics;
EXPECT_EQ(CacheGuidToTag(""), service()->GetClientTag(entity_data));
}
TEST_F(DeviceInfoServiceTest, TestWithLocalData) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics(GenerateTestSpecifics());
store()->WriteData(batch.get(), specifics.cache_guid(),
specifics.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
AssertEqual(specifics, *all_device_info[0]);
AssertEqual(specifics,
*service()->GetDeviceInfo(specifics.cache_guid()).get());
}
TEST_F(DeviceInfoServiceTest, TestWithLocalMetadata) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DataTypeState state;
state.set_encryption_key_name("ekn");
store()->WriteGlobalMetadata(batch.get(), state.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(1u, all_device_info.size());
ASSERT_TRUE(
local_device()->GetLocalDeviceInfo()->Equals(*all_device_info[0]));
EXPECT_EQ(1u, processor()->put_map().size());
}
TEST_F(DeviceInfoServiceTest, TestWithLocalDataAndMetadata) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics(GenerateTestSpecifics());
store()->WriteData(batch.get(), specifics.cache_guid(),
specifics.SerializeAsString());
DataTypeState state;
state.set_encryption_key_name("ekn");
store()->WriteGlobalMetadata(batch.get(), state.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
DeviceInfoList all_device_info(service()->GetAllDeviceInfo());
ASSERT_EQ(2u, all_device_info.size());
AssertEqual(specifics,
*service()->GetDeviceInfo(specifics.cache_guid()).get());
ASSERT_TRUE(processor()->metadata());
ASSERT_EQ(state.encryption_key_name(),
processor()->metadata()->GetDataTypeState().encryption_key_name());
}
TEST_F(DeviceInfoServiceTest, GetData) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics1(GenerateTestSpecifics());
DeviceInfoSpecifics specifics2(GenerateTestSpecifics());
DeviceInfoSpecifics specifics3(GenerateTestSpecifics());
store()->WriteData(batch.get(), specifics1.cache_guid(),
specifics1.SerializeAsString());
store()->WriteData(batch.get(), specifics2.cache_guid(),
specifics2.SerializeAsString());
store()->WriteData(batch.get(), specifics3.cache_guid(),
specifics3.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
std::map<std::string, DeviceInfoSpecifics> expected;
expected[specifics1.cache_guid()] = specifics1;
expected[specifics3.cache_guid()] = specifics3;
StorageKeyList storage_keys;
storage_keys.push_back(specifics1.cache_guid());
storage_keys.push_back(specifics3.cache_guid());
service()->GetData(storage_keys,
base::Bind(&AssertExpectedFromDataBatch, expected));
}
TEST_F(DeviceInfoServiceTest, GetDataMissing) {
InitializeAndPump();
std::map<std::string, DeviceInfoSpecifics> expected;
StorageKeyList storage_keys;
storage_keys.push_back("does_not_exist");
service()->GetData(storage_keys,
base::Bind(&AssertExpectedFromDataBatch, expected));
}
TEST_F(DeviceInfoServiceTest, GetAllData) {
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
DeviceInfoSpecifics specifics1(GenerateTestSpecifics());
DeviceInfoSpecifics specifics2(GenerateTestSpecifics());
const std::string& guid1 = specifics1.cache_guid();
const std::string& guid2 = specifics2.cache_guid();
store()->WriteData(batch.get(), specifics1.cache_guid(),
specifics1.SerializeAsString());
store()->WriteData(batch.get(), specifics2.cache_guid(),
specifics2.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPump();
std::map<std::string, DeviceInfoSpecifics> expected;
expected[guid1] = specifics1;
expected[guid2] = specifics2;
StorageKeyList storage_keys;
storage_keys.push_back(guid1);
storage_keys.push_back(guid2);
service()->GetData(storage_keys,
base::Bind(&AssertExpectedFromDataBatch, expected));
}
TEST_F(DeviceInfoServiceTest, ApplySyncChangesEmpty) {
InitializeAndPump();
const SyncError error = service()->ApplySyncChanges(
service()->CreateMetadataChangeList(), EntityChangeList());
EXPECT_FALSE(error.IsSet());
EXPECT_EQ(0, change_count());
}
TEST_F(DeviceInfoServiceTest, ApplySyncChangesInMemory) {
InitializeAndPump();
DeviceInfoSpecifics specifics = GenerateTestSpecifics();
EntityChangeList add_changes;
PushBackEntityChangeAdd(specifics, &add_changes);
SyncError error = service()->ApplySyncChanges(
service()->CreateMetadataChangeList(), add_changes);
EXPECT_FALSE(error.IsSet());
std::unique_ptr<DeviceInfo> info =
service()->GetDeviceInfo(specifics.cache_guid());
ASSERT_TRUE(info);
AssertEqual(specifics, *info.get());
EXPECT_EQ(1, change_count());
EntityChangeList delete_changes;
delete_changes.push_back(EntityChange::CreateDelete(specifics.cache_guid()));
error = service()->ApplySyncChanges(service()->CreateMetadataChangeList(),
delete_changes);
EXPECT_FALSE(error.IsSet());
EXPECT_FALSE(service()->GetDeviceInfo(specifics.cache_guid()));
EXPECT_EQ(2, change_count());
}
TEST_F(DeviceInfoServiceTest, ApplySyncChangesStore) {
InitializeAndPump();
DeviceInfoSpecifics specifics = GenerateTestSpecifics();
EntityChangeList data_changes;
PushBackEntityChangeAdd(specifics, &data_changes);
DataTypeState state;
state.set_encryption_key_name("ekn");
std::unique_ptr<MetadataChangeList> metadata_changes(
service()->CreateMetadataChangeList());
metadata_changes->UpdateDataTypeState(state);
const SyncError error =
service()->ApplySyncChanges(std::move(metadata_changes), data_changes);
EXPECT_FALSE(error.IsSet());
EXPECT_EQ(1, change_count());
RestartService();
std::unique_ptr<DeviceInfo> info =
service()->GetDeviceInfo(specifics.cache_guid());
ASSERT_TRUE(info);
AssertEqual(specifics, *info.get());
EXPECT_TRUE(processor()->metadata());
EXPECT_EQ(state.encryption_key_name(),
processor()->metadata()->GetDataTypeState().encryption_key_name());
}
TEST_F(DeviceInfoServiceTest, ApplySyncChangesWithLocalGuid) {
InitializeAndPumpAndStart();
// The point of this test is to try to apply remote changes that have the same
// cache guid as the local device. The service should ignore these changes
// since only it should be performing writes on its data.
DeviceInfoSpecifics specifics =
GenerateTestSpecifics(local_device()->GetLocalDeviceInfo()->guid());
EntityChangeList change_list;
PushBackEntityChangeAdd(specifics, &change_list);
// Should have a single change from reconciliation.
EXPECT_TRUE(
service()->GetDeviceInfo(local_device()->GetLocalDeviceInfo()->guid()));
EXPECT_EQ(1, change_count());
// Ensure |last_updated| is about now, plus or minus a little bit.
Time last_updated(
syncer::ProtoTimeToTime(processor()
->put_map()
.begin()
->second->specifics.device_info()
.last_updated_timestamp()));
EXPECT_LT(Time::Now() - TimeDelta::FromMinutes(1), last_updated);
EXPECT_GT(Time::Now() + TimeDelta::FromMinutes(1), last_updated);
EXPECT_FALSE(
service()
->ApplySyncChanges(service()->CreateMetadataChangeList(), change_list)
.IsSet());
EXPECT_EQ(1, change_count());
change_list.clear();
change_list.push_back(EntityChange::CreateDelete(specifics.cache_guid()));
EXPECT_FALSE(
service()
->ApplySyncChanges(service()->CreateMetadataChangeList(), change_list)
.IsSet());
EXPECT_EQ(1, change_count());
}
TEST_F(DeviceInfoServiceTest, ApplyDeleteNonexistent) {
InitializeAndPumpAndStart();
EXPECT_EQ(1, change_count());
EntityChangeList delete_changes;
delete_changes.push_back(EntityChange::CreateDelete("guid"));
const SyncError error = service()->ApplySyncChanges(
service()->CreateMetadataChangeList(), delete_changes);
EXPECT_FALSE(error.IsSet());
EXPECT_EQ(1, change_count());
}
TEST_F(DeviceInfoServiceTest, MergeEmpty) {
InitializeAndPumpAndStart();
EXPECT_EQ(1, change_count());
const SyncError error = service()->MergeSyncData(
service()->CreateMetadataChangeList(), EntityDataMap());
EXPECT_FALSE(error.IsSet());
EXPECT_EQ(1, change_count());
EXPECT_EQ(1u, processor()->put_map().size());
EXPECT_EQ(0u, processor()->delete_set().size());
}
TEST_F(DeviceInfoServiceTest, MergeWithData) {
const std::string conflict_guid = "conflict_guid";
const DeviceInfoSpecifics unique_local(
GenerateTestSpecifics("unique_local_guid"));
const DeviceInfoSpecifics conflict_local(
GenerateTestSpecifics(conflict_guid));
const DeviceInfoSpecifics conflict_remote(
GenerateTestSpecifics(conflict_guid));
const DeviceInfoSpecifics unique_remote(
GenerateTestSpecifics("unique_remote_guid"));
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
store()->WriteData(batch.get(), unique_local.cache_guid(),
unique_local.SerializeAsString());
store()->WriteData(batch.get(), conflict_local.cache_guid(),
conflict_local.SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPumpAndStart();
EXPECT_EQ(1, change_count());
EntityDataMap remote_input;
remote_input[conflict_remote.cache_guid()] =
SpecificsToEntity(conflict_remote);
remote_input[unique_remote.cache_guid()] = SpecificsToEntity(unique_remote);
DataTypeState state;
state.set_encryption_key_name("ekn");
std::unique_ptr<MetadataChangeList> metadata_changes(
service()->CreateMetadataChangeList());
metadata_changes->UpdateDataTypeState(state);
const SyncError error =
service()->MergeSyncData(std::move(metadata_changes), remote_input);
EXPECT_FALSE(error.IsSet());
EXPECT_EQ(2, change_count());
// The remote should beat the local in conflict.
EXPECT_EQ(4u, service()->GetAllDeviceInfo().size());
AssertEqual(unique_local,
*service()->GetDeviceInfo(unique_local.cache_guid()).get());
AssertEqual(unique_remote,
*service()->GetDeviceInfo(unique_remote.cache_guid()).get());
AssertEqual(conflict_remote, *service()->GetDeviceInfo(conflict_guid).get());
// Service should have told the processor about the existance of unique_local.
EXPECT_TRUE(processor()->delete_set().empty());
EXPECT_EQ(2u, processor()->put_map().size());
const auto& it = processor()->put_map().find(unique_local.cache_guid());
ASSERT_NE(processor()->put_map().end(), it);
AssertEqual(unique_local, it->second->specifics.device_info());
RestartService();
ASSERT_EQ(state.encryption_key_name(),
processor()->metadata()->GetDataTypeState().encryption_key_name());
}
TEST_F(DeviceInfoServiceTest, MergeLocalGuid) {
const DeviceInfo* local_device_info = local_device()->GetLocalDeviceInfo();
std::unique_ptr<DeviceInfoSpecifics> specifics(
CopyToSpecifics(*local_device_info));
specifics->set_last_updated_timestamp(syncer::TimeToProtoTime(Time::Now()));
const std::string guid = local_device_info->guid();
std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
store()->WriteData(batch.get(), guid, specifics->SerializeAsString());
store()->CommitWriteBatch(std::move(batch),
base::Bind(&AssertResultIsSuccess));
InitializeAndPumpAndStart();
EntityDataMap remote_input;
remote_input[guid] = SpecificsToEntity(*specifics);
const SyncError error = service()->MergeSyncData(
service()->CreateMetadataChangeList(), remote_input);
EXPECT_FALSE(error.IsSet());
EXPECT_EQ(0, change_count());
EXPECT_EQ(1u, service()->GetAllDeviceInfo().size());
EXPECT_TRUE(processor()->delete_set().empty());
EXPECT_TRUE(processor()->put_map().empty());
}
TEST_F(DeviceInfoServiceTest, GetLastUpdateTime) {
Time time1(Time() + TimeDelta::FromDays(1));
DeviceInfoSpecifics specifics1(GenerateTestSpecifics());
DeviceInfoSpecifics specifics2(GenerateTestSpecifics());
specifics2.set_last_updated_timestamp(syncer::TimeToProtoTime(time1));
EXPECT_EQ(Time(), GetLastUpdateTime(specifics1));
EXPECT_EQ(time1, GetLastUpdateTime(specifics2));
}
TEST_F(DeviceInfoServiceTest, CountActiveDevices) {
InitializeAndPump();
EXPECT_EQ(0, service()->CountActiveDevices());
DeviceInfoSpecifics specifics =
GenerateTestSpecifics(local_device()->GetLocalDeviceInfo()->guid());
EntityChangeList change_list;
PushBackEntityChangeAdd(specifics, &change_list);
service()->ApplySyncChanges(service()->CreateMetadataChangeList(),
change_list);
EXPECT_EQ(0, service()->CountActiveDevices());
change_list.clear();
specifics.set_last_updated_timestamp(syncer::TimeToProtoTime(Time::Now()));
PushBackEntityChangeAdd(specifics, &change_list);
service()->ApplySyncChanges(service()->CreateMetadataChangeList(),
change_list);
EXPECT_EQ(0, service()->CountActiveDevices());
change_list.clear();
specifics.set_cache_guid("non-local");
PushBackEntityChangeAdd(specifics, &change_list);
service()->ApplySyncChanges(service()->CreateMetadataChangeList(),
change_list);
EXPECT_EQ(1, service()->CountActiveDevices());
}
} // namespace
} // namespace sync_driver_v2