Commit 69fc00a5 authored by Jan Krcal's avatar Jan Krcal Committed by Commit Bot

[Autofill profile] Implement remote changes for the USS bridge

This CL completes full functionality for the sync bridge for
autofill_profile model type. The CL only converts applicable
unit-tests from AutofillProfileSyncableServiceTest. Tests corresponding
to ProfileSyncServiceAutofillTest will come in a later CL.

Bug: 836718
Change-Id: I5cf596e117d74c85d2ac40a356b0beaab98e1a1f
Reviewed-on: https://chromium-review.googlesource.com/1100762Reviewed-by: default avatarSebastien Seguin-Gagnon <sebsg@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Commit-Queue: Jan Krcal <jkrcal@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572552}
parent 9df7b32d
......@@ -170,6 +170,8 @@ static_library("browser") {
"webdata/autofill_profile_data_type_controller.h",
"webdata/autofill_profile_sync_bridge.cc",
"webdata/autofill_profile_sync_bridge.h",
"webdata/autofill_profile_sync_difference_tracker.cc",
"webdata/autofill_profile_sync_difference_tracker.h",
"webdata/autofill_profile_syncable_service.cc",
"webdata/autofill_profile_syncable_service.h",
"webdata/autofill_table.cc",
......@@ -444,6 +446,7 @@ source_set("unit_tests") {
"validation_unittest.cc",
"webdata/autocomplete_sync_bridge_unittest.cc",
"webdata/autofill_profile_sync_bridge_unittest.cc",
"webdata/autofill_profile_sync_difference_tracker_unittest.cc",
"webdata/autofill_profile_syncable_service_unittest.cc",
"webdata/autofill_table_unittest.cc",
"webdata/autofill_wallet_metadata_syncable_service_unittest.cc",
......
......@@ -254,14 +254,14 @@ AutofillProfile::~AutofillProfile() {
}
AutofillProfile& AutofillProfile::operator=(const AutofillProfile& profile) {
if (this == &profile)
return *this;
set_use_count(profile.use_count());
set_use_date(profile.use_date());
set_previous_use_date(profile.previous_use_date());
set_modification_date(profile.modification_date());
if (this == &profile)
return *this;
set_guid(profile.guid());
set_origin(profile.origin());
......@@ -465,6 +465,30 @@ bool AutofillProfile::IsSubsetOfForFieldSet(
return true;
}
void AutofillProfile::OverwriteDataFrom(const AutofillProfile& profile) {
// Verified profiles should never be overwritten with unverified data.
DCHECK(!IsVerified() || profile.IsVerified());
DCHECK_EQ(guid(), profile.guid());
// Some fields should not got overwritten by empty values; back-up the
// values.
std::string language_code_value = language_code();
std::string origin_value = origin();
int validity_bitfield_value = GetValidityBitfieldValue();
base::string16 name_full_value = GetRawInfo(NAME_FULL);
*this = profile;
if (origin().empty())
set_origin(origin_value);
if (language_code().empty())
set_language_code(language_code_value);
if (GetValidityBitfieldValue() == 0)
SetValidityFromBitfieldValue(validity_bitfield_value);
if (!HasRawInfo(NAME_FULL))
SetRawInfo(NAME_FULL, name_full_value);
}
bool AutofillProfile::MergeDataFrom(const AutofillProfile& profile,
const std::string& app_locale) {
// Verified profiles should never be overwritten with unverified data.
......
......@@ -122,6 +122,10 @@ class AutofillProfile : public AutofillDataModel,
const std::string& app_locale,
const ServerFieldTypeSet& types) const;
// Overwrites the data of |this| profile with data from the given |profile|.
// Expects that the profiles have the same guid.
void OverwriteDataFrom(const AutofillProfile& profile);
// Merges the data from |this| profile and the given |profile| into |this|
// profile. Expects that |this| and |profile| have already been deemed
// mergeable by an AutofillProfileComparator.
......
......@@ -29,13 +29,17 @@ std::string TruncateUTF8(const std::string& data) {
return trimmed_value;
}
bool IsAutofillProfileSpecificsValid(
const AutofillProfileSpecifics& specifics) {
return base::IsValidGUID(specifics.guid());
}
} // namespace
std::unique_ptr<EntityData> CreateEntityDataFromAutofillProfile(
const AutofillProfile& entry) {
if (!base::IsValidGUID(entry.guid())) {
return nullptr;
}
// Validity of the guid is guaranteed by the database layer.
DCHECK(base::IsValidGUID(entry.guid()));
auto entity_data = std::make_unique<EntityData>();
entity_data->non_unique_name = entry.guid();
......@@ -124,7 +128,7 @@ std::unique_ptr<EntityData> CreateEntityDataFromAutofillProfile(
std::unique_ptr<AutofillProfile> CreateAutofillProfileFromSpecifics(
const AutofillProfileSpecifics& specifics) {
if (!base::IsValidGUID(specifics.guid())) {
if (!IsAutofillProfileSpecificsValid(specifics)) {
return nullptr;
}
std::unique_ptr<AutofillProfile> profile =
......@@ -215,15 +219,14 @@ std::unique_ptr<AutofillProfile> CreateAutofillProfileFromSpecifics(
}
std::string GetStorageKeyFromAutofillProfile(const AutofillProfile& entry) {
if (!base::IsValidGUID(entry.guid())) {
return std::string();
}
// Validity of the guid is guaranteed by the database layer.
DCHECK(base::IsValidGUID(entry.guid()));
return entry.guid();
}
std::string GetStorageKeyFromAutofillProfileSpecifics(
const AutofillProfileSpecifics& specifics) {
if (!base::IsValidGUID(specifics.guid())) {
if (!IsAutofillProfileSpecificsValid(specifics)) {
return std::string();
}
return specifics.guid();
......
......@@ -21,13 +21,13 @@ namespace autofill {
class AutofillProfile;
// Converts the given |entry| into a syncer EntityData with equivalent
// autofill profile specifics. Returns nullptr if |entry| has invalid guid.
// autofill profile specifics. Returns nullptr if |entry| is invalid.
// Shortens all string fields to AutofillTable::kMaxDataLength.
std::unique_ptr<syncer::EntityData> CreateEntityDataFromAutofillProfile(
const AutofillProfile& entry);
// Converts the given autofill profile |specifics| into an equivalent
// AutofillProfile. Returns nullptr if |specifics| has invalid guid.
// AutofillProfile. Returns nullptr if |specifics| is invalid.
std::unique_ptr<AutofillProfile> CreateAutofillProfileFromSpecifics(
const sync_pb::AutofillProfileSpecifics& specifics);
......
......@@ -142,14 +142,6 @@ TEST_F(AutofillProfileSyncUtilTest, CreateEntityDataFromAutofillProfile_Empty) {
EXPECT_FALSE(entity_data->specifics.autofill_profile().has_company_name());
}
// Test that nullptr is produced if the input guid is invalid.
TEST_F(AutofillProfileSyncUtilTest,
CreateEntityDataFromAutofillProfile_Invalid) {
AutofillProfile profile(kGuidInvalid, std::string());
EXPECT_EQ(nullptr, CreateEntityDataFromAutofillProfile(profile));
}
// Test that long fields get trimmed.
TEST_F(AutofillProfileSyncUtilTest,
CreateEntityDataFromAutofillProfile_Trimmed) {
......@@ -275,13 +267,6 @@ TEST_F(AutofillProfileSyncUtilTest, GetStorageKeyFromAutofillProfile) {
EXPECT_EQ(kGuid, GetStorageKeyFromAutofillProfile(profile));
}
// Tests that empty string is returned for entry with invalid guid.
TEST_F(AutofillProfileSyncUtilTest, GetStorageKeyFromAutofillProfile_Invalid) {
AutofillProfile profile(kGuidInvalid, std::string());
EXPECT_EQ(std::string(), GetStorageKeyFromAutofillProfile(profile));
}
// Tests that guid is returned as storage key.
TEST_F(AutofillProfileSyncUtilTest, GetStorageKeyFromAutofillProfileSpecifics) {
AutofillProfileSpecifics specifics;
......
......@@ -17,6 +17,7 @@
#include "components/autofill/core/browser/country_names.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/proto/autofill_sync.pb.h"
#include "components/autofill/core/browser/webdata/autofill_profile_sync_difference_tracker.h"
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
......@@ -105,8 +106,30 @@ Optional<syncer::ModelError> AutofillProfileSyncBridge::MergeSyncData(
syncer::EntityChangeList entity_data) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(entity_data.empty());
// TODO(jkrcal): Implement non-empty initial merge.
AutofillProfileInitialSyncDifferenceTracker initial_sync_tracker(
GetAutofillTable());
for (const auto& change : entity_data) {
DCHECK(change.data().specifics.has_autofill_profile());
std::unique_ptr<AutofillProfile> remote =
CreateAutofillProfileFromSpecifics(
change.data().specifics.autofill_profile());
if (!remote) {
DVLOG(2) << "[AUTOFILL SYNC] Invalid remote specifics "
<< change.data().specifics.autofill_profile().SerializeAsString()
<< " received from the server in an initial sync.";
continue;
}
RETURN_IF_ERROR(
initial_sync_tracker.IncorporateRemoteProfile(std::move(remote)));
}
RETURN_IF_ERROR(
initial_sync_tracker.MergeSimilarEntriesForInitialSync(app_locale_));
RETURN_IF_ERROR(
FlushSyncTracker(std::move(metadata_change_list), &initial_sync_tracker));
web_data_backend_->NotifyThatSyncHasStarted(syncer::AUTOFILL_PROFILE);
return base::nullopt;
}
......@@ -115,9 +138,27 @@ Optional<ModelError> AutofillProfileSyncBridge::ApplySyncChanges(
syncer::EntityChangeList entity_changes) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(entity_changes.empty());
// TODO(jkrcal): Implement non-empty sync changes.
return base::nullopt;
AutofillProfileSyncDifferenceTracker tracker(GetAutofillTable());
for (const syncer::EntityChange& change : entity_changes) {
if (change.type() == syncer::EntityChange::ACTION_DELETE) {
RETURN_IF_ERROR(tracker.IncorporateRemoteDelete(change.storage_key()));
} else {
DCHECK(change.data().specifics.has_autofill_profile());
std::unique_ptr<AutofillProfile> remote =
CreateAutofillProfileFromSpecifics(
change.data().specifics.autofill_profile());
if (!remote) {
DVLOG(2)
<< "[AUTOFILL SYNC] Invalid remote specifics "
<< change.data().specifics.autofill_profile().SerializeAsString()
<< " received from the server in an initial sync.";
continue;
}
RETURN_IF_ERROR(tracker.IncorporateRemoteProfile(std::move(remote)));
}
}
return FlushSyncTracker(std::move(metadata_change_list), &tracker);
}
void AutofillProfileSyncBridge::GetData(StorageKeyList storage_keys,
......@@ -194,6 +235,29 @@ void AutofillProfileSyncBridge::ActOnLocalChange(
}
}
base::Optional<syncer::ModelError> AutofillProfileSyncBridge::FlushSyncTracker(
std::unique_ptr<MetadataChangeList> metadata_change_list,
AutofillProfileSyncDifferenceTracker* tracker) {
DCHECK(tracker);
RETURN_IF_ERROR(tracker->FlushToLocal(
base::BindOnce(&AutofillWebDataBackend::NotifyOfMultipleAutofillChanges,
base::Unretained(web_data_backend_))));
std::vector<std::unique_ptr<AutofillProfile>> profiles_to_upload_to_sync;
RETURN_IF_ERROR(tracker->FlushToSync(&profiles_to_upload_to_sync));
for (const std::unique_ptr<AutofillProfile>& entry :
profiles_to_upload_to_sync) {
change_processor()->Put(GetStorageKeyFromAutofillProfile(*entry),
CreateEntityDataFromAutofillProfile(*entry),
metadata_change_list.get());
}
return static_cast<syncer::SyncMetadataStoreChangeList*>(
metadata_change_list.get())
->TakeError();
}
void AutofillProfileSyncBridge::LoadMetadata() {
if (!web_data_backend_ || !web_data_backend_->GetDatabase() ||
!GetAutofillTable()) {
......
......@@ -26,6 +26,7 @@ class ModelTypeChangeProcessor;
namespace autofill {
class AutofillProfileSyncDifferenceTracker;
class AutofillTable;
class AutofillWebDataBackend;
class AutofillWebDataService;
......@@ -87,6 +88,11 @@ class AutofillProfileSyncBridge
// changes.
void ActOnLocalChange(const AutofillProfileChange& change);
// Flushes changes accumulated within |tracker| both to local and to sync.
base::Optional<syncer::ModelError> FlushSyncTracker(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
AutofillProfileSyncDifferenceTracker* tracker);
// Synchronously load sync metadata from the autofill table and pass it to the
// processor so that it can start tracking changes.
void LoadMetadata();
......
......@@ -19,6 +19,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_profile_sync_util.h"
#include "components/autofill/core/browser/country_names.h"
......@@ -62,12 +63,17 @@ using testing::ElementsAre;
using testing::Eq;
using testing::Property;
using testing::Return;
using testing::UnorderedElementsAre;
namespace {
// Some guids for testing.
const char kGuidA[] = "EDC609ED-7EEE-4F27-B00C-423242A9C44A";
const char kGuidB[] = "EDC609ED-7EEE-4F27-B00C-423242A9C44B";
const char kGuidC[] = "EDC609ED-7EEE-4F27-B00C-423242A9C44C";
const char kGuidD[] = "EDC609ED-7EEE-4F27-B00C-423242A9C44D";
const char kGuidInvalid[] = "EDC609ED-7EEE-4F27-B00C";
const char kHttpOrigin[] = "http://www.example.com/";
const char kHttpsOrigin[] = "https://www.example.com/";
const int kValidityStateBitfield = 1984;
const char kLocaleString[] = "en-US";
......@@ -109,15 +115,35 @@ AutofillProfileSpecifics CreateAutofillProfileSpecifics(
return entity_data->specifics.autofill_profile();
}
AutofillProfileSpecifics CreateAutofillProfileSpecifics(
const std::string& guid,
const std::string& origin) {
AutofillProfileSpecifics specifics;
specifics.set_guid(guid);
specifics.set_origin(origin);
// Make it consistent with the constructor of AutofillProfile constructor (the
// clock value is overrided by TestAutofillClock in the test fixture).
specifics.set_use_count(1);
specifics.set_use_date(kJune2017.ToTimeT());
return specifics;
}
MATCHER_P(HasSpecifics, expected, "") {
const AutofillProfileSpecifics& s1 = arg->specifics.autofill_profile();
const AutofillProfileSpecifics& s2 = expected;
AutofillProfile p1 = CreateAutofillProfile(s1);
AutofillProfile p2 = CreateAutofillProfile(s2);
if (!p1.EqualsIncludingUsageStatsForTesting(p2)) {
*result_listener << "entry\n[" << p1 << "]\n"
<< "did not match expected\n[" << p2 << "]";
AutofillProfile arg_profile =
CreateAutofillProfile(arg->specifics.autofill_profile());
AutofillProfile expected_profile = CreateAutofillProfile(expected);
if (!arg_profile.EqualsIncludingUsageStatsForTesting(expected_profile)) {
*result_listener << "entry\n[" << arg_profile << "]\n"
<< "did not match expected\n[" << expected_profile << "]";
return false;
}
return true;
}
MATCHER_P(WithUsageStats, expected, "") {
if (!arg.EqualsIncludingUsageStatsForTesting(expected)) {
*result_listener << "entry\n[" << arg << "]\n"
<< "did not match expected\n[" << expected << "]";
return false;
}
return true;
......@@ -133,6 +159,79 @@ void ExtractAutofillProfilesFromDataBatch(
}
}
// Returns a profile with all fields set. Contains identical data to the data
// returned from ConstructCompleteSpecifics().
AutofillProfile ConstructCompleteProfile() {
AutofillProfile profile(kGuidA, kHttpsOrigin);
profile.set_use_count(7);
profile.set_use_date(base::Time::FromTimeT(1423182152));
profile.SetRawInfo(NAME_FULL, ASCIIToUTF16("John K. Doe, Jr."));
profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
profile.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("K."));
profile.SetRawInfo(NAME_LAST, ASCIIToUTF16("Doe"));
profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("user@example.com"));
profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("1.800.555.1234"));
profile.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("123 Fake St.\n"
"Apt. 42"));
EXPECT_EQ(ASCIIToUTF16("123 Fake St."),
profile.GetRawInfo(ADDRESS_HOME_LINE1));
EXPECT_EQ(ASCIIToUTF16("Apt. 42"), profile.GetRawInfo(ADDRESS_HOME_LINE2));
profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Google, Inc."));
profile.SetRawInfo(ADDRESS_HOME_CITY, ASCIIToUTF16("Mountain View"));
profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California"));
profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("94043"));
profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US"));
profile.SetRawInfo(ADDRESS_HOME_SORTING_CODE, ASCIIToUTF16("CEDEX"));
profile.SetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY,
ASCIIToUTF16("Santa Clara"));
profile.set_language_code("en");
profile.SetValidityFromBitfieldValue(kValidityStateBitfield);
return profile;
}
// Returns AutofillProfileSpecifics with all Autofill profile fields set.
// Contains identical data to the data returned from ConstructCompleteProfile().
AutofillProfileSpecifics ConstructCompleteSpecifics() {
AutofillProfileSpecifics specifics;
specifics.set_guid(kGuidA);
specifics.set_origin(kHttpsOrigin);
specifics.set_use_count(7);
specifics.set_use_date(1423182152);
specifics.add_name_first("John");
specifics.add_name_middle("K.");
specifics.add_name_last("Doe");
specifics.add_name_full("John K. Doe, Jr.");
specifics.add_email_address("user@example.com");
specifics.add_phone_home_whole_number("1.800.555.1234");
specifics.set_address_home_line1("123 Fake St.");
specifics.set_address_home_line2("Apt. 42");
specifics.set_address_home_street_address(
"123 Fake St.\n"
"Apt. 42");
specifics.set_company_name("Google, Inc.");
specifics.set_address_home_city("Mountain View");
specifics.set_address_home_state("California");
specifics.set_address_home_zip("94043");
specifics.set_address_home_country("US");
specifics.set_address_home_sorting_code("CEDEX");
specifics.set_address_home_dependent_locality("Santa Clara");
specifics.set_address_home_language_code("en");
specifics.set_validity_state_bitfield(kValidityStateBitfield);
return specifics;
}
} // namespace
class AutofillProfileSyncBridgeTest : public testing::Test {
......@@ -188,6 +287,12 @@ class AutofillProfileSyncBridgeTest : public testing::Test {
real_processor_->OnUpdateReceived(state, initial_updates);
}
void ApplySyncChanges(const EntityChangeList& changes) {
const base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(), changes);
EXPECT_FALSE(error) << error->ToString();
}
void AddAutofillProfilesToTable(
const std::vector<AutofillProfile>& profile_list) {
for (const auto& profile : profile_list) {
......@@ -195,7 +300,7 @@ class AutofillProfileSyncBridgeTest : public testing::Test {
}
}
std::vector<AutofillProfile> GetAllData() {
std::vector<AutofillProfile> GetAllLocalData() {
std::vector<AutofillProfile> data;
// Perform an async call synchronously for testing.
base::RunLoop loop;
......@@ -308,6 +413,34 @@ TEST_F(AutofillProfileSyncBridgeTest, AutofillProfileChanged_Updated) {
bridge()->AutofillProfileChanged(change);
}
// Usage stats should be updated by the client.
TEST_F(AutofillProfileSyncBridgeTest,
AutofillProfileChanged_Updated_UsageStatsOverwrittenByClient) {
// Remote data has a profile with usage stats.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_address_home_language_code("en");
remote.set_use_count(9);
remote.set_use_date(25);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(),
ElementsAre(WithUsageStats(CreateAutofillProfile(remote))));
// Update to the usage stats for that profile.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.set_language_code("en");
local.set_use_count(10U);
local.set_use_date(base::Time::FromTimeT(30));
AutofillProfileChange change(AutofillProfileChange::UPDATE, kGuidA, &local);
EXPECT_CALL(
mock_processor(),
Put(kGuidA, HasSpecifics(CreateAutofillProfileSpecifics(local)), _));
bridge()->AutofillProfileChanged(change);
}
// Server profile updates should be ignored.
TEST_F(AutofillProfileSyncBridgeTest,
AutofillProfileChanged_Updated_IgnoreServerProfiles) {
......@@ -340,7 +473,7 @@ TEST_F(AutofillProfileSyncBridgeTest, GetAllDataForDebugging) {
local2.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("2 2nd st"));
AddAutofillProfilesToTable({local1, local2});
EXPECT_THAT(GetAllData(), ElementsAre(local1, local2));
EXPECT_THAT(GetAllLocalData(), UnorderedElementsAre(local1, local2));
}
TEST_F(AutofillProfileSyncBridgeTest, GetData) {
......@@ -366,6 +499,333 @@ TEST_F(AutofillProfileSyncBridgeTest, GetData) {
EXPECT_THAT(data, ElementsAre(local1));
}
TEST_F(AutofillProfileSyncBridgeTest, MergeSyncData) {
AutofillProfile local1 = AutofillProfile(kGuidA, kHttpOrigin);
local1.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
local1.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AutofillProfile local2 = AutofillProfile(kGuidB, std::string());
local2.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Tom"));
local2.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("2 2nd st"));
AddAutofillProfilesToTable({local1, local2});
AutofillProfileSpecifics remote1 =
CreateAutofillProfileSpecifics(kGuidC, kHttpsOrigin);
remote1.add_name_first("Jane");
AutofillProfileSpecifics remote2 =
CreateAutofillProfileSpecifics(kGuidD, kSettingsOrigin);
remote2.add_name_first("Harry");
// This one will have the name and origin updated.
AutofillProfileSpecifics remote3 =
CreateAutofillProfileSpecifics(kGuidB, kSettingsOrigin);
remote3.add_name_first("Tom Doe");
EXPECT_CALL(
mock_processor(),
Put(kGuidA, HasSpecifics(CreateAutofillProfileSpecifics(local1)), _));
EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
StartSyncing({remote1, remote2, remote3});
EXPECT_THAT(GetAllLocalData(),
UnorderedElementsAre(local1, CreateAutofillProfile(remote1),
CreateAutofillProfile(remote2),
CreateAutofillProfile(remote3)));
}
// Ensure that all profile fields are able to be synced up from the client to
// the server.
TEST_F(AutofillProfileSyncBridgeTest, MergeSyncData_SyncAllFieldsToServer) {
AutofillProfile local = ConstructCompleteProfile();
AddAutofillProfilesToTable({local});
// This complete profile is fully uploaded to sync.
EXPECT_CALL(mock_processor(),
Put(_, HasSpecifics(ConstructCompleteSpecifics()), _));
StartSyncing({});
// No changes locally.
EXPECT_THAT(GetAllLocalData(), ElementsAre(WithUsageStats(local)));
}
// Ensure that all profile fields are able to be synced down from the server to
// the client (and nothing gets uploaded back).
TEST_F(AutofillProfileSyncBridgeTest, MergeSyncData_SyncAllFieldsToClient) {
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({ConstructCompleteSpecifics()});
EXPECT_THAT(GetAllLocalData(),
ElementsAre(WithUsageStats(ConstructCompleteProfile())));
}
TEST_F(AutofillProfileSyncBridgeTest, MergeSyncData_IdenticalProfiles) {
AutofillProfile local1 = AutofillProfile(kGuidA, kHttpOrigin);
local1.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
local1.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AutofillProfile local2 = AutofillProfile(kGuidB, kSettingsOrigin);
local2.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Tom"));
local2.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("2 2nd st"));
AddAutofillProfilesToTable({local1, local2});
// The synced profiles are identical to the local ones, except that the guids
// are different.
AutofillProfileSpecifics remote1 =
CreateAutofillProfileSpecifics(kGuidC, kHttpsOrigin);
remote1.add_name_first("John");
remote1.set_address_home_street_address("1 1st st");
AutofillProfileSpecifics remote2 =
CreateAutofillProfileSpecifics(kGuidD, kHttpsOrigin);
remote2.add_name_first("Tom");
remote2.set_address_home_street_address("2 2nd st");
// Both remote profiles win, only the verified origin is taken over for the
// second profile.
AutofillProfileSpecifics merged2(remote2);
merged2.set_origin(kSettingsOrigin);
EXPECT_CALL(mock_processor(), Put(kGuidD, HasSpecifics(merged2), _));
StartSyncing({remote1, remote2});
EXPECT_THAT(GetAllLocalData(),
UnorderedElementsAre(CreateAutofillProfile(remote1),
CreateAutofillProfile(merged2)));
}
TEST_F(AutofillProfileSyncBridgeTest, MergeSyncData_SimilarProfiles) {
AutofillProfile local1 = AutofillProfile(kGuidA, kHttpOrigin);
local1.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
local1.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
local1.set_use_count(27);
AutofillProfile local2 = AutofillProfile(kGuidB, kSettingsOrigin);
local2.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Tom"));
local2.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("2 2nd st"));
AddAutofillProfilesToTable({local1, local2});
// The synced profiles are identical to the local ones, except that the guids
// and use_count values are different.
AutofillProfileSpecifics remote1 =
CreateAutofillProfileSpecifics(kGuidC, kHttpsOrigin);
remote1.add_name_first("John");
remote1.set_address_home_street_address("1 1st st");
remote1.set_company_name("Frobbers, Inc.");
remote1.set_use_count(13);
AutofillProfileSpecifics remote2 =
CreateAutofillProfileSpecifics(kGuidD, kHttpsOrigin);
remote2.add_name_first("Tom");
remote2.set_address_home_street_address("2 2nd st");
remote2.set_company_name("Fizzbang, LLC.");
remote2.set_use_count(4);
// The first profile should have its origin updated.
// The second profile should remain as-is, because an unverified profile
// should never overwrite a verified one.
AutofillProfileSpecifics merged1(remote1);
merged1.set_origin(kHttpOrigin);
// TODO(jkrcal): This is taken over from the previous test suite without any
// reasoning why this happens. This indeed happens, deep in
// AutofillProfileComparator when merging profiles both without NAME_FULL, we
// obtain a profile with NAME_FULL. Not sure if intended.
merged1.add_name_full("John");
// Merging two profile takes their max use count.
merged1.set_use_count(27);
// Expect updating the first (merged) profile and adding the second local one.
EXPECT_CALL(mock_processor(), Put(kGuidC, HasSpecifics(merged1), _));
EXPECT_CALL(
mock_processor(),
Put(kGuidB, HasSpecifics(CreateAutofillProfileSpecifics(local2)), _));
EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
StartSyncing({remote1, remote2});
EXPECT_THAT(GetAllLocalData(),
UnorderedElementsAre(
WithUsageStats(CreateAutofillProfile(merged1)), local2,
WithUsageStats(CreateAutofillProfile(remote2))));
}
// TODO(jkrcal): All the MergeSimilarProfiles_* tests need some diff in Info to
// trigger the merge similar code path (we create the diff using phone number).
// Otherwise, we trigger the merge same code path and none of the tests pass. Is
// it desired?
// Tests that MergeSimilarProfiles keeps the most recent use date of the two
// profiles being merged.
TEST_F(AutofillProfileSyncBridgeTest,
MergeSyncData_SimilarProfiles_OlderUseDate) {
// Different guids, same origin, difference in the phone number.
AutofillProfile local(kGuidA, kHttpOrigin);
local.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("650234567"));
local.set_use_date(base::Time::FromTimeT(30));
AddAutofillProfilesToTable({local});
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidB, kHttpOrigin);
// |local| has a more recent use date.
remote.set_use_date(25);
// The use date of |local| should replace the use date of |remote|.
AutofillProfileSpecifics merged(remote);
merged.set_use_date(30);
merged.add_phone_home_whole_number("650234567");
EXPECT_CALL(mock_processor(), Put(kGuidB, HasSpecifics(merged), _));
StartSyncing({remote});
}
// Tests that MergeSimilarProfiles keeps the most recent use date of the two
// profiles being merged.
TEST_F(AutofillProfileSyncBridgeTest,
MergeSyncData_SimilarProfiles_NewerUseDate) {
// Different guids, same origin, difference in the phone number.
AutofillProfile local(kGuidA, kHttpOrigin);
local.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("650234567"));
local.set_use_date(base::Time::FromTimeT(30));
AddAutofillProfilesToTable({local});
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidB, kHttpOrigin);
// |remote| has a more recent use date.
remote.set_use_date(35);
// The use date of |local| should _not_ replace the use date of |remote|.
AutofillProfileSpecifics merged(remote);
merged.add_phone_home_whole_number("650234567");
EXPECT_CALL(mock_processor(), Put(kGuidB, HasSpecifics(merged), _));
StartSyncing({remote});
}
// Tests that MergeSimilarProfiles saves the max of the use counts of the two
// profiles in |remote|.
TEST_F(AutofillProfileSyncBridgeTest,
MergeSyncData_SimilarProfiles_NonZeroUseCounts) {
// Different guids, same origin, difference in the phone number.
AutofillProfile local(kGuidA, kHttpOrigin);
local.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("650234567"));
local.set_use_count(12);
AddAutofillProfilesToTable({local});
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidB, kHttpOrigin);
remote.set_use_count(5);
// The use count of |local| should replace the use count of |remote|.
AutofillProfileSpecifics merged(remote);
merged.set_use_count(12);
merged.add_phone_home_whole_number("650234567");
EXPECT_CALL(mock_processor(), Put(kGuidB, HasSpecifics(merged), _));
StartSyncing({remote});
}
// Tests that when merging similar profiles for initial sync, we add the
// additional information of |local| into |remote|.
TEST_F(AutofillProfileSyncBridgeTest,
MergeSyncData_SimilarProfiles_LocalOriginPreserved) {
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("650234567"));
AddAutofillProfilesToTable({local});
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidB, kHttpOrigin);
remote.set_address_home_language_code("en");
// Expect that the resulting merged profile is written back to sync and that
// it has the phone number and origin from |local|.
AutofillProfileSpecifics merged(remote);
merged.set_origin(kHttpsOrigin);
merged.add_phone_home_whole_number("650234567");
// TODO(jkrcal): Is this expected that language code gets deleted? Not
// explicitly covered by previous tests but happens.
merged.set_address_home_language_code("");
EXPECT_CALL(mock_processor(), Put(kGuidB, HasSpecifics(merged), _));
StartSyncing({remote});
}
// Sync data without origin should not overwrite existing origin in local
// autofill profile.
TEST_F(AutofillProfileSyncBridgeTest,
MergeSyncData_SimilarProfiles_LocalExistingOriginPreserved) {
AutofillProfile local(kGuidA, kHttpsOrigin);
AddAutofillProfilesToTable({local});
// Remote data does not have an origin value.
AutofillProfileSpecifics remote = CreateAutofillProfileSpecifics(kGuidA, "");
remote.clear_origin();
remote.add_name_first("John");
ASSERT_FALSE(remote.has_origin());
// Expect no sync events to add origin to the remote data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
// Expect the local autofill profile to still have an origin after sync.
AutofillProfile merged(local);
merged.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
// Ensure that no Sync events are generated to fill in missing origins from Sync
// with explicitly present empty ones. This ensures that the migration to add
// origins to profiles does not generate lots of needless Sync updates.
TEST_F(AutofillProfileSyncBridgeTest,
MergeSyncData_SimilarProfiles_LocalMissingOriginPreserved) {
AutofillProfile local = AutofillProfile(kGuidA, std::string());
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
AddAutofillProfilesToTable({local});
// Create a Sync profile identical to |local|, except with no origin set.
AutofillProfileSpecifics remote = CreateAutofillProfileSpecifics(kGuidA, "");
remote.clear_origin();
remote.add_name_first("John");
ASSERT_FALSE(remote.has_origin());
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
TEST_F(AutofillProfileSyncBridgeTest, ApplySyncChanges) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpsOrigin);
AddAutofillProfilesToTable({local});
StartSyncing({});
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidB, kHttpOrigin);
remote.add_name_first("Jane");
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
ApplySyncChanges(
{EntityChange::CreateDelete(kGuidA),
EntityChange::CreateAdd(kGuidB, SpecificsToEntity(remote))});
EXPECT_THAT(GetAllLocalData(), ElementsAre(CreateAutofillProfile(remote)));
}
// Ensure that entries with invalid specifics are ignored.
TEST_F(AutofillProfileSyncBridgeTest, ApplySyncChanges_OmitsInvalidSpecifics) {
StartSyncing({});
AutofillProfileSpecifics remote_valid =
CreateAutofillProfileSpecifics(kGuidA, std::string());
AutofillProfileSpecifics remote_invalid =
CreateAutofillProfileSpecifics(kGuidInvalid, std::string());
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
ApplySyncChanges(
{EntityChange::CreateAdd(kGuidA, SpecificsToEntity(remote_valid)),
EntityChange::CreateAdd(kGuidInvalid,
SpecificsToEntity(remote_invalid))});
EXPECT_THAT(GetAllLocalData(),
ElementsAre(CreateAutofillProfile(remote_valid)));
}
// Verifies that setting the street address field also sets the (deprecated)
// address line 1 and line 2 fields.
TEST_F(AutofillProfileSyncBridgeTest, StreetAddress_SplitAutomatically) {
......@@ -402,4 +862,363 @@ TEST_F(AutofillProfileSyncBridgeTest, StreetAddress_JointAutomatically) {
EXPECT_FALSE(remote.has_address_home_street_address());
}
// Ensure that the street address field takes precedence over the (deprecated)
// address line 1 and line 2 fields, even though these are expected to always be
// in sync in practice.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_StreetAddress_TakesPrecedenceOverAddressLines) {
// Create remote entry with conflicting address data in the street address
// field vs. the address line 1 and address line 2 fields.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_address_home_line1("123 Example St.");
remote.set_address_home_line2("Apt. 42");
remote.set_address_home_street_address(
"456 El Camino Real\n"
"Suite #1337");
StartSyncing({remote});
// Verify that full street address takes precedence over address lines.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS,
ASCIIToUTF16("456 El Camino Real\n"
"Suite #1337"));
local.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("456 El Camino Real"));
local.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("Suite #1337"));
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
// Ensure that no Sync events are generated to fill in missing street address
// fields from Sync with explicitly present ones identical to the data stored in
// the line1 and line2 fields. This ensures that the migration to add the
// street address field to profiles does not generate lots of needless Sync
// updates.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_StreetAddress_NoUpdateToEmptyStreetAddressSyncedUp) {
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("123 Example St.\n"
"Apt. 42"));
AddAutofillProfilesToTable({local});
// Create a Sync profile identical to |profile|, except without street address
// explicitly set.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_address_home_line1("123 Example St.");
remote.set_address_home_line2("Apt. 42");
// No update to sync, no change in local data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
// Missing language code field should not generate sync events.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_LanguageCode_MissingCodesNoSync) {
AutofillProfile local(kGuidA, kHttpsOrigin);
ASSERT_TRUE(local.language_code().empty());
AddAutofillProfilesToTable({local});
// Remote data does not have a language code value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
ASSERT_FALSE(remote.has_address_home_language_code());
// No update to sync, no change in local data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
// Empty language code should be overwritten by sync.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_LanguageCode_ExistingRemoteWinsOverMissingLocal) {
AutofillProfile local(kGuidA, kHttpsOrigin);
ASSERT_TRUE(local.language_code().empty());
AddAutofillProfilesToTable({local});
// Remote data has "en" language code.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_address_home_language_code("en");
// No update to sync, remote language code overwrites the empty local one.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(CreateAutofillProfile(remote)));
}
// Local language code should be overwritten by remote one.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_LanguageCode_ExistingRemoteWinsOverExistingLocal) {
AutofillProfile local(kGuidA, kHttpsOrigin);
local.set_language_code("de");
AddAutofillProfilesToTable({local});
// Remote data has "en" language code.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_address_home_language_code("en");
// No update to sync, remote language code overwrites the local one.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(CreateAutofillProfile(remote)));
}
// Sync data without language code should not overwrite existing language code
// in local autofill profile.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_LanguageCode_ExistingLocalWinsOverMissingRemote) {
// Local autofill profile has "en" language code.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.set_language_code("en");
AddAutofillProfilesToTable({local});
// Remote data does not have a language code value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.add_name_first("John");
ASSERT_FALSE(remote.has_address_home_language_code());
// Expect local autofill profile to still have "en" language code after
AutofillProfile merged(local);
merged.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
// No update to sync, remote language code overwrites the local one.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
// Missing validity state bitifield should not generate sync events.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_ValidityState_DefaultValueNoSync) {
AutofillProfile local(kGuidA, kHttpsOrigin);
ASSERT_EQ(0, local.GetValidityBitfieldValue());
AddAutofillProfilesToTable({local});
// Remote data does not have a validity state bitfield value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
ASSERT_FALSE(remote.has_validity_state_bitfield());
// No update to sync, no change in local data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
// Default validity state bitfield should be overwritten by sync.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_ValidityState_ExistingRemoteWinsOverMissingLocal) {
AutofillProfile local(kGuidA, kHttpsOrigin);
ASSERT_EQ(0, local.GetValidityBitfieldValue());
AddAutofillProfilesToTable({local});
// Remote data has a non default validity state bitfield value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_validity_state_bitfield(kValidityStateBitfield);
ASSERT_TRUE(remote.has_validity_state_bitfield());
// No update to sync, the validity bitfield should be stored to local.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(CreateAutofillProfile(remote)));
}
// Local validity state bitfield should be overwritten by sync.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_ValidityState_ExistingRemoteWinsOverExistingLocal) {
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetValidityFromBitfieldValue(kValidityStateBitfield + 1);
AddAutofillProfilesToTable({local});
// Remote data has a non default validity state bitfield value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_validity_state_bitfield(kValidityStateBitfield);
ASSERT_TRUE(remote.has_validity_state_bitfield());
// No update to sync, the remote validity bitfield should overwrite local.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(CreateAutofillProfile(remote)));
}
// Sync data without a default validity state bitfield should not overwrite
// an existing validity state bitfield in local autofill profile.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_ValidityState_ExistingLocalWinsOverMissingRemote) {
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetValidityFromBitfieldValue(kValidityStateBitfield);
AddAutofillProfilesToTable({local});
// Remote data has a non default validity state bitfield value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.add_name_first("John");
ASSERT_FALSE(remote.has_validity_state_bitfield());
// Expect local autofill profile to still have the validity state after.
AutofillProfile merged(local);
merged.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
// No update to sync, the local validity bitfield should stay untouched.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
// Missing full name field should not generate sync events.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_FullName_MissingValueNoSync) {
// Local autofill profile has an empty full name.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
AddAutofillProfilesToTable({local});
// Remote data does not have a full name value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.add_name_first(std::string("John"));
// No update to sync, no change in local data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_FullName_ExistingLocalWinsOverMissingRemote) {
// Local autofill profile has a full name.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.SetRawInfo(NAME_FULL, ASCIIToUTF16("John Jacob Smith, Jr"));
AddAutofillProfilesToTable({local});
// Remote data does not have a full name value.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.add_name_first(std::string("John"));
remote.add_name_middle(std::string("Jacob"));
remote.add_name_last(std::string("Smith"));
// Expect local autofill profile to still have the same full name after.
AutofillProfile merged(local);
merged.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
merged.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Jacob"));
merged.SetRawInfo(NAME_LAST, ASCIIToUTF16("Smith"));
// No update to sync, no change in local data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
// Missing use_count/use_date fields should not generate sync events.
TEST_F(AutofillProfileSyncBridgeTest,
RemoteWithSameGuid_UsageStats_MissingValueNoSync) {
// Local autofill profile has 0 for use_count/use_date.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.set_language_code("en");
local.set_use_count(0);
local.set_use_date(base::Time());
AddAutofillProfilesToTable({local});
// Remote data does not have use_count/use_date.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.clear_use_count();
remote.clear_use_date();
remote.set_address_home_language_code("en");
// No update to sync, no change in local data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(WithUsageStats(local)));
}
struct UpdatesUsageStatsTestCase {
size_t local_use_count;
base::Time local_use_date;
size_t remote_use_count;
int remote_use_date;
size_t merged_use_count;
base::Time merged_use_date;
};
class AutofillProfileSyncBridgeUpdatesUsageStatsTest
: public AutofillProfileSyncBridgeTest,
public testing::WithParamInterface<UpdatesUsageStatsTestCase> {
public:
AutofillProfileSyncBridgeUpdatesUsageStatsTest() {}
~AutofillProfileSyncBridgeUpdatesUsageStatsTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(AutofillProfileSyncBridgeUpdatesUsageStatsTest);
};
TEST_P(AutofillProfileSyncBridgeUpdatesUsageStatsTest, UpdatesUsageStats) {
auto test_case = GetParam();
// Local data has usage stats.
AutofillProfile local(kGuidA, kHttpsOrigin);
local.set_language_code("en");
local.set_use_count(test_case.local_use_count);
local.set_use_date(test_case.local_use_date);
ASSERT_EQ(test_case.local_use_count, local.use_count());
ASSERT_EQ(test_case.local_use_date, local.use_date());
AddAutofillProfilesToTable({local});
// Remote data has usage stats.
AutofillProfileSpecifics remote =
CreateAutofillProfileSpecifics(kGuidA, kHttpsOrigin);
remote.set_address_home_language_code("en");
remote.set_use_count(test_case.remote_use_count);
remote.set_use_date(test_case.remote_use_date);
ASSERT_TRUE(remote.has_use_count());
ASSERT_TRUE(remote.has_use_date());
// Expect the local autofill profile to have usage stats after sync.
AutofillProfile merged(local);
merged.set_use_count(test_case.merged_use_count);
merged.set_use_date(test_case.merged_use_date);
// Expect no changes to remote data.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
StartSyncing({remote});
EXPECT_THAT(GetAllLocalData(), ElementsAre(WithUsageStats(merged)));
}
INSTANTIATE_TEST_CASE_P(
AutofillProfileSyncBridgeTest,
AutofillProfileSyncBridgeUpdatesUsageStatsTest,
testing::Values(
// Local profile with default stats.
UpdatesUsageStatsTestCase{
/*local_use_count=*/0U,
/*local_use_date=*/base::Time(),
/*remote_use_count=*/9U,
/*remote_use_date=*/4321,
/*merged_use_count=*/9U,
/*merged_use_date=*/base::Time::FromTimeT(4321)},
// Local profile has older stats than the server.
UpdatesUsageStatsTestCase{
/*local_use_count=*/3U,
/*local_use_date=*/base::Time::FromTimeT(1234),
/*remote_use_count=*/9U, /*remote_use_date=*/4321,
/*merged_use_count=*/9U,
/*merged_use_date=*/base::Time::FromTimeT(4321)},
// Local profile has newer stats than the server
UpdatesUsageStatsTestCase{
/*local_use_count=*/10U,
/*local_use_date=*/base::Time::FromTimeT(9999),
/*remote_use_count=*/9U, /*remote_use_date=*/4321,
/*merged_use_count=*/9U,
/*merged_use_date=*/base::Time::FromTimeT(4321)}));
} // namespace autofill
// Copyright 2018 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/autofill/core/browser/webdata/autofill_profile_sync_difference_tracker.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_profile_comparator.h"
#include "components/autofill/core/browser/autofill_profile_sync_util.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/webdata/autofill_profile_sync_bridge.h"
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include "components/sync/model/model_error.h"
namespace autofill {
using base::Optional;
using syncer::ModelError;
AutofillProfileSyncDifferenceTracker::AutofillProfileSyncDifferenceTracker(
AutofillTable* table)
: table_(table) {}
AutofillProfileSyncDifferenceTracker::~AutofillProfileSyncDifferenceTracker() {}
Optional<ModelError>
AutofillProfileSyncDifferenceTracker::IncorporateRemoteProfile(
std::unique_ptr<AutofillProfile> remote) {
const std::string remote_storage_key =
GetStorageKeyFromAutofillProfile(*remote);
if (!GetLocalOnlyEntries()) {
return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
}
Optional<AutofillProfile> local_with_same_storage_key =
ReadEntry(remote_storage_key);
if (local_with_same_storage_key) {
// The remote profile already exists locally with the same key. Update
// the local entry with remote data.
std::unique_ptr<AutofillProfile> updated =
std::make_unique<AutofillProfile>(local_with_same_storage_key.value());
// We ignore remote updates to a verified profile because we want to keep
// the exact version that the user edited by hand.
if (local_with_same_storage_key->IsVerified() && !remote->IsVerified()) {
return base::nullopt;
}
updated->OverwriteDataFrom(*remote);
// TODO(jkrcal): if |updated| deviates from |remote|, we should sync it back
// up. The only way |updated| can differ is having some extra fields
// compared to |remote|. Thus, this cannot lead to an infinite loop of
// commits from two clients as each commit decreases the set of empty
// fields. This invariant depends on the implementation of
// OverwriteDataFrom() and thus should be enforced by a DCHECK.
if (!updated->EqualsForSyncPurposes(*local_with_same_storage_key)) {
// We need to write back locally new changes in this entry.
update_to_local_.push_back(std::move(updated));
}
GetLocalOnlyEntries()->erase(remote_storage_key);
return base::nullopt;
}
// Check if profile appears under a different storage key to be de-duplicated.
for (const auto& pair : *GetLocalOnlyEntries()) {
const std::string& local_storage_key = pair.first;
const AutofillProfile& local = *pair.second;
// Look for exact duplicates, compare only profile contents (and
// ignore origin and language code in comparison).
if (local.Compare(*remote) == 0) {
// We found a duplicate, we keep the new (remote) one and delete the
// local one.
DVLOG(2)
<< "[AUTOFILL SYNC] The profile "
<< base::UTF16ToUTF8(local.GetRawInfo(NAME_FIRST))
<< base::UTF16ToUTF8(local.GetRawInfo(NAME_LAST))
<< " already exists with a different storage key; keep the remote key"
<< remote_storage_key << " and delete the local key "
<< local_storage_key;
// Ensure that a verified profile can never revert back to an unverified
// one. In such a case, take over the local origin for the new (remote)
// entry.
if (local.IsVerified() && !remote->IsVerified()) {
remote->set_origin(local.origin());
// Save a copy of the remote profile also to sync.
save_to_sync_.push_back(std::make_unique<AutofillProfile>(*remote));
}
// Delete the local profile that gets replaced by |remote|.
DeleteFromLocal(local_storage_key);
break;
}
}
add_to_local_.push_back(std::move(remote));
return base::nullopt;
}
Optional<ModelError>
AutofillProfileSyncDifferenceTracker::IncorporateRemoteDelete(
const std::string& storage_key) {
DCHECK(!storage_key.empty());
DeleteFromLocal(storage_key);
return base::nullopt;
}
Optional<ModelError> AutofillProfileSyncDifferenceTracker::FlushToLocal(
base::OnceClosure autofill_changes_callback) {
for (const std::string& storage_key : delete_from_local_) {
if (!table_->RemoveAutofillProfile(storage_key)) {
return ModelError(FROM_HERE, "Failed deleting from WebDatabase");
}
}
for (const std::unique_ptr<AutofillProfile>& entry : add_to_local_) {
if (!table_->AddAutofillProfile(*entry)) {
return ModelError(FROM_HERE, "Failed updating WebDatabase");
}
}
for (const std::unique_ptr<AutofillProfile>& entry : update_to_local_) {
if (!table_->UpdateAutofillProfile(*entry)) {
return ModelError(FROM_HERE, "Failed updating WebDatabase");
}
}
if (!delete_from_local_.empty() || !add_to_local_.empty() ||
!update_to_local_.empty()) {
std::move(autofill_changes_callback).Run();
}
return base::nullopt;
}
Optional<ModelError> AutofillProfileSyncDifferenceTracker::FlushToSync(
std::vector<std::unique_ptr<AutofillProfile>>* profiles_to_upload_to_sync) {
for (std::unique_ptr<AutofillProfile>& entry : save_to_sync_) {
profiles_to_upload_to_sync->push_back(std::move(entry));
}
return base::nullopt;
}
Optional<AutofillProfile> AutofillProfileSyncDifferenceTracker::ReadEntry(
const std::string& storage_key) {
DCHECK(GetLocalOnlyEntries());
auto iter = GetLocalOnlyEntries()->find(storage_key);
if (iter != GetLocalOnlyEntries()->end()) {
return *iter->second;
}
return base::nullopt;
}
void AutofillProfileSyncDifferenceTracker::DeleteFromLocal(
const std::string& storage_key) {
DCHECK(GetLocalOnlyEntries());
delete_from_local_.insert(storage_key);
GetLocalOnlyEntries()->erase(storage_key);
}
std::map<std::string, std::unique_ptr<AutofillProfile>>*
AutofillProfileSyncDifferenceTracker::GetLocalOnlyEntries() {
if (!InitializeLocalOnlyEntriesIfNeeded()) {
return nullptr;
}
return &local_only_entries_;
}
bool AutofillProfileSyncDifferenceTracker::
InitializeLocalOnlyEntriesIfNeeded() {
if (local_only_entries_initialized_) {
return true;
}
std::vector<std::unique_ptr<AutofillProfile>> entries;
if (!table_->GetAutofillProfiles(&entries)) {
return false;
}
for (std::unique_ptr<AutofillProfile>& entry : entries) {
std::string storage_key = GetStorageKeyFromAutofillProfile(*entry);
local_only_entries_[storage_key] = std::move(entry);
}
local_only_entries_initialized_ = true;
return true;
}
AutofillProfileInitialSyncDifferenceTracker::
AutofillProfileInitialSyncDifferenceTracker(AutofillTable* table)
: AutofillProfileSyncDifferenceTracker(table) {}
AutofillProfileInitialSyncDifferenceTracker::
~AutofillProfileInitialSyncDifferenceTracker() {}
Optional<ModelError>
AutofillProfileInitialSyncDifferenceTracker::IncorporateRemoteDelete(
const std::string& storage_key) {
// Remote delete is not allowed in initial sync.
NOTREACHED();
return base::nullopt;
}
Optional<ModelError> AutofillProfileInitialSyncDifferenceTracker::FlushToSync(
std::vector<std::unique_ptr<AutofillProfile>>* profiles_to_upload_to_sync) {
// First, flush standard updates to sync.
AutofillProfileSyncDifferenceTracker::FlushToSync(profiles_to_upload_to_sync);
// For initial sync, we additionally need to upload all local only entries.
if (!GetLocalOnlyEntries()) {
return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
}
for (auto& pair : *GetLocalOnlyEntries()) {
std::string storage_key = pair.first;
// No deletions coming from remote are allowed for initial sync.
DCHECK(delete_from_local_.count(storage_key) == 0);
profiles_to_upload_to_sync->push_back(std::move(pair.second));
}
return base::nullopt;
}
Optional<ModelError>
AutofillProfileInitialSyncDifferenceTracker::MergeSimilarEntriesForInitialSync(
const std::string& app_locale) {
if (!GetLocalOnlyEntries()) {
return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
}
// This merge cannot happen on the fly during IncorporateRemoteSpecifics().
// Namely, we do not want to merge a local entry with a _similar_ remote
// entry if anoter perfectly fitting remote entry comes later during the
// initial sync (a remote entry fits perfectly to a given local entry if
// it has fully equal data or even the same storage key). After all the calls
// to IncorporateRemoteSpecifics() are over, GetLocalOnlyEntries() only
// contains unmatched entries that can be safely merged with similar remote
// entries.
AutofillProfileComparator comparator(app_locale);
// Loop over all new remote entries to find merge candidates. Using
// non-const reference because we want to update |remote| in place if
// needed.
for (std::unique_ptr<AutofillProfile>& remote : add_to_local_) {
Optional<AutofillProfile> local =
FindMergeableLocalEntry(*remote, comparator);
if (!local) {
continue;
}
DVLOG(2)
<< "[AUTOFILL SYNC] A similar profile to "
<< base::UTF16ToUTF8(remote->GetRawInfo(NAME_FIRST))
<< base::UTF16ToUTF8(remote->GetRawInfo(NAME_LAST))
<< " already exists with a different storage key; keep the remote key"
<< GetStorageKeyFromAutofillProfile(*remote)
<< ", merge local data into it and delete the local key"
<< GetStorageKeyFromAutofillProfile(*local);
// For similar profile pairs, the local profile is always removed and its
// content merged (if applicable) in the profile that came from sync.
AutofillProfile remote_before_merge = *remote;
remote->MergeDataFrom(*local, app_locale);
if (!remote->EqualsForSyncPurposes(remote_before_merge)) {
// We need to sync new changes in the entry back to the server.
save_to_sync_.push_back(std::make_unique<AutofillProfile>(*remote));
// |remote| is updated in place within |add_to_local_| so the newest
// merged version is stored to local.
}
DeleteFromLocal(GetStorageKeyFromAutofillProfile(*local));
}
return base::nullopt;
}
Optional<AutofillProfile>
AutofillProfileInitialSyncDifferenceTracker::FindMergeableLocalEntry(
const AutofillProfile& remote,
const AutofillProfileComparator& comparator) {
DCHECK(GetLocalOnlyEntries());
// Both the remote and the local entry need to be non-verified to be
// mergeable.
if (remote.IsVerified()) {
return base::nullopt;
}
// Check if there is a mergeable local profile.
for (const auto& pair : *GetLocalOnlyEntries()) {
const AutofillProfile& local_candidate = *pair.second;
if (!local_candidate.IsVerified() &&
comparator.AreMergeable(local_candidate, remote)) {
return local_candidate;
}
}
return base::nullopt;
}
} // namespace autofill
// Copyright 2018 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.
#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_PROFILE_SYNC_DIFFERENCE_TRACKER_H_
#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_PROFILE_SYNC_DIFFERENCE_TRACKER_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
namespace syncer {
class ModelError;
} // namespace syncer
namespace autofill {
class AutofillProfile;
class AutofillProfileComparator;
class AutofillTable;
// This is used to respond to ApplySyncChanges() and MergeSyncData(). Attempts
// to lazily load local data, and then react to sync data by maintaining
// internal state until flush calls are made, at which point the applicable
// modification should be sent towards local and sync directions.
class AutofillProfileSyncDifferenceTracker {
public:
explicit AutofillProfileSyncDifferenceTracker(AutofillTable* table);
virtual ~AutofillProfileSyncDifferenceTracker();
// Adds a new |remote| entry to the diff tracker, originating from the sync
// server. The provided |remote| entry must be valid.
base::Optional<syncer::ModelError> IncorporateRemoteProfile(
std::unique_ptr<AutofillProfile> remote);
// Informs the diff tracker that the entry with |storage_key| has been deleted
// from the sync server. |storage_key| must be non-empty.
virtual base::Optional<syncer::ModelError> IncorporateRemoteDelete(
const std::string& storage_key);
// Writes all local changes to the provided autofill |table_|. After flushing,
// not further remote changes should get incorporated.
base::Optional<syncer::ModelError> FlushToLocal(
base::OnceClosure autofill_changes_callback);
// Writes into |profiles_to_upload_to_sync| all autofill profiles to be sent
// to the sync server. After flushing, not further remote changes should get
// incorporated.
virtual base::Optional<syncer::ModelError> FlushToSync(
std::vector<std::unique_ptr<AutofillProfile>>*
profiles_to_upload_to_sync);
protected:
// If the entry is found, |entry| will be return, otherwise base::nullopt is
// returned.
base::Optional<AutofillProfile> ReadEntry(const std::string& storage_key);
// Tries to find a local entry that is mergeable with |remote| (according to
// |comparator|). If such an entry is found, it is returned. Otherwise,
// base::nullopt is returned.
base::Optional<AutofillProfile> FindMergeableLocalEntry(
const AutofillProfile& remote,
const AutofillProfileComparator& comparator);
// Informs the tracker that a local entry with |storage_key| should get
// deleted.
void DeleteFromLocal(const std::string& storage_key);
// Accessor for data that is only stored local. Initializes the data if
// needed. Returns nullptr if initialization failed.
std::map<std::string, std::unique_ptr<AutofillProfile>>*
GetLocalOnlyEntries();
// Helper function called by GetLocalOnlyEntries().
bool InitializeLocalOnlyEntriesIfNeeded();
// The table for reading local data.
AutofillTable* const table_;
// This class loads local data from |table_| lazily. This field tracks if that
// has happened or not yet.
bool local_only_entries_initialized_ = false;
// We use unique_ptrs for storing AutofillProfile to avoid unnecessary copies.
// Local data, mapped by storage key. Use unique_to_local() to access it.
std::map<std::string, std::unique_ptr<AutofillProfile>> local_only_entries_;
// Contain changes (originating from sync) that need to be saved to the local
// store.
std::set<std::string> delete_from_local_;
std::vector<std::unique_ptr<AutofillProfile>> add_to_local_;
std::vector<std::unique_ptr<AutofillProfile>> update_to_local_;
// Contains merged data for entries that existed on both sync and local sides
// and need to be saved back to sync.
std::vector<std::unique_ptr<AutofillProfile>> save_to_sync_;
private:
DISALLOW_COPY_AND_ASSIGN(AutofillProfileSyncDifferenceTracker);
};
class AutofillProfileInitialSyncDifferenceTracker
: public AutofillProfileSyncDifferenceTracker {
public:
explicit AutofillProfileInitialSyncDifferenceTracker(AutofillTable* table);
~AutofillProfileInitialSyncDifferenceTracker() override;
base::Optional<syncer::ModelError> IncorporateRemoteDelete(
const std::string& storage_key) override;
base::Optional<syncer::ModelError> FlushToSync(
std::vector<std::unique_ptr<AutofillProfile>>* profiles_to_upload_to_sync)
override;
// Performs an additional pass through remote entries incorporated from sync
// to find any similarities with local entries. Should be run after all
// entries get incorporated but before flushing results to local/sync.
base::Optional<syncer::ModelError> MergeSimilarEntriesForInitialSync(
const std::string& app_locale);
private:
// Returns a local entry that is mergeable with |remote| if it exists.
// Otherwise, returns base::nullopt.
base::Optional<AutofillProfile> FindMergeableLocalEntry(
const AutofillProfile& remote,
const AutofillProfileComparator& comparator);
DISALLOW_COPY_AND_ASSIGN(AutofillProfileInitialSyncDifferenceTracker);
};
} // namespace autofill
#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_PROFILE_SYNC_DIFFERENCE_TRACKER_H_
// Copyright 2018 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/autofill/core/browser/webdata/autofill_profile_sync_difference_tracker.h"
#include "base/bind_helpers.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_profile_sync_util.h"
#include "components/autofill/core/browser/country_names.h"
#include "components/autofill/core/browser/test_autofill_clock.h"
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/sync/model/model_error.h"
#include "components/webdata/common/web_database.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace autofill {
namespace {
using base::ASCIIToUTF16;
using base::MockCallback;
using testing::ElementsAre;
using testing::IsEmpty;
// Some guids for testing.
const char kGuidA[] = "EDC609ED-7EEE-4F27-B00C-423242A9C44A";
const char kGuidB[] = "EDC609ED-7EEE-4F27-B00C-423242A9C44B";
const char kHttpOrigin[] = "http://www.example.com/";
const char kHttpsOrigin[] = "https://www.example.com/";
const char kLocaleString[] = "en-US";
const base::Time kJune2017 = base::Time::FromDoubleT(1497552271);
} // namespace
class AutofillProfileSyncDifferenceTrackerTestBase : public testing::Test {
public:
AutofillProfileSyncDifferenceTrackerTestBase() {}
~AutofillProfileSyncDifferenceTrackerTestBase() override {}
void SetUp() override {
// Fix a time for implicitly constructed use_dates in AutofillProfile.
test_clock_.SetNow(kJune2017);
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
db_.AddTable(&table_);
db_.Init(temp_dir_.GetPath().AppendASCII("SyncTestWebDatabase"));
}
void AddAutofillProfilesToTable(
const std::vector<AutofillProfile>& profile_list) {
for (const auto& profile : profile_list) {
table_.AddAutofillProfile(profile);
}
}
void IncorporateRemoteProfile(const AutofillProfile& profile) {
EXPECT_EQ(base::nullopt, tracker()->IncorporateRemoteProfile(
std::make_unique<AutofillProfile>(profile)));
}
std::vector<AutofillProfile> FlushAndReturnProfilesToUploadToSync() {
EXPECT_EQ(base::nullopt,
tracker()->FlushToLocal(
/*autofill_changes_callback=*/base::DoNothing()));
std::vector<std::unique_ptr<AutofillProfile>> vector_of_unique_ptrs;
EXPECT_EQ(base::nullopt,
tracker()->FlushToSync(
/*profiles_to_upload_to_sync=*/&vector_of_unique_ptrs));
// Copy all the elements by value so that we have a vector that is easier to
// work with in the test.
std::vector<AutofillProfile> vector_of_values;
for (const std::unique_ptr<AutofillProfile>& entry :
vector_of_unique_ptrs) {
vector_of_values.push_back(*entry);
}
return vector_of_values;
}
std::vector<AutofillProfile> GetAllLocalData() {
std::vector<std::unique_ptr<AutofillProfile>> vector_of_unique_ptrs;
// Meant as an assertion but I cannot use ASSERT_TRUE in non-void function.
EXPECT_TRUE(table()->GetAutofillProfiles(&vector_of_unique_ptrs));
// Copy all the elements by value so that we have a vector that is easier to
// work with in the test.
std::vector<AutofillProfile> local_data;
for (const std::unique_ptr<AutofillProfile>& entry :
vector_of_unique_ptrs) {
local_data.push_back(*entry);
}
return local_data;
}
virtual AutofillProfileSyncDifferenceTracker* tracker() = 0;
AutofillTable* table() { return &table_; }
private:
autofill::TestAutofillClock test_clock_;
base::ScopedTempDir temp_dir_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
AutofillTable table_;
WebDatabase db_;
DISALLOW_COPY_AND_ASSIGN(AutofillProfileSyncDifferenceTrackerTestBase);
};
class AutofillProfileSyncDifferenceTrackerTest
: public AutofillProfileSyncDifferenceTrackerTestBase {
public:
AutofillProfileSyncDifferenceTrackerTest() : tracker_(table()) {}
~AutofillProfileSyncDifferenceTrackerTest() override {}
AutofillProfileSyncDifferenceTracker* tracker() override { return &tracker_; }
private:
AutofillProfileSyncDifferenceTracker tracker_;
DISALLOW_COPY_AND_ASSIGN(AutofillProfileSyncDifferenceTrackerTest);
};
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
IncorporateRemoteProfileShouldOverwriteProfileWithSameKey) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
AddAutofillProfilesToTable({local});
// The remote profile is completely different but it has the same key.
AutofillProfile remote = AutofillProfile(kGuidA, kHttpsOrigin);
remote.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Tom"));
IncorporateRemoteProfile(remote);
// Nothing gets uploaded to sync and the remote profile wins.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), IsEmpty());
EXPECT_THAT(GetAllLocalData(), ElementsAre(remote));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
IncorporateRemoteProfileShouldOverwriteUnverifiedProfileByVerified) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpsOrigin);
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
AddAutofillProfilesToTable({local});
// The remote profile has the same key but it is not verified.
AutofillProfile remote = AutofillProfile(kGuidA, kSettingsOrigin);
remote.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Tom"));
IncorporateRemoteProfile(remote);
// Nothing gets uploaded to sync and the local profile wins.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), IsEmpty());
EXPECT_THAT(GetAllLocalData(), ElementsAre(remote));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
IncorporateRemoteProfileShouldNotOverwriteVerifiedProfileByUnverified) {
AutofillProfile local = AutofillProfile(kGuidA, kSettingsOrigin);
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
AddAutofillProfilesToTable({local});
// The remote profile has the same key but it is not verified.
AutofillProfile remote = AutofillProfile(kGuidA, kHttpsOrigin);
remote.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Tom"));
IncorporateRemoteProfile(remote);
// Nothing gets uploaded to sync and the local profile wins.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), IsEmpty());
EXPECT_THAT(GetAllLocalData(), ElementsAre(local));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
IncorporateRemoteProfileShouldNotOverwriteFullNameByEmptyString) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(NAME_FULL, ASCIIToUTF16("John"));
AddAutofillProfilesToTable({local});
// The remote profile has the same key.
AutofillProfile remote = AutofillProfile(kGuidA, kHttpsOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("2 2st st"));
AutofillProfile merged(remote);
merged.SetRawInfo(NAME_FULL, ASCIIToUTF16("John"));
IncorporateRemoteProfile(remote);
// Nothing gets uploaded to sync and the remote profile wins except for the
// full name.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), IsEmpty());
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
IncorporateRemoteProfileShouldMergeIdenticalProfilesWithDifferentKeys) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AddAutofillProfilesToTable({local});
// The remote profile is identical to the local one, except that the guids and
// origins are different.
AutofillProfile remote = AutofillProfile(kGuidB, kHttpsOrigin);
remote.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
IncorporateRemoteProfile(remote);
// Nothing gets uploaded to sync and the remote profile wins.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), IsEmpty());
EXPECT_THAT(GetAllLocalData(), ElementsAre(remote));
}
TEST_F(
AutofillProfileSyncDifferenceTrackerTest,
IncorporateRemoteProfileShouldMergeIdenticalProfilesWithDifferentKeysButKeepVerifiedOrigin) {
AutofillProfile local = AutofillProfile(kGuidA, kSettingsOrigin);
local.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AddAutofillProfilesToTable({local});
// The remote profile has the same key.
AutofillProfile remote = AutofillProfile(kGuidB, kHttpsOrigin);
remote.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John"));
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AutofillProfile merged(remote);
merged.set_origin(kSettingsOrigin);
IncorporateRemoteProfile(remote);
// Nothing gets uploaded to sync and the remote profile wins except for the
// full name.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), ElementsAre(merged));
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
FlushToLocalShouldNotCallbackWhenNotNeeded) {
MockCallback<base::OnceClosure> autofill_changes_callback;
EXPECT_CALL(autofill_changes_callback, Run()).Times(0);
EXPECT_EQ(base::nullopt,
tracker()->FlushToLocal(autofill_changes_callback.Get()));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
FlushToLocalShouldCallbackWhenProfileDeleted) {
AutofillProfile local = AutofillProfile(kGuidA, kSettingsOrigin);
AddAutofillProfilesToTable({local});
tracker()->IncorporateRemoteDelete(kGuidA);
MockCallback<base::OnceClosure> autofill_changes_callback;
EXPECT_CALL(autofill_changes_callback, Run()).Times(1);
EXPECT_EQ(base::nullopt,
tracker()->FlushToLocal(autofill_changes_callback.Get()));
// On top of that, the profile should also get deleted.
EXPECT_THAT(GetAllLocalData(), IsEmpty());
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
FlushToLocalShouldCallbackWhenProfileAdded) {
AutofillProfile remote = AutofillProfile(kGuidA, kSettingsOrigin);
IncorporateRemoteProfile(remote);
MockCallback<base::OnceClosure> autofill_changes_callback;
EXPECT_CALL(autofill_changes_callback, Run()).Times(1);
EXPECT_EQ(base::nullopt,
tracker()->FlushToLocal(autofill_changes_callback.Get()));
// On top of that, the profile should also get added.
EXPECT_THAT(GetAllLocalData(), ElementsAre(remote));
}
TEST_F(AutofillProfileSyncDifferenceTrackerTest,
FlushToLocalShouldCallbackWhenProfileUpdated) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpsOrigin);
AddAutofillProfilesToTable({local});
AutofillProfile remote = AutofillProfile(kGuidA, kHttpsOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
IncorporateRemoteProfile(remote);
MockCallback<base::OnceClosure> autofill_changes_callback;
EXPECT_CALL(autofill_changes_callback, Run()).Times(1);
EXPECT_EQ(base::nullopt,
tracker()->FlushToLocal(autofill_changes_callback.Get()));
// On top of that, the profile with key kGuidA should also get updated.
EXPECT_THAT(GetAllLocalData(), ElementsAre(remote));
}
class AutofillProfileInitialSyncDifferenceTrackerTest
: public AutofillProfileSyncDifferenceTrackerTestBase {
public:
AutofillProfileInitialSyncDifferenceTrackerTest()
: initial_tracker_(table()) {}
~AutofillProfileInitialSyncDifferenceTrackerTest() override {}
void MergeSimilarEntriesForInitialSync() {
initial_tracker_.MergeSimilarEntriesForInitialSync(kLocaleString);
}
AutofillProfileSyncDifferenceTracker* tracker() override {
return &initial_tracker_;
}
private:
AutofillProfileInitialSyncDifferenceTracker initial_tracker_;
DISALLOW_COPY_AND_ASSIGN(AutofillProfileInitialSyncDifferenceTrackerTest);
};
TEST_F(AutofillProfileInitialSyncDifferenceTrackerTest,
MergeSimilarEntriesForInitialSyncShouldSyncUpChanges) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
local.set_use_count(27);
AddAutofillProfilesToTable({local});
// The remote profile matches the local one (except for origin and use count).
AutofillProfile remote = AutofillProfile(kGuidB, kHttpsOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
remote.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Frobbers, Inc."));
remote.set_use_count(13);
// The remote profile wins (as regards the storage key).
AutofillProfile merged(remote);
// The local origin wins when merging.
merged.set_origin(kHttpOrigin);
// Merging two profile takes their max use count.
merged.set_use_count(27);
IncorporateRemoteProfile(remote);
MergeSimilarEntriesForInitialSync();
// The merged profile needs to get uploaded back to sync and stored locally.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), ElementsAre(merged));
EXPECT_THAT(GetAllLocalData(), ElementsAre(merged));
}
TEST_F(AutofillProfileInitialSyncDifferenceTrackerTest,
MergeSimilarEntriesForInitialSyncShouldNotSyncUpWhenNotNeeded) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
local.set_use_count(13);
AddAutofillProfilesToTable({local});
// The remote profile matches the local one and has some additional data.
AutofillProfile remote = AutofillProfile(kGuidB, kHttpOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
remote.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Frobbers, Inc."));
// Merging two profile takes their max use count, so use count of 27 is taken.
remote.set_use_count(27);
IncorporateRemoteProfile(remote);
MergeSimilarEntriesForInitialSync();
// Nothing gets uploaded to sync and the remote profile wins.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), IsEmpty());
EXPECT_THAT(GetAllLocalData(), ElementsAre(remote));
}
TEST_F(AutofillProfileInitialSyncDifferenceTrackerTest,
MergeSimilarEntriesForInitialSyncNotMatchNonsimilarEntries) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
local.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Frobbers, Inc."));
AddAutofillProfilesToTable({local});
// The remote profile has a different street address.
AutofillProfile remote = AutofillProfile(kGuidB, kHttpOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("2 2st st"));
remote.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Frobbers, Inc."));
IncorporateRemoteProfile(remote);
MergeSimilarEntriesForInitialSync();
// The local profile gets uploaded (due to initial sync) and the remote
// profile gets stored locally.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), ElementsAre(local));
EXPECT_THAT(GetAllLocalData(), ElementsAre(local, remote));
}
TEST_F(AutofillProfileInitialSyncDifferenceTrackerTest,
MergeSimilarEntriesForInitialSyncDoesNotMatchLocalVerifiedEntry) {
// The local entry is verified, should not get merged.
AutofillProfile local = AutofillProfile(kGuidA, kSettingsOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AddAutofillProfilesToTable({local});
// The remote profile is similar to the local one.
AutofillProfile remote = AutofillProfile(kGuidB, kHttpOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
remote.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Frobbers, Inc."));
IncorporateRemoteProfile(remote);
MergeSimilarEntriesForInitialSync();
// The local profile gets uploaded (due to initial sync) and the remote
// profile gets stored locally.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), ElementsAre(local));
EXPECT_THAT(GetAllLocalData(), ElementsAre(local, remote));
}
TEST_F(AutofillProfileInitialSyncDifferenceTrackerTest,
MergeSimilarEntriesForInitialSyncDoesNotMatchRemoteVerifiedEntry) {
AutofillProfile local = AutofillProfile(kGuidA, kHttpOrigin);
local.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
AddAutofillProfilesToTable({local});
// The remote profile is similar to the local one but is verified and thus it
// should not get merged.
AutofillProfile remote = AutofillProfile(kGuidB, kSettingsOrigin);
remote.SetRawInfo(ADDRESS_HOME_STREET_ADDRESS, ASCIIToUTF16("1 1st st"));
remote.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Frobbers, Inc."));
IncorporateRemoteProfile(remote);
MergeSimilarEntriesForInitialSync();
// The local profile gets uploaded (due to initial sync) and the remote
// profile gets stored locally.
EXPECT_THAT(FlushAndReturnProfilesToUploadToSync(), ElementsAre(local));
EXPECT_THAT(GetAllLocalData(), ElementsAre(local, remote));
}
} // namespace autofill
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment