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