Home | History | Annotate | Download | only in history
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/history/typed_url_syncable_service.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/memory/ref_counted.h"
      9 #include "base/memory/scoped_ptr.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/history/history_backend.h"
     12 #include "chrome/browser/history/history_types.h"
     13 #include "content/public/browser/notification_types.h"
     14 #include "sync/api/fake_sync_change_processor.h"
     15 #include "sync/api/sync_change_processor_wrapper_for_test.h"
     16 #include "sync/api/sync_error.h"
     17 #include "sync/api/sync_error_factory_mock.h"
     18 #include "sync/protocol/sync.pb.h"
     19 #include "sync/protocol/typed_url_specifics.pb.h"
     20 #include "testing/gtest/include/gtest/gtest.h"
     21 
     22 using history::HistoryBackend;
     23 using history::URLID;
     24 using history::URLRow;
     25 using history::URLRows;
     26 using history::VisitRow;
     27 using history::VisitVector;
     28 
     29 namespace {
     30 
     31 // Constants used to limit size of visits processed.
     32 const int kMaxTypedUrlVisits = 100;
     33 
     34 // Visits with this timestamp are treated as expired.
     35 const int EXPIRED_VISIT = -1;
     36 
     37 // TestHistoryBackend ----------------------------------------------------------
     38 
     39 class TestHistoryBackend : public HistoryBackend {
     40  public:
     41   TestHistoryBackend() : HistoryBackend(base::FilePath(), NULL, NULL) {}
     42 
     43   // HistoryBackend test implementation.
     44   virtual bool IsExpiredVisitTime(const base::Time& time) OVERRIDE {
     45     return time.ToInternalValue() == EXPIRED_VISIT;
     46   }
     47 
     48   virtual bool GetMostRecentVisitsForURL(
     49       URLID id,
     50       int max_visits,
     51       VisitVector* visits) OVERRIDE {
     52     if (local_db_visits_[id].empty())
     53       return false;
     54 
     55     visits->insert(visits->end(),
     56                    local_db_visits_[id].begin(),
     57                    local_db_visits_[id].end());
     58     return true;
     59   }
     60 
     61   // Helpers.
     62   void SetVisitsForUrl(URLID id, VisitVector* visits) {
     63     if (!local_db_visits_[id].empty()) {
     64       local_db_visits_[id].clear();
     65     }
     66 
     67     local_db_visits_[id].insert(local_db_visits_[id].end(),
     68                                 visits->begin(),
     69                                 visits->end());
     70   }
     71 
     72   void DeleteVisitsForUrl(const URLID& id) {
     73     local_db_visits_.erase(id);
     74   }
     75 
     76  private:
     77   virtual ~TestHistoryBackend() {}
     78 
     79   // Mock of visit table in local db.
     80   std::map<URLID, VisitVector> local_db_visits_;
     81 };
     82 
     83 }  // namespace
     84 
     85 namespace history {
     86 
     87 // TypedUrlSyncableServiceTest -------------------------------------------------
     88 
     89 class TypedUrlSyncableServiceTest : public testing::Test {
     90  public:
     91   // Create a new row object and add a typed visit to the |visits| vector.
     92   // Note that the real history db returns visits in reverse chronological
     93   // order, so |visits| is treated this way. If the newest (first) visit
     94   // in visits does not match |last_visit|, then a typed visit for this
     95   // time is prepended to the front (or if |last_visit| is too old, it is
     96   // set equal to the time of the newest visit).
     97   static URLRow MakeTypedUrlRow(const char* url,
     98                                 const char* title,
     99                                 int typed_count,
    100                                 int64 last_visit,
    101                                 bool hidden,
    102                                 VisitVector* visits);
    103 
    104   static void AddNewestVisit(URLRow* url,
    105                              VisitVector* visits,
    106                              content::PageTransition transition,
    107                              int64 visit_time);
    108 
    109   static void AddOldestVisit(URLRow* url,
    110                              VisitVector* visits,
    111                              content::PageTransition transition,
    112                              int64 visit_time);
    113 
    114   static bool URLsEqual(URLRow& row,
    115                         sync_pb::TypedUrlSpecifics& specifics) {
    116     return ((row.url().spec().compare(specifics.url()) == 0) &&
    117             (base::UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) &&
    118             (row.hidden() == specifics.hidden()));
    119   }
    120 
    121   bool InitiateServerState(unsigned int num_typed_urls,
    122                            unsigned int num_reload_urls,
    123                            URLRows* rows,
    124                            std::vector<VisitVector>* visit_vectors,
    125                            const std::vector<const char*>& urls);
    126 
    127  protected:
    128   TypedUrlSyncableServiceTest() {}
    129 
    130   virtual ~TypedUrlSyncableServiceTest() {}
    131 
    132   virtual void SetUp() OVERRIDE {
    133     fake_history_backend_ = new TestHistoryBackend();
    134     typed_url_sync_service_.reset(
    135         new TypedUrlSyncableService(fake_history_backend_.get()));
    136     fake_change_processor_.reset(new syncer::FakeSyncChangeProcessor);
    137   }
    138 
    139   scoped_refptr<HistoryBackend> fake_history_backend_;
    140   scoped_ptr<TypedUrlSyncableService> typed_url_sync_service_;
    141   scoped_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_;
    142 };
    143 
    144 URLRow TypedUrlSyncableServiceTest::MakeTypedUrlRow(
    145     const char* url,
    146     const char* title,
    147     int typed_count,
    148     int64 last_visit,
    149     bool hidden,
    150     VisitVector* visits) {
    151   DCHECK(visits->empty());
    152 
    153   // Give each URL a unique ID, to mimic the behavior of the real database.
    154   static int unique_url_id = 0;
    155   GURL gurl(url);
    156   URLRow history_url(gurl, ++unique_url_id);
    157   history_url.set_title(base::UTF8ToUTF16(title));
    158   history_url.set_typed_count(typed_count);
    159   history_url.set_hidden(hidden);
    160 
    161   base::Time last_visit_time = base::Time::FromInternalValue(last_visit);
    162   history_url.set_last_visit(last_visit_time);
    163 
    164   VisitVector::iterator first = visits->begin();
    165   if (typed_count > 0) {
    166     // Add a typed visit for time |last_visit|.
    167     visits->insert(first,
    168                    VisitRow(history_url.id(), last_visit_time, 0,
    169                             content::PAGE_TRANSITION_TYPED, 0));
    170   } else {
    171     // Add a non-typed visit for time |last_visit|.
    172     visits->insert(first,
    173                    VisitRow(history_url.id(), last_visit_time, 0,
    174                             content::PAGE_TRANSITION_RELOAD, 0));
    175   }
    176 
    177   history_url.set_visit_count(visits->size());
    178   return history_url;
    179 }
    180 
    181 void TypedUrlSyncableServiceTest::AddNewestVisit(
    182     URLRow* url,
    183     VisitVector* visits,
    184     content::PageTransition transition,
    185     int64 visit_time) {
    186   base::Time time = base::Time::FromInternalValue(visit_time);
    187   visits->insert(visits->begin(),
    188                  VisitRow(url->id(), time, 0, transition, 0));
    189 
    190   if (transition == content::PAGE_TRANSITION_TYPED) {
    191     url->set_typed_count(url->typed_count() + 1);
    192   }
    193 
    194   url->set_last_visit(time);
    195   url->set_visit_count(visits->size());
    196 }
    197 
    198 void TypedUrlSyncableServiceTest::AddOldestVisit(
    199     URLRow* url,
    200     VisitVector* visits,
    201     content::PageTransition transition,
    202     int64 visit_time) {
    203   base::Time time = base::Time::FromInternalValue(visit_time);
    204   visits->push_back(VisitRow(url->id(), time, 0, transition, 0));
    205 
    206   if (transition == content::PAGE_TRANSITION_TYPED) {
    207     url->set_typed_count(url->typed_count() + 1);
    208   }
    209 
    210   url->set_visit_count(visits->size());
    211 }
    212 
    213 bool TypedUrlSyncableServiceTest::InitiateServerState(
    214     unsigned int num_typed_urls,
    215     unsigned int num_reload_urls,
    216     URLRows* rows,
    217     std::vector<VisitVector>* visit_vectors,
    218     const std::vector<const char*>& urls) {
    219   unsigned int total_urls = num_typed_urls + num_reload_urls;
    220   DCHECK(urls.size() >= total_urls);
    221   if (!typed_url_sync_service_.get())
    222     return false;
    223 
    224   // Set change processor.
    225   syncer::SyncMergeResult result =
    226       typed_url_sync_service_->MergeDataAndStartSyncing(
    227           syncer::TYPED_URLS,
    228           syncer::SyncDataList(),
    229           scoped_ptr<syncer::SyncChangeProcessor>(
    230               new syncer::SyncChangeProcessorWrapperForTest(
    231                   fake_change_processor_.get())),
    232           scoped_ptr<syncer::SyncErrorFactory>(
    233               new syncer::SyncErrorFactoryMock()));
    234   EXPECT_FALSE(result.error().IsSet()) << result.error().message();
    235 
    236   if (total_urls) {
    237     // Create new URL rows, populate the mock backend with its visits, and
    238     // send to the sync service.
    239     URLRows changed_urls;
    240 
    241     for (unsigned int i = 0; i < total_urls; ++i) {
    242       int typed = i < num_typed_urls ? 1 : 0;
    243       VisitVector visits;
    244       visit_vectors->push_back(visits);
    245       rows->push_back(MakeTypedUrlRow(
    246           urls[i], "pie", typed, i + 3, false, &visit_vectors->back()));
    247       static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    248           SetVisitsForUrl(rows->back().id(), &visit_vectors->back());
    249       changed_urls.push_back(rows->back());
    250     }
    251 
    252     typed_url_sync_service_->OnUrlsModified(&changed_urls);
    253   }
    254   // Check that communication with sync was successful.
    255   if (num_typed_urls != fake_change_processor_->changes().size())
    256     return false;
    257   return true;
    258 }
    259 
    260 TEST_F(TypedUrlSyncableServiceTest, AddLocalTypedUrlAndSync) {
    261   // Create a local typed URL (simulate a typed visit) that is not already
    262   // in sync. Check that sync is sent an ADD change for the existing URL.
    263   URLRows url_rows;
    264   std::vector<VisitVector> visit_vectors;
    265   std::vector<const char*> urls;
    266   urls.push_back("http://pie.com/");
    267 
    268   ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
    269 
    270   URLRow url_row = url_rows.front();
    271   VisitVector visits = visit_vectors.front();
    272 
    273   // Check change processor.
    274   const syncer::SyncChangeList& changes = fake_change_processor_->changes();
    275   ASSERT_EQ(1u, changes.size());
    276   ASSERT_TRUE(changes[0].IsValid());
    277   EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
    278   EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
    279 
    280   // Get typed url specifics.
    281   sync_pb::TypedUrlSpecifics url_specifics =
    282       changes[0].sync_data().GetSpecifics().typed_url();
    283 
    284   EXPECT_TRUE(URLsEqual(url_row, url_specifics));
    285   ASSERT_EQ(1, url_specifics.visits_size());
    286   ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
    287   EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(0));
    288   EXPECT_EQ(static_cast<const int>(visits[0].transition),
    289             url_specifics.visit_transitions(0));
    290 
    291   // Check that in-memory representation of sync state is accurate.
    292   std::set<GURL> sync_state;
    293   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
    294   EXPECT_FALSE(sync_state.empty());
    295   EXPECT_EQ(1u, sync_state.size());
    296   EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
    297 }
    298 
    299 TEST_F(TypedUrlSyncableServiceTest, UpdateLocalTypedUrlAndSync) {
    300   URLRows url_rows;
    301   std::vector<VisitVector> visit_vectors;
    302   std::vector<const char*> urls;
    303   urls.push_back("http://pie.com/");
    304 
    305   ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
    306   syncer::SyncChangeList& changes = fake_change_processor_->changes();
    307   changes.clear();
    308 
    309   // Update the URL row, adding another typed visit to the visit vector.
    310   URLRow url_row = url_rows.front();
    311   VisitVector visits = visit_vectors.front();
    312 
    313   URLRows changed_urls;
    314   AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, 7);
    315   static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    316       SetVisitsForUrl(url_row.id(), &visits);
    317   changed_urls.push_back(url_row);
    318 
    319   // Notify typed url sync service of the update.
    320   typed_url_sync_service_->OnUrlsModified(&changed_urls);
    321 
    322   ASSERT_EQ(1u, changes.size());
    323   ASSERT_TRUE(changes[0].IsValid());
    324   EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
    325   EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());
    326 
    327   sync_pb::TypedUrlSpecifics url_specifics =
    328       changes[0].sync_data().GetSpecifics().typed_url();
    329 
    330   EXPECT_TRUE(URLsEqual(url_row, url_specifics));
    331   ASSERT_EQ(2, url_specifics.visits_size());
    332   ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
    333 
    334   // Check that each visit has been translated/communicated correctly.
    335   // Note that the specifics record visits in chronological order, and the
    336   // visits from the db are in reverse chronological order.
    337   EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(1));
    338   EXPECT_EQ(static_cast<const int>(visits[0].transition),
    339             url_specifics.visit_transitions(1));
    340   EXPECT_EQ(visits[1].visit_time.ToInternalValue(), url_specifics.visits(0));
    341   EXPECT_EQ(static_cast<const int>(visits[1].transition),
    342             url_specifics.visit_transitions(0));
    343 
    344   // Check that in-memory representation of sync state is accurate.
    345   std::set<GURL> sync_state;
    346   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
    347   EXPECT_FALSE(sync_state.empty());
    348   EXPECT_EQ(1u, sync_state.size());
    349   EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
    350 }
    351 
    352 TEST_F(TypedUrlSyncableServiceTest, LinkVisitLocalTypedUrlAndSync) {
    353   URLRows url_rows;
    354   std::vector<VisitVector> visit_vectors;
    355   std::vector<const char*> urls;
    356   urls.push_back("http://pie.com/");
    357 
    358   ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
    359   syncer::SyncChangeList& changes = fake_change_processor_->changes();
    360   changes.clear();
    361 
    362   URLRow url_row = url_rows.front();
    363   VisitVector visits = visit_vectors.front();
    364 
    365   // Update the URL row, adding a non-typed visit to the visit vector.
    366   AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, 6);
    367   static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    368       SetVisitsForUrl(url_row.id(), &visits);
    369 
    370   content::PageTransition transition = content::PAGE_TRANSITION_LINK;
    371   // Notify typed url sync service of non-typed visit, expect no change.
    372   typed_url_sync_service_->OnUrlVisited(transition, &url_row);
    373   ASSERT_EQ(0u, changes.size());
    374 }
    375 
    376 TEST_F(TypedUrlSyncableServiceTest, TypedVisitLocalTypedUrlAndSync) {
    377   URLRows url_rows;
    378   std::vector<VisitVector> visit_vectors;
    379   std::vector<const char*> urls;
    380   urls.push_back("http://pie.com/");
    381 
    382   ASSERT_TRUE(InitiateServerState(1, 0, &url_rows, &visit_vectors, urls));
    383   syncer::SyncChangeList& changes = fake_change_processor_->changes();
    384   changes.clear();
    385 
    386   URLRow url_row = url_rows.front();
    387   VisitVector visits = visit_vectors.front();
    388 
    389   // Update the URL row, adding another typed visit to the visit vector.
    390   AddOldestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, 1);
    391   AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, 6);
    392   AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, 7);
    393   static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    394       SetVisitsForUrl(url_row.id(), &visits);
    395 
    396   // Notify typed url sync service of typed visit.
    397   content::PageTransition transition = content::PAGE_TRANSITION_TYPED;
    398   typed_url_sync_service_->OnUrlVisited(transition, &url_row);
    399 
    400   ASSERT_EQ(1u, changes.size());
    401   ASSERT_TRUE(changes[0].IsValid());
    402   EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
    403   EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());
    404 
    405   sync_pb::TypedUrlSpecifics url_specifics =
    406       changes[0].sync_data().GetSpecifics().typed_url();
    407 
    408   EXPECT_TRUE(URLsEqual(url_row, url_specifics));
    409   ASSERT_EQ(4u, visits.size());
    410   EXPECT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
    411 
    412   // Check that each visit has been translated/communicated correctly.
    413   // Note that the specifics record visits in chronological order, and the
    414   // visits from the db are in reverse chronological order.
    415   int r = url_specifics.visits_size() - 1;
    416   for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
    417     EXPECT_EQ(visits[i].visit_time.ToInternalValue(), url_specifics.visits(r));
    418     EXPECT_EQ(static_cast<const int>(visits[i].transition),
    419               url_specifics.visit_transitions(r));
    420   }
    421 
    422   // Check that in-memory representation of sync state is accurate.
    423   std::set<GURL> sync_state;
    424   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
    425   EXPECT_FALSE(sync_state.empty());
    426   EXPECT_EQ(1u, sync_state.size());
    427   EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
    428 }
    429 
    430 TEST_F(TypedUrlSyncableServiceTest, DeleteLocalTypedUrlAndSync) {
    431   URLRows url_rows;
    432   std::vector<VisitVector> visit_vectors;
    433   std::vector<const char*> urls;
    434   urls.push_back("http://pie.com/");
    435   urls.push_back("http://cake.com/");
    436   urls.push_back("http://google.com/");
    437   urls.push_back("http://foo.com/");
    438   urls.push_back("http://bar.com/");
    439 
    440   ASSERT_TRUE(InitiateServerState(4, 1, &url_rows, &visit_vectors, urls));
    441   syncer::SyncChangeList& changes = fake_change_processor_->changes();
    442   changes.clear();
    443 
    444   // Check that in-memory representation of sync state is accurate.
    445   std::set<GURL> sync_state;
    446   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
    447   EXPECT_FALSE(sync_state.empty());
    448   EXPECT_EQ(4u, sync_state.size());
    449 
    450   // Simulate visit expiry of typed visit, no syncing is done
    451   // This is to test that sync relies on the in-memory cache to know
    452   // which urls were typed and synced, and should be deleted.
    453   url_rows[0].set_typed_count(0);
    454   VisitVector visits;
    455   static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    456       SetVisitsForUrl(url_rows[0].id(), &visits);
    457 
    458   // Delete some urls from backend and create deleted row vector.
    459   URLRows rows;
    460   for (size_t i = 0; i < 3u; ++i) {
    461     static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    462         DeleteVisitsForUrl(url_rows[i].id());
    463     rows.push_back(url_rows[i]);
    464   }
    465 
    466   // Notify typed url sync service.
    467   typed_url_sync_service_->OnUrlsDeleted(false, false, &rows);
    468 
    469   ASSERT_EQ(3u, changes.size());
    470   for (size_t i = 0; i < changes.size(); ++i) {
    471     ASSERT_TRUE(changes[i].IsValid());
    472     ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
    473     EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
    474     sync_pb::TypedUrlSpecifics url_specifics =
    475         changes[i].sync_data().GetSpecifics().typed_url();
    476     EXPECT_EQ(url_rows[i].url().spec(), url_specifics.url());
    477   }
    478 
    479   // Check that in-memory representation of sync state is accurate.
    480   std::set<GURL> sync_state_deleted;
    481   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state_deleted);
    482   ASSERT_EQ(1u, sync_state_deleted.size());
    483   EXPECT_TRUE(sync_state_deleted.end() !=
    484               sync_state_deleted.find(url_rows[3].url()));
    485 }
    486 
    487 TEST_F(TypedUrlSyncableServiceTest, DeleteAllLocalTypedUrlAndSync) {
    488   URLRows url_rows;
    489   std::vector<VisitVector> visit_vectors;
    490   std::vector<const char*> urls;
    491   urls.push_back("http://pie.com/");
    492   urls.push_back("http://cake.com/");
    493   urls.push_back("http://google.com/");
    494   urls.push_back("http://foo.com/");
    495   urls.push_back("http://bar.com/");
    496 
    497   ASSERT_TRUE(InitiateServerState(4, 1, &url_rows, &visit_vectors, urls));
    498   syncer::SyncChangeList& changes = fake_change_processor_->changes();
    499   changes.clear();
    500 
    501   // Check that in-memory representation of sync state is accurate.
    502   std::set<GURL> sync_state;
    503   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
    504   EXPECT_EQ(4u, sync_state.size());
    505 
    506   // Delete urls from backend.
    507   for (size_t i = 0; i < 4u; ++ i) {
    508     static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    509         DeleteVisitsForUrl(url_rows[i].id());
    510   }
    511   // Delete urls with |all_history| flag set.
    512   bool all_history = true;
    513 
    514   // Notify typed url sync service.
    515   typed_url_sync_service_->OnUrlsDeleted(all_history, false, NULL);
    516 
    517   ASSERT_EQ(4u, changes.size());
    518   for (size_t i = 0; i < changes.size(); ++i) {
    519     ASSERT_TRUE(changes[i].IsValid());
    520     ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
    521     EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
    522   }
    523   // Check that in-memory representation of sync state is accurate.
    524   std::set<GURL> sync_state_deleted;
    525   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state_deleted);
    526   EXPECT_TRUE(sync_state_deleted.empty());
    527 }
    528 
    529 TEST_F(TypedUrlSyncableServiceTest, MaxVisitLocalTypedUrlAndSync) {
    530   URLRows url_rows;
    531   std::vector<VisitVector> visit_vectors;
    532   std::vector<const char*> urls;
    533   urls.push_back("http://pie.com/");
    534 
    535   ASSERT_TRUE(InitiateServerState(0, 1, &url_rows, &visit_vectors, urls));
    536 
    537   URLRow url_row = url_rows.front();
    538   VisitVector visits;
    539 
    540   // Add |kMaxTypedUrlVisits| + 10 visits to the url. The 10 oldest
    541   // non-typed visits are expected to be skipped.
    542   int i = 1;
    543   for (; i <= kMaxTypedUrlVisits - 20; ++i)
    544     AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, i);
    545   for (; i <= kMaxTypedUrlVisits; ++i)
    546     AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_LINK, i);
    547   for (; i <= kMaxTypedUrlVisits + 10; ++i)
    548     AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, i);
    549 
    550   static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    551       SetVisitsForUrl(url_row.id(), &visits);
    552 
    553   // Notify typed url sync service of typed visit.
    554   content::PageTransition transition = content::PAGE_TRANSITION_TYPED;
    555   typed_url_sync_service_->OnUrlVisited(transition, &url_row);
    556 
    557   const syncer::SyncChangeList& changes = fake_change_processor_->changes();
    558   ASSERT_EQ(1u, changes.size());
    559   ASSERT_TRUE(changes[0].IsValid());
    560   sync_pb::TypedUrlSpecifics url_specifics =
    561       changes[0].sync_data().GetSpecifics().typed_url();
    562   ASSERT_EQ(kMaxTypedUrlVisits, url_specifics.visits_size());
    563 
    564   // Check that each visit has been translated/communicated correctly.
    565   // Note that the specifics record visits in chronological order, and the
    566   // visits from the db are in reverse chronological order.
    567   int num_typed_visits_synced = 0;
    568   int num_other_visits_synced = 0;
    569   int r = url_specifics.visits_size() - 1;
    570   for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
    571     if (url_specifics.visit_transitions(i) == content::PAGE_TRANSITION_TYPED) {
    572       ++num_typed_visits_synced;
    573     } else {
    574       ++num_other_visits_synced;
    575     }
    576   }
    577   EXPECT_EQ(kMaxTypedUrlVisits - 10, num_typed_visits_synced);
    578   EXPECT_EQ(10, num_other_visits_synced);
    579 }
    580 
    581 TEST_F(TypedUrlSyncableServiceTest, ThrottleVisitLocalTypedUrlSync) {
    582   URLRows url_rows;
    583   std::vector<VisitVector> visit_vectors;
    584   std::vector<const char*> urls;
    585   urls.push_back("http://pie.com/");
    586 
    587   ASSERT_TRUE(InitiateServerState(0, 1, &url_rows, &visit_vectors, urls));
    588 
    589   URLRow url_row = url_rows.front();
    590   VisitVector visits;
    591 
    592   // Add enough visits to the url so that typed count is above the throttle
    593   // limit, and not right on the interval that gets synced.
    594   for (int i = 1; i < 42; ++i)
    595     AddNewestVisit(&url_row, &visits, content::PAGE_TRANSITION_TYPED, i);
    596 
    597   static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
    598       SetVisitsForUrl(url_row.id(), &visits);
    599 
    600   // Notify typed url sync service of typed visit.
    601   content::PageTransition transition = content::PAGE_TRANSITION_TYPED;
    602   typed_url_sync_service_->OnUrlVisited(transition, &url_row);
    603 
    604   // Should throttle, so sync and local cache should not update.
    605   const syncer::SyncChangeList& changes = fake_change_processor_->changes();
    606   ASSERT_EQ(0u, changes.size());
    607   std::set<GURL> sync_state;
    608   typed_url_sync_service_.get()->GetSyncedUrls(&sync_state);
    609   EXPECT_TRUE(sync_state.empty());
    610 }
    611 
    612 }  // namespace history
    613