| // Copyright 2016 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/autocomplete_sync_bridge.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "components/autofill/core/browser/webdata/autofill_entry.h" |
| #include "components/autofill/core/browser/webdata/autofill_table.h" |
| #include "components/autofill/core/browser/webdata/mock_autofill_webdata_backend.h" |
| #include "components/sync/base/hash_util.h" |
| #include "components/sync/model/data_batch.h" |
| #include "components/sync/model/data_type_activation_request.h" |
| #include "components/sync/model/metadata_batch.h" |
| #include "components/sync/model/mock_model_type_change_processor.h" |
| #include "components/sync/model/model_error.h" |
| #include "components/sync/model_impl/client_tag_based_model_type_processor.h" |
| #include "components/sync/test/test_matchers.h" |
| #include "components/webdata/common/web_database.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::ScopedTempDir; |
| using base::Time; |
| using base::TimeDelta; |
| using base::UTF8ToUTF16; |
| using sync_pb::AutofillSpecifics; |
| using sync_pb::EntityMetadata; |
| using sync_pb::EntitySpecifics; |
| using sync_pb::ModelTypeState; |
| using syncer::DataBatch; |
| using syncer::EntityChange; |
| using syncer::EntityChangeList; |
| using syncer::EntityData; |
| using syncer::EntityDataPtr; |
| using syncer::HasInitialSyncDone; |
| using syncer::IsEmptyMetadataBatch; |
| using syncer::KeyAndData; |
| using syncer::MockModelTypeChangeProcessor; |
| using syncer::ModelError; |
| using syncer::ModelType; |
| using syncer::ModelTypeChangeProcessor; |
| using syncer::ModelTypeSyncBridge; |
| using testing::_; |
| using testing::IsEmpty; |
| using testing::Not; |
| using testing::Return; |
| using testing::SizeIs; |
| |
| namespace autofill { |
| |
| namespace { |
| |
| const char kNameFormat[] = "name %d"; |
| const char kValueFormat[] = "value %d"; |
| |
| MATCHER_P(HasSpecifics, expected, "") { |
| const AutofillSpecifics& s1 = arg->specifics.autofill(); |
| const AutofillSpecifics& s2 = expected; |
| |
| if (s1.usage_timestamp().size() != s2.usage_timestamp().size()) { |
| *result_listener << "usage_timstamp().size() not equal: " |
| << s1.usage_timestamp().size() |
| << "!=" << s2.usage_timestamp().size(); |
| return false; |
| } |
| int size = std::min(s1.usage_timestamp().size(), s2.usage_timestamp().size()); |
| for (int i = 0; i < size; ++i) { |
| if (s1.usage_timestamp(i) != s2.usage_timestamp(i)) { |
| *result_listener << "usage_timstamp(" << i |
| << ") not equal: " << s1.usage_timestamp(i) |
| << "!=" << s2.usage_timestamp(i); |
| return false; |
| } |
| } |
| |
| if (s1.name() != s2.name()) { |
| *result_listener << "name() not equal: " << s1.name() << "!=" << s2.name(); |
| return false; |
| } |
| |
| if (s1.value() != s2.value()) { |
| *result_listener << "value() not equal: " << s1.value() |
| << "!=" << s2.value(); |
| return false; |
| } |
| |
| if (s1.has_profile() != s2.has_profile()) { |
| *result_listener << "has_profile() not equal: " << s1.has_profile() |
| << "!=" << s2.has_profile(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void VerifyDataBatch(std::map<std::string, AutofillSpecifics> expected, |
| std::unique_ptr<DataBatch> batch) { |
| while (batch->HasNext()) { |
| const KeyAndData& data_pair = batch->Next(); |
| auto expected_iter = expected.find(data_pair.first); |
| ASSERT_NE(expected_iter, expected.end()); |
| EXPECT_THAT(data_pair.second, HasSpecifics(expected_iter->second)); |
| // Removing allows us to verify we don't see the same item multiple times, |
| // and that we saw everything we expected. |
| expected.erase(expected_iter); |
| } |
| EXPECT_TRUE(expected.empty()); |
| } |
| |
| AutofillEntry CreateAutofillEntry(const AutofillSpecifics& autofill_specifics) { |
| AutofillKey key(UTF8ToUTF16(autofill_specifics.name()), |
| UTF8ToUTF16(autofill_specifics.value())); |
| Time date_created, date_last_used; |
| const google::protobuf::RepeatedField<int64_t>& timestamps = |
| autofill_specifics.usage_timestamp(); |
| if (!timestamps.empty()) { |
| date_created = Time::FromInternalValue(*timestamps.begin()); |
| date_last_used = Time::FromInternalValue(*timestamps.rbegin()); |
| } |
| return AutofillEntry(key, date_created, date_last_used); |
| } |
| |
| } // namespace |
| |
| class AutocompleteSyncBridgeTest : public testing::Test { |
| public: |
| AutocompleteSyncBridgeTest() {} |
| ~AutocompleteSyncBridgeTest() override {} |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| db_.AddTable(&table_); |
| db_.Init(temp_dir_.GetPath().AppendASCII("SyncTestWebDatabase")); |
| ON_CALL(*backend(), GetDatabase()).WillByDefault(Return(&db_)); |
| ResetProcessor(); |
| ResetBridge(); |
| } |
| |
| void ResetProcessor() { |
| real_processor_ = |
| std::make_unique<syncer::ClientTagBasedModelTypeProcessor>( |
| syncer::AUTOFILL, /*dump_stack=*/base::DoNothing(), |
| /*commit_only=*/false); |
| mock_processor_.DelegateCallsByDefaultTo(real_processor_.get()); |
| } |
| |
| void ResetBridge() { |
| bridge_.reset(new AutocompleteSyncBridge( |
| &backend_, mock_processor_.CreateForwardingProcessor())); |
| } |
| |
| void StartSyncing(const std::vector<AutofillSpecifics>& remote_data = {}) { |
| base::RunLoop loop; |
| syncer::DataTypeActivationRequest request; |
| request.error_handler = base::DoNothing(); |
| real_processor_->OnSyncStarting( |
| request, |
| base::BindLambdaForTesting( |
| [&loop](std::unique_ptr<syncer::DataTypeActivationResponse>) { |
| loop.Quit(); |
| })); |
| loop.Run(); |
| |
| // Initialize the processor with initial_sync_done. |
| sync_pb::ModelTypeState state; |
| state.set_initial_sync_done(true); |
| syncer::UpdateResponseDataList initial_updates; |
| for (const AutofillSpecifics& specifics : remote_data) { |
| initial_updates.push_back(SpecificsToUpdateResponse(specifics)); |
| } |
| real_processor_->OnUpdateReceived(state, initial_updates); |
| } |
| |
| void SaveSpecificsToTable( |
| const std::vector<AutofillSpecifics>& specifics_list) { |
| std::vector<AutofillEntry> new_entries; |
| for (const auto& specifics : specifics_list) { |
| new_entries.push_back(CreateAutofillEntry(specifics)); |
| } |
| table_.UpdateAutofillEntries(new_entries); |
| } |
| |
| AutofillSpecifics CreateSpecifics(const std::string& name, |
| const std::string& value, |
| const std::vector<int>& timestamps) { |
| AutofillSpecifics specifics; |
| specifics.set_name(name); |
| specifics.set_value(value); |
| for (int timestamp : timestamps) { |
| specifics.add_usage_timestamp( |
| Time::FromTimeT(timestamp).ToInternalValue()); |
| } |
| return specifics; |
| } |
| |
| AutofillSpecifics CreateSpecifics(int suffix, |
| const std::vector<int>& timestamps) { |
| return CreateSpecifics(base::StringPrintf(kNameFormat, suffix), |
| base::StringPrintf(kValueFormat, suffix), |
| timestamps); |
| } |
| |
| AutofillSpecifics CreateSpecifics(int suffix) { |
| return CreateSpecifics(suffix, std::vector<int>{0}); |
| } |
| |
| std::string GetClientTag(const AutofillSpecifics& specifics) { |
| std::string tag = |
| bridge()->GetClientTag(SpecificsToEntity(specifics).value()); |
| EXPECT_FALSE(tag.empty()); |
| return tag; |
| } |
| |
| std::string GetStorageKey(const AutofillSpecifics& specifics) { |
| std::string key = |
| bridge()->GetStorageKey(SpecificsToEntity(specifics).value()); |
| EXPECT_FALSE(key.empty()); |
| return key; |
| } |
| |
| EntityChangeList CreateEntityAddList( |
| const std::vector<AutofillSpecifics>& specifics_vector) { |
| EntityChangeList changes; |
| for (const auto& specifics : specifics_vector) { |
| changes.push_back(EntityChange::CreateAdd(GetStorageKey(specifics), |
| SpecificsToEntity(specifics))); |
| } |
| return changes; |
| } |
| |
| EntityDataPtr SpecificsToEntity(const AutofillSpecifics& specifics) { |
| EntityData data; |
| *data.specifics.mutable_autofill() = specifics; |
| data.client_tag_hash = syncer::GenerateSyncableHash( |
| syncer::AUTOFILL, bridge()->GetClientTag(data)); |
| return data.PassToPtr(); |
| } |
| |
| syncer::UpdateResponseData SpecificsToUpdateResponse( |
| const AutofillSpecifics& specifics) { |
| syncer::UpdateResponseData data; |
| data.entity = SpecificsToEntity(specifics); |
| return data; |
| } |
| |
| void ApplyChanges(const EntityChangeList& changes) { |
| const auto error = bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), changes); |
| EXPECT_FALSE(error); |
| } |
| |
| void ApplyAdds(const std::vector<AutofillSpecifics>& specifics) { |
| ApplyChanges(CreateEntityAddList(specifics)); |
| } |
| |
| std::map<std::string, AutofillSpecifics> ExpectedMap( |
| const std::vector<AutofillSpecifics>& specifics_vector) { |
| std::map<std::string, AutofillSpecifics> map; |
| for (const auto& specifics : specifics_vector) { |
| map[GetStorageKey(specifics)] = specifics; |
| } |
| return map; |
| } |
| |
| void VerifyAllData(const std::vector<AutofillSpecifics>& expected) { |
| bridge()->GetAllDataForDebugging( |
| base::BindOnce(&VerifyDataBatch, ExpectedMap(expected))); |
| } |
| |
| AutocompleteSyncBridge* bridge() { return bridge_.get(); } |
| |
| syncer::MockModelTypeChangeProcessor& mock_processor() { |
| return mock_processor_; |
| } |
| |
| AutofillTable* table() { return &table_; } |
| |
| MockAutofillWebDataBackend* backend() { return &backend_; } |
| |
| private: |
| ScopedTempDir temp_dir_; |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| testing::NiceMock<MockAutofillWebDataBackend> backend_; |
| AutofillTable table_; |
| WebDatabase db_; |
| std::unique_ptr<AutocompleteSyncBridge> bridge_; |
| testing::NiceMock<MockModelTypeChangeProcessor> mock_processor_; |
| std::unique_ptr<syncer::ClientTagBasedModelTypeProcessor> real_processor_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AutocompleteSyncBridgeTest); |
| }; |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetClientTag) { |
| std::string tag = GetClientTag(CreateSpecifics(1)); |
| EXPECT_EQ(tag, GetClientTag(CreateSpecifics(1))); |
| EXPECT_NE(tag, GetClientTag(CreateSpecifics(2))); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetClientTagNotAffectedByTimestamp) { |
| AutofillSpecifics specifics = CreateSpecifics(1); |
| std::string tag = GetClientTag(specifics); |
| |
| specifics.add_usage_timestamp(1); |
| EXPECT_EQ(tag, GetClientTag(specifics)); |
| |
| specifics.add_usage_timestamp(0); |
| EXPECT_EQ(tag, GetClientTag(specifics)); |
| |
| specifics.add_usage_timestamp(-1); |
| EXPECT_EQ(tag, GetClientTag(specifics)); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetClientTagRespectsNullCharacter) { |
| AutofillSpecifics specifics; |
| std::string tag = GetClientTag(specifics); |
| |
| specifics.set_value(std::string("\0", 1)); |
| EXPECT_NE(tag, GetClientTag(specifics)); |
| } |
| |
| // The client tags should never change as long as we want to maintain backwards |
| // compatibility with the previous iteration of autocomplete-sync integration, |
| // AutocompleteSyncableService and Sync's Directory. This is because old clients |
| // will re-generate client tags and then hashes on local changes, and this |
| // process must create identical values to what this client has created. If this |
| // test case starts failing, you should not alter the fixed values here unless |
| // you know what you're doing. |
| TEST_F(AutocompleteSyncBridgeTest, GetClientTagFixed) { |
| EXPECT_EQ("autofill_entry|name%201|value%201", |
| GetClientTag(CreateSpecifics(1))); |
| EXPECT_EQ("autofill_entry|name%202|value%202", |
| GetClientTag(CreateSpecifics(2))); |
| EXPECT_EQ("autofill_entry||", GetClientTag(AutofillSpecifics())); |
| AutofillSpecifics specifics; |
| specifics.set_name("\xEC\xA4\x91"); |
| specifics.set_value("\xD0\x80"); |
| EXPECT_EQ("autofill_entry|%EC%A4%91|%D0%80", GetClientTag(specifics)); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetStorageKey) { |
| std::string key = GetStorageKey(CreateSpecifics(1)); |
| EXPECT_EQ(key, GetStorageKey(CreateSpecifics(1))); |
| EXPECT_NE(key, GetStorageKey(CreateSpecifics(2))); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetStorageKeyNotAffectedByTimestamp) { |
| AutofillSpecifics specifics = CreateSpecifics(1); |
| std::string key = GetStorageKey(specifics); |
| |
| specifics.add_usage_timestamp(1); |
| EXPECT_EQ(key, GetStorageKey(specifics)); |
| |
| specifics.add_usage_timestamp(0); |
| EXPECT_EQ(key, GetStorageKey(specifics)); |
| |
| specifics.add_usage_timestamp(-1); |
| EXPECT_EQ(key, GetStorageKey(specifics)); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetStorageKeyRespectsNullCharacter) { |
| AutofillSpecifics specifics; |
| std::string key = GetStorageKey(specifics); |
| |
| specifics.set_value(std::string("\0", 1)); |
| EXPECT_NE(key, GetStorageKey(specifics)); |
| } |
| |
| // The storage key should never accidentally change for existing data. This |
| // would cause lookups to fail and either lose or duplicate user data. It should |
| // be possible for the model type to migrate storage key formats, but doing so |
| // would need to be done very carefully. |
| TEST_F(AutocompleteSyncBridgeTest, GetStorageKeyFixed) { |
| EXPECT_EQ("\n\x6name 1\x12\avalue 1", GetStorageKey(CreateSpecifics(1))); |
| EXPECT_EQ("\n\x6name 2\x12\avalue 2", GetStorageKey(CreateSpecifics(2))); |
| // This literal contains the null terminating character, which causes |
| // std::string to stop copying early if we don't tell it how much to read. |
| EXPECT_EQ(std::string("\n\0\x12\0", 4), GetStorageKey(AutofillSpecifics())); |
| AutofillSpecifics specifics; |
| specifics.set_name("\xEC\xA4\x91"); |
| specifics.set_value("\xD0\x80"); |
| EXPECT_EQ("\n\x3\xEC\xA4\x91\x12\x2\xD0\x80", GetStorageKey(specifics)); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetData) { |
| const AutofillSpecifics specifics1 = CreateSpecifics(1); |
| const AutofillSpecifics specifics2 = CreateSpecifics(2); |
| const AutofillSpecifics specifics3 = CreateSpecifics(3); |
| SaveSpecificsToTable({specifics1, specifics2, specifics3}); |
| bridge()->GetData( |
| {GetStorageKey(specifics1), GetStorageKey(specifics3)}, |
| base::BindOnce(&VerifyDataBatch, ExpectedMap({specifics1, specifics3}))); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetDataNotExist) { |
| const AutofillSpecifics specifics1 = CreateSpecifics(1); |
| const AutofillSpecifics specifics2 = CreateSpecifics(2); |
| const AutofillSpecifics specifics3 = CreateSpecifics(3); |
| SaveSpecificsToTable({specifics1, specifics2}); |
| bridge()->GetData( |
| {GetStorageKey(specifics1), GetStorageKey(specifics2), |
| GetStorageKey(specifics3)}, |
| base::BindOnce(&VerifyDataBatch, ExpectedMap({specifics1, specifics2}))); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, GetAllData) { |
| const AutofillSpecifics specifics1 = CreateSpecifics(1); |
| const AutofillSpecifics specifics2 = CreateSpecifics(2); |
| const AutofillSpecifics specifics3 = CreateSpecifics(3); |
| SaveSpecificsToTable({specifics1, specifics2, specifics3}); |
| VerifyAllData({specifics1, specifics2, specifics3}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesEmpty) { |
| // TODO(skym, crbug.com/672619): Ideally would like to verify that the db is |
| // not accessed. |
| ApplyAdds(std::vector<AutofillSpecifics>()); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesSimple) { |
| AutofillSpecifics specifics1 = CreateSpecifics(1); |
| AutofillSpecifics specifics2 = CreateSpecifics(2); |
| ASSERT_NE(specifics1.SerializeAsString(), specifics2.SerializeAsString()); |
| ASSERT_NE(GetStorageKey(specifics1), GetStorageKey(specifics2)); |
| |
| ApplyAdds({specifics1, specifics2}); |
| VerifyAllData({specifics1, specifics2}); |
| |
| ApplyChanges({EntityChange::CreateDelete(GetStorageKey(specifics1))}); |
| VerifyAllData({specifics2}); |
| } |
| |
| // Should be resilient to deleting and updating non-existent things, and adding |
| // existing ones. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesWrongChangeType) { |
| AutofillSpecifics specifics = CreateSpecifics(1, {1}); |
| ApplyChanges({EntityChange::CreateDelete(GetStorageKey(specifics))}); |
| VerifyAllData(std::vector<AutofillSpecifics>()); |
| |
| ApplyChanges({EntityChange::CreateUpdate(GetStorageKey(specifics), |
| SpecificsToEntity(specifics))}); |
| VerifyAllData({specifics}); |
| |
| specifics.add_usage_timestamp(Time::FromTimeT(2).ToInternalValue()); |
| ApplyAdds({specifics}); |
| VerifyAllData({specifics}); |
| } |
| |
| // The format in the table has a fixed 2 timestamp format. Round tripping is |
| // lossy and the middle value should be thrown out. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesThreeTimestamps) { |
| ApplyAdds({CreateSpecifics(1, {1, 2, 3})}); |
| VerifyAllData({CreateSpecifics(1, {1, 3})}); |
| } |
| |
| // The correct format of timestamps is that the first should be smaller and the |
| // second should be larger. Bad foreign data should be repaired. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesWrongOrder) { |
| ApplyAdds({CreateSpecifics(1, {3, 2})}); |
| VerifyAllData({CreateSpecifics(1, {2, 3})}); |
| } |
| |
| // In a minor attempt to save bandwidth, we only send one of the two timestamps |
| // when they share a value. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesRepeatedTime) { |
| ApplyAdds({CreateSpecifics(1, {2, 2})}); |
| VerifyAllData({CreateSpecifics(1, {2})}); |
| } |
| |
| // Again, the format in the table is lossy, and cannot distinguish between no |
| // time, and valid time zero. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesNoTime) { |
| ApplyAdds({CreateSpecifics(1, std::vector<int>())}); |
| VerifyAllData({CreateSpecifics(1, {0})}); |
| } |
| |
| // If has_value() returns false, then the specifics are determined to be old |
| // style and ignored. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesNoValue) { |
| AutofillSpecifics input = CreateSpecifics(1, {2, 3}); |
| input.clear_value(); |
| ApplyAdds({input}); |
| VerifyAllData(std::vector<AutofillSpecifics>()); |
| } |
| |
| // Should be treated the same as an empty string name. This inconsistency is |
| // being perpetuated from the previous sync integration. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesNoName) { |
| AutofillSpecifics input = CreateSpecifics(1, {2, 3}); |
| input.clear_name(); |
| ApplyAdds({input}); |
| VerifyAllData({input}); |
| } |
| |
| // UTF-8 characters should not be dropped when round tripping, including middle |
| // of string \0 characters. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesUTF) { |
| const AutofillSpecifics specifics = |
| CreateSpecifics(std::string("\n\0\x12\0", 4), "\xEC\xA4\x91", {1}); |
| ApplyAdds({specifics}); |
| VerifyAllData({specifics}); |
| } |
| |
| // Timestamps should always use the oldest creation time, and the most recent |
| // usage time. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesMinMaxTimestamps) { |
| const AutofillSpecifics initial = CreateSpecifics(1, {3, 6}); |
| ApplyAdds({initial}); |
| VerifyAllData({initial}); |
| |
| ApplyAdds({CreateSpecifics(1, {2, 5})}); |
| VerifyAllData({CreateSpecifics(1, {2, 6})}); |
| |
| ApplyAdds({CreateSpecifics(1, {4, 7})}); |
| VerifyAllData({CreateSpecifics(1, {2, 7})}); |
| } |
| |
| // An error should be generated when parsing the storage key happens. This |
| // should never happen in practice because storage keys should be generated at |
| // runtime by the bridge and not loaded from disk. |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesBadStorageKey) { |
| const auto error = bridge()->ApplySyncChanges( |
| bridge()->CreateMetadataChangeList(), |
| {EntityChange::CreateDelete("bogus storage key")}); |
| EXPECT_TRUE(error); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, ApplySyncChangesDatabaseFailure) { |
| // TODO(skym, crbug.com/672619): Should have tests that get false back when |
| // making db calls and verify the errors are propagated up. |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, LocalEntriesAdded) { |
| StartSyncing(); |
| const AutofillSpecifics added_specifics1 = CreateSpecifics(1, {2, 3}); |
| const AutofillSpecifics added_specifics2 = CreateSpecifics(2, {2, 3}); |
| |
| const AutofillEntry added_entry1 = CreateAutofillEntry(added_specifics1); |
| const AutofillEntry added_entry2 = CreateAutofillEntry(added_specifics2); |
| |
| table()->UpdateAutofillEntries({added_entry1, added_entry2}); |
| |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(added_specifics1), _)); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(added_specifics2), _)); |
| bridge()->AutofillEntriesChanged( |
| {AutofillChange(AutofillChange::ADD, added_entry1.key()), |
| AutofillChange(AutofillChange::ADD, added_entry2.key())}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, LocalEntryAddedThenUpdated) { |
| StartSyncing(); |
| const AutofillSpecifics added_specifics = CreateSpecifics(1, {2, 3}); |
| const AutofillEntry added_entry = CreateAutofillEntry(added_specifics); |
| table()->UpdateAutofillEntries({added_entry}); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(added_specifics), _)); |
| |
| bridge()->AutofillEntriesChanged( |
| {AutofillChange(AutofillChange::ADD, added_entry.key())}); |
| |
| const AutofillSpecifics updated_specifics = CreateSpecifics(1, {2, 4}); |
| const AutofillEntry updated_entry = CreateAutofillEntry(updated_specifics); |
| table()->UpdateAutofillEntries({updated_entry}); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(updated_specifics), _)); |
| |
| bridge()->AutofillEntriesChanged( |
| {AutofillChange(AutofillChange::UPDATE, updated_entry.key())}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, LocalEntryDeleted) { |
| StartSyncing(); |
| const AutofillSpecifics deleted_specifics = CreateSpecifics(1, {2, 3}); |
| const AutofillEntry deleted_entry = CreateAutofillEntry(deleted_specifics); |
| const std::string storage_key = GetStorageKey(deleted_specifics); |
| |
| EXPECT_CALL(mock_processor(), Delete(storage_key, _)); |
| bridge()->AutofillEntriesChanged( |
| {AutofillChange(AutofillChange::REMOVE, deleted_entry.key())}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, LoadMetadataCalled) { |
| ModelTypeState model_type_state; |
| model_type_state.set_initial_sync_done(true); |
| EXPECT_TRUE( |
| table()->UpdateModelTypeState(syncer::AUTOFILL, model_type_state)); |
| EXPECT_TRUE( |
| table()->UpdateSyncMetadata(syncer::AUTOFILL, "key", EntityMetadata())); |
| |
| ResetProcessor(); |
| EXPECT_CALL(mock_processor(), ModelReadyToSync(MetadataBatchContains( |
| /*state=*/HasInitialSyncDone(), |
| /*entities=*/SizeIs(1)))); |
| ResetBridge(); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, LoadMetadataReportsErrorForMissingDB) { |
| ON_CALL(*backend(), GetDatabase()).WillByDefault(Return(nullptr)); |
| EXPECT_CALL(mock_processor(), ReportError(_)); |
| ResetBridge(); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataEmpty) { |
| EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0); |
| EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0); |
| |
| StartSyncing(/*remote_data=*/std::vector<AutofillSpecifics>()); |
| |
| VerifyAllData(std::vector<AutofillSpecifics>()); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataRemoteOnly) { |
| const AutofillSpecifics specifics1 = CreateSpecifics(1, {2}); |
| const AutofillSpecifics specifics2 = CreateSpecifics(2, {3, 4}); |
| |
| EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0); |
| EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0); |
| |
| StartSyncing(/*remote_data=*/{specifics1, specifics2}); |
| |
| VerifyAllData({specifics1, specifics2}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataLocalOnly) { |
| const AutofillSpecifics specifics1 = CreateSpecifics(1, {2}); |
| const AutofillSpecifics specifics2 = CreateSpecifics(2, {3, 4}); |
| |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(specifics1), _)); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(specifics2), _)); |
| EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0); |
| |
| ApplyAdds({specifics1, specifics2}); |
| VerifyAllData({specifics1, specifics2}); |
| |
| StartSyncing(/*remote_data=*/{}); |
| VerifyAllData({specifics1, specifics2}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataAllMerged) { |
| const AutofillSpecifics local1 = CreateSpecifics(1, {2}); |
| const AutofillSpecifics local2 = CreateSpecifics(2, {3, 4}); |
| const AutofillSpecifics local3 = CreateSpecifics(3, {4}); |
| const AutofillSpecifics local4 = CreateSpecifics(4, {5, 6}); |
| const AutofillSpecifics local5 = CreateSpecifics(5, {6, 9}); |
| const AutofillSpecifics local6 = CreateSpecifics(6, {7, 9}); |
| const AutofillSpecifics remote1 = local1; |
| const AutofillSpecifics remote2 = local2; |
| const AutofillSpecifics remote3 = CreateSpecifics(3, {5}); |
| const AutofillSpecifics remote4 = CreateSpecifics(4, {7, 8}); |
| const AutofillSpecifics remote5 = CreateSpecifics(5, {8, 9}); |
| const AutofillSpecifics remote6 = CreateSpecifics(6, {8, 10}); |
| const AutofillSpecifics merged1 = local1; |
| const AutofillSpecifics merged2 = local2; |
| const AutofillSpecifics merged3 = CreateSpecifics(3, {4, 5}); |
| const AutofillSpecifics merged4 = CreateSpecifics(4, {5, 8}); |
| const AutofillSpecifics merged5 = local5; |
| const AutofillSpecifics merged6 = CreateSpecifics(6, {7, 10}); |
| |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(merged3), _)); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(merged4), _)); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(merged5), _)); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(merged6), _)); |
| EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0); |
| |
| ApplyAdds({local1, local2, local3, local4, local5, local6}); |
| |
| StartSyncing( |
| /*remote_data=*/{remote1, remote2, remote3, remote4, remote5, remote6}); |
| VerifyAllData({merged1, merged2, merged3, merged4, merged5, merged6}); |
| } |
| |
| TEST_F(AutocompleteSyncBridgeTest, MergeSyncDataMixed) { |
| const AutofillSpecifics local1 = CreateSpecifics(1, {2, 3}); |
| const AutofillSpecifics remote2 = CreateSpecifics(2, {2, 3}); |
| const AutofillSpecifics specifics3 = CreateSpecifics(3, {2, 3}); |
| const AutofillSpecifics local4 = CreateSpecifics(4, {1, 3}); |
| const AutofillSpecifics remote4 = CreateSpecifics(4, {2, 4}); |
| const AutofillSpecifics merged4 = CreateSpecifics(4, {1, 4}); |
| |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(local1), _)); |
| EXPECT_CALL(mock_processor(), Put(_, HasSpecifics(merged4), _)); |
| EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0); |
| |
| ApplyAdds({local1, specifics3, local4}); |
| |
| StartSyncing(/*remote_data=*/{remote2, specifics3, remote4}); |
| |
| VerifyAllData({local1, remote2, specifics3, merged4}); |
| } |
| |
| } // namespace autofill |