1// Copyright (c) 2012 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 <string>
6#include <utility>
7#include <vector>
8
9#include "testing/gtest/include/gtest/gtest.h"
10
11#include "base/bind.h"
12#include "base/bind_helpers.h"
13#include "base/callback.h"
14#include "base/location.h"
15#include "base/memory/ref_counted.h"
16#include "base/strings/string16.h"
17#include "base/strings/utf_string_conversions.h"
18#include "base/threading/thread.h"
19#include "base/time/time.h"
20#include "chrome/browser/chrome_notification_types.h"
21#include "chrome/browser/history/history_backend.h"
22#include "chrome/browser/history/history_db_task.h"
23#include "chrome/browser/history/history_notifications.h"
24#include "chrome/browser/history/history_service.h"
25#include "chrome/browser/history/history_service_factory.h"
26#include "chrome/browser/invalidation/fake_invalidation_service.h"
27#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
28#include "chrome/browser/prefs/pref_service_syncable.h"
29#include "chrome/browser/signin/fake_profile_oauth2_token_service.h"
30#include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h"
31#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
32#include "chrome/browser/signin/signin_manager_factory.h"
33#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
34#include "chrome/browser/sync/glue/sync_backend_host.h"
35#include "chrome/browser/sync/glue/typed_url_change_processor.h"
36#include "chrome/browser/sync/glue/typed_url_data_type_controller.h"
37#include "chrome/browser/sync/glue/typed_url_model_associator.h"
38#include "chrome/browser/sync/profile_sync_components_factory.h"
39#include "chrome/browser/sync/profile_sync_components_factory_mock.h"
40#include "chrome/browser/sync/profile_sync_service.h"
41#include "chrome/browser/sync/profile_sync_service_factory.h"
42#include "chrome/browser/sync/profile_sync_test_util.h"
43#include "chrome/browser/sync/test_profile_sync_service.h"
44#include "chrome/test/base/testing_browser_process.h"
45#include "chrome/test/base/testing_profile.h"
46#include "chrome/test/base/testing_profile_manager.h"
47#include "components/history/core/browser/history_types.h"
48#include "components/invalidation/invalidation_service.h"
49#include "components/invalidation/profile_invalidation_provider.h"
50#include "components/keyed_service/content/refcounted_browser_context_keyed_service.h"
51#include "components/signin/core/browser/signin_manager.h"
52#include "components/sync_driver/data_type_error_handler_mock.h"
53#include "content/public/browser/notification_service.h"
54#include "google_apis/gaia/gaia_constants.h"
55#include "sync/internal_api/public/read_node.h"
56#include "sync/internal_api/public/read_transaction.h"
57#include "sync/internal_api/public/write_node.h"
58#include "sync/internal_api/public/write_transaction.h"
59#include "sync/protocol/typed_url_specifics.pb.h"
60#include "testing/gmock/include/gmock/gmock.h"
61#include "url/gurl.h"
62
63using base::Thread;
64using base::Time;
65using browser_sync::TypedUrlChangeProcessor;
66using browser_sync::TypedUrlDataTypeController;
67using browser_sync::TypedUrlModelAssociator;
68using history::HistoryBackend;
69using history::URLID;
70using history::URLRow;
71using syncer::syncable::WriteTransaction;
72using testing::DoAll;
73using testing::Return;
74using testing::SetArgumentPointee;
75using testing::_;
76
77namespace content {
78class BrowserContext;
79}
80
81namespace {
82
83const char kTestProfileName[] = "test-profile";
84
85// Visits with this timestamp are treated as expired.
86static const int EXPIRED_VISIT = -1;
87
88class HistoryBackendMock : public HistoryBackend {
89 public:
90  HistoryBackendMock() : HistoryBackend(base::FilePath(), NULL, NULL) {}
91  virtual bool IsExpiredVisitTime(const base::Time& time) OVERRIDE {
92    return time.ToInternalValue() == EXPIRED_VISIT;
93  }
94  MOCK_METHOD1(GetAllTypedURLs, bool(history::URLRows* entries));
95  MOCK_METHOD3(GetMostRecentVisitsForURL, bool(history::URLID id,
96                                               int max_visits,
97                                               history::VisitVector* visits));
98  MOCK_METHOD2(UpdateURL, bool(history::URLID id, const history::URLRow& url));
99  MOCK_METHOD3(AddVisits, bool(const GURL& url,
100                               const std::vector<history::VisitInfo>& visits,
101                               history::VisitSource visit_source));
102  MOCK_METHOD1(RemoveVisits, bool(const history::VisitVector& visits));
103  MOCK_METHOD2(GetURL, bool(const GURL& url_id, history::URLRow* url_row));
104  MOCK_METHOD2(SetPageTitle, void(const GURL& url,
105                                  const base::string16& title));
106  MOCK_METHOD1(DeleteURL, void(const GURL& url));
107
108 private:
109  virtual ~HistoryBackendMock() {}
110};
111
112class HistoryServiceMock : public HistoryService {
113 public:
114  HistoryServiceMock(history::HistoryClient* client, Profile* profile)
115      : HistoryService(client, profile), backend_(NULL) {}
116
117  virtual void ScheduleDBTask(scoped_ptr<history::HistoryDBTask> task,
118                              base::CancelableTaskTracker* tracker) OVERRIDE {
119    history::HistoryDBTask* task_raw = task.get();
120    task_runner_->PostTaskAndReply(
121        FROM_HERE,
122        base::Bind(&HistoryServiceMock::RunTaskOnDBThread,
123                   base::Unretained(this), task_raw),
124        base::Bind(&base::DeletePointer<history::HistoryDBTask>,
125                   task.release()));
126  }
127
128  MOCK_METHOD0(Shutdown, void());
129
130  void ShutdownBaseService() {
131    HistoryService::Shutdown();
132  }
133
134  void set_task_runner(
135      scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
136    DCHECK(task_runner.get());
137    task_runner_ = task_runner;
138  }
139
140  void set_backend(scoped_refptr<history::HistoryBackend> backend) {
141    backend_ = backend;
142  }
143
144 private:
145  virtual ~HistoryServiceMock() {}
146
147  void RunTaskOnDBThread(history::HistoryDBTask* task) {
148    EXPECT_TRUE(task->RunOnDBThread(backend_.get(), NULL));
149  }
150
151  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
152  scoped_refptr<history::HistoryBackend> backend_;
153};
154
155KeyedService* BuildFakeProfileInvalidationProvider(
156    content::BrowserContext* context) {
157  return new invalidation::ProfileInvalidationProvider(
158      scoped_ptr<invalidation::InvalidationService>(
159          new invalidation::FakeInvalidationService));
160}
161
162KeyedService* BuildHistoryService(content::BrowserContext* profile) {
163  return new HistoryServiceMock(NULL, static_cast<Profile*>(profile));
164}
165
166class TestTypedUrlModelAssociator : public TypedUrlModelAssociator {
167 public:
168  TestTypedUrlModelAssociator(
169      ProfileSyncService* sync_service,
170      history::HistoryBackend* history_backend,
171      sync_driver::DataTypeErrorHandler* error_handler) :
172      TypedUrlModelAssociator(sync_service, history_backend, error_handler) {}
173
174 protected:
175  // Don't clear error stats - that way we can verify their values in our
176  // tests.
177  virtual void ClearErrorStats() OVERRIDE {}
178};
179
180ACTION_P2(ShutdownHistoryService, thread, service) {
181  service->ShutdownBaseService();
182  delete thread;
183}
184
185ACTION_P6(MakeTypedUrlSyncComponents,
186              profile,
187              service,
188              hb,
189              dtc,
190              error_handler,
191              model_associator) {
192  *model_associator =
193      new TestTypedUrlModelAssociator(service, hb, error_handler);
194  TypedUrlChangeProcessor* change_processor =
195      new TypedUrlChangeProcessor(profile, *model_associator, hb, dtc);
196  return ProfileSyncComponentsFactory::SyncComponents(*model_associator,
197                                                      change_processor);
198}
199
200class ProfileSyncServiceTypedUrlTest : public AbstractProfileSyncServiceTest {
201 public:
202  void AddTypedUrlSyncNode(const history::URLRow& url,
203                           const history::VisitVector& visits) {
204    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
205    syncer::ReadNode typed_url_root(&trans);
206    ASSERT_EQ(syncer::BaseNode::INIT_OK,
207              typed_url_root.InitTypeRoot(syncer::TYPED_URLS));
208
209    syncer::WriteNode node(&trans);
210    std::string tag = url.url().spec();
211    syncer::WriteNode::InitUniqueByCreationResult result =
212        node.InitUniqueByCreation(syncer::TYPED_URLS, typed_url_root, tag);
213    ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result);
214    TypedUrlModelAssociator::WriteToSyncNode(url, visits, &node);
215  }
216
217 protected:
218  ProfileSyncServiceTypedUrlTest()
219      : profile_manager_(TestingBrowserProcess::GetGlobal()) {
220    history_thread_.reset(new Thread("history"));
221  }
222
223  virtual void SetUp() {
224    AbstractProfileSyncServiceTest::SetUp();
225    ASSERT_TRUE(profile_manager_.SetUp());
226    TestingProfile::TestingFactories testing_factories;
227    testing_factories.push_back(std::make_pair(
228        ProfileOAuth2TokenServiceFactory::GetInstance(),
229        BuildAutoIssuingFakeProfileOAuth2TokenService));
230    profile_ = profile_manager_.CreateTestingProfile(
231        kTestProfileName,
232        scoped_ptr<PrefServiceSyncable>(),
233        base::UTF8ToUTF16(kTestProfileName),
234        0,
235        std::string(),
236        testing_factories);
237    invalidation::ProfileInvalidationProviderFactory::GetInstance()->
238        SetTestingFactory(profile_, BuildFakeProfileInvalidationProvider);
239    history_thread_->Start();
240    history_backend_ = new HistoryBackendMock();
241    history_service_ = static_cast<HistoryServiceMock*>(
242        HistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse(
243            profile_, BuildHistoryService));
244    history_service_->set_task_runner(history_thread_->task_runner());
245    history_service_->set_backend(history_backend_);
246  }
247
248  virtual void TearDown() {
249    EXPECT_CALL((*history_service_), Shutdown())
250        .WillOnce(ShutdownHistoryService(history_thread_.release(),
251                                         history_service_));
252    profile_ = NULL;
253    profile_manager_.DeleteTestingProfile(kTestProfileName);
254    AbstractProfileSyncServiceTest::TearDown();
255  }
256
257  TypedUrlModelAssociator* StartSyncService(const base::Closure& callback) {
258    TypedUrlModelAssociator* model_associator = NULL;
259    if (!sync_service_) {
260      SigninManagerBase* signin = SigninManagerFactory::GetForProfile(profile_);
261      signin->SetAuthenticatedUsername("test");
262      sync_service_ = TestProfileSyncService::BuildAutoStartAsyncInit(profile_,
263                                                                      callback);
264      ProfileSyncComponentsFactoryMock* components =
265          sync_service_->components_factory_mock();
266      TypedUrlDataTypeController* data_type_controller =
267          new TypedUrlDataTypeController(components, profile_, sync_service_);
268
269      EXPECT_CALL(*components, CreateTypedUrlSyncComponents(_, _, _)).
270          WillOnce(MakeTypedUrlSyncComponents(profile_,
271                                              sync_service_,
272                                              history_backend_.get(),
273                                              data_type_controller,
274                                              &error_handler_,
275                                              &model_associator));
276      EXPECT_CALL(*components, CreateDataTypeManager(_, _, _, _, _)).
277          WillOnce(ReturnNewDataTypeManager());
278
279      ProfileOAuth2TokenService* oauth2_token_service =
280          ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
281      oauth2_token_service->UpdateCredentials("test", "oauth2_login_token");
282
283      sync_service_->RegisterDataTypeController(data_type_controller);
284
285      sync_service_->Initialize();
286      base::MessageLoop::current()->Run();
287    }
288    return model_associator;
289  }
290
291  void GetTypedUrlsFromSyncDB(history::URLRows* urls) {
292    urls->clear();
293    syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
294    syncer::ReadNode typed_url_root(&trans);
295    if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
296        syncer::BaseNode::INIT_OK)
297      return;
298
299    int64 child_id = typed_url_root.GetFirstChildId();
300    while (child_id != syncer::kInvalidId) {
301      syncer::ReadNode child_node(&trans);
302      if (child_node.InitByIdLookup(child_id) != syncer::BaseNode::INIT_OK)
303        return;
304
305      const sync_pb::TypedUrlSpecifics& typed_url(
306          child_node.GetTypedUrlSpecifics());
307      history::URLRow new_url(GURL(typed_url.url()));
308
309      new_url.set_title(base::UTF8ToUTF16(typed_url.title()));
310      DCHECK(typed_url.visits_size());
311      DCHECK_EQ(typed_url.visits_size(), typed_url.visit_transitions_size());
312      new_url.set_last_visit(base::Time::FromInternalValue(
313          typed_url.visits(typed_url.visits_size() - 1)));
314      new_url.set_hidden(typed_url.hidden());
315
316      urls->push_back(new_url);
317      child_id = child_node.GetSuccessorId();
318    }
319  }
320
321  void SetIdleChangeProcessorExpectations() {
322    EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).Times(0);
323    EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).Times(0);
324    EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).Times(0);
325    EXPECT_CALL((*history_backend_.get()), DeleteURL(_)).Times(0);
326  }
327
328  static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
329    // Only verify the fields we explicitly sync (i.e. don't verify typed_count
330    // or visit_count because we rely on the history DB to manage those values
331    // and they are left unchanged by HistoryBackendMock).
332    return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
333           (lhs.title().compare(rhs.title()) == 0) &&
334           (lhs.last_visit() == rhs.last_visit()) &&
335           (lhs.hidden() == rhs.hidden());
336  }
337
338  static history::URLRow MakeTypedUrlEntry(const char* url,
339                                           const char* title,
340                                           int typed_count,
341                                           int64 last_visit,
342                                           bool hidden,
343                                           history::VisitVector* visits) {
344    // Give each URL a unique ID, to mimic the behavior of the real database.
345    static int unique_url_id = 0;
346    GURL gurl(url);
347    URLRow history_url(gurl, ++unique_url_id);
348    history_url.set_title(base::UTF8ToUTF16(title));
349    history_url.set_typed_count(typed_count);
350    history_url.set_last_visit(
351        base::Time::FromInternalValue(last_visit));
352    history_url.set_hidden(hidden);
353    visits->push_back(history::VisitRow(
354        history_url.id(), history_url.last_visit(), 0,
355        ui::PAGE_TRANSITION_TYPED, 0));
356    history_url.set_visit_count(visits->size());
357    return history_url;
358  }
359
360  scoped_ptr<Thread> history_thread_;
361  TestingProfileManager profile_manager_;
362  TestingProfile* profile_;
363  scoped_refptr<HistoryBackendMock> history_backend_;
364  HistoryServiceMock* history_service_;
365  sync_driver::DataTypeErrorHandlerMock error_handler_;
366};
367
368void AddTypedUrlEntries(ProfileSyncServiceTypedUrlTest* test,
369                        const history::URLRows& entries) {
370  test->CreateRoot(syncer::TYPED_URLS);
371  for (size_t i = 0; i < entries.size(); ++i) {
372    history::VisitVector visits;
373    visits.push_back(history::VisitRow(
374        entries[i].id(), entries[i].last_visit(), 0,
375        ui::PageTransitionFromInt(0), 0));
376    test->AddTypedUrlSyncNode(entries[i], visits);
377  }
378}
379
380} // namespace
381
382TEST_F(ProfileSyncServiceTypedUrlTest, EmptyNativeEmptySync) {
383  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
384      WillOnce(Return(true));
385  SetIdleChangeProcessorExpectations();
386  CreateRootHelper create_root(this, syncer::TYPED_URLS);
387  TypedUrlModelAssociator* associator =
388      StartSyncService(create_root.callback());
389  history::URLRows sync_entries;
390  GetTypedUrlsFromSyncDB(&sync_entries);
391  EXPECT_EQ(0U, sync_entries.size());
392  ASSERT_EQ(0, associator->GetErrorPercentage());
393}
394
395TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeEmptySync) {
396  history::URLRows entries;
397  history::VisitVector visits;
398  entries.push_back(MakeTypedUrlEntry("http://foo.com", "bar",
399                                      2, 15, false, &visits));
400
401  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
402      WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
403  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
404      WillRepeatedly(DoAll(SetArgumentPointee<2>(visits), Return(true)));
405  SetIdleChangeProcessorExpectations();
406  CreateRootHelper create_root(this, syncer::TYPED_URLS);
407  TypedUrlModelAssociator* associator =
408      StartSyncService(create_root.callback());
409  history::URLRows sync_entries;
410  GetTypedUrlsFromSyncDB(&sync_entries);
411  ASSERT_EQ(1U, sync_entries.size());
412  EXPECT_TRUE(URLsEqual(entries[0], sync_entries[0]));
413  ASSERT_EQ(0, associator->GetErrorPercentage());
414}
415
416TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeErrorReadingVisits) {
417  history::URLRows entries;
418  history::VisitVector visits;
419  history::URLRow native_entry1(MakeTypedUrlEntry("http://foo.com", "bar",
420                                                  2, 15, false, &visits));
421  history::URLRow native_entry2(MakeTypedUrlEntry("http://foo2.com", "bar",
422                                                  3, 15, false, &visits));
423  entries.push_back(native_entry1);
424  entries.push_back(native_entry2);
425  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
426      WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
427  // Return an error from GetMostRecentVisitsForURL() for the second URL.
428  EXPECT_CALL((*history_backend_.get()),
429              GetMostRecentVisitsForURL(native_entry1.id(), _, _)).
430                  WillRepeatedly(Return(true));
431  EXPECT_CALL((*history_backend_.get()),
432              GetMostRecentVisitsForURL(native_entry2.id(), _, _)).
433                  WillRepeatedly(Return(false));
434  SetIdleChangeProcessorExpectations();
435  CreateRootHelper create_root(this, syncer::TYPED_URLS);
436  StartSyncService(create_root.callback());
437  history::URLRows sync_entries;
438  GetTypedUrlsFromSyncDB(&sync_entries);
439  ASSERT_EQ(1U, sync_entries.size());
440  EXPECT_TRUE(URLsEqual(native_entry1, sync_entries[0]));
441}
442
443TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeWithBlankEmptySync) {
444  std::vector<history::URLRow> entries;
445  history::VisitVector visits;
446  // Add an empty URL.
447  entries.push_back(MakeTypedUrlEntry("", "bar",
448                                      2, 15, false, &visits));
449  entries.push_back(MakeTypedUrlEntry("http://foo.com", "bar",
450                                      2, 15, false, &visits));
451  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
452      WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
453  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
454      WillRepeatedly(DoAll(SetArgumentPointee<2>(visits), Return(true)));
455  SetIdleChangeProcessorExpectations();
456  CreateRootHelper create_root(this, syncer::TYPED_URLS);
457  StartSyncService(create_root.callback());
458  std::vector<history::URLRow> sync_entries;
459  GetTypedUrlsFromSyncDB(&sync_entries);
460  // The empty URL should be ignored.
461  ASSERT_EQ(1U, sync_entries.size());
462  EXPECT_TRUE(URLsEqual(entries[1], sync_entries[0]));
463}
464
465TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncNoMerge) {
466  history::VisitVector native_visits;
467  history::VisitVector sync_visits;
468  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
469                                                 2, 15, false, &native_visits));
470  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
471                                               3, 16, false, &sync_visits));
472
473  history::URLRows native_entries;
474  native_entries.push_back(native_entry);
475  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
476      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
477  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
478      WillRepeatedly(DoAll(SetArgumentPointee<2>(native_visits), Return(true)));
479  EXPECT_CALL((*history_backend_.get()),
480      AddVisits(_, _, history::SOURCE_SYNCED)).WillRepeatedly(Return(true));
481
482  history::URLRows sync_entries;
483  sync_entries.push_back(sync_entry);
484
485  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
486      WillRepeatedly(Return(true));
487  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
488
489  std::map<std::string, history::URLRow> expected;
490  expected[native_entry.url().spec()] = native_entry;
491  expected[sync_entry.url().spec()] = sync_entry;
492
493  history::URLRows new_sync_entries;
494  GetTypedUrlsFromSyncDB(&new_sync_entries);
495
496  EXPECT_TRUE(new_sync_entries.size() == expected.size());
497  for (history::URLRows::iterator entry = new_sync_entries.begin();
498       entry != new_sync_entries.end(); ++entry) {
499    EXPECT_TRUE(URLsEqual(expected[entry->url().spec()], *entry));
500  }
501}
502
503TEST_F(ProfileSyncServiceTypedUrlTest, EmptyNativeExpiredSync) {
504  history::VisitVector sync_visits;
505  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
506                                               3, EXPIRED_VISIT, false,
507                                               &sync_visits));
508  history::URLRows sync_entries;
509  sync_entries.push_back(sync_entry);
510
511  // Since all our URLs are expired, no backend calls to add new URLs will be
512  // made.
513  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
514      WillOnce(Return(true));
515  SetIdleChangeProcessorExpectations();
516
517  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
518}
519
520TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncMerge) {
521  history::VisitVector native_visits;
522  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
523                                                 2, 15, false, &native_visits));
524  history::VisitVector sync_visits;
525  history::URLRow sync_entry(MakeTypedUrlEntry("http://native.com", "name",
526                                               1, 17, false, &sync_visits));
527  history::VisitVector merged_visits;
528  merged_visits.push_back(history::VisitRow(
529      sync_entry.id(), base::Time::FromInternalValue(15), 0,
530      ui::PageTransitionFromInt(0), 0));
531
532  history::URLRow merged_entry(MakeTypedUrlEntry("http://native.com", "name",
533                                                 2, 17, false, &merged_visits));
534
535  history::URLRows native_entries;
536  native_entries.push_back(native_entry);
537  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
538      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
539  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
540      WillRepeatedly(DoAll(SetArgumentPointee<2>(native_visits), Return(true)));
541  EXPECT_CALL((*history_backend_.get()),
542      AddVisits(_, _, history::SOURCE_SYNCED)). WillRepeatedly(Return(true));
543
544  history::URLRows sync_entries;
545  sync_entries.push_back(sync_entry);
546
547  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
548      WillRepeatedly(Return(true));
549  EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).
550      WillRepeatedly(Return());
551  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
552
553  history::URLRows new_sync_entries;
554  GetTypedUrlsFromSyncDB(&new_sync_entries);
555  ASSERT_EQ(1U, new_sync_entries.size());
556  EXPECT_TRUE(URLsEqual(merged_entry, new_sync_entries[0]));
557}
558
559TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeWithErrorHasSyncMerge) {
560  history::VisitVector native_visits;
561  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "native",
562                                                 2, 15, false, &native_visits));
563  history::VisitVector sync_visits;
564  history::URLRow sync_entry(MakeTypedUrlEntry("http://native.com", "sync",
565                                               1, 17, false, &sync_visits));
566
567  history::URLRows native_entries;
568  native_entries.push_back(native_entry);
569  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
570      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
571  // Return an error getting the visits for the native URL.
572  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
573      WillRepeatedly(Return(false));
574  EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).
575      WillRepeatedly(DoAll(SetArgumentPointee<1>(native_entry), Return(true)));
576  EXPECT_CALL((*history_backend_.get()),
577      AddVisits(_, _, history::SOURCE_SYNCED)). WillRepeatedly(Return(true));
578
579  history::URLRows sync_entries;
580  sync_entries.push_back(sync_entry);
581
582  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
583      WillRepeatedly(Return(true));
584  EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).
585      WillRepeatedly(Return());
586  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
587
588  history::URLRows new_sync_entries;
589  GetTypedUrlsFromSyncDB(&new_sync_entries);
590  ASSERT_EQ(1U, new_sync_entries.size());
591  EXPECT_TRUE(URLsEqual(sync_entry, new_sync_entries[0]));
592}
593
594TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeAdd) {
595  history::VisitVector added_visits;
596  history::URLRow added_entry(MakeTypedUrlEntry("http://added.com", "entry",
597                                                2, 15, false, &added_visits));
598
599  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
600      WillOnce(Return(true));
601  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
602      WillOnce(DoAll(SetArgumentPointee<2>(added_visits), Return(true)));
603
604  SetIdleChangeProcessorExpectations();
605  CreateRootHelper create_root(this, syncer::TYPED_URLS);
606  StartSyncService(create_root.callback());
607
608  history::URLsModifiedDetails details;
609  details.changed_urls.push_back(added_entry);
610  scoped_refptr<ThreadNotifier> notifier(
611      new ThreadNotifier(history_thread_.get()));
612  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
613                   content::Source<Profile>(profile_),
614                   content::Details<history::URLsModifiedDetails>(&details));
615
616  history::URLRows new_sync_entries;
617  GetTypedUrlsFromSyncDB(&new_sync_entries);
618  ASSERT_EQ(1U, new_sync_entries.size());
619  EXPECT_TRUE(URLsEqual(added_entry, new_sync_entries[0]));
620}
621
622TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeAddWithBlank) {
623  history::VisitVector added_visits;
624  history::URLRow empty_entry(MakeTypedUrlEntry("", "entry",
625                                                2, 15, false, &added_visits));
626  history::URLRow added_entry(MakeTypedUrlEntry("http://added.com", "entry",
627                                                2, 15, false, &added_visits));
628
629  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
630      WillOnce(Return(true));
631  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
632      WillRepeatedly(DoAll(SetArgumentPointee<2>(added_visits), Return(true)));
633
634  SetIdleChangeProcessorExpectations();
635  CreateRootHelper create_root(this, syncer::TYPED_URLS);
636  StartSyncService(create_root.callback());
637
638  history::URLsModifiedDetails details;
639  details.changed_urls.push_back(empty_entry);
640  details.changed_urls.push_back(added_entry);
641  scoped_refptr<ThreadNotifier> notifier(
642      new ThreadNotifier(history_thread_.get()));
643  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
644                   content::Source<Profile>(profile_),
645                   content::Details<history::URLsModifiedDetails>(&details));
646
647  std::vector<history::URLRow> new_sync_entries;
648  GetTypedUrlsFromSyncDB(&new_sync_entries);
649  ASSERT_EQ(1U, new_sync_entries.size());
650  EXPECT_TRUE(URLsEqual(added_entry, new_sync_entries[0]));
651}
652
653TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeUpdate) {
654  history::VisitVector original_visits;
655  history::URLRow original_entry(MakeTypedUrlEntry("http://mine.com", "entry",
656                                                   2, 15, false,
657                                                   &original_visits));
658  history::URLRows original_entries;
659  original_entries.push_back(original_entry);
660
661  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
662      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
663  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
664      WillOnce(DoAll(SetArgumentPointee<2>(original_visits),
665                     Return(true)));
666  CreateRootHelper create_root(this, syncer::TYPED_URLS);
667  StartSyncService(create_root.callback());
668
669  history::VisitVector updated_visits;
670  history::URLRow updated_entry(MakeTypedUrlEntry("http://mine.com", "entry",
671                                                  7, 17, false,
672                                                  &updated_visits));
673  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
674      WillOnce(DoAll(SetArgumentPointee<2>(updated_visits),
675                     Return(true)));
676
677  history::URLsModifiedDetails details;
678  details.changed_urls.push_back(updated_entry);
679  scoped_refptr<ThreadNotifier> notifier(
680      new ThreadNotifier(history_thread_.get()));
681  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
682                   content::Source<Profile>(profile_),
683                   content::Details<history::URLsModifiedDetails>(&details));
684
685  history::URLRows new_sync_entries;
686  GetTypedUrlsFromSyncDB(&new_sync_entries);
687  ASSERT_EQ(1U, new_sync_entries.size());
688  EXPECT_TRUE(URLsEqual(updated_entry, new_sync_entries[0]));
689}
690
691TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeAddFromVisit) {
692  history::VisitVector added_visits;
693  history::URLRow added_entry(MakeTypedUrlEntry("http://added.com", "entry",
694                                                2, 15, false, &added_visits));
695
696  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
697      WillOnce(Return(true));
698  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
699      WillOnce(DoAll(SetArgumentPointee<2>(added_visits), Return(true)));
700
701  SetIdleChangeProcessorExpectations();
702  CreateRootHelper create_root(this, syncer::TYPED_URLS);
703  StartSyncService(create_root.callback());
704
705  history::URLVisitedDetails details;
706  details.row = added_entry;
707  details.transition = ui::PAGE_TRANSITION_TYPED;
708  scoped_refptr<ThreadNotifier> notifier(
709      new ThreadNotifier(history_thread_.get()));
710  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
711                   content::Source<Profile>(profile_),
712                   content::Details<history::URLVisitedDetails>(&details));
713
714  history::URLRows new_sync_entries;
715  GetTypedUrlsFromSyncDB(&new_sync_entries);
716  ASSERT_EQ(1U, new_sync_entries.size());
717  EXPECT_TRUE(URLsEqual(added_entry, new_sync_entries[0]));
718}
719
720TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeUpdateFromVisit) {
721  history::VisitVector original_visits;
722  history::URLRow original_entry(MakeTypedUrlEntry("http://mine.com", "entry",
723                                                   2, 15, false,
724                                                   &original_visits));
725  history::URLRows original_entries;
726  original_entries.push_back(original_entry);
727
728  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
729      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
730  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
731      WillOnce(DoAll(SetArgumentPointee<2>(original_visits),
732                           Return(true)));
733  CreateRootHelper create_root(this, syncer::TYPED_URLS);
734  StartSyncService(create_root.callback());
735
736  history::VisitVector updated_visits;
737  history::URLRow updated_entry(MakeTypedUrlEntry("http://mine.com", "entry",
738                                                  7, 17, false,
739                                                  &updated_visits));
740  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
741      WillOnce(DoAll(SetArgumentPointee<2>(updated_visits),
742                           Return(true)));
743
744  history::URLVisitedDetails details;
745  details.row = updated_entry;
746  details.transition = ui::PAGE_TRANSITION_TYPED;
747  scoped_refptr<ThreadNotifier> notifier(
748      new ThreadNotifier(history_thread_.get()));
749  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
750                   content::Source<Profile>(profile_),
751                   content::Details<history::URLVisitedDetails>(&details));
752
753  history::URLRows new_sync_entries;
754  GetTypedUrlsFromSyncDB(&new_sync_entries);
755  ASSERT_EQ(1U, new_sync_entries.size());
756  EXPECT_TRUE(URLsEqual(updated_entry, new_sync_entries[0]));
757}
758
759TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserIgnoreChangeUpdateFromVisit) {
760  history::VisitVector original_visits;
761  history::URLRow original_entry(MakeTypedUrlEntry("http://mine.com", "entry",
762                                                   2, 15, false,
763                                                   &original_visits));
764  history::URLRows original_entries;
765  original_entries.push_back(original_entry);
766
767  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
768      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
769  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
770      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits),
771                           Return(true)));
772  CreateRootHelper create_root(this, syncer::TYPED_URLS);
773  StartSyncService(create_root.callback());
774  history::URLRows new_sync_entries;
775  GetTypedUrlsFromSyncDB(&new_sync_entries);
776  ASSERT_EQ(1U, new_sync_entries.size());
777  EXPECT_TRUE(URLsEqual(original_entry, new_sync_entries[0]));
778
779  history::VisitVector updated_visits;
780  history::URLRow updated_entry(MakeTypedUrlEntry("http://mine.com", "entry",
781                                                  7, 15, false,
782                                                  &updated_visits));
783  history::URLVisitedDetails details;
784  details.row = updated_entry;
785
786  // Should ignore this change because it's not TYPED.
787  details.transition = ui::PAGE_TRANSITION_RELOAD;
788  scoped_refptr<ThreadNotifier> notifier(
789      new ThreadNotifier(history_thread_.get()));
790  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
791                   content::Source<Profile>(profile_),
792                   content::Details<history::URLVisitedDetails>(&details));
793
794  GetTypedUrlsFromSyncDB(&new_sync_entries);
795
796  // Should be no changes to the sync DB from this notification.
797  ASSERT_EQ(1U, new_sync_entries.size());
798  EXPECT_TRUE(URLsEqual(original_entry, new_sync_entries[0]));
799
800  // Now, try updating it with a large number of visits not divisible by 10
801  // (should ignore this visit).
802  history::URLRow twelve_visits(MakeTypedUrlEntry("http://mine.com", "entry",
803                                                  12, 15, false,
804                                                  &updated_visits));
805  details.row = twelve_visits;
806  details.transition = ui::PAGE_TRANSITION_TYPED;
807  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
808                   content::Source<Profile>(profile_),
809                   content::Details<history::URLVisitedDetails>(&details));
810  GetTypedUrlsFromSyncDB(&new_sync_entries);
811  // Should be no changes to the sync DB from this notification.
812  ASSERT_EQ(1U, new_sync_entries.size());
813  EXPECT_TRUE(URLsEqual(original_entry, new_sync_entries[0]));
814
815  // Now, try updating it with a large number of visits that is divisible by 10
816  // (should *not* be ignored).
817  history::URLRow twenty_visits(MakeTypedUrlEntry("http://mine.com", "entry",
818                                                  20, 15, false,
819                                                  &updated_visits));
820  details.row = twenty_visits;
821  details.transition = ui::PAGE_TRANSITION_TYPED;
822  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
823                   content::Source<Profile>(profile_),
824                   content::Details<history::URLVisitedDetails>(&details));
825  GetTypedUrlsFromSyncDB(&new_sync_entries);
826  ASSERT_EQ(1U, new_sync_entries.size());
827  EXPECT_TRUE(URLsEqual(twenty_visits, new_sync_entries[0]));
828}
829
830TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeRemove) {
831  history::VisitVector original_visits1;
832  history::URLRow original_entry1(MakeTypedUrlEntry("http://mine.com", "entry",
833                                                    2, 15, false,
834                                                    &original_visits1));
835  history::VisitVector original_visits2;
836  history::URLRow original_entry2(MakeTypedUrlEntry("http://mine2.com",
837                                                    "entry2",
838                                                    3, 15, false,
839                                                    &original_visits2));
840  history::URLRows original_entries;
841  original_entries.push_back(original_entry1);
842  original_entries.push_back(original_entry2);
843
844  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
845      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
846  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
847      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits1),
848                           Return(true)));
849  CreateRootHelper create_root(this, syncer::TYPED_URLS);
850  StartSyncService(create_root.callback());
851
852  history::URLsDeletedDetails changes;
853  changes.all_history = false;
854  changes.rows.push_back(history::URLRow(GURL("http://mine.com")));
855  scoped_refptr<ThreadNotifier> notifier(
856      new ThreadNotifier(history_thread_.get()));
857  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
858                   content::Source<Profile>(profile_),
859                   content::Details<history::URLsDeletedDetails>(&changes));
860
861  history::URLRows new_sync_entries;
862  GetTypedUrlsFromSyncDB(&new_sync_entries);
863  ASSERT_EQ(1U, new_sync_entries.size());
864  EXPECT_TRUE(URLsEqual(original_entry2, new_sync_entries[0]));
865}
866
867TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeRemoveExpired) {
868  history::VisitVector original_visits1;
869  history::URLRow original_entry1(MakeTypedUrlEntry("http://mine.com", "entry",
870                                                    2, 15, false,
871                                                    &original_visits1));
872  history::VisitVector original_visits2;
873  history::URLRow original_entry2(MakeTypedUrlEntry("http://mine2.com",
874                                                    "entry2",
875                                                    3, 15, false,
876                                                    &original_visits2));
877  history::URLRows original_entries;
878  original_entries.push_back(original_entry1);
879  original_entries.push_back(original_entry2);
880
881  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
882      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
883  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
884      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits1),
885                           Return(true)));
886  CreateRootHelper create_root(this, syncer::TYPED_URLS);
887  StartSyncService(create_root.callback());
888
889  history::URLsDeletedDetails changes;
890  changes.all_history = false;
891  // Setting expired=true should cause the sync code to ignore this deletion.
892  changes.expired = true;
893  changes.rows.push_back(history::URLRow(GURL("http://mine.com")));
894  scoped_refptr<ThreadNotifier> notifier(
895      new ThreadNotifier(history_thread_.get()));
896  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
897                   content::Source<Profile>(profile_),
898                   content::Details<history::URLsDeletedDetails>(&changes));
899
900  history::URLRows new_sync_entries;
901  GetTypedUrlsFromSyncDB(&new_sync_entries);
902  // Both URLs should still be there.
903  ASSERT_EQ(2U, new_sync_entries.size());
904}
905
906TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeRemoveAll) {
907  history::VisitVector original_visits1;
908  history::URLRow original_entry1(MakeTypedUrlEntry("http://mine.com", "entry",
909                                                    2, 15, false,
910                                                    &original_visits1));
911  history::VisitVector original_visits2;
912  history::URLRow original_entry2(MakeTypedUrlEntry("http://mine2.com",
913                                                    "entry2",
914                                                    3, 15, false,
915                                                    &original_visits2));
916  history::URLRows original_entries;
917  original_entries.push_back(original_entry1);
918  original_entries.push_back(original_entry2);
919
920  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
921      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
922  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
923      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits1),
924                           Return(true)));
925  CreateRootHelper create_root(this, syncer::TYPED_URLS);
926  StartSyncService(create_root.callback());
927
928  history::URLRows new_sync_entries;
929  GetTypedUrlsFromSyncDB(&new_sync_entries);
930  ASSERT_EQ(2U, new_sync_entries.size());
931
932  history::URLsDeletedDetails changes;
933  changes.all_history = true;
934  scoped_refptr<ThreadNotifier> notifier(
935      new ThreadNotifier(history_thread_.get()));
936  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
937                   content::Source<Profile>(profile_),
938                   content::Details<history::URLsDeletedDetails>(&changes));
939
940  GetTypedUrlsFromSyncDB(&new_sync_entries);
941  ASSERT_EQ(0U, new_sync_entries.size());
942}
943
944TEST_F(ProfileSyncServiceTypedUrlTest, FailWriteToHistoryBackend) {
945  history::VisitVector native_visits;
946  history::VisitVector sync_visits;
947  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
948                                                 2, 15, false, &native_visits));
949  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
950                                               3, 16, false, &sync_visits));
951
952  history::URLRows native_entries;
953  native_entries.push_back(native_entry);
954  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
955      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
956  EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).
957      WillOnce(DoAll(SetArgumentPointee<1>(native_entry), Return(true)));
958  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
959      WillRepeatedly(DoAll(SetArgumentPointee<2>(native_visits), Return(true)));
960  EXPECT_CALL((*history_backend_.get()),
961      AddVisits(_, _, history::SOURCE_SYNCED)).WillRepeatedly(Return(false));
962
963  history::URLRows sync_entries;
964  sync_entries.push_back(sync_entry);
965
966  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
967      WillRepeatedly(Return(false));
968  TypedUrlModelAssociator* associator =
969      StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
970  // Errors writing to the DB should be recorded, but should not cause an
971  // unrecoverable error.
972  ASSERT_FALSE(
973      sync_service_->data_type_status_table().GetFailedTypes().Has(
974          syncer::TYPED_URLS));
975  // Some calls should have succeeded, so the error percentage should be
976  // somewhere > 0 and < 100.
977  ASSERT_NE(0, associator->GetErrorPercentage());
978  ASSERT_NE(100, associator->GetErrorPercentage());
979}
980
981TEST_F(ProfileSyncServiceTypedUrlTest, FailToGetTypedURLs) {
982  history::VisitVector native_visits;
983  history::VisitVector sync_visits;
984  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
985                                                 2, 15, false, &native_visits));
986  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
987                                               3, 16, false, &sync_visits));
988
989  history::URLRows native_entries;
990  native_entries.push_back(native_entry);
991  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
992      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(false)));
993
994  history::URLRows sync_entries;
995  sync_entries.push_back(sync_entry);
996
997  EXPECT_CALL(error_handler_, CreateAndUploadError(_, _, _)).
998              WillOnce(Return(syncer::SyncError(
999                                  FROM_HERE,
1000                                  syncer::SyncError::DATATYPE_ERROR,
1001                                  "Unit test",
1002                                  syncer::TYPED_URLS)));
1003  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
1004  // Errors getting typed URLs will cause an unrecoverable error (since we can
1005  // do *nothing* in that case).
1006  ASSERT_TRUE(
1007      sync_service_->data_type_status_table().GetFailedTypes().Has(
1008          syncer::TYPED_URLS));
1009  ASSERT_EQ(
1010      1u, sync_service_->data_type_status_table().GetFailedTypes().Size());
1011  // Can't check GetErrorPercentage(), because generating an unrecoverable
1012  // error will free the model associator.
1013}
1014
1015TEST_F(ProfileSyncServiceTypedUrlTest, IgnoreLocalFileURL) {
1016  history::VisitVector original_visits;
1017  // Create http and file url.
1018  history::URLRow url_entry(MakeTypedUrlEntry("http://yey.com",
1019                                              "yey", 12, 15, false,
1020                                              &original_visits));
1021  history::URLRow file_entry(MakeTypedUrlEntry("file:///kitty.jpg",
1022                                               "kitteh", 12, 15, false,
1023                                               &original_visits));
1024
1025  history::URLRows original_entries;
1026  original_entries.push_back(url_entry);
1027  original_entries.push_back(file_entry);
1028
1029  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
1030      WillRepeatedly(DoAll(SetArgumentPointee<0>(original_entries),
1031                     Return(true)));
1032  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
1033      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits),
1034                     Return(true)));
1035  CreateRootHelper create_root(this, syncer::TYPED_URLS);
1036  StartSyncService(create_root.callback());
1037
1038  history::VisitVector updated_visits;
1039  // Create updates for the previous urls + a new file one.
1040  history::URLRow updated_url_entry(MakeTypedUrlEntry("http://yey.com",
1041                                                      "yey", 20, 15, false,
1042                                                      &updated_visits));
1043  history::URLRow updated_file_entry(MakeTypedUrlEntry("file:///cat.jpg",
1044                                                       "cat", 20, 15, false,
1045                                                       &updated_visits));
1046  history::URLRow new_file_entry(MakeTypedUrlEntry("file:///dog.jpg",
1047                                                   "dog", 20, 15, false,
1048                                                   &updated_visits));
1049  history::URLsModifiedDetails details;
1050  details.changed_urls.push_back(updated_url_entry);
1051  details.changed_urls.push_back(updated_file_entry);
1052  details.changed_urls.push_back(new_file_entry);
1053  scoped_refptr<ThreadNotifier> notifier(
1054      new ThreadNotifier(history_thread_.get()));
1055  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
1056                   content::Source<Profile>(profile_),
1057                   content::Details<history::URLsModifiedDetails>(&details));
1058
1059  history::URLRows new_sync_entries;
1060  GetTypedUrlsFromSyncDB(&new_sync_entries);
1061
1062  // We should ignore the local file urls (existing and updated),
1063  // and only be left with the updated http url.
1064  ASSERT_EQ(1U, new_sync_entries.size());
1065  EXPECT_TRUE(URLsEqual(updated_url_entry, new_sync_entries[0]));
1066}
1067
1068TEST_F(ProfileSyncServiceTypedUrlTest, IgnoreLocalhostURL) {
1069  history::VisitVector original_visits;
1070  // Create http and localhost url.
1071  history::URLRow url_entry(MakeTypedUrlEntry("http://yey.com",
1072                                              "yey", 12, 15, false,
1073                                              &original_visits));
1074  history::URLRow localhost_entry(MakeTypedUrlEntry("http://localhost",
1075                                              "localhost", 12, 15, false,
1076                                              &original_visits));
1077
1078  history::URLRows original_entries;
1079  original_entries.push_back(url_entry);
1080  original_entries.push_back(localhost_entry);
1081
1082  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
1083      WillRepeatedly(DoAll(SetArgumentPointee<0>(original_entries),
1084                     Return(true)));
1085  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
1086      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits),
1087                     Return(true)));
1088  CreateRootHelper create_root(this, syncer::TYPED_URLS);
1089  StartSyncService(create_root.callback());
1090
1091  history::VisitVector updated_visits;
1092  // Update the previous entries and add a new localhost.
1093  history::URLRow updated_url_entry(MakeTypedUrlEntry("http://yey.com",
1094                                                  "yey", 20, 15, false,
1095                                                  &updated_visits));
1096  history::URLRow updated_localhost_entry(MakeTypedUrlEntry(
1097                                                  "http://localhost:80",
1098                                                  "localhost", 20, 15, false,
1099                                                  &original_visits));
1100  history::URLRow localhost_ip_entry(MakeTypedUrlEntry("http://127.0.0.1",
1101                                                  "localhost", 12, 15, false,
1102                                                  &original_visits));
1103  history::URLsModifiedDetails details;
1104  details.changed_urls.push_back(updated_url_entry);
1105  details.changed_urls.push_back(updated_localhost_entry);
1106  details.changed_urls.push_back(localhost_ip_entry);
1107  scoped_refptr<ThreadNotifier> notifier(
1108      new ThreadNotifier(history_thread_.get()));
1109  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
1110                   content::Source<Profile>(profile_),
1111                   content::Details<history::URLsModifiedDetails>(&details));
1112
1113  history::URLRows new_sync_entries;
1114  GetTypedUrlsFromSyncDB(&new_sync_entries);
1115
1116  // We should ignore the localhost urls and left only with http url.
1117  ASSERT_EQ(1U, new_sync_entries.size());
1118  EXPECT_TRUE(URLsEqual(updated_url_entry, new_sync_entries[0]));
1119}
1120
1121TEST_F(ProfileSyncServiceTypedUrlTest, IgnoreModificationWithoutValidVisit) {
1122  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
1123      WillRepeatedly(Return(true));
1124  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
1125      WillRepeatedly(Return(true));
1126
1127  CreateRootHelper create_root(this, syncer::TYPED_URLS);
1128  StartSyncService(create_root.callback());
1129
1130  history::VisitVector updated_visits;
1131  history::URLRow updated_url_entry(MakeTypedUrlEntry("http://yey.com",
1132                                                  "yey", 20, 0, false,
1133                                                  &updated_visits));
1134  history::URLsModifiedDetails details;
1135  details.changed_urls.push_back(updated_url_entry);
1136  scoped_refptr<ThreadNotifier> notifier(
1137      new ThreadNotifier(history_thread_.get()));
1138  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
1139                   content::Source<Profile>(profile_),
1140                   content::Details<history::URLsModifiedDetails>(&details));
1141
1142  history::URLRows new_sync_entries;
1143  GetTypedUrlsFromSyncDB(&new_sync_entries);
1144
1145  // The change should be ignored.
1146  ASSERT_EQ(0U, new_sync_entries.size());
1147}
1148