root/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. IsExpiredVisitTime
  2. ShutdownBaseService
  3. BuildHistoryService
  4. ClearErrorStats
  5. RunOnDBThreadCallback
  6. ACTION_P2
  7. ACTION_P2
  8. ACTION_P6
  9. AddTypedUrlSyncNode
  10. SetUp
  11. TearDown
  12. StartSyncService
  13. GetTypedUrlsFromSyncDB
  14. SetIdleChangeProcessorExpectations
  15. URLsEqual
  16. MakeTypedUrlEntry
  17. AddTypedUrlEntries
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F
  23. TEST_F
  24. TEST_F
  25. TEST_F
  26. TEST_F
  27. TEST_F
  28. TEST_F
  29. TEST_F
  30. TEST_F
  31. TEST_F
  32. TEST_F
  33. TEST_F
  34. TEST_F
  35. TEST_F
  36. TEST_F
  37. TEST_F
  38. TEST_F

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>
#include <utility>
#include <vector>

#include "testing/gtest/include/gtest/gtest.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/history/history_backend.h"
#include "chrome/browser/history/history_db_task.h"
#include "chrome/browser/history/history_notifications.h"
#include "chrome/browser/history/history_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/invalidation/fake_invalidation_service.h"
#include "chrome/browser/invalidation/invalidation_service_factory.h"
#include "chrome/browser/prefs/pref_service_syncable.h"
#include "chrome/browser/signin/fake_profile_oauth2_token_service.h"
#include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
#include "chrome/browser/sync/glue/typed_url_change_processor.h"
#include "chrome/browser/sync/glue/typed_url_data_type_controller.h"
#include "chrome/browser/sync/glue/typed_url_model_associator.h"
#include "chrome/browser/sync/profile_sync_components_factory.h"
#include "chrome/browser/sync/profile_sync_components_factory_mock.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/profile_sync_test_util.h"
#include "chrome/browser/sync/test_profile_sync_service.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/keyed_service/content/refcounted_browser_context_keyed_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/sync_driver/data_type_error_handler_mock.h"
#include "content/public/browser/notification_service.h"
#include "google_apis/gaia/gaia_constants.h"
#include "sync/internal_api/public/read_node.h"
#include "sync/internal_api/public/read_transaction.h"
#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
#include "sync/protocol/typed_url_specifics.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "url/gurl.h"

using base::Thread;
using base::Time;
using browser_sync::TypedUrlChangeProcessor;
using browser_sync::TypedUrlDataTypeController;
using browser_sync::TypedUrlModelAssociator;
using history::HistoryBackend;
using history::URLID;
using history::URLRow;
using syncer::syncable::WriteTransaction;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
using testing::_;

namespace {

const char kTestProfileName[] = "test-profile";

// Visits with this timestamp are treated as expired.
static const int EXPIRED_VISIT = -1;

class HistoryBackendMock : public HistoryBackend {
 public:
  HistoryBackendMock() : HistoryBackend(base::FilePath(), NULL, NULL) {}
  virtual bool IsExpiredVisitTime(const base::Time& time) OVERRIDE {
    return time.ToInternalValue() == EXPIRED_VISIT;
  }
  MOCK_METHOD1(GetAllTypedURLs, bool(history::URLRows* entries));
  MOCK_METHOD3(GetMostRecentVisitsForURL, bool(history::URLID id,
                                               int max_visits,
                                               history::VisitVector* visits));
  MOCK_METHOD2(UpdateURL, bool(history::URLID id, const history::URLRow& url));
  MOCK_METHOD3(AddVisits, bool(const GURL& url,
                               const std::vector<history::VisitInfo>& visits,
                               history::VisitSource visit_source));
  MOCK_METHOD1(RemoveVisits, bool(const history::VisitVector& visits));
  MOCK_METHOD2(GetURL, bool(const GURL& url_id, history::URLRow* url_row));
  MOCK_METHOD2(SetPageTitle, void(const GURL& url,
                                  const base::string16& title));
  MOCK_METHOD1(DeleteURL, void(const GURL& url));

 private:
  virtual ~HistoryBackendMock() {}
};

class HistoryServiceMock : public HistoryService {
 public:
  explicit HistoryServiceMock(Profile* profile) : HistoryService(profile) {}
  MOCK_METHOD2(ScheduleDBTask, void(history::HistoryDBTask*,
                                    CancelableRequestConsumerBase*));
  MOCK_METHOD0(Shutdown, void());

  void ShutdownBaseService() {
    HistoryService::Shutdown();
  }

 private:
  virtual ~HistoryServiceMock() {}
};

KeyedService* BuildHistoryService(content::BrowserContext* profile) {
  return new HistoryServiceMock(static_cast<Profile*>(profile));
}

class TestTypedUrlModelAssociator : public TypedUrlModelAssociator {
 public:
  TestTypedUrlModelAssociator(
      ProfileSyncService* sync_service,
      history::HistoryBackend* history_backend,
      browser_sync::DataTypeErrorHandler* error_handler) :
      TypedUrlModelAssociator(sync_service, history_backend, error_handler) {}

 protected:
  // Don't clear error stats - that way we can verify their values in our
  // tests.
  virtual void ClearErrorStats() OVERRIDE {}
};

void RunOnDBThreadCallback(HistoryBackend* backend,
                           history::HistoryDBTask* task) {
  task->RunOnDBThread(backend, NULL);
}

ACTION_P2(RunTaskOnDBThread, thread, backend) {
  // ScheduleDBTask takes ownership of its task argument, so we
  // should, too.
  scoped_refptr<history::HistoryDBTask> task(arg0);
  thread->message_loop()->PostTask(
      FROM_HERE, base::Bind(&RunOnDBThreadCallback, base::Unretained(backend),
                            task));
}

ACTION_P2(ShutdownHistoryService, thread, service) {
  service->ShutdownBaseService();
  delete thread;
}

ACTION_P6(MakeTypedUrlSyncComponents,
              profile,
              service,
              hb,
              dtc,
              error_handler,
              model_associator) {
  *model_associator =
      new TestTypedUrlModelAssociator(service, hb, error_handler);
  TypedUrlChangeProcessor* change_processor =
      new TypedUrlChangeProcessor(profile, *model_associator, hb, dtc);
  return ProfileSyncComponentsFactory::SyncComponents(*model_associator,
                                                      change_processor);
}

class ProfileSyncServiceTypedUrlTest : public AbstractProfileSyncServiceTest {
 public:
  void AddTypedUrlSyncNode(const history::URLRow& url,
                           const history::VisitVector& visits) {
    syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::ReadNode typed_url_root(&trans);
    ASSERT_EQ(syncer::BaseNode::INIT_OK,
              typed_url_root.InitByTagLookup(browser_sync::kTypedUrlTag));

    syncer::WriteNode node(&trans);
    std::string tag = url.url().spec();
    syncer::WriteNode::InitUniqueByCreationResult result =
        node.InitUniqueByCreation(syncer::TYPED_URLS, typed_url_root, tag);
    ASSERT_EQ(syncer::WriteNode::INIT_SUCCESS, result);
    TypedUrlModelAssociator::WriteToSyncNode(url, visits, &node);
  }

 protected:
  ProfileSyncServiceTypedUrlTest()
      : profile_manager_(TestingBrowserProcess::GetGlobal()) {
    history_thread_.reset(new Thread("history"));
  }

  virtual void SetUp() {
    AbstractProfileSyncServiceTest::SetUp();
    ASSERT_TRUE(profile_manager_.SetUp());
    TestingProfile::TestingFactories testing_factories;
    testing_factories.push_back(std::make_pair(
        ProfileOAuth2TokenServiceFactory::GetInstance(),
        BuildAutoIssuingFakeProfileOAuth2TokenService));
    profile_ = profile_manager_.CreateTestingProfile(
        kTestProfileName,
        scoped_ptr<PrefServiceSyncable>(),
        base::UTF8ToUTF16(kTestProfileName),
        0,
        std::string(),
        testing_factories);
    invalidation::InvalidationServiceFactory::GetInstance()->SetTestingFactory(
        profile_, invalidation::FakeInvalidationService::Build);
    history_backend_ = new HistoryBackendMock();
    history_service_ = static_cast<HistoryServiceMock*>(
        HistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse(
            profile_, BuildHistoryService));
    EXPECT_CALL((*history_service_), ScheduleDBTask(_, _))
        .WillRepeatedly(RunTaskOnDBThread(history_thread_.get(),
                                          history_backend_.get()));
    history_thread_->Start();
  }

  virtual void TearDown() {
    EXPECT_CALL((*history_service_), Shutdown())
        .WillOnce(ShutdownHistoryService(history_thread_.release(),
                                         history_service_));
    profile_ = NULL;
    profile_manager_.DeleteTestingProfile(kTestProfileName);
    AbstractProfileSyncServiceTest::TearDown();
  }

  TypedUrlModelAssociator* StartSyncService(const base::Closure& callback) {
    TypedUrlModelAssociator* model_associator = NULL;
    if (!sync_service_) {
      SigninManagerBase* signin = SigninManagerFactory::GetForProfile(profile_);
      signin->SetAuthenticatedUsername("test");
      sync_service_ = TestProfileSyncService::BuildAutoStartAsyncInit(profile_,
                                                                      callback);
      ProfileSyncComponentsFactoryMock* components =
          sync_service_->components_factory_mock();
      TypedUrlDataTypeController* data_type_controller =
          new TypedUrlDataTypeController(components, profile_, sync_service_);

      EXPECT_CALL(*components, CreateTypedUrlSyncComponents(_, _, _)).
          WillOnce(MakeTypedUrlSyncComponents(profile_,
                                              sync_service_,
                                              history_backend_.get(),
                                              data_type_controller,
                                              &error_handler_,
                                              &model_associator));
      EXPECT_CALL(*components, CreateDataTypeManager(_, _, _, _, _, _)).
          WillOnce(ReturnNewDataTypeManager());

      ProfileOAuth2TokenService* oauth2_token_service =
          ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
      oauth2_token_service->UpdateCredentials("test", "oauth2_login_token");

      sync_service_->RegisterDataTypeController(data_type_controller);

      sync_service_->Initialize();
      base::MessageLoop::current()->Run();
    }
    return model_associator;
  }

  void GetTypedUrlsFromSyncDB(history::URLRows* urls) {
    urls->clear();
    syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
    syncer::ReadNode typed_url_root(&trans);
    if (typed_url_root.InitByTagLookup(browser_sync::kTypedUrlTag) !=
            syncer::BaseNode::INIT_OK)
      return;

    int64 child_id = typed_url_root.GetFirstChildId();
    while (child_id != syncer::kInvalidId) {
      syncer::ReadNode child_node(&trans);
      if (child_node.InitByIdLookup(child_id) != syncer::BaseNode::INIT_OK)
        return;

      const sync_pb::TypedUrlSpecifics& typed_url(
          child_node.GetTypedUrlSpecifics());
      history::URLRow new_url(GURL(typed_url.url()));

      new_url.set_title(base::UTF8ToUTF16(typed_url.title()));
      DCHECK(typed_url.visits_size());
      DCHECK_EQ(typed_url.visits_size(), typed_url.visit_transitions_size());
      new_url.set_last_visit(base::Time::FromInternalValue(
          typed_url.visits(typed_url.visits_size() - 1)));
      new_url.set_hidden(typed_url.hidden());

      urls->push_back(new_url);
      child_id = child_node.GetSuccessorId();
    }
  }

  void SetIdleChangeProcessorExpectations() {
    EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).Times(0);
    EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).Times(0);
    EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).Times(0);
    EXPECT_CALL((*history_backend_.get()), DeleteURL(_)).Times(0);
  }

  static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
    // Only verify the fields we explicitly sync (i.e. don't verify typed_count
    // or visit_count because we rely on the history DB to manage those values
    // and they are left unchanged by HistoryBackendMock).
    return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
           (lhs.title().compare(rhs.title()) == 0) &&
           (lhs.last_visit() == rhs.last_visit()) &&
           (lhs.hidden() == rhs.hidden());
  }

  static history::URLRow MakeTypedUrlEntry(const char* url,
                                           const char* title,
                                           int typed_count,
                                           int64 last_visit,
                                           bool hidden,
                                           history::VisitVector* visits) {
    // Give each URL a unique ID, to mimic the behavior of the real database.
    static int unique_url_id = 0;
    GURL gurl(url);
    URLRow history_url(gurl, ++unique_url_id);
    history_url.set_title(base::UTF8ToUTF16(title));
    history_url.set_typed_count(typed_count);
    history_url.set_last_visit(
        base::Time::FromInternalValue(last_visit));
    history_url.set_hidden(hidden);
    visits->push_back(history::VisitRow(
        history_url.id(), history_url.last_visit(), 0,
        content::PAGE_TRANSITION_TYPED, 0));
    history_url.set_visit_count(visits->size());
    return history_url;
  }

  scoped_ptr<Thread> history_thread_;
  TestingProfileManager profile_manager_;
  TestingProfile* profile_;
  scoped_refptr<HistoryBackendMock> history_backend_;
  HistoryServiceMock* history_service_;
  browser_sync::DataTypeErrorHandlerMock error_handler_;
};

void AddTypedUrlEntries(ProfileSyncServiceTypedUrlTest* test,
                        const history::URLRows& entries) {
  test->CreateRoot(syncer::TYPED_URLS);
  for (size_t i = 0; i < entries.size(); ++i) {
    history::VisitVector visits;
    visits.push_back(history::VisitRow(
        entries[i].id(), entries[i].last_visit(), 0,
        content::PageTransitionFromInt(0), 0));
    test->AddTypedUrlSyncNode(entries[i], visits);
  }
}

} // namespace

TEST_F(ProfileSyncServiceTypedUrlTest, EmptyNativeEmptySync) {
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(Return(true));
  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  TypedUrlModelAssociator* associator =
      StartSyncService(create_root.callback());
  history::URLRows sync_entries;
  GetTypedUrlsFromSyncDB(&sync_entries);
  EXPECT_EQ(0U, sync_entries.size());
  ASSERT_EQ(0, associator->GetErrorPercentage());
}

TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeEmptySync) {
  history::URLRows entries;
  history::VisitVector visits;
  entries.push_back(MakeTypedUrlEntry("http://foo.com", "bar",
                                      2, 15, false, &visits));

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(visits), Return(true)));
  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  TypedUrlModelAssociator* associator =
      StartSyncService(create_root.callback());
  history::URLRows sync_entries;
  GetTypedUrlsFromSyncDB(&sync_entries);
  ASSERT_EQ(1U, sync_entries.size());
  EXPECT_TRUE(URLsEqual(entries[0], sync_entries[0]));
  ASSERT_EQ(0, associator->GetErrorPercentage());
}

TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeErrorReadingVisits) {
  history::URLRows entries;
  history::VisitVector visits;
  history::URLRow native_entry1(MakeTypedUrlEntry("http://foo.com", "bar",
                                                  2, 15, false, &visits));
  history::URLRow native_entry2(MakeTypedUrlEntry("http://foo2.com", "bar",
                                                  3, 15, false, &visits));
  entries.push_back(native_entry1);
  entries.push_back(native_entry2);
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
  // Return an error from GetMostRecentVisitsForURL() for the second URL.
  EXPECT_CALL((*history_backend_.get()),
              GetMostRecentVisitsForURL(native_entry1.id(), _, _)).
                  WillRepeatedly(Return(true));
  EXPECT_CALL((*history_backend_.get()),
              GetMostRecentVisitsForURL(native_entry2.id(), _, _)).
                  WillRepeatedly(Return(false));
  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());
  history::URLRows sync_entries;
  GetTypedUrlsFromSyncDB(&sync_entries);
  ASSERT_EQ(1U, sync_entries.size());
  EXPECT_TRUE(URLsEqual(native_entry1, sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeWithBlankEmptySync) {
  std::vector<history::URLRow> entries;
  history::VisitVector visits;
  // Add an empty URL.
  entries.push_back(MakeTypedUrlEntry("", "bar",
                                      2, 15, false, &visits));
  entries.push_back(MakeTypedUrlEntry("http://foo.com", "bar",
                                      2, 15, false, &visits));
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(visits), Return(true)));
  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());
  std::vector<history::URLRow> sync_entries;
  GetTypedUrlsFromSyncDB(&sync_entries);
  // The empty URL should be ignored.
  ASSERT_EQ(1U, sync_entries.size());
  EXPECT_TRUE(URLsEqual(entries[1], sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncNoMerge) {
  history::VisitVector native_visits;
  history::VisitVector sync_visits;
  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
                                                 2, 15, false, &native_visits));
  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
                                               3, 16, false, &sync_visits));

  history::URLRows native_entries;
  native_entries.push_back(native_entry);
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(native_visits), Return(true)));
  EXPECT_CALL((*history_backend_.get()),
      AddVisits(_, _, history::SOURCE_SYNCED)).WillRepeatedly(Return(true));

  history::URLRows sync_entries;
  sync_entries.push_back(sync_entry);

  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
      WillRepeatedly(Return(true));
  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));

  std::map<std::string, history::URLRow> expected;
  expected[native_entry.url().spec()] = native_entry;
  expected[sync_entry.url().spec()] = sync_entry;

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);

  EXPECT_TRUE(new_sync_entries.size() == expected.size());
  for (history::URLRows::iterator entry = new_sync_entries.begin();
       entry != new_sync_entries.end(); ++entry) {
    EXPECT_TRUE(URLsEqual(expected[entry->url().spec()], *entry));
  }
}

TEST_F(ProfileSyncServiceTypedUrlTest, EmptyNativeExpiredSync) {
  history::VisitVector sync_visits;
  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
                                               3, EXPIRED_VISIT, false,
                                               &sync_visits));
  history::URLRows sync_entries;
  sync_entries.push_back(sync_entry);

  // Since all our URLs are expired, no backend calls to add new URLs will be
  // made.
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(Return(true));
  SetIdleChangeProcessorExpectations();

  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
}

TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncMerge) {
  history::VisitVector native_visits;
  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
                                                 2, 15, false, &native_visits));
  history::VisitVector sync_visits;
  history::URLRow sync_entry(MakeTypedUrlEntry("http://native.com", "name",
                                               1, 17, false, &sync_visits));
  history::VisitVector merged_visits;
  merged_visits.push_back(history::VisitRow(
      sync_entry.id(), base::Time::FromInternalValue(15), 0,
      content::PageTransitionFromInt(0), 0));

  history::URLRow merged_entry(MakeTypedUrlEntry("http://native.com", "name",
                                                 2, 17, false, &merged_visits));

  history::URLRows native_entries;
  native_entries.push_back(native_entry);
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(native_visits), Return(true)));
  EXPECT_CALL((*history_backend_.get()),
      AddVisits(_, _, history::SOURCE_SYNCED)). WillRepeatedly(Return(true));

  history::URLRows sync_entries;
  sync_entries.push_back(sync_entry);

  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
      WillRepeatedly(Return(true));
  EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).
      WillRepeatedly(Return());
  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(merged_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeWithErrorHasSyncMerge) {
  history::VisitVector native_visits;
  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "native",
                                                 2, 15, false, &native_visits));
  history::VisitVector sync_visits;
  history::URLRow sync_entry(MakeTypedUrlEntry("http://native.com", "sync",
                                               1, 17, false, &sync_visits));

  history::URLRows native_entries;
  native_entries.push_back(native_entry);
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
  // Return an error getting the visits for the native URL.
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(Return(false));
  EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<1>(native_entry), Return(true)));
  EXPECT_CALL((*history_backend_.get()),
      AddVisits(_, _, history::SOURCE_SYNCED)). WillRepeatedly(Return(true));

  history::URLRows sync_entries;
  sync_entries.push_back(sync_entry);

  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
      WillRepeatedly(Return(true));
  EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).
      WillRepeatedly(Return());
  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(sync_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeAdd) {
  history::VisitVector added_visits;
  history::URLRow added_entry(MakeTypedUrlEntry("http://added.com", "entry",
                                                2, 15, false, &added_visits));

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(Return(true));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillOnce(DoAll(SetArgumentPointee<2>(added_visits), Return(true)));

  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::URLsModifiedDetails details;
  details.changed_urls.push_back(added_entry);
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsModifiedDetails>(&details));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(added_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeAddWithBlank) {
  history::VisitVector added_visits;
  history::URLRow empty_entry(MakeTypedUrlEntry("", "entry",
                                                2, 15, false, &added_visits));
  history::URLRow added_entry(MakeTypedUrlEntry("http://added.com", "entry",
                                                2, 15, false, &added_visits));

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(Return(true));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(added_visits), Return(true)));

  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::URLsModifiedDetails details;
  details.changed_urls.push_back(empty_entry);
  details.changed_urls.push_back(added_entry);
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsModifiedDetails>(&details));

  std::vector<history::URLRow> new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(added_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeUpdate) {
  history::VisitVector original_visits;
  history::URLRow original_entry(MakeTypedUrlEntry("http://mine.com", "entry",
                                                   2, 15, false,
                                                   &original_visits));
  history::URLRows original_entries;
  original_entries.push_back(original_entry);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillOnce(DoAll(SetArgumentPointee<2>(original_visits),
                     Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::VisitVector updated_visits;
  history::URLRow updated_entry(MakeTypedUrlEntry("http://mine.com", "entry",
                                                  7, 17, false,
                                                  &updated_visits));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillOnce(DoAll(SetArgumentPointee<2>(updated_visits),
                     Return(true)));

  history::URLsModifiedDetails details;
  details.changed_urls.push_back(updated_entry);
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsModifiedDetails>(&details));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(updated_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeAddFromVisit) {
  history::VisitVector added_visits;
  history::URLRow added_entry(MakeTypedUrlEntry("http://added.com", "entry",
                                                2, 15, false, &added_visits));

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(Return(true));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillOnce(DoAll(SetArgumentPointee<2>(added_visits), Return(true)));

  SetIdleChangeProcessorExpectations();
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::URLVisitedDetails details;
  details.row = added_entry;
  details.transition = content::PAGE_TRANSITION_TYPED;
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLVisitedDetails>(&details));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(added_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeUpdateFromVisit) {
  history::VisitVector original_visits;
  history::URLRow original_entry(MakeTypedUrlEntry("http://mine.com", "entry",
                                                   2, 15, false,
                                                   &original_visits));
  history::URLRows original_entries;
  original_entries.push_back(original_entry);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillOnce(DoAll(SetArgumentPointee<2>(original_visits),
                           Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::VisitVector updated_visits;
  history::URLRow updated_entry(MakeTypedUrlEntry("http://mine.com", "entry",
                                                  7, 17, false,
                                                  &updated_visits));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillOnce(DoAll(SetArgumentPointee<2>(updated_visits),
                           Return(true)));

  history::URLVisitedDetails details;
  details.row = updated_entry;
  details.transition = content::PAGE_TRANSITION_TYPED;
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLVisitedDetails>(&details));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(updated_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserIgnoreChangeUpdateFromVisit) {
  history::VisitVector original_visits;
  history::URLRow original_entry(MakeTypedUrlEntry("http://mine.com", "entry",
                                                   2, 15, false,
                                                   &original_visits));
  history::URLRows original_entries;
  original_entries.push_back(original_entry);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits),
                           Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());
  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(original_entry, new_sync_entries[0]));

  history::VisitVector updated_visits;
  history::URLRow updated_entry(MakeTypedUrlEntry("http://mine.com", "entry",
                                                  7, 15, false,
                                                  &updated_visits));
  history::URLVisitedDetails details;
  details.row = updated_entry;

  // Should ignore this change because it's not TYPED.
  details.transition = content::PAGE_TRANSITION_RELOAD;
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLVisitedDetails>(&details));

  GetTypedUrlsFromSyncDB(&new_sync_entries);

  // Should be no changes to the sync DB from this notification.
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(original_entry, new_sync_entries[0]));

  // Now, try updating it with a large number of visits not divisible by 10
  // (should ignore this visit).
  history::URLRow twelve_visits(MakeTypedUrlEntry("http://mine.com", "entry",
                                                  12, 15, false,
                                                  &updated_visits));
  details.row = twelve_visits;
  details.transition = content::PAGE_TRANSITION_TYPED;
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLVisitedDetails>(&details));
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  // Should be no changes to the sync DB from this notification.
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(original_entry, new_sync_entries[0]));

  // Now, try updating it with a large number of visits that is divisible by 10
  // (should *not* be ignored).
  history::URLRow twenty_visits(MakeTypedUrlEntry("http://mine.com", "entry",
                                                  20, 15, false,
                                                  &updated_visits));
  details.row = twenty_visits;
  details.transition = content::PAGE_TRANSITION_TYPED;
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URL_VISITED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLVisitedDetails>(&details));
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(twenty_visits, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeRemove) {
  history::VisitVector original_visits1;
  history::URLRow original_entry1(MakeTypedUrlEntry("http://mine.com", "entry",
                                                    2, 15, false,
                                                    &original_visits1));
  history::VisitVector original_visits2;
  history::URLRow original_entry2(MakeTypedUrlEntry("http://mine2.com",
                                                    "entry2",
                                                    3, 15, false,
                                                    &original_visits2));
  history::URLRows original_entries;
  original_entries.push_back(original_entry1);
  original_entries.push_back(original_entry2);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits1),
                           Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::URLsDeletedDetails changes;
  changes.all_history = false;
  changes.rows.push_back(history::URLRow(GURL("http://mine.com")));
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsDeletedDetails>(&changes));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(original_entry2, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeRemoveArchive) {
  history::VisitVector original_visits1;
  history::URLRow original_entry1(MakeTypedUrlEntry("http://mine.com", "entry",
                                                    2, 15, false,
                                                    &original_visits1));
  history::VisitVector original_visits2;
  history::URLRow original_entry2(MakeTypedUrlEntry("http://mine2.com",
                                                    "entry2",
                                                    3, 15, false,
                                                    &original_visits2));
  history::URLRows original_entries;
  original_entries.push_back(original_entry1);
  original_entries.push_back(original_entry2);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits1),
                           Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::URLsDeletedDetails changes;
  changes.all_history = false;
  // Setting archived=true should cause the sync code to ignore this deletion.
  changes.archived = true;
  changes.rows.push_back(history::URLRow(GURL("http://mine.com")));
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsDeletedDetails>(&changes));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  // Both URLs should still be there.
  ASSERT_EQ(2U, new_sync_entries.size());
}

TEST_F(ProfileSyncServiceTypedUrlTest, ProcessUserChangeRemoveAll) {
  history::VisitVector original_visits1;
  history::URLRow original_entry1(MakeTypedUrlEntry("http://mine.com", "entry",
                                                    2, 15, false,
                                                    &original_visits1));
  history::VisitVector original_visits2;
  history::URLRow original_entry2(MakeTypedUrlEntry("http://mine2.com",
                                                    "entry2",
                                                    3, 15, false,
                                                    &original_visits2));
  history::URLRows original_entries;
  original_entries.push_back(original_entry1);
  original_entries.push_back(original_entry2);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits1),
                           Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(2U, new_sync_entries.size());

  history::URLsDeletedDetails changes;
  changes.all_history = true;
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_DELETED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsDeletedDetails>(&changes));

  GetTypedUrlsFromSyncDB(&new_sync_entries);
  ASSERT_EQ(0U, new_sync_entries.size());
}

TEST_F(ProfileSyncServiceTypedUrlTest, FailWriteToHistoryBackend) {
  history::VisitVector native_visits;
  history::VisitVector sync_visits;
  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
                                                 2, 15, false, &native_visits));
  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
                                               3, 16, false, &sync_visits));

  history::URLRows native_entries;
  native_entries.push_back(native_entry);
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).
      WillOnce(DoAll(SetArgumentPointee<1>(native_entry), Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(native_visits), Return(true)));
  EXPECT_CALL((*history_backend_.get()),
      AddVisits(_, _, history::SOURCE_SYNCED)).WillRepeatedly(Return(false));

  history::URLRows sync_entries;
  sync_entries.push_back(sync_entry);

  EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).
      WillRepeatedly(Return(false));
  TypedUrlModelAssociator* associator =
      StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
  // Errors writing to the DB should be recorded, but should not cause an
  // unrecoverable error.
  ASSERT_FALSE(
      sync_service_->failed_data_types_handler().GetFailedTypes().Has(
          syncer::TYPED_URLS));
  // Some calls should have succeeded, so the error percentage should be
  // somewhere > 0 and < 100.
  ASSERT_NE(0, associator->GetErrorPercentage());
  ASSERT_NE(100, associator->GetErrorPercentage());
}

TEST_F(ProfileSyncServiceTypedUrlTest, FailToGetTypedURLs) {
  history::VisitVector native_visits;
  history::VisitVector sync_visits;
  history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry",
                                                 2, 15, false, &native_visits));
  history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry",
                                               3, 16, false, &sync_visits));

  history::URLRows native_entries;
  native_entries.push_back(native_entry);
  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(false)));

  history::URLRows sync_entries;
  sync_entries.push_back(sync_entry);

  EXPECT_CALL(error_handler_, CreateAndUploadError(_, _, _)).
              WillOnce(Return(syncer::SyncError(
                                  FROM_HERE,
                                  syncer::SyncError::DATATYPE_ERROR,
                                  "Unit test",
                                  syncer::TYPED_URLS)));
  StartSyncService(base::Bind(&AddTypedUrlEntries, this, sync_entries));
  // Errors getting typed URLs will cause an unrecoverable error (since we can
  // do *nothing* in that case).
  ASSERT_TRUE(
      sync_service_->failed_data_types_handler().GetFailedTypes().Has(
          syncer::TYPED_URLS));
  ASSERT_EQ(
      1u, sync_service_->failed_data_types_handler().GetFailedTypes().Size());
  // Can't check GetErrorPercentage(), because generating an unrecoverable
  // error will free the model associator.
}

TEST_F(ProfileSyncServiceTypedUrlTest, IgnoreLocalFileURL) {
  history::VisitVector original_visits;
  // Create http and file url.
  history::URLRow url_entry(MakeTypedUrlEntry("http://yey.com",
                                              "yey", 12, 15, false,
                                              &original_visits));
  history::URLRow file_entry(MakeTypedUrlEntry("file:///kitty.jpg",
                                               "kitteh", 12, 15, false,
                                               &original_visits));

  history::URLRows original_entries;
  original_entries.push_back(url_entry);
  original_entries.push_back(file_entry);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillRepeatedly(DoAll(SetArgumentPointee<0>(original_entries),
                     Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits),
                     Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::VisitVector updated_visits;
  // Create updates for the previous urls + a new file one.
  history::URLRow updated_url_entry(MakeTypedUrlEntry("http://yey.com",
                                                      "yey", 20, 15, false,
                                                      &updated_visits));
  history::URLRow updated_file_entry(MakeTypedUrlEntry("file:///cat.jpg",
                                                       "cat", 20, 15, false,
                                                       &updated_visits));
  history::URLRow new_file_entry(MakeTypedUrlEntry("file:///dog.jpg",
                                                   "dog", 20, 15, false,
                                                   &updated_visits));
  history::URLsModifiedDetails details;
  details.changed_urls.push_back(updated_url_entry);
  details.changed_urls.push_back(updated_file_entry);
  details.changed_urls.push_back(new_file_entry);
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsModifiedDetails>(&details));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);

  // We should ignore the local file urls (existing and updated),
  // and only be left with the updated http url.
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(updated_url_entry, new_sync_entries[0]));
}

TEST_F(ProfileSyncServiceTypedUrlTest, IgnoreLocalhostURL) {
  history::VisitVector original_visits;
  // Create http and localhost url.
  history::URLRow url_entry(MakeTypedUrlEntry("http://yey.com",
                                              "yey", 12, 15, false,
                                              &original_visits));
  history::URLRow localhost_entry(MakeTypedUrlEntry("http://localhost",
                                              "localhost", 12, 15, false,
                                              &original_visits));

  history::URLRows original_entries;
  original_entries.push_back(url_entry);
  original_entries.push_back(localhost_entry);

  EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)).
      WillRepeatedly(DoAll(SetArgumentPointee<0>(original_entries),
                     Return(true)));
  EXPECT_CALL((*history_backend_.get()), GetMostRecentVisitsForURL(_, _, _)).
      WillRepeatedly(DoAll(SetArgumentPointee<2>(original_visits),
                     Return(true)));
  CreateRootHelper create_root(this, syncer::TYPED_URLS);
  StartSyncService(create_root.callback());

  history::VisitVector updated_visits;
  // Update the previous entries and add a new localhost.
  history::URLRow updated_url_entry(MakeTypedUrlEntry("http://yey.com",
                                                  "yey", 20, 15, false,
                                                  &updated_visits));
  history::URLRow updated_localhost_entry(MakeTypedUrlEntry(
                                                  "http://localhost:80",
                                                  "localhost", 20, 15, false,
                                                  &original_visits));
  history::URLRow localhost_ip_entry(MakeTypedUrlEntry("http://127.0.0.1",
                                                  "localhost", 12, 15, false,
                                                  &original_visits));
  history::URLsModifiedDetails details;
  details.changed_urls.push_back(updated_url_entry);
  details.changed_urls.push_back(updated_localhost_entry);
  details.changed_urls.push_back(localhost_ip_entry);
  scoped_refptr<ThreadNotifier> notifier(
      new ThreadNotifier(history_thread_.get()));
  notifier->Notify(chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
                   content::Source<Profile>(profile_),
                   content::Details<history::URLsModifiedDetails>(&details));

  history::URLRows new_sync_entries;
  GetTypedUrlsFromSyncDB(&new_sync_entries);

  // We should ignore the localhost urls and left only with http url.
  ASSERT_EQ(1U, new_sync_entries.size());
  EXPECT_TRUE(URLsEqual(updated_url_entry, new_sync_entries[0]));
}

/* [<][>][^][v][top][bottom][index][help] */