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