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 "components/history/core/browser/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
22using history::HistoryBackend;
23using history::URLID;
24using history::URLRow;
25using history::URLRows;
26using history::VisitRow;
27using history::VisitVector;
28
29namespace {
30
31// Constants used to limit size of visits processed.
32const int kMaxTypedUrlVisits = 100;
33
34// Visits with this timestamp are treated as expired.
35const int EXPIRED_VISIT = -1;
36
37// TestHistoryBackend ----------------------------------------------------------
38
39class 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
85namespace history {
86
87// TypedUrlSyncableServiceTest -------------------------------------------------
88
89class 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                             ui::PageTransition transition,
107                             int64 visit_time);
108
109  static void AddOldestVisit(URLRow* url,
110                             VisitVector* visits,
111                             ui::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
144URLRow 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                            ui::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                            ui::PAGE_TRANSITION_RELOAD, 0));
175  }
176
177  history_url.set_visit_count(visits->size());
178  return history_url;
179}
180
181void TypedUrlSyncableServiceTest::AddNewestVisit(
182    URLRow* url,
183    VisitVector* visits,
184    ui::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 == ui::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
198void TypedUrlSyncableServiceTest::AddOldestVisit(
199    URLRow* url,
200    VisitVector* visits,
201    ui::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 == ui::PAGE_TRANSITION_TYPED) {
207    url->set_typed_count(url->typed_count() + 1);
208  }
209
210  url->set_visit_count(visits->size());
211}
212
213bool 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
260TEST_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
299TEST_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, ui::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
352TEST_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, ui::PAGE_TRANSITION_LINK, 6);
367  static_cast<TestHistoryBackend*>(fake_history_backend_.get())->
368      SetVisitsForUrl(url_row.id(), &visits);
369
370  ui::PageTransition transition = ui::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
376TEST_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, ui::PAGE_TRANSITION_LINK, 1);
391  AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_LINK, 6);
392  AddNewestVisit(&url_row, &visits, ui::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  ui::PageTransition transition = ui::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
430TEST_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
487TEST_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
529TEST_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, ui::PAGE_TRANSITION_TYPED, i);
545  for (; i <= kMaxTypedUrlVisits; ++i)
546    AddNewestVisit(&url_row, &visits, ui::PAGE_TRANSITION_LINK, i);
547  for (; i <= kMaxTypedUrlVisits + 10; ++i)
548    AddNewestVisit(&url_row, &visits, ui::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  ui::PageTransition transition = ui::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) == ui::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
581TEST_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, ui::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  ui::PageTransition transition = ui::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