diff options
Diffstat (limited to 'chromium/components/history/core/browser/typed_url_sync_bridge_unittest.cc')
-rw-r--r-- | chromium/components/history/core/browser/typed_url_sync_bridge_unittest.cc | 1195 |
1 files changed, 1176 insertions, 19 deletions
diff --git a/chromium/components/history/core/browser/typed_url_sync_bridge_unittest.cc b/chromium/components/history/core/browser/typed_url_sync_bridge_unittest.cc index 4a36d3aca53..e598864298f 100644 --- a/chromium/components/history/core/browser/typed_url_sync_bridge_unittest.cc +++ b/chromium/components/history/core/browser/typed_url_sync_bridge_unittest.cc @@ -4,6 +4,7 @@ #include "components/history/core/browser/typed_url_sync_bridge.h" +#include "base/big_endian.h" #include "base/files/scoped_temp_dir.h" #include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" @@ -26,12 +27,19 @@ using syncer::EntityData; using syncer::EntityDataPtr; using syncer::KeyAndData; using syncer::MetadataBatch; +using syncer::MetadataChangeList; using syncer::RecordingModelTypeChangeProcessor; namespace history { namespace { +// Constants used to limit size of visits processed. See +// equivalent constants in typed_url_sync_bridge.cc for descriptions. +const int kMaxTypedUrlVisits = 100; +const int kVisitThrottleThreshold = 10; +const int kVisitThrottleMultiple = 10; + // Visits with this timestamp are treated as expired. const int kExpiredVisit = -1; @@ -41,6 +49,49 @@ const char kTitle2[] = "cookie"; const char kURL[] = "http://pie.com/"; const char kURL2[] = "http://cookie.com/"; +bool URLsEqual(URLRow& row, sync_pb::TypedUrlSpecifics& specifics) { + return ((row.url().spec().compare(specifics.url()) == 0) && + (base::UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) && + (row.hidden() == specifics.hidden())); +} + +bool URLsEqual(URLRow& lhs, URLRow& rhs) { + // Only compare synced fields (ignore typed_count and visit_count as those + // are maintained by the history subsystem). + return (lhs.url().spec().compare(rhs.url().spec()) == 0) && + (lhs.title().compare(rhs.title()) == 0) && + (lhs.hidden() == rhs.hidden()); +} + +void AddNewestVisit(ui::PageTransition transition, + int64_t visit_time, + URLRow* url, + VisitVector* visits) { + base::Time time = base::Time::FromInternalValue(visit_time); + visits->insert(visits->begin(), VisitRow(url->id(), time, 0, transition, 0)); + + if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)) { + url->set_typed_count(url->typed_count() + 1); + } + + url->set_last_visit(time); + url->set_visit_count(visits->size()); +} + +void AddOldestVisit(ui::PageTransition transition, + int64_t visit_time, + URLRow* url, + VisitVector* visits) { + base::Time time = base::Time::FromInternalValue(visit_time); + visits->push_back(VisitRow(url->id(), time, 0, transition, 0)); + + if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)) { + url->set_typed_count(url->typed_count() + 1); + } + + url->set_visit_count(visits->size()); +} + // Create a new row object and the typed visit çorresponding with the time at // |last_visit| in the |visits| vector. URLRow MakeTypedUrlRow(const std::string& url, @@ -104,6 +155,12 @@ void VerifyDataBatch(std::map<std::string, TypedUrlSpecifics> expected, EXPECT_TRUE(expected.empty()); } +std::string IntToStroageKey(int id) { + std::string storage_key(sizeof(URLID), 0); + base::WriteBigEndian<URLID>(&storage_key[0], id); + return storage_key; +} + class TestHistoryBackendDelegate : public HistoryBackend::Delegate { public: TestHistoryBackendDelegate() {} @@ -149,16 +206,17 @@ class TestHistoryBackend : public HistoryBackend { } void SetVisitsForUrl(URLRow& new_url, const VisitVector visits) { - std::vector<history::VisitInfo> added_visits; - URLRows new_urls; - DeleteURL(new_url.url()); + if (!GetURL(new_url.url(), nullptr)) { + URLRows new_urls; + new_urls.push_back(new_url); + AddPagesWithDetails(new_urls, SOURCE_SYNCED); + } + + std::vector<VisitInfo> added_visits; for (const auto& visit : visits) { - added_visits.push_back( - history::VisitInfo(visit.visit_time, visit.transition)); + added_visits.push_back(VisitInfo(visit.visit_time, visit.transition)); } - new_urls.push_back(new_url); - AddPagesWithDetails(new_urls, history::SOURCE_SYNCED); - AddVisits(new_url.url(), added_visits, history::SOURCE_SYNCED); + AddVisits(new_url.url(), added_visits, SOURCE_SYNCED); new_url.set_id(GetIdByUrl(new_url.url())); } @@ -189,7 +247,10 @@ class TypedURLSyncBridgeTest : public testing::Test { fake_history_backend_->SetTypedURLSyncBridgeForTest(std::move(bridge)); } - void TearDown() override { fake_history_backend_->Closing(); } + void TearDown() override { + VerifyProcessorReceivedValidEntityData(); + fake_history_backend_->Closing(); + } // Starts sync for |typed_url_sync_bridge_| with |initial_data| as the // initial sync data. @@ -202,6 +263,80 @@ class TypedURLSyncBridgeTest : public testing::Test { EXPECT_FALSE(error); } + bool BuildAndPushLocalChanges(unsigned int num_typed_urls, + unsigned int num_reload_urls, + const std::vector<std::string>& urls, + URLRows* rows, + std::vector<VisitVector>* visit_vectors) { + unsigned int total_urls = num_typed_urls + num_reload_urls; + DCHECK(urls.size() >= total_urls); + if (!bridge()) + return false; + + if (total_urls) { + // Create new URL rows, populate the mock backend with its visits, and + // send to the sync service. + URLRows changed_urls; + + for (unsigned int i = 0; i < total_urls; ++i) { + int typed = i < num_typed_urls ? 1 : 0; + VisitVector visits; + visit_vectors->push_back(visits); + rows->push_back(MakeTypedUrlRow(urls[i], kTitle, typed, i + 3, false, + &visit_vectors->back())); + fake_history_backend_->SetVisitsForUrl(rows->back(), + visit_vectors->back()); + changed_urls.push_back(rows->back()); + } + + bridge()->OnURLsModified(fake_history_backend_.get(), changed_urls); + } + + // Check that communication with sync was successful. + if (num_typed_urls != processor().put_multimap().size()) + return false; + return true; + } + + VisitVector ApplyUrlAndVisitsChange(const std::string& url, + const std::string& title, + int typed_count, + int64_t last_visit, + bool hidden, + EntityChange::ChangeType change_type) { + VisitVector visits; + URLRow row = + MakeTypedUrlRow(url, title, typed_count, last_visit, hidden, &visits); + sync_pb::TypedUrlSpecifics typed_url_specifics; + WriteToTypedUrlSpecifics(row, visits, &typed_url_specifics); + std::unique_ptr<MetadataChangeList> metadata_changes = + bridge()->CreateMetadataChangeList(); + EntityChangeList entity_changes; + switch (change_type) { + case EntityChange::ACTION_ADD: + entity_changes.push_back(EntityChange::CreateAdd( + std::string(), SpecificsToEntity(typed_url_specifics))); + break; + case EntityChange::ACTION_UPDATE: + entity_changes.push_back( + EntityChange::CreateUpdate(GetStorageKey(typed_url_specifics.url()), + SpecificsToEntity(typed_url_specifics))); + break; + case EntityChange::ACTION_DELETE: + entity_changes.push_back(EntityChange::CreateDelete( + GetStorageKey(typed_url_specifics.url()))); + break; + } + bridge()->ApplySyncChanges(std::move(metadata_changes), entity_changes); + return visits; + } + + void AddObserver() { + bridge()->history_backend_observer_.Add(fake_history_backend_.get()); + } + + void RemoveObserver() { bridge()->history_backend_observer_.RemoveAll(); } + // Fills |specifics| with the sync data for |url| and |visits|. static bool WriteToTypedUrlSpecifics(const URLRow& url, const VisitVector& visits, @@ -209,9 +344,8 @@ class TypedURLSyncBridgeTest : public testing::Test { return TypedURLSyncBridge::WriteToTypedUrlSpecifics(url, visits, specifics); } - std::string GetStorageKey(const TypedUrlSpecifics& specifics) { - std::string key = bridge()->GetStorageKeyInternal(specifics.url()); - return key; + std::string GetStorageKey(const std::string& url) { + return bridge()->GetStorageKeyInternal(url); } EntityDataPtr SpecificsToEntity(const TypedUrlSpecifics& specifics) { @@ -226,7 +360,7 @@ class TypedURLSyncBridgeTest : public testing::Test { EntityChangeList entity_change_list; for (const auto& specifics : specifics_vector) { entity_change_list.push_back(EntityChange::CreateAdd( - GetStorageKey(specifics), SpecificsToEntity(specifics))); + GetStorageKey(specifics.url()), SpecificsToEntity(specifics))); } return entity_change_list; } @@ -235,15 +369,86 @@ class TypedURLSyncBridgeTest : public testing::Test { const std::vector<TypedUrlSpecifics>& specifics_vector) { std::map<std::string, TypedUrlSpecifics> map; for (const auto& specifics : specifics_vector) { - map[GetStorageKey(specifics)] = specifics; + map[GetStorageKey(specifics.url())] = specifics; } return map; } - void VerifyLocalHistoryData(const std::vector<TypedUrlSpecifics>& expected) { + void VerifyAllLocalHistoryData( + const std::vector<TypedUrlSpecifics>& expected) { bridge()->GetAllData(base::Bind(&VerifyDataBatch, ExpectedMap(expected))); } + void VerifyGetData(TypedURLSyncBridge::StorageKeyList storage_keys, + const std::vector<TypedUrlSpecifics>& expected) { + bridge()->GetData(storage_keys, + base::Bind(&VerifyDataBatch, ExpectedMap(expected))); + } + + void VerifyProcessorReceivedValidEntityData() { + for (const auto& it : processor().put_multimap()) { + EXPECT_GT(TypedURLSyncMetadataDatabase::StorageKeyToURLID(it.first), 0); + EXPECT_TRUE(it.second->specifics.has_typed_url()); + } + } + + sync_pb::TypedUrlSpecifics GetLastUpdateForURL(const std::string& url) { + const std::string storage_key = GetStorageKey(url); + auto eq_range = processor().put_multimap().equal_range(storage_key); + if (eq_range.first == eq_range.second) + return sync_pb::TypedUrlSpecifics(); + + auto recorded_specifics_iterator = --eq_range.second; + EXPECT_NE(processor().put_multimap().end(), recorded_specifics_iterator); + EXPECT_TRUE(recorded_specifics_iterator->second->specifics.has_typed_url()); + + return recorded_specifics_iterator->second->specifics.typed_url(); + } + + static void DiffVisits(const VisitVector& history_visits, + const sync_pb::TypedUrlSpecifics& sync_specifics, + std::vector<VisitInfo>* new_visits, + VisitVector* removed_visits) { + TypedURLSyncBridge::DiffVisits(history_visits, sync_specifics, new_visits, + removed_visits); + } + + static VisitRow CreateVisit(ui::PageTransition type, int64_t timestamp) { + return VisitRow(0, base::Time::FromInternalValue(timestamp), 0, type, 0); + } + + static TypedURLSyncBridge::MergeResult MergeUrls( + const sync_pb::TypedUrlSpecifics& typed_url, + const URLRow& url, + VisitVector* visits, + URLRow* new_url, + std::vector<VisitInfo>* new_visits) { + return TypedURLSyncBridge::MergeUrls(typed_url, url, visits, new_url, + new_visits); + } + + static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url, + const char* title, + int64_t last_visit, + bool hidden) { + sync_pb::TypedUrlSpecifics typed_url; + typed_url.set_url(url); + typed_url.set_title(title); + typed_url.set_hidden(hidden); + typed_url.add_visits(last_visit); + typed_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED); + return typed_url; + } + + static const TypedURLSyncBridge::MergeResult DIFF_NONE = + TypedURLSyncBridge::DIFF_NONE; + static const TypedURLSyncBridge::MergeResult DIFF_UPDATE_NODE = + TypedURLSyncBridge::DIFF_UPDATE_NODE; + static const TypedURLSyncBridge::MergeResult DIFF_LOCAL_ROW_CHANGED = + TypedURLSyncBridge::DIFF_LOCAL_ROW_CHANGED; + static const TypedURLSyncBridge::MergeResult DIFF_LOCAL_VISITS_ADDED = + TypedURLSyncBridge::DIFF_LOCAL_VISITS_ADDED; + TypedURLSyncBridge* bridge() { return typed_url_sync_bridge_; } TypedURLSyncMetadataDatabase* metadata_store() { @@ -277,7 +482,26 @@ TEST_F(TypedURLSyncBridgeTest, GetAllData) { WriteToTypedUrlSpecifics(row2, visits2, &typed_url2); // Check that the local cache is still correct. - VerifyLocalHistoryData({typed_url1, typed_url2}); + VerifyAllLocalHistoryData({typed_url1, typed_url2}); +} + +// Add two typed urls locally and verify bridge can get them from GetData. +TEST_F(TypedURLSyncBridgeTest, GetData) { + // Add two urls to backend. + VisitVector visits1, visits2; + URLRow row1 = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits1); + URLRow row2 = MakeTypedUrlRow(kURL2, kTitle2, 2, 4, false, &visits2); + fake_history_backend_->SetVisitsForUrl(row1, visits1); + fake_history_backend_->SetVisitsForUrl(row2, visits2); + + // Create the same data in sync. + TypedUrlSpecifics typed_url1, typed_url2; + WriteToTypedUrlSpecifics(row1, visits1, &typed_url1); + WriteToTypedUrlSpecifics(row2, visits2, &typed_url2); + + // Check that the local cache is still correct. + VerifyGetData({IntToStroageKey(1)}, {typed_url1}); + VerifyGetData({IntToStroageKey(2)}, {typed_url2}); } // Add a typed url locally and one to sync with the same data. Starting sync @@ -296,8 +520,19 @@ TEST_F(TypedURLSyncBridgeTest, MergeUrlNoChange) { StartSyncing({*typed_url}); EXPECT_TRUE(processor().put_multimap().empty()); + // Even Sync already know the url, bridge still need to tell sync about + // storage keys. + EXPECT_EQ(1u, processor().update_multimap().size()); + + // Verify processor receive correct upate storage key. + const auto& it = processor().update_multimap().begin(); + EXPECT_EQ(it->first, IntToStroageKey(1)); + EXPECT_TRUE(it->second->specifics.has_typed_url()); + EXPECT_EQ(it->second->specifics.typed_url().url(), kURL); + EXPECT_EQ(it->second->specifics.typed_url().title(), kTitle); + // Check that the local cache was is still correct. - VerifyLocalHistoryData({*typed_url}); + VerifyAllLocalHistoryData({*typed_url}); } // Add a corupted typed url locally, has typed url count 1, but no real typed @@ -332,12 +567,12 @@ TEST_F(TypedURLSyncBridgeTest, MergeUrlEmptySync) { sync_pb::EntitySpecifics entity_specifics; sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url(); WriteToTypedUrlSpecifics(row, visits, typed_url); - VerifyLocalHistoryData({*typed_url}); + VerifyAllLocalHistoryData({*typed_url}); // Check that the server was updated correctly. ASSERT_EQ(1U, processor().put_multimap().size()); auto recorded_specifics_iterator = - processor().put_multimap().find(GetStorageKey(*typed_url)); + processor().put_multimap().find(GetStorageKey(typed_url->url())); EXPECT_NE(processor().put_multimap().end(), recorded_specifics_iterator); TypedUrlSpecifics recorded_specifics = recorded_specifics_iterator->second->specifics.typed_url(); @@ -361,6 +596,14 @@ TEST_F(TypedURLSyncBridgeTest, MergeUrlEmptyLocal) { StartSyncing({*typed_url}); EXPECT_EQ(0u, processor().put_multimap().size()); + EXPECT_EQ(1u, processor().update_multimap().size()); + + // Verify processor receive correct upate storage key. + const auto& it = processor().update_multimap().begin(); + EXPECT_EQ(it->first, IntToStroageKey(1)); + EXPECT_TRUE(it->second->specifics.has_typed_url()); + EXPECT_EQ(it->second->specifics.typed_url().url(), kURL); + EXPECT_EQ(it->second->specifics.typed_url().title(), kTitle); // Check that the backend was updated correctly. VisitVector all_visits; @@ -374,6 +617,129 @@ TEST_F(TypedURLSyncBridgeTest, MergeUrlEmptyLocal) { all_visits[0].transition, visits[0].transition)); } +// Add a url to the local and sync data before sync begins, with the sync data +// having more recent visits. Check that starting sync updates the backend +// with the sync visit, while the older local visit is not pushed to sync. +// The title should be updated to the sync version due to the more recent +// timestamp. +TEST_F(TypedURLSyncBridgeTest, MergeUrlOldLocal) { + // Add a url to backend. + VisitVector visits; + URLRow local_row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits); + fake_history_backend_->SetVisitsForUrl(local_row, visits); + + // Create sync data for the same url with a more recent visit. + VisitVector server_visits; + URLRow server_row = + MakeTypedUrlRow(kURL, kTitle2, 1, 6, false, &server_visits); + server_row.set_id(fake_history_backend_->GetIdByUrl(GURL(kURL))); + sync_pb::EntitySpecifics entity_specifics; + sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url(); + WriteToTypedUrlSpecifics(server_row, server_visits, typed_url); + StartSyncing({*typed_url}); + + // Check that the backend was updated correctly. + VisitVector all_visits; + base::Time server_time = base::Time::FromInternalValue(6); + URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_NE(0, url_id); + fake_history_backend_->GetVisitsForURL(url_id, &all_visits); + ASSERT_EQ(2U, all_visits.size()); + EXPECT_EQ(server_time, all_visits.back().visit_time); + EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( + all_visits.back().transition, server_visits[0].transition)); + URLRow url_row; + EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row)); + EXPECT_EQ(kTitle2, base::UTF16ToUTF8(url_row.title())); + + // Check that the sync was updated correctly. + // The local history visit should not be added to sync because it is older + // than sync's oldest visit. + ASSERT_EQ(1U, processor().put_multimap().size()); + + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + ASSERT_EQ(1, url_specifics.visits_size()); + EXPECT_EQ(6, url_specifics.visits(0)); + ASSERT_EQ(1, url_specifics.visit_transitions_size()); + EXPECT_EQ(static_cast<const int>(visits[0].transition), + url_specifics.visit_transitions(0)); +} + +// Add a url to the local and sync data before sync begins, with the local data +// having more recent visits. Check that starting sync updates the sync +// with the local visits, while the older sync visit is not pushed to the +// backend. Sync's title should be updated to the local version due to the more +// recent timestamp. +TEST_F(TypedURLSyncBridgeTest, MergeUrlOldSync) { + // Add a url to backend. + VisitVector visits; + URLRow local_row = MakeTypedUrlRow(kURL, kTitle2, 1, 3, false, &visits); + fake_history_backend_->SetVisitsForUrl(local_row, visits); + + // Create sync data for the same url with an older visit. + VisitVector server_visits; + URLRow server_row = + MakeTypedUrlRow(kURL, kTitle, 1, 2, false, &server_visits); + sync_pb::EntitySpecifics entity_specifics; + sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url(); + WriteToTypedUrlSpecifics(server_row, server_visits, typed_url); + StartSyncing({*typed_url}); + + // Check that the backend was not updated. + VisitVector all_visits; + base::Time local_visit_time = base::Time::FromInternalValue(3); + URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_NE(0, url_id); + fake_history_backend_->GetVisitsForURL(url_id, &all_visits); + ASSERT_EQ(1U, all_visits.size()); + EXPECT_EQ(local_visit_time, all_visits[0].visit_time); + + // Check that the server was updated correctly. + // The local history visit should not be added to sync because it is older + // than sync's oldest visit. + ASSERT_EQ(1U, processor().put_multimap().size()); + + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + ASSERT_EQ(1, url_specifics.visits_size()); + EXPECT_EQ(3, url_specifics.visits(0)); + EXPECT_EQ(kTitle2, url_specifics.title()); + ASSERT_EQ(1, url_specifics.visit_transitions_size()); + EXPECT_EQ(static_cast<const int>(visits[0].transition), + url_specifics.visit_transitions(0)); +} + +// Check that there is no crash during start sync, if history backend and sync +// have same url, but sync has username/password in it. +// Also check sync will not accept url with username and password. +TEST_F(TypedURLSyncBridgeTest, MergeUrlsWithUsernameAndPassword) { + const char kURLWithUsernameAndPassword[] = + "http://username:password@pie.com/"; + + // Add a url to backend. + VisitVector visits; + URLRow local_row = MakeTypedUrlRow(kURL, kTitle2, 1, 3, false, &visits); + fake_history_backend_->SetVisitsForUrl(local_row, visits); + + // Create sync data for the same url but contain username and password. + VisitVector server_visits; + URLRow server_row = MakeTypedUrlRow(kURLWithUsernameAndPassword, kTitle, 1, 3, + false, &server_visits); + sync_pb::EntitySpecifics entity_specifics; + sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url(); + WriteToTypedUrlSpecifics(server_row, server_visits, typed_url); + + // Make sure there is no crash when merge two urls. + StartSyncing({*typed_url}); + + // Notify typed url sync service of the update. + bridge()->OnURLVisited(fake_history_backend_.get(), ui::PAGE_TRANSITION_TYPED, + server_row, RedirectList(), + base::Time::FromInternalValue(7)); + + // Check username/password url is not synced. + ASSERT_EQ(1U, processor().put_multimap().size()); +} + // Starting sync with both local and sync have same typed URL, but different // visit. After merge, both local and sync should have two same visits. TEST_F(TypedURLSyncBridgeTest, SimpleMerge) { @@ -406,4 +772,795 @@ TEST_F(TypedURLSyncBridgeTest, SimpleMerge) { all_visits[1].transition, visits2[0].transition)); } +// Create a local typed URL with one TYPED visit after sync has started. Check +// that sync is sent an ADD change for the new URL. +TEST_F(TypedURLSyncBridgeTest, AddLocalTypedUrl) { + // Create a local typed URL (simulate a typed visit) that is not already + // in sync. Check that sync is sent an ADD change for the existing URL. + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors)); + + URLRow url_row = url_rows.front(); + VisitVector visits = visit_vectors.front(); + + // Check change processor. + ASSERT_EQ(1U, processor().put_multimap().size()); + + // Get typed url specifics. + auto it = processor().put_multimap().begin(); + sync_pb::TypedUrlSpecifics url_specifics = it->second->specifics.typed_url(); + + EXPECT_TRUE(URLsEqual(url_row, url_specifics)); + ASSERT_EQ(1, url_specifics.visits_size()); + ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size()); + EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(0)); + EXPECT_EQ(static_cast<const int>(visits[0].transition), + url_specifics.visit_transitions(0)); +} + +// Update a local typed URL that is already synced. Check that sync is sent an +// UPDATE for the existing url, but RELOAD visits aren't synced. +TEST_F(TypedURLSyncBridgeTest, UpdateLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + + // Update the URL row, adding another typed visit to the visit vector. + URLRows changed_urls; + VisitVector visits; + URLRow url_row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits); + AddNewestVisit(ui::PAGE_TRANSITION_TYPED, 7, &url_row, &visits); + AddNewestVisit(ui::PAGE_TRANSITION_RELOAD, 8, &url_row, &visits); + AddNewestVisit(ui::PAGE_TRANSITION_LINK, 9, &url_row, &visits); + fake_history_backend_->SetVisitsForUrl(url_row, visits); + changed_urls.push_back(url_row); + + // Notify typed url sync service of the update. + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(0U, changes_multimap.size()); + bridge()->OnURLsModified(fake_history_backend_.get(), changed_urls); + ASSERT_EQ(1U, changes_multimap.size()); + + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + EXPECT_TRUE(URLsEqual(url_row, url_specifics)); + ASSERT_EQ(3, url_specifics.visits_size()); + + // Check that each visit has been translated/communicated correctly. + // Note that the specifics record visits in chronological order, and the + // visits from the db are in reverse chronological order. + EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(2)); + EXPECT_EQ(static_cast<const int>(visits[0].transition), + url_specifics.visit_transitions(2)); + EXPECT_EQ(visits[2].visit_time.ToInternalValue(), url_specifics.visits(1)); + EXPECT_EQ(static_cast<const int>(visits[2].transition), + url_specifics.visit_transitions(1)); + EXPECT_EQ(visits[3].visit_time.ToInternalValue(), url_specifics.visits(0)); + EXPECT_EQ(static_cast<const int>(visits[3].transition), + url_specifics.visit_transitions(0)); +} + +// Append a RELOAD visit to a typed url that is already synced. Check that sync +// does not receive any updates. +TEST_F(TypedURLSyncBridgeTest, ReloadVisitLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors)); + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(1U, changes_multimap.size()); + + // Update the URL row, adding another typed visit to the visit vector. + URLRow url_row = url_rows.front(); + URLRows changed_urls; + VisitVector new_visits; + AddNewestVisit(ui::PAGE_TRANSITION_RELOAD, 7, &url_row, &new_visits); + fake_history_backend_->SetVisitsForUrl(url_row, new_visits); + changed_urls.push_back(url_row); + + // Notify typed url sync service of the update. + bridge()->OnURLVisited(fake_history_backend_.get(), + ui::PAGE_TRANSITION_RELOAD, url_row, RedirectList(), + base::Time::FromInternalValue(7)); + // No change pass to processor + ASSERT_EQ(1U, changes_multimap.size()); +} + +// Appends a LINK visit to an existing typed url. Check that sync does not +// receive any changes. +TEST_F(TypedURLSyncBridgeTest, LinkVisitLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors)); + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(1U, changes_multimap.size()); + + // Update the URL row, adding a non-typed visit to the visit vector. + URLRow url_row = url_rows.front(); + VisitVector new_visits; + AddNewestVisit(ui::PAGE_TRANSITION_LINK, 6, &url_row, &new_visits); + fake_history_backend_->SetVisitsForUrl(url_row, new_visits); + + ui::PageTransition transition = ui::PAGE_TRANSITION_LINK; + // Notify typed url sync service of non-typed visit, expect no change. + bridge()->OnURLVisited(fake_history_backend_.get(), transition, url_row, + RedirectList(), base::Time::FromInternalValue(6)); + // No change pass to processor + ASSERT_EQ(1U, changes_multimap.size()); +} + +// Appends a series of LINK visits followed by a TYPED one to an existing typed +// url. Check that sync receives an UPDATE with the newest visit data. +TEST_F(TypedURLSyncBridgeTest, TypedVisitLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + + // Update the URL row, adding another typed visit to the visit vector. + VisitVector visits; + URLRow url_row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits); + AddOldestVisit(ui::PAGE_TRANSITION_LINK, 1, &url_row, &visits); + AddNewestVisit(ui::PAGE_TRANSITION_LINK, 6, &url_row, &visits); + AddNewestVisit(ui::PAGE_TRANSITION_TYPED, 7, &url_row, &visits); + fake_history_backend_->SetVisitsForUrl(url_row, visits); + + // Notify typed url sync service of typed visit. + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(0U, changes_multimap.size()); + ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED; + bridge()->OnURLVisited(fake_history_backend_.get(), transition, url_row, + RedirectList(), base::Time::Now()); + + ASSERT_EQ(1U, changes_multimap.size()); + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + + EXPECT_TRUE(URLsEqual(url_row, url_specifics)); + EXPECT_EQ(4, url_specifics.visits_size()); + + // Check that each visit has been translated/communicated correctly. + // Note that the specifics record visits in chronological order, and the + // visits from the db are in reverse chronological order. + int r = url_specifics.visits_size() - 1; + for (int i = 0; i < url_specifics.visits_size(); ++i, --r) { + EXPECT_EQ(visits[i].visit_time.ToInternalValue(), url_specifics.visits(r)); + EXPECT_EQ(static_cast<const int>(visits[i].transition), + url_specifics.visit_transitions(r)); + } +} + +// Delete several (but not all) local typed urls. Check that sync receives the +// DELETE changes, and the non-deleted urls remain synced. +TEST_F(TypedURLSyncBridgeTest, DeleteLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back("http://pie.com/"); + urls.push_back("http://cake.com/"); + urls.push_back("http://google.com/"); + urls.push_back("http://foo.com/"); + urls.push_back("http://bar.com/"); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(4, 1, urls, &url_rows, &visit_vectors)); + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(4U, changes_multimap.size()); + + // Simulate visit expiry of typed visit, no syncing is done + // This is to test that sync relies on the in-memory cache to know + // which urls were typed and synced, and should be deleted. + url_rows[0].set_typed_count(0); + VisitVector visits; + fake_history_backend_->SetVisitsForUrl(url_rows[0], visits); + + // Delete some urls from backend and create deleted row vector. + URLRows rows; + std::set<std::string> deleted_storage_keys; + for (size_t i = 0; i < 3u; ++i) { + std::string storage_key = GetStorageKey(url_rows[i].url().spec()); + deleted_storage_keys.insert(storage_key); + fake_history_backend_->DeleteURL(url_rows[i].url()); + rows.push_back(url_rows[i]); + } + + // Notify typed url sync service. + bridge()->OnURLsDeleted(fake_history_backend_.get(), false, false, rows, + std::set<GURL>()); + + const auto& delete_set = processor().delete_set(); + ASSERT_EQ(3U, delete_set.size()); + for (const std::string& storage_key : delete_set) { + EXPECT_TRUE(deleted_storage_keys.find(storage_key) != + deleted_storage_keys.end()); + deleted_storage_keys.erase(storage_key); + } + ASSERT_TRUE(deleted_storage_keys.empty()); +} + +// Saturate the visits for a typed url with both TYPED and LINK navigations. +// Check that no more than kMaxTypedURLVisits are synced, and that LINK visits +// are dropped rather than TYPED ones. +TEST_F(TypedURLSyncBridgeTest, MaxVisitLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(0, 1, urls, &url_rows, &visit_vectors)); + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(0U, changes_multimap.size()); + + URLRow url_row = url_rows.front(); + VisitVector visits; + + // Add |kMaxTypedUrlVisits| + 10 visits to the url. The 10 oldest + // non-typed visits are expected to be skipped. + int i = 1; + for (; i <= kMaxTypedUrlVisits - 20; ++i) + AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits); + for (; i <= kMaxTypedUrlVisits; ++i) + AddNewestVisit(ui::PAGE_TRANSITION_LINK, i, &url_row, &visits); + for (; i <= kMaxTypedUrlVisits + 10; ++i) + AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits); + + fake_history_backend_->SetVisitsForUrl(url_row, visits); + + // Notify typed url sync service of typed visit. + ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED; + bridge()->OnURLVisited(fake_history_backend_.get(), transition, url_row, + RedirectList(), base::Time::Now()); + + ASSERT_EQ(1U, changes_multimap.size()); + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + ASSERT_EQ(kMaxTypedUrlVisits, url_specifics.visits_size()); + + // Check that each visit has been translated/communicated correctly. + // Note that the specifics records visits in chronological order, and the + // visits from the db are in reverse chronological order. + int num_typed_visits_synced = 0; + int num_other_visits_synced = 0; + int r = url_specifics.visits_size() - 1; + for (int i = 0; i < url_specifics.visits_size(); ++i, --r) { + if (url_specifics.visit_transitions(i) == + static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED)) { + ++num_typed_visits_synced; + } else { + ++num_other_visits_synced; + } + } + EXPECT_EQ(kMaxTypedUrlVisits - 10, num_typed_visits_synced); + EXPECT_EQ(10, num_other_visits_synced); +} + +// Add enough visits to trigger throttling of updates to a typed url. Check that +// sync does not receive an update until the proper throttle interval has been +// reached. +TEST_F(TypedURLSyncBridgeTest, ThrottleVisitLocalTypedUrl) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(0, 1, urls, &url_rows, &visit_vectors)); + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(0U, changes_multimap.size()); + + URLRow url_row = url_rows.front(); + VisitVector visits; + + // Add enough visits to the url so that typed count is above the throttle + // limit, and not right on the interval that gets synced. + int i = 1; + for (; i < kVisitThrottleThreshold + kVisitThrottleMultiple / 2; ++i) + AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits); + fake_history_backend_->SetVisitsForUrl(url_row, visits); + + // Notify typed url sync service of typed visit. + ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED; + bridge()->OnURLVisited(fake_history_backend_.get(), transition, url_row, + RedirectList(), base::Time::Now()); + + // Should throttle, so sync and local cache should not update. + ASSERT_EQ(0U, changes_multimap.size()); + + visits.clear(); + for (; i % kVisitThrottleMultiple != 1; ++i) + AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits); + --i; // Account for the increment before the condition ends. + fake_history_backend_->SetVisitsForUrl(url_row, visits); + + // Notify typed url sync service of typed visit. + bridge()->OnURLVisited(fake_history_backend_.get(), transition, url_row, + RedirectList(), base::Time::Now()); + + ASSERT_EQ(1U, changes_multimap.size()); + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + ASSERT_EQ(i, url_specifics.visits_size()); +} + +// Create a remote typed URL and visit, then send to sync bridge after sync +// has started. Check that local DB is received the new URL and visit. +TEST_F(TypedURLSyncBridgeTest, AddUrlAndVisits) { + StartSyncing(std::vector<TypedUrlSpecifics>()); + VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false, + EntityChange::ACTION_ADD); + + ASSERT_EQ(0U, processor().put_multimap().size()); + ASSERT_EQ(1U, processor().update_multimap().size()); + ASSERT_EQ(0U, processor().untrack_set().size()); + + // Verify processor receive correct upate storage key. + const auto& it = processor().update_multimap().begin(); + EXPECT_EQ(it->first, IntToStroageKey(1)); + EXPECT_TRUE(it->second->specifics.has_typed_url()); + EXPECT_EQ(it->second->specifics.typed_url().url(), kURL); + EXPECT_EQ(it->second->specifics.typed_url().title(), kTitle); + + base::Time visit_time = base::Time::FromInternalValue(3); + VisitVector all_visits; + URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_NE(0, url_id); + fake_history_backend_->GetVisitsForURL(url_id, &all_visits); + EXPECT_EQ(1U, all_visits.size()); + EXPECT_EQ(visit_time, all_visits[0].visit_time); + EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( + all_visits[0].transition, visits[0].transition)); + URLRow url_row; + EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row)); + EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title())); +} + +// Create a remote typed URL with expired visit, then send to sync bridge after +// sync has started. Check that local DB did not receive the expired URL and +// visit. +TEST_F(TypedURLSyncBridgeTest, AddExpiredUrlAndVisits) { + StartSyncing(std::vector<TypedUrlSpecifics>()); + VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, kExpiredVisit, + false, EntityChange::ACTION_ADD); + + ASSERT_EQ(0U, processor().put_multimap().size()); + ASSERT_EQ(0U, processor().update_multimap().size()); + ASSERT_EQ(1U, processor().untrack_set().size()); + + URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_EQ(0, url_id); +} + +// Update a remote typed URL and create a new visit that is already synced, then +// send the update to sync bridge. Check that local DB is received an +// UPDATE for the existing url and new visit. +TEST_F(TypedURLSyncBridgeTest, UpdateUrlAndVisits) { + StartSyncing(std::vector<TypedUrlSpecifics>()); + + VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false, + EntityChange::ACTION_ADD); + base::Time visit_time = base::Time::FromInternalValue(3); + VisitVector all_visits; + URLRow url_row; + + URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_NE(0, url_id); + + fake_history_backend_->GetVisitsForURL(url_id, &all_visits); + + EXPECT_EQ(1U, all_visits.size()); + EXPECT_EQ(visit_time, all_visits[0].visit_time); + EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( + all_visits[0].transition, visits[0].transition)); + EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row)); + EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title())); + + VisitVector new_visits = ApplyUrlAndVisitsChange(kURL, kTitle2, 2, 6, false, + EntityChange::ACTION_UPDATE); + + base::Time new_visit_time = base::Time::FromInternalValue(6); + url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_NE(0, url_id); + fake_history_backend_->GetVisitsForURL(url_id, &all_visits); + + EXPECT_EQ(2U, all_visits.size()); + EXPECT_EQ(new_visit_time, all_visits.back().visit_time); + EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( + all_visits.back().transition, new_visits[0].transition)); + EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row)); + EXPECT_EQ(kTitle2, base::UTF16ToUTF8(url_row.title())); +} + +// Delete a typed urls which already synced. Check that local DB receives the +// DELETE changes. +TEST_F(TypedURLSyncBridgeTest, DeleteUrlAndVisits) { + URLRows url_rows; + std::vector<VisitVector> visit_vectors; + std::vector<std::string> urls; + urls.push_back(kURL); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors)); + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(1U, changes_multimap.size()); + + base::Time visit_time = base::Time::FromInternalValue(3); + VisitVector all_visits; + URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_NE(0, url_id); + fake_history_backend_->GetVisitsForURL(url_id, &all_visits); + EXPECT_EQ(1U, all_visits.size()); + EXPECT_EQ(visit_time, all_visits[0].visit_time); + EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( + all_visits[0].transition, visit_vectors[0][0].transition)); + URLRow url_row; + EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row)); + EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title())); + + // Add observer back to check if TypedUrlSyncBridge receive delete + // changes back from fake_history_backend_. + AddObserver(); + + ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false, + EntityChange::ACTION_DELETE); + + EXPECT_FALSE(fake_history_backend_->GetURL(GURL(kURL), &url_row)); + url_id = fake_history_backend_->GetIdByUrl(GURL(kURL)); + ASSERT_EQ(0, url_id); + + // Check TypedUrlSyncBridge did not receive update since the update is + // trigered by it. + ASSERT_EQ(1U, changes_multimap.size()); +} + +// Create two set of visits for history DB and sync DB, two same set of visits +// are same. Check DiffVisits will return empty set of diff visits. +TEST_F(TypedURLSyncBridgeTest, DiffVisitsSame) { + VisitVector old_visits; + sync_pb::TypedUrlSpecifics new_url; + + const int64_t visits[] = {1024, 2065, 65534, 1237684}; + + for (int64_t visit : visits) { + old_visits.push_back(VisitRow(0, base::Time::FromInternalValue(visit), 0, + ui::PAGE_TRANSITION_TYPED, 0)); + new_url.add_visits(visit); + new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED); + } + + std::vector<VisitInfo> new_visits; + VisitVector removed_visits; + + DiffVisits(old_visits, new_url, &new_visits, &removed_visits); + EXPECT_TRUE(new_visits.empty()); + EXPECT_TRUE(removed_visits.empty()); +} + +// Create two set of visits for history DB and sync DB. Check DiffVisits will +// return correct set of diff visits. +TEST_F(TypedURLSyncBridgeTest, DiffVisitsRemove) { + VisitVector old_visits; + sync_pb::TypedUrlSpecifics new_url; + + const int64_t visits_left[] = {1, 2, 1024, 1500, 2065, + 6000, 65534, 1237684, 2237684}; + const int64_t visits_right[] = {1024, 2065, 65534, 1237684}; + + // DiffVisits will not remove the first visit, because we never delete visits + // from the start of the array (since those visits can get truncated by the + // size-limiting code). + const int64_t visits_removed[] = {1500, 6000, 2237684}; + + for (int64_t visit : visits_left) { + old_visits.push_back(VisitRow(0, base::Time::FromInternalValue(visit), 0, + ui::PAGE_TRANSITION_TYPED, 0)); + } + + for (int64_t visit : visits_right) { + new_url.add_visits(visit); + new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED); + } + + std::vector<VisitInfo> new_visits; + VisitVector removed_visits; + + DiffVisits(old_visits, new_url, &new_visits, &removed_visits); + EXPECT_TRUE(new_visits.empty()); + ASSERT_EQ(removed_visits.size(), arraysize(visits_removed)); + for (size_t i = 0; i < arraysize(visits_removed); ++i) { + EXPECT_EQ(removed_visits[i].visit_time.ToInternalValue(), + visits_removed[i]); + } +} + +// Create two set of visits for history DB and sync DB. Check DiffVisits will +// return correct set of diff visits. +TEST_F(TypedURLSyncBridgeTest, DiffVisitsAdd) { + VisitVector old_visits; + sync_pb::TypedUrlSpecifics new_url; + + const int64_t visits_left[] = {1024, 2065, 65534, 1237684}; + const int64_t visits_right[] = {1, 1024, 1500, 2065, + 6000, 65534, 1237684, 2237684}; + + const int64_t visits_added[] = {1, 1500, 6000, 2237684}; + + for (int64_t visit : visits_left) { + old_visits.push_back(VisitRow(0, base::Time::FromInternalValue(visit), 0, + ui::PAGE_TRANSITION_TYPED, 0)); + } + + for (int64_t visit : visits_right) { + new_url.add_visits(visit); + new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED); + } + + std::vector<VisitInfo> new_visits; + VisitVector removed_visits; + + DiffVisits(old_visits, new_url, &new_visits, &removed_visits); + EXPECT_TRUE(removed_visits.empty()); + ASSERT_TRUE(new_visits.size() == arraysize(visits_added)); + for (size_t i = 0; i < arraysize(visits_added); ++i) { + EXPECT_EQ(new_visits[i].first.ToInternalValue(), visits_added[i]); + EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( + new_visits[i].second, ui::PAGE_TRANSITION_TYPED)); + } +} + +// Create three visits, check RELOAD visit is removed by +// WriteToTypedUrlSpecifics so it won't apply to sync DB. +TEST_F(TypedURLSyncBridgeTest, WriteTypedUrlSpecifics) { + VisitVector visits; + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, 1)); + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, 2)); + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, 3)); + + URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, 100, false, &visits)); + sync_pb::TypedUrlSpecifics typed_url; + WriteToTypedUrlSpecifics(url, visits, &typed_url); + // RELOAD visits should be removed. + EXPECT_EQ(2, typed_url.visits_size()); + EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size()); + EXPECT_EQ(1, typed_url.visits(0)); + EXPECT_EQ(3, typed_url.visits(1)); + EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED), + typed_url.visit_transitions(0)); + EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_LINK), + typed_url.visit_transitions(1)); +} + +// Create 101 visits, check WriteToTypedUrlSpecifics will only keep 100 visits. +TEST_F(TypedURLSyncBridgeTest, TooManyVisits) { + VisitVector visits; + int64_t timestamp = 1000; + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++)); + for (int i = 0; i < 100; ++i) { + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++)); + } + URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, timestamp++, false, &visits)); + sync_pb::TypedUrlSpecifics typed_url; + WriteToTypedUrlSpecifics(url, visits, &typed_url); + // # visits should be capped at 100. + EXPECT_EQ(100, typed_url.visits_size()); + EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size()); + EXPECT_EQ(1000, typed_url.visits(0)); + // Visit with timestamp of 1001 should be omitted since we should have + // skipped that visit to stay under the cap. + EXPECT_EQ(1002, typed_url.visits(1)); + EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED), + typed_url.visit_transitions(0)); + EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_LINK), + typed_url.visit_transitions(1)); +} + +// Create 306 visits, check WriteToTypedUrlSpecifics will only keep 100 typed +// visits. +TEST_F(TypedURLSyncBridgeTest, TooManyTypedVisits) { + VisitVector visits; + int64_t timestamp = 1000; + for (int i = 0; i < 102; ++i) { + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++)); + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++)); + visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, timestamp++)); + } + URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, timestamp++, false, &visits)); + sync_pb::TypedUrlSpecifics typed_url; + WriteToTypedUrlSpecifics(url, visits, &typed_url); + // # visits should be capped at 100. + EXPECT_EQ(100, typed_url.visits_size()); + EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size()); + // First two typed visits should be skipped. + EXPECT_EQ(1006, typed_url.visits(0)); + + // Ensure there are no non-typed visits since that's all that should fit. + for (int i = 0; i < typed_url.visits_size(); ++i) { + EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED), + typed_url.visit_transitions(i)); + } +} + +// Create a typed url without visit, check WriteToTypedUrlSpecifics will return +// false for it. +TEST_F(TypedURLSyncBridgeTest, NoTypedVisits) { + VisitVector visits; + URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, 1000, false, &visits)); + sync_pb::TypedUrlSpecifics typed_url; + EXPECT_FALSE(WriteToTypedUrlSpecifics(url, visits, &typed_url)); + // URLs with no typed URL visits should not been written to specifics. + EXPECT_EQ(0, typed_url.visits_size()); +} + +TEST_F(TypedURLSyncBridgeTest, MergeUrls) { + VisitVector visits1; + URLRow row1(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits1)); + sync_pb::TypedUrlSpecifics specs1( + MakeTypedUrlSpecifics(kURL, kTitle, 3, false)); + URLRow new_row1((GURL(kURL))); + std::vector<VisitInfo> new_visits1; + EXPECT_TRUE(TypedURLSyncBridgeTest::MergeUrls(specs1, row1, &visits1, + &new_row1, &new_visits1) == + TypedURLSyncBridgeTest::DIFF_NONE); + + VisitVector visits2; + URLRow row2(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits2)); + sync_pb::TypedUrlSpecifics specs2( + MakeTypedUrlSpecifics(kURL, kTitle, 3, true)); + VisitVector expected_visits2; + URLRow expected2( + MakeTypedUrlRow(kURL, kTitle, 2, 3, true, &expected_visits2)); + URLRow new_row2((GURL(kURL))); + std::vector<VisitInfo> new_visits2; + EXPECT_TRUE(TypedURLSyncBridgeTest::MergeUrls(specs2, row2, &visits2, + &new_row2, &new_visits2) == + TypedURLSyncBridgeTest::DIFF_LOCAL_ROW_CHANGED); + EXPECT_TRUE(URLsEqual(new_row2, expected2)); + + VisitVector visits3; + URLRow row3(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits3)); + sync_pb::TypedUrlSpecifics specs3( + MakeTypedUrlSpecifics(kURL, kTitle2, 3, true)); + VisitVector expected_visits3; + URLRow expected3( + MakeTypedUrlRow(kURL, kTitle2, 2, 3, true, &expected_visits3)); + URLRow new_row3((GURL(kURL))); + std::vector<VisitInfo> new_visits3; + EXPECT_EQ(TypedURLSyncBridgeTest::DIFF_LOCAL_ROW_CHANGED | + TypedURLSyncBridgeTest::DIFF_NONE, + TypedURLSyncBridgeTest::MergeUrls(specs3, row3, &visits3, &new_row3, + &new_visits3)); + EXPECT_TRUE(URLsEqual(new_row3, expected3)); + + // Create one node in history DB with timestamp of 3, and one node in sync + // DB with timestamp of 4. Result should contain one new item (4). + VisitVector visits4; + URLRow row4(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits4)); + sync_pb::TypedUrlSpecifics specs4( + MakeTypedUrlSpecifics(kURL, kTitle2, 4, false)); + VisitVector expected_visits4; + URLRow expected4( + MakeTypedUrlRow(kURL, kTitle2, 2, 4, false, &expected_visits4)); + URLRow new_row4((GURL(kURL))); + std::vector<VisitInfo> new_visits4; + EXPECT_EQ(TypedURLSyncBridgeTest::DIFF_UPDATE_NODE | + TypedURLSyncBridgeTest::DIFF_LOCAL_ROW_CHANGED | + TypedURLSyncBridgeTest::DIFF_LOCAL_VISITS_ADDED, + TypedURLSyncBridgeTest::MergeUrls(specs4, row4, &visits4, &new_row4, + &new_visits4)); + EXPECT_EQ(1U, new_visits4.size()); + EXPECT_EQ(specs4.visits(0), new_visits4[0].first.ToInternalValue()); + EXPECT_TRUE(URLsEqual(new_row4, expected4)); + EXPECT_EQ(2U, visits4.size()); + + VisitVector visits5; + URLRow row5(MakeTypedUrlRow(kURL, kTitle, 1, 4, false, &visits5)); + sync_pb::TypedUrlSpecifics specs5( + MakeTypedUrlSpecifics(kURL, kTitle, 3, false)); + VisitVector expected_visits5; + URLRow expected5( + MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &expected_visits5)); + URLRow new_row5((GURL(kURL))); + std::vector<VisitInfo> new_visits5; + + // UPDATE_NODE should be set because row5 has a newer last_visit timestamp. + EXPECT_EQ(TypedURLSyncBridgeTest::DIFF_UPDATE_NODE | + TypedURLSyncBridgeTest::DIFF_NONE, + TypedURLSyncBridgeTest::MergeUrls(specs5, row5, &visits5, &new_row5, + &new_visits5)); + EXPECT_TRUE(URLsEqual(new_row5, expected5)); + EXPECT_EQ(0U, new_visits5.size()); +} + +// Tests to ensure that we don't resurrect expired URLs (URLs that have been +// deleted from the history DB but still exist in the sync DB). +TEST_F(TypedURLSyncBridgeTest, MergeUrlsAfterExpiration) { + // First, create a history row that has two visits, with timestamps 2 and 3. + VisitVector(history_visits); + history_visits.push_back(VisitRow(0, base::Time::FromInternalValue(2), 0, + ui::PAGE_TRANSITION_TYPED, 0)); + URLRow history_url( + MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &history_visits)); + + // Now, create a sync node with visits at timestamps 1, 2, 3, 4. + sync_pb::TypedUrlSpecifics node( + MakeTypedUrlSpecifics(kURL, kTitle, 1, false)); + node.add_visits(2); + node.add_visits(3); + node.add_visits(4); + node.add_visit_transitions(2); + node.add_visit_transitions(3); + node.add_visit_transitions(4); + URLRow new_history_url(history_url.url()); + std::vector<VisitInfo> new_visits; + EXPECT_EQ( + TypedURLSyncBridgeTest::DIFF_NONE | + TypedURLSyncBridgeTest::DIFF_LOCAL_VISITS_ADDED, + TypedURLSyncBridgeTest::MergeUrls(node, history_url, &history_visits, + &new_history_url, &new_visits)); + EXPECT_TRUE(URLsEqual(history_url, new_history_url)); + EXPECT_EQ(1U, new_visits.size()); + EXPECT_EQ(4U, new_visits[0].first.ToInternalValue()); + // We should not sync the visit with timestamp #1 since it is earlier than + // any other visit for this URL in the history DB. But we should sync visit + // #4. + EXPECT_EQ(3U, history_visits.size()); + EXPECT_EQ(2U, history_visits[0].visit_time.ToInternalValue()); + EXPECT_EQ(3U, history_visits[1].visit_time.ToInternalValue()); + EXPECT_EQ(4U, history_visits[2].visit_time.ToInternalValue()); +} + +// Create a local typed URL with one expired TYPED visit, +// MergeSyncData should not pass it to sync. And then add a non +// expired visit, OnURLsModified should only send the non expired visit to sync. +TEST_F(TypedURLSyncBridgeTest, LocalExpiredTypedUrlDoNotSync) { + URLRow row; + URLRows changed_urls; + VisitVector visits; + + // Add an expired typed URL to local. + row = MakeTypedUrlRow(kURL, kTitle, 1, kExpiredVisit, false, &visits); + fake_history_backend_->SetVisitsForUrl(row, visits); + + StartSyncing(std::vector<TypedUrlSpecifics>()); + + // Check change processor did not receive expired typed URL. + const auto& changes_multimap = processor().put_multimap(); + ASSERT_EQ(0U, changes_multimap.size()); + + // Add a non expired typed URL to local. + row = MakeTypedUrlRow(kURL, kTitle, 2, 1, false, &visits); + fake_history_backend_->SetVisitsForUrl(row, visits); + + changed_urls.push_back(row); + // Notify typed url sync service of the update. + bridge()->OnURLsModified(fake_history_backend_.get(), changed_urls); + + // Check change processor did not receive expired typed URL. + ASSERT_EQ(1U, changes_multimap.size()); + + // Get typed url specifics. Verify only a non-expired visit received. + sync_pb::TypedUrlSpecifics url_specifics = GetLastUpdateForURL(kURL); + + EXPECT_TRUE(URLsEqual(row, url_specifics)); + ASSERT_EQ(1, url_specifics.visits_size()); + ASSERT_EQ(static_cast<const int>(visits.size() - 1), + url_specifics.visits_size()); + EXPECT_EQ(visits[1].visit_time.ToInternalValue(), url_specifics.visits(0)); + EXPECT_EQ(static_cast<const int>(visits[1].transition), + url_specifics.visit_transitions(0)); +} + } // namespace history |