// Copyright (c) 2013 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 "components/history/core/browser/typed_url_syncable_service.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/history/core/browser/history_backend.h"
#include "components/history/core/browser/history_backend_client.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_types.h"
#include "components/history/core/browser/in_memory_history_backend.h"
#include "components/history/core/test/test_history_database.h"
#include "components/sync/model/attachments/attachment_service_proxy_for_test.h"
#include "components/sync/model/fake_sync_change_processor.h"
#include "components/sync/model/sync_change_processor_wrapper_for_test.h"
#include "components/sync/model/sync_error.h"
#include "components/sync/model/sync_error_factory_mock.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync/protocol/typed_url_specifics.pb.h"
#include "testing/gtest/include/gtest/gtest.h"

using history::HistoryBackend;
using history::URLID;
using history::URLRow;
using history::URLRows;
using history::VisitRow;
using history::VisitVector;

namespace history {

namespace {

// Constants used to limit size of visits processed. See
// equivalent constants in typed_url_syncable_service.cc for descriptions.
const int kMaxTypedUrlVisits = 100;
const int kVisitThrottleThreshold = 10;
const int kVisitThrottleMultiple = 10;

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

// Helper constants for tests.
const char kTitle[] = "pie";
const char kTitle2[] = "pie2";
const char kURL[] = "http://pie.com/";

bool URLsEqual(URLRow& row, sync_pb::TypedUrlSpecifics& specifics) {
  return ((row.url().spec().compare(specifics.url()) == 0) &&
          (base::UTF16ToUTF8(row.title()).compare(specifics.title()) == 0) &&
          (row.hidden() == specifics.hidden()));
}

bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
  // Only compare synced fields (ignore typed_count and visit_count as those
  // are maintained by the history subsystem).
  return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
         (lhs.title().compare(rhs.title()) == 0) &&
         (lhs.hidden() == rhs.hidden());
}

void AddNewestVisit(ui::PageTransition transition,
                    int64_t visit_time,
                    URLRow* url,
                    VisitVector* visits) {
  base::Time time = base::Time::FromInternalValue(visit_time);
  visits->insert(visits->begin(), VisitRow(url->id(), time, 0, transition, 0));

  if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)) {
    url->set_typed_count(url->typed_count() + 1);
  }

  url->set_last_visit(time);
  url->set_visit_count(visits->size());
}

void AddOldestVisit(ui::PageTransition transition,
                    int64_t visit_time,
                    URLRow* url,
                    VisitVector* visits) {
  base::Time time = base::Time::FromInternalValue(visit_time);
  visits->push_back(VisitRow(url->id(), time, 0, transition, 0));

  if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED)) {
    url->set_typed_count(url->typed_count() + 1);
  }

  url->set_visit_count(visits->size());
}

// Create a new row object and the typed visit çorresponding with the time at
// |last_visit| in the |visits| vector.
URLRow MakeTypedUrlRow(const std::string& url,
                       const std::string& title,
                       int typed_count,
                       int64_t last_visit,
                       bool hidden,
                       VisitVector* visits) {
  // Give each URL a unique ID, to mimic the behavior of the real database.
  GURL gurl(url);
  URLRow history_url(gurl);
  history_url.set_title(base::UTF8ToUTF16(title));
  history_url.set_typed_count(typed_count);
  history_url.set_hidden(hidden);

  base::Time last_visit_time = base::Time::FromInternalValue(last_visit);
  history_url.set_last_visit(last_visit_time);

  if (typed_count > 0) {
    // Add a typed visit for time |last_visit|.
    visits->push_back(VisitRow(history_url.id(), last_visit_time, 0,
                               ui::PAGE_TRANSITION_TYPED, 0));
  } else {
    // Add a non-typed visit for time |last_visit|.
    visits->push_back(VisitRow(history_url.id(), last_visit_time, 0,
                               ui::PAGE_TRANSITION_RELOAD, 0));
  }

  history_url.set_visit_count(visits->size());
  return history_url;
}

static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
                                                        const char* title,
                                                        int64_t last_visit,
                                                        bool hidden) {
  sync_pb::TypedUrlSpecifics typed_url;
  typed_url.set_url(url);
  typed_url.set_title(title);
  typed_url.set_hidden(hidden);
  typed_url.add_visits(last_visit);
  typed_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
  return typed_url;
}

class TestHistoryBackend;

class TestHistoryBackendDelegate : public HistoryBackend::Delegate {
 public:
  TestHistoryBackendDelegate() {}

  void NotifyProfileError(sql::InitStatus init_status,
                          const std::string& diagnostics) override {}
  void SetInMemoryBackend(
      std::unique_ptr<InMemoryHistoryBackend> backend) override {}
  void NotifyFaviconsChanged(const std::set<GURL>& page_urls,
                             const GURL& icon_url) override {}
  void NotifyURLVisited(ui::PageTransition transition,
                        const URLRow& row,
                        const RedirectList& redirects,
                        base::Time visit_time) override {}
  void NotifyURLsModified(const URLRows& changed_urls) override {}
  void NotifyURLsDeleted(bool all_history,
                         bool expired,
                         const URLRows& deleted_rows,
                         const std::set<GURL>& favicon_urls) override {}
  void NotifyKeywordSearchTermUpdated(const URLRow& row,
                                      KeywordID keyword_id,
                                      const base::string16& term) override {}
  void NotifyKeywordSearchTermDeleted(URLID url_id) override {}
  void DBLoaded() override {}

 private:
  DISALLOW_COPY_AND_ASSIGN(TestHistoryBackendDelegate);
};

class TestHistoryBackend : public HistoryBackend {
 public:
  TestHistoryBackend()
      : HistoryBackend(new TestHistoryBackendDelegate(),
                       nullptr,
                       base::ThreadTaskRunnerHandle::Get()) {}

  bool IsExpiredVisitTime(const base::Time& time) override {
    return time.ToInternalValue() == kExpiredVisit;
  }

  URLID GetIdByUrl(const GURL& gurl) {
    return db()->GetRowForURL(gurl, nullptr);
  }

  void SetVisitsForUrl(URLRow& new_url, const VisitVector visits) {
    std::vector<history::VisitInfo> added_visits;
    URLRows new_urls;
    DeleteURL(new_url.url());
    for (const auto& visit : visits) {
      added_visits.push_back(
          history::VisitInfo(visit.visit_time, visit.transition));
    }
    new_urls.push_back(new_url);
    AddPagesWithDetails(new_urls, history::SOURCE_SYNCED);
    AddVisits(new_url.url(), added_visits, history::SOURCE_SYNCED);
    new_url.set_id(GetIdByUrl(new_url.url()));
  }

 private:
  ~TestHistoryBackend() override {}
};

}  // namespace

class TypedUrlSyncableServiceTest : public testing::Test {
 public:
  TypedUrlSyncableServiceTest() : typed_url_sync_service_(NULL) {}
  ~TypedUrlSyncableServiceTest() override {}

  void SetUp() override {
    fake_history_backend_ = new TestHistoryBackend();
    ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
    fake_history_backend_->Init(
        false, TestHistoryDatabaseParamsForPath(test_dir_.GetPath()));
    typed_url_sync_service_ =
        fake_history_backend_->GetTypedUrlSyncableService();
    fake_change_processor_.reset(new syncer::FakeSyncChangeProcessor);
  }

  void TearDown() override {
    fake_history_backend_->Closing();
  }

  // Starts sync for |typed_url_sync_service_| with |initial_data| as the
  // initial sync data.
  void StartSyncing(const syncer::SyncDataList& initial_data);

  // Builds a set of url rows and visit vectors based on |num_typed_urls| and
  // |num_reload_urls|, and |urls|. The rows are stored into |rows|, the visit
  // vectors in |visit_vectors|, and the changes are pushed into the history
  // backend.
  // Returns true if sync receives the proper number of changes, false
  // otherwise.
  bool BuildAndPushLocalChanges(unsigned int num_typed_urls,
                                unsigned int num_reload_urls,
                                const std::vector<std::string>& urls,
                                URLRows* rows,
                                std::vector<VisitVector>* visit_vectors);

  // Fills |urls| with the set of synced urls within |typed_url_sync_service_|.
  void GetSyncedUrls(std::set<GURL>* urls) const;

  // Create and apply a change for url and its visits into history backend.
  VisitVector ApplyUrlAndVisitsChange(
      const std::string& url,
      const std::string& title,
      int typed_count,
      int64_t last_visit,
      bool hidden,
      syncer::SyncChange::SyncChangeType change_type);

  // Add typed_url_sync_service_ to fake_history_backend_'s observer's list.
  void AddObserver();

  // Fills |specifics| with the sync data for |url| and |visits|.
  static bool WriteToTypedUrlSpecifics(const URLRow& url,
                                       const VisitVector& visits,
                                       sync_pb::TypedUrlSpecifics* specifics);

  // Helper to call TypedUrlSyncableService's MergeURLs method.
  static TypedUrlSyncableService::MergeResult MergeUrls(
      const sync_pb::TypedUrlSpecifics& typed_url,
      const history::URLRow& url,
      history::VisitVector* visits,
      history::URLRow* new_url,
      std::vector<history::VisitInfo>* new_visits);

  // Helper to call TypedUrlSyncableService's DiffVisits method.
  static void DiffVisits(const history::VisitVector& history_visits,
                         const sync_pb::TypedUrlSpecifics& sync_specifics,
                         std::vector<history::VisitInfo>* new_visits,
                         history::VisitVector* removed_visits);

  // Create a new row associated with a specific visit's time.
  static history::VisitRow CreateVisit(ui::PageTransition type,
                                       int64_t timestamp);

  static const TypedUrlSyncableService::MergeResult DIFF_NONE =
      TypedUrlSyncableService::DIFF_NONE;
  static const TypedUrlSyncableService::MergeResult DIFF_UPDATE_NODE =
      TypedUrlSyncableService::DIFF_UPDATE_NODE;
  static const TypedUrlSyncableService::MergeResult DIFF_LOCAL_ROW_CHANGED =
      TypedUrlSyncableService::DIFF_LOCAL_ROW_CHANGED;
  static const TypedUrlSyncableService::MergeResult DIFF_LOCAL_VISITS_ADDED =
      TypedUrlSyncableService::DIFF_LOCAL_VISITS_ADDED;

 protected:
  base::MessageLoop message_loop_;
  base::ScopedTempDir test_dir_;
  scoped_refptr<TestHistoryBackend> fake_history_backend_;
  TypedUrlSyncableService* typed_url_sync_service_;
  std::unique_ptr<syncer::FakeSyncChangeProcessor> fake_change_processor_;
};

void TypedUrlSyncableServiceTest::StartSyncing(
    const syncer::SyncDataList& initial_data) {
  DCHECK(fake_change_processor_.get());

  // Set change processor.
  syncer::SyncMergeResult result =
      typed_url_sync_service_->MergeDataAndStartSyncing(
          syncer::TYPED_URLS, initial_data,
          std::unique_ptr<syncer::SyncChangeProcessor>(
              new syncer::SyncChangeProcessorWrapperForTest(
                  fake_change_processor_.get())),
          std::unique_ptr<syncer::SyncErrorFactory>(
              new syncer::SyncErrorFactoryMock()));
  typed_url_sync_service_->history_backend_observer_.RemoveAll();
  EXPECT_FALSE(result.error().IsSet()) << result.error().message();
}

bool TypedUrlSyncableServiceTest::BuildAndPushLocalChanges(
    unsigned int num_typed_urls,
    unsigned int num_reload_urls,
    const std::vector<std::string>& urls,
    URLRows* rows,
    std::vector<VisitVector>* visit_vectors) {
  unsigned int total_urls = num_typed_urls + num_reload_urls;
  DCHECK(urls.size() >= total_urls);
  if (!typed_url_sync_service_)
    return false;

  if (total_urls) {
    // Create new URL rows, populate the mock backend with its visits, and
    // send to the sync service.
    URLRows changed_urls;

    for (unsigned int i = 0; i < total_urls; ++i) {
      int typed = i < num_typed_urls ? 1 : 0;
      VisitVector visits;
      visit_vectors->push_back(visits);
      rows->push_back(MakeTypedUrlRow(urls[i], kTitle, typed, i + 3, false,
                                      &visit_vectors->back()));
      fake_history_backend_->SetVisitsForUrl(rows->back(),
                                             visit_vectors->back());
      changed_urls.push_back(rows->back());
    }

    typed_url_sync_service_->OnURLsModified(fake_history_backend_.get(),
                                            changed_urls);
  }

  // Check that communication with sync was successful.
  if (num_typed_urls != fake_change_processor_->changes().size())
    return false;
  return true;
}

void TypedUrlSyncableServiceTest::GetSyncedUrls(std::set<GURL>* urls) const {
  return typed_url_sync_service_->GetSyncedUrls(urls);
}

VisitVector TypedUrlSyncableServiceTest::ApplyUrlAndVisitsChange(
    const std::string& url,
    const std::string& title,
    int typed_count,
    int64_t last_visit,
    bool hidden,
    syncer::SyncChange::SyncChangeType change_type) {
  VisitVector visits;
  URLRow row =
      MakeTypedUrlRow(url, title, typed_count, last_visit, hidden, &visits);
  syncer::SyncChangeList change_list;
  sync_pb::EntitySpecifics entity_specifics;
  sync_pb::TypedUrlSpecifics* typed_url_specifics =
      entity_specifics.mutable_typed_url();
  WriteToTypedUrlSpecifics(row, visits, typed_url_specifics);
  syncer::SyncData sync_data = syncer::SyncData::CreateRemoteData(
      1, entity_specifics, base::Time(), syncer::AttachmentIdList(),
      syncer::AttachmentServiceProxyForTest::Create());
  syncer::SyncChange sync_change(FROM_HERE, change_type, sync_data);
  change_list.push_back(sync_change);
  typed_url_sync_service_->ProcessSyncChanges(FROM_HERE, change_list);
  return visits;
}

void TypedUrlSyncableServiceTest::AddObserver() {
  typed_url_sync_service_->history_backend_observer_.Add(
      fake_history_backend_.get());
}

// Static.
bool TypedUrlSyncableServiceTest::WriteToTypedUrlSpecifics(
    const URLRow& url,
    const VisitVector& visits,
    sync_pb::TypedUrlSpecifics* specifics) {
  return TypedUrlSyncableService::WriteToTypedUrlSpecifics(url, visits,
                                                           specifics);
}

// Static.
TypedUrlSyncableService::MergeResult TypedUrlSyncableServiceTest::MergeUrls(
    const sync_pb::TypedUrlSpecifics& typed_url,
    const history::URLRow& url,
    history::VisitVector* visits,
    history::URLRow* new_url,
    std::vector<history::VisitInfo>* new_visits) {
  return TypedUrlSyncableService::MergeUrls(typed_url, url, visits, new_url,
                                            new_visits);
}

// Static.
void TypedUrlSyncableServiceTest::DiffVisits(
    const history::VisitVector& history_visits,
    const sync_pb::TypedUrlSpecifics& sync_specifics,
    std::vector<history::VisitInfo>* new_visits,
    history::VisitVector* removed_visits) {
  TypedUrlSyncableService::DiffVisits(history_visits, sync_specifics,
                                      new_visits, removed_visits);
}

// Static.
history::VisitRow TypedUrlSyncableServiceTest::CreateVisit(
    ui::PageTransition type,
    int64_t timestamp) {
  return history::VisitRow(0, base::Time::FromInternalValue(timestamp), 0, type,
                           0);
}

// Create a local typed URL with one TYPED visit after sync has started. Check
// that sync is sent an ADD change for the new URL.
TEST_F(TypedUrlSyncableServiceTest, AddLocalTypedUrl) {
  // Create a local typed URL (simulate a typed visit) that is not already
  // in sync. Check that sync is sent an ADD change for the existing URL.
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));

  URLRow url_row = url_rows.front();
  VisitVector visits = visit_vectors.front();

  // Check change processor.
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());

  // Get typed url specifics.
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();

  EXPECT_TRUE(URLsEqual(url_row, url_specifics));
  ASSERT_EQ(1, url_specifics.visits_size());
  ASSERT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());
  EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(0));
  EXPECT_EQ(static_cast<const int>(visits[0].transition),
            url_specifics.visit_transitions(0));

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}

// Update a local typed URL that is already synced. Check that sync is sent an
// UPDATE for the existing url, but RELOAD visits aren't synced.
TEST_F(TypedUrlSyncableServiceTest, UpdateLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  // Update the URL row, adding another typed visit to the visit vector.
  URLRow url_row = url_rows.front();
  VisitVector visits = visit_vectors.front();

  URLRows changed_urls;
  AddNewestVisit(ui::PAGE_TRANSITION_TYPED, 7, &url_row, &visits);
  AddNewestVisit(ui::PAGE_TRANSITION_RELOAD, 8, &url_row, &visits);
  AddNewestVisit(ui::PAGE_TRANSITION_LINK, 9, &url_row, &visits);
  fake_history_backend_->SetVisitsForUrl(url_row, visits);
  changed_urls.push_back(url_row);

  // Notify typed url sync service of the update.
  typed_url_sync_service_->OnURLsModified(fake_history_backend_.get(),
                                          changed_urls);

  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());

  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();

  EXPECT_TRUE(URLsEqual(url_row, url_specifics));
  ASSERT_EQ(3, url_specifics.visits_size());
  ASSERT_EQ(static_cast<const int>(visits.size()) - 1,
            url_specifics.visits_size());

  // Check that each visit has been translated/communicated correctly.
  // Note that the specifics record visits in chronological order, and the
  // visits from the db are in reverse chronological order.
  EXPECT_EQ(visits[0].visit_time.ToInternalValue(), url_specifics.visits(2));
  EXPECT_EQ(static_cast<const int>(visits[0].transition),
            url_specifics.visit_transitions(2));
  EXPECT_EQ(visits[2].visit_time.ToInternalValue(), url_specifics.visits(1));
  EXPECT_EQ(static_cast<const int>(visits[3].transition),
            url_specifics.visit_transitions(1));
  EXPECT_EQ(visits[3].visit_time.ToInternalValue(), url_specifics.visits(0));
  EXPECT_EQ(static_cast<const int>(visits[3].transition),
            url_specifics.visit_transitions(0));

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}

// Append a RELOAD visit to a typed url that is already synced. Check that sync
// does not receive any updates.
TEST_F(TypedUrlSyncableServiceTest, ReloadVisitLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  // Update the URL row, adding another typed visit to the visit vector.
  URLRow url_row = url_rows.front();
  VisitVector visits = visit_vectors.front();

  URLRows changed_urls;
  AddNewestVisit(ui::PAGE_TRANSITION_RELOAD, 7, &url_row, &visits);
  fake_history_backend_->SetVisitsForUrl(url_row, visits);
  changed_urls.push_back(url_row);

  // Notify typed url sync service of the update.
  typed_url_sync_service_->OnURLVisited(
      fake_history_backend_.get(), ui::PAGE_TRANSITION_RELOAD, url_row,
      RedirectList(), base::Time::FromInternalValue(7));

  ASSERT_EQ(0U, changes.size());

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}

// Appends a LINK visit to an existing typed url. Check that sync does not
// receive any changes.
TEST_F(TypedUrlSyncableServiceTest, LinkVisitLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  URLRow url_row = url_rows.front();
  VisitVector visits = visit_vectors.front();

  // Update the URL row, adding a non-typed visit to the visit vector.
  AddNewestVisit(ui::PAGE_TRANSITION_LINK, 6, &url_row, &visits);
  fake_history_backend_->SetVisitsForUrl(url_row, visits);

  ui::PageTransition transition = ui::PAGE_TRANSITION_LINK;
  // Notify typed url sync service of non-typed visit, expect no change.
  typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
                                        url_row, RedirectList(),
                                        base::Time::FromInternalValue(6));
  ASSERT_EQ(0u, changes.size());
}

// Appends a series of LINK visits followed by a TYPED one to an existing typed
// url. Check that sync receives an UPDATE with the newest visit data.
TEST_F(TypedUrlSyncableServiceTest, TypedVisitLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  URLRow url_row = url_rows.front();
  VisitVector visits = visit_vectors.front();

  // Update the URL row, adding another typed visit to the visit vector.
  AddOldestVisit(ui::PAGE_TRANSITION_LINK, 1, &url_row, &visits);
  AddNewestVisit(ui::PAGE_TRANSITION_LINK, 6, &url_row, &visits);
  AddNewestVisit(ui::PAGE_TRANSITION_TYPED, 7, &url_row, &visits);
  fake_history_backend_->SetVisitsForUrl(url_row, visits);

  // Notify typed url sync service of typed visit.
  ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
  typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
                                        url_row, RedirectList(),
                                        base::Time::Now());

  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE, changes[0].change_type());

  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();

  EXPECT_TRUE(URLsEqual(url_row, url_specifics));
  ASSERT_EQ(4u, visits.size());
  EXPECT_EQ(static_cast<const int>(visits.size()), url_specifics.visits_size());

  // Check that each visit has been translated/communicated correctly.
  // Note that the specifics record visits in chronological order, and the
  // visits from the db are in reverse chronological order.
  int r = url_specifics.visits_size() - 1;
  for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
    EXPECT_EQ(visits[i].visit_time.ToInternalValue(), url_specifics.visits(r));
    EXPECT_EQ(static_cast<const int>(visits[i].transition),
              url_specifics.visit_transitions(r));
  }

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_TRUE(sync_state.end() != sync_state.find(url_row.url()));
}

// Delete several (but not all) local typed urls. Check that sync receives the
// DELETE changes, and the non-deleted urls remain synced.
TEST_F(TypedUrlSyncableServiceTest, DeleteLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back("http://pie.com/");
  urls.push_back("http://cake.com/");
  urls.push_back("http://google.com/");
  urls.push_back("http://foo.com/");
  urls.push_back("http://bar.com/");

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(4, 1, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(4u, sync_state.size());

  // Simulate visit expiry of typed visit, no syncing is done
  // This is to test that sync relies on the in-memory cache to know
  // which urls were typed and synced, and should be deleted.
  url_rows[0].set_typed_count(0);
  VisitVector visits;
  fake_history_backend_->SetVisitsForUrl(url_rows[0], visits);

  // Delete some urls from backend and create deleted row vector.
  URLRows rows;
  for (size_t i = 0; i < 3u; ++i) {
    fake_history_backend_->DeleteURL(url_rows[i].url());
    rows.push_back(url_rows[i]);
  }

  // Notify typed url sync service.
  typed_url_sync_service_->OnURLsDeleted(fake_history_backend_.get(), false,
                                         false, rows, std::set<GURL>());

  ASSERT_EQ(3u, changes.size());
  for (size_t i = 0; i < changes.size(); ++i) {
    ASSERT_TRUE(changes[i].IsValid());
    ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
    EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
    sync_pb::TypedUrlSpecifics url_specifics =
        changes[i].sync_data().GetSpecifics().typed_url();
    EXPECT_EQ(url_rows[i].url().spec(), url_specifics.url());
  }

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state_deleted;
  GetSyncedUrls(&sync_state_deleted);
  ASSERT_EQ(1u, sync_state_deleted.size());
  EXPECT_TRUE(sync_state_deleted.end() !=
              sync_state_deleted.find(url_rows[3].url()));
}

// Delete all local typed urls. Check that sync receives them all the DELETE
// changes, and that the sync state afterwards is empty.
TEST_F(TypedUrlSyncableServiceTest, DeleteAllLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back("http://pie.com/");
  urls.push_back("http://cake.com/");
  urls.push_back("http://google.com/");
  urls.push_back("http://foo.com/");
  urls.push_back("http://bar.com/");

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(4, 1, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(4u, sync_state.size());

  // Delete urls from backend.
  for (size_t i = 0; i < 4u; ++ i) {
    fake_history_backend_->DeleteURL(url_rows[i].url());
  }
  // Delete urls with |all_history| flag set.
  bool all_history = true;

  // Notify typed url sync service.
  typed_url_sync_service_->OnURLsDeleted(fake_history_backend_.get(),
                                         all_history, false, URLRows(),
                                         std::set<GURL>());

  ASSERT_EQ(4u, changes.size());
  for (size_t i = 0; i < changes.size(); ++i) {
    ASSERT_TRUE(changes[i].IsValid());
    ASSERT_EQ(syncer::TYPED_URLS, changes[i].sync_data().GetDataType());
    EXPECT_EQ(syncer::SyncChange::ACTION_DELETE, changes[i].change_type());
  }
  // Check that in-memory representation of sync state is accurate.
  std::set<GURL> sync_state_deleted;
  GetSyncedUrls(&sync_state_deleted);
  EXPECT_TRUE(sync_state_deleted.empty());
}

// Saturate the visits for a typed url with both TYPED and LINK navigations.
// Check that no more than kMaxTypedURLVisits are synced, and that LINK visits
// are dropped rather than TYPED ones.
TEST_F(TypedUrlSyncableServiceTest, MaxVisitLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(0, 1, urls, &url_rows, &visit_vectors));

  URLRow url_row = url_rows.front();
  VisitVector visits;

  // Add |kMaxTypedUrlVisits| + 10 visits to the url. The 10 oldest
  // non-typed visits are expected to be skipped.
  int i = 1;
  for (; i <= kMaxTypedUrlVisits - 20; ++i)
    AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
  for (; i <= kMaxTypedUrlVisits; ++i)
    AddNewestVisit(ui::PAGE_TRANSITION_LINK, i, &url_row, &visits);
  for (; i <= kMaxTypedUrlVisits + 10; ++i)
    AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);

  fake_history_backend_->SetVisitsForUrl(url_row, visits);

  // Notify typed url sync service of typed visit.
  ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
  typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
                                        url_row, RedirectList(),
                                        base::Time::Now());

  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  ASSERT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();
  ASSERT_EQ(kMaxTypedUrlVisits, url_specifics.visits_size());

  // Check that each visit has been translated/communicated correctly.
  // Note that the specifics records visits in chronological order, and the
  // visits from the db are in reverse chronological order.
  int num_typed_visits_synced = 0;
  int num_other_visits_synced = 0;
  int r = url_specifics.visits_size() - 1;
  for (int i = 0; i < url_specifics.visits_size(); ++i, --r) {
    if (url_specifics.visit_transitions(i) ==
        static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED)) {
      ++num_typed_visits_synced;
    } else {
      ++num_other_visits_synced;
    }
  }
  EXPECT_EQ(kMaxTypedUrlVisits - 10, num_typed_visits_synced);
  EXPECT_EQ(10, num_other_visits_synced);
}

// Add enough visits to trigger throttling of updates to a typed url. Check that
// sync does not receive an update until the proper throttle interval has been
// reached.
TEST_F(TypedUrlSyncableServiceTest, ThrottleVisitLocalTypedUrl) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(0, 1, urls, &url_rows, &visit_vectors));

  URLRow url_row = url_rows.front();
  VisitVector visits;

  // Add enough visits to the url so that typed count is above the throttle
  // limit, and not right on the interval that gets synced.
  int i = 1;
  for (; i < kVisitThrottleThreshold + kVisitThrottleMultiple / 2; ++i)
    AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
  fake_history_backend_->SetVisitsForUrl(url_row, visits);

  // Notify typed url sync service of typed visit.
  ui::PageTransition transition = ui::PAGE_TRANSITION_TYPED;
  typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
                                        url_row, RedirectList(),
                                        base::Time::Now());

  // Should throttle, so sync and local cache should not update.
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(0u, changes.size());
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_TRUE(sync_state.empty());

  for (; i % kVisitThrottleMultiple != 1; ++i)
    AddNewestVisit(ui::PAGE_TRANSITION_TYPED, i, &url_row, &visits);
  --i;  // Account for the increment before the condition ends.
  fake_history_backend_->SetVisitsForUrl(url_row, visits);

  // Notify typed url sync service of typed visit.
  typed_url_sync_service_->OnURLVisited(fake_history_backend_.get(), transition,
                                        url_row, RedirectList(),
                                        base::Time::Now());

  ASSERT_EQ(1u, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  ASSERT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();
  ASSERT_EQ(i, url_specifics.visits_size());

  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
}

// Add a typed url locally and one to sync with the same data. Starting sync
// should result in no changes.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlNoChange) {
  // Add a url to backend.
  VisitVector visits;
  URLRow row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
  fake_history_backend_->SetVisitsForUrl(row, visits);

  // Create the same data in sync.
  syncer::SyncDataList initial_sync_data;
  sync_pb::EntitySpecifics entity_specifics;
  sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
  WriteToTypedUrlSpecifics(row, visits, typed_url);
  syncer::SyncData data =
      syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);

  initial_sync_data.push_back(data);
  StartSyncing(initial_sync_data);
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  EXPECT_TRUE(changes.empty());

  // Check that the local cache was is still correct.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(sync_state.count(row.url()), 1U);
}

// Add a corupted typed url locally, has typed url count 1, but no real typed
// url visit. Starting sync should not pick up this url.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlNoTypedUrl) {
  // Add a url to backend.
  VisitVector visits;
  URLRow row = MakeTypedUrlRow(kURL, kTitle, 0, 3, false, &visits);

  // Mark typed_count to 1 even when there is no typed url visit.
  row.set_typed_count(1);
  fake_history_backend_->SetVisitsForUrl(row, visits);

  StartSyncing(syncer::SyncDataList());
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  EXPECT_TRUE(changes.empty());

  // Check that the local cache was is still correct.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_TRUE(sync_state.empty());
  EXPECT_EQ(sync_state.count(row.url()), 0U);
}

// Starting sync with no sync data should just push the local url to sync.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlEmptySync) {
  // Add a url to backend.
  VisitVector visits;
  URLRow row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
  fake_history_backend_->SetVisitsForUrl(row, visits);

  StartSyncing(syncer::SyncDataList());

  // Check that the local cache is still correct.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(sync_state.count(row.url()), 1U);

  // Check that the server was updated correctly.
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();
  ASSERT_EQ(1, url_specifics.visits_size());
  EXPECT_EQ(3, url_specifics.visits(0));
  ASSERT_EQ(1, url_specifics.visit_transitions_size());
  EXPECT_EQ(static_cast<const int>(visits[0].transition),
            url_specifics.visit_transitions(0));
}

// Starting sync with no local data should just push the synced url into the
// backend.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlEmptyLocal) {
  // Create the sync data.
  VisitVector visits;
  URLRow row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
  syncer::SyncDataList initial_sync_data;
  sync_pb::EntitySpecifics entity_specifics;
  sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
  WriteToTypedUrlSpecifics(row, visits, typed_url);
  syncer::SyncData data =
      syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);

  initial_sync_data.push_back(data);
  StartSyncing(initial_sync_data);
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  EXPECT_TRUE(changes.empty());

  // Check that the local cache is updated correctly.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());
  EXPECT_EQ(sync_state.count(row.url()), 1U);

  // Check that the backend was updated correctly.
  VisitVector all_visits;
  base::Time server_time = base::Time::FromInternalValue(3);
  URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);
  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
  ASSERT_EQ(1U, all_visits.size());
  EXPECT_EQ(server_time, all_visits[0].visit_time);
  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
      all_visits[0].transition, visits[0].transition));
}

// Add a url to the local and sync data before sync begins, with the sync data
// having more recent visits. Check that starting sync updates the backend
// with the sync visit, while the older local visit is not pushed to sync.
// The title should be updated to the sync version due to the more recent
// timestamp.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlOldLocal) {
  // Add a url to backend.
  VisitVector visits;
  URLRow local_row = MakeTypedUrlRow(kURL, kTitle, 1, 3, false, &visits);
  fake_history_backend_->SetVisitsForUrl(local_row, visits);

  // Create sync data for the same url with a more recent visit.
  VisitVector server_visits;
  URLRow server_row =
      MakeTypedUrlRow(kURL, kTitle2, 1, 6, false, &server_visits);
  server_row.set_id(fake_history_backend_->GetIdByUrl(GURL(kURL)));
  syncer::SyncDataList initial_sync_data;
  sync_pb::EntitySpecifics entity_specifics;
  sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
  WriteToTypedUrlSpecifics(server_row, server_visits, typed_url);
  syncer::SyncData data =
      syncer::SyncData::CreateLocalData(kURL, kTitle2, entity_specifics);

  initial_sync_data.push_back(data);
  StartSyncing(initial_sync_data);

  // Check that the local cache was updated correctly.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());

  // Check that the backend was updated correctly.
  VisitVector all_visits;
  base::Time server_time = base::Time::FromInternalValue(6);
  URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);
  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
  ASSERT_EQ(2U, all_visits.size());
  EXPECT_EQ(server_time, all_visits.back().visit_time);
  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
      all_visits.back().transition, server_visits[0].transition));
  URLRow url_row;
  EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
  EXPECT_EQ(kTitle2, base::UTF16ToUTF8(url_row.title()));

  // Check that the server was updated correctly.
  // The local history visit should not be added to sync because it is older
  // than sync's oldest visit.
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();
  ASSERT_EQ(1, url_specifics.visits_size());
  EXPECT_EQ(6, url_specifics.visits(0));
  ASSERT_EQ(1, url_specifics.visit_transitions_size());
  EXPECT_EQ(static_cast<const int>(visits[0].transition),
            url_specifics.visit_transitions(0));
}

// Add a url to the local and sync data before sync begins, with the local data
// having more recent visits. Check that starting sync updates the sync
// with the local visits, while the older sync visit is not pushed to the
// backend. Sync's title should be updated to the local version due to the more
// recent timestamp.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlOldSync) {
  // Add a url to backend.
  VisitVector visits;
  URLRow local_row = MakeTypedUrlRow(kURL, kTitle2, 1, 3, false, &visits);
  fake_history_backend_->SetVisitsForUrl(local_row, visits);

  // Create sync data for the same url with an older visit.
  VisitVector server_visits;
  URLRow server_row =
      MakeTypedUrlRow(kURL, kTitle, 1, 2, false, &server_visits);
  syncer::SyncDataList initial_sync_data;
  sync_pb::EntitySpecifics entity_specifics;
  sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
  WriteToTypedUrlSpecifics(server_row, server_visits, typed_url);
  syncer::SyncData data =
      syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);

  initial_sync_data.push_back(data);
  StartSyncing(initial_sync_data);

  // Check that the local cache was updated correctly.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_FALSE(sync_state.empty());

  // Check that the backend was not updated.
  VisitVector all_visits;
  base::Time local_visit_time = base::Time::FromInternalValue(3);
  URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);
  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
  ASSERT_EQ(1U, all_visits.size());
  EXPECT_EQ(local_visit_time, all_visits[0].visit_time);

  // Check that the server was updated correctly.
  // The local history visit should not be added to sync because it is older
  // than sync's oldest visit.
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();
  ASSERT_EQ(1, url_specifics.visits_size());
  EXPECT_EQ(3, url_specifics.visits(0));
  EXPECT_EQ(kTitle2, url_specifics.title());
  ASSERT_EQ(1, url_specifics.visit_transitions_size());
  EXPECT_EQ(static_cast<const int>(visits[0].transition),
            url_specifics.visit_transitions(0));
}

// Check that there is no crash during start sync, if history backend and sync
// have same url, but sync has username/password in it.
// Also check sync will not accept url with username and password.
TEST_F(TypedUrlSyncableServiceTest, MergeUrlsWithUsernameAndPassword) {
  const char kURLWithUsernameAndPassword[] =
      "http://username:password@pie.com/";

  // Add a url to backend.
  VisitVector visits;
  URLRow local_row = MakeTypedUrlRow(kURL, kTitle2, 1, 3, false, &visits);
  fake_history_backend_->SetVisitsForUrl(local_row, visits);

  // Create sync data for the same url but contain username and password.
  VisitVector server_visits;
  URLRow server_row = MakeTypedUrlRow(kURLWithUsernameAndPassword, kTitle, 1, 3,
                                      false, &server_visits);
  syncer::SyncDataList initial_sync_data;
  sync_pb::EntitySpecifics entity_specifics;
  sync_pb::TypedUrlSpecifics* typed_url = entity_specifics.mutable_typed_url();
  WriteToTypedUrlSpecifics(server_row, server_visits, typed_url);
  syncer::SyncData data =
      syncer::SyncData::CreateLocalData(kURL, kTitle, entity_specifics);

  // Make sure there is no crash when merge two urls.
  initial_sync_data.push_back(data);
  StartSyncing(initial_sync_data);

  // Check that the username and password url did not get merged.
  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_EQ(1U, sync_state.count(GURL(kURL)));

  // Notify typed url sync service of the update.
  typed_url_sync_service_->OnURLVisited(
      fake_history_backend_.get(), ui::PAGE_TRANSITION_TYPED, server_row,
      RedirectList(), base::Time::FromInternalValue(7));

  // Check username/password url is not synced.
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_EQ(1U, sync_state.count(GURL(kURL)));
}

// Create a remote typed URL and visit, then send to syncable service after sync
// has started. Check that local DB is received the new URL and visit.
TEST_F(TypedUrlSyncableServiceTest, AddUrlAndVisits) {
  StartSyncing(syncer::SyncDataList());
  VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false,
                                               syncer::SyncChange::ACTION_ADD);

  base::Time visit_time = base::Time::FromInternalValue(3);
  VisitVector all_visits;
  URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);
  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
  EXPECT_EQ(1U, all_visits.size());
  EXPECT_EQ(visit_time, all_visits[0].visit_time);
  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
      all_visits[0].transition, visits[0].transition));
  URLRow url_row;
  EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
  EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title()));
}

// Update a remote typed URL and create a new visit that is already synced, then
// send the update to syncable service. Check that local DB is received an
// UPDATE for the existing url and new visit.
TEST_F(TypedUrlSyncableServiceTest, UpdateUrlAndVisits) {
  StartSyncing(syncer::SyncDataList());

  std::set<GURL> sync_state;
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(0U, sync_state.size());

  VisitVector visits = ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false,
                                               syncer::SyncChange::ACTION_ADD);
  base::Time visit_time = base::Time::FromInternalValue(3);
  VisitVector all_visits;
  URLRow url_row;

  URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);

  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
  sync_state.clear();
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(1U, all_visits.size());
  EXPECT_EQ(visit_time, all_visits[0].visit_time);
  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
      all_visits[0].transition, visits[0].transition));
  EXPECT_EQ(1U, sync_state.size());
  EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
  EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title()));

  VisitVector new_visits = ApplyUrlAndVisitsChange(
      kURL, kTitle2, 2, 6, false, syncer::SyncChange::ACTION_UPDATE);

  sync_state.clear();
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(1U, sync_state.size());
  base::Time new_visit_time = base::Time::FromInternalValue(6);
  url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);
  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);

  EXPECT_EQ(2U, all_visits.size());
  EXPECT_EQ(new_visit_time, all_visits.back().visit_time);
  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
      all_visits.back().transition, new_visits[0].transition));
  EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
  EXPECT_EQ(kTitle2, base::UTF16ToUTF8(url_row.title()));
}

// Delete a typed urls which already synced. Check that local DB receives the
// DELETE changes.
TEST_F(TypedUrlSyncableServiceTest, DeleteUrlAndVisits) {
  URLRows url_rows;
  std::vector<VisitVector> visit_vectors;
  std::vector<std::string> urls;
  std::set<GURL> sync_state;
  urls.push_back(kURL);

  StartSyncing(syncer::SyncDataList());
  ASSERT_TRUE(BuildAndPushLocalChanges(1, 0, urls, &url_rows, &visit_vectors));
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  changes.clear();

  base::Time visit_time = base::Time::FromInternalValue(3);
  VisitVector all_visits;
  URLID url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_NE(0, url_id);
  fake_history_backend_->GetVisitsForURL(url_id, &all_visits);
  EXPECT_EQ(1U, all_visits.size());
  EXPECT_EQ(visit_time, all_visits[0].visit_time);
  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
      all_visits[0].transition, visit_vectors[0][0].transition));
  URLRow url_row;
  EXPECT_TRUE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
  EXPECT_EQ(kTitle, base::UTF16ToUTF8(url_row.title()));
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(1U, sync_state.size());

  // Add observer back to check if TypedUrlSyncableService receive delete
  // changes back from fake_history_backend_.
  AddObserver();

  ApplyUrlAndVisitsChange(kURL, kTitle, 1, 3, false,
                          syncer::SyncChange::ACTION_DELETE);

  sync_state.clear();
  GetSyncedUrls(&sync_state);
  EXPECT_EQ(0U, sync_state.size());
  EXPECT_FALSE(fake_history_backend_->GetURL(GURL(kURL), &url_row));
  url_id = fake_history_backend_->GetIdByUrl(GURL(kURL));
  ASSERT_EQ(0, url_id);

  // Check TypedUrlSyncableService did not receive update since the update is
  // trigered by it.
  ASSERT_EQ(0u, changes.size());
}

// Create two set of visits for history DB and sync DB, two same set of visits
// are same. Check DiffVisits will return empty set of diff visits.
TEST_F(TypedUrlSyncableServiceTest, DiffVisitsSame) {
  history::VisitVector old_visits;
  sync_pb::TypedUrlSpecifics new_url;

  const int64_t visits[] = {1024, 2065, 65534, 1237684};

  for (int64_t visit : visits) {
    old_visits.push_back(history::VisitRow(0,
                                           base::Time::FromInternalValue(visit),
                                           0, ui::PAGE_TRANSITION_TYPED, 0));
    new_url.add_visits(visit);
    new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
  }

  std::vector<history::VisitInfo> new_visits;
  history::VisitVector removed_visits;

  DiffVisits(old_visits, new_url, &new_visits, &removed_visits);
  EXPECT_TRUE(new_visits.empty());
  EXPECT_TRUE(removed_visits.empty());
}

// Create two set of visits for history DB and sync DB. Check DiffVisits will
// return correct set of diff visits.
TEST_F(TypedUrlSyncableServiceTest, DiffVisitsRemove) {
  history::VisitVector old_visits;
  sync_pb::TypedUrlSpecifics new_url;

  const int64_t visits_left[] = {1,    2,     1024,    1500,   2065,
                                 6000, 65534, 1237684, 2237684};
  const int64_t visits_right[] = {1024, 2065, 65534, 1237684};

  // DiffVisits will not remove the first visit, because we never delete visits
  // from the start of the array (since those visits can get truncated by the
  // size-limiting code).
  const int64_t visits_removed[] = {1500, 6000, 2237684};

  for (int64_t visit : visits_left) {
    old_visits.push_back(history::VisitRow(0,
                                           base::Time::FromInternalValue(visit),
                                           0, ui::PAGE_TRANSITION_TYPED, 0));
  }

  for (int64_t visit : visits_right) {
    new_url.add_visits(visit);
    new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
  }

  std::vector<history::VisitInfo> new_visits;
  history::VisitVector removed_visits;

  DiffVisits(old_visits, new_url, &new_visits, &removed_visits);
  EXPECT_TRUE(new_visits.empty());
  ASSERT_EQ(removed_visits.size(), arraysize(visits_removed));
  for (size_t i = 0; i < arraysize(visits_removed); ++i) {
    EXPECT_EQ(removed_visits[i].visit_time.ToInternalValue(),
              visits_removed[i]);
  }
}

// Create two set of visits for history DB and sync DB. Check DiffVisits will
// return correct set of diff visits.
TEST_F(TypedUrlSyncableServiceTest, DiffVisitsAdd) {
  history::VisitVector old_visits;
  sync_pb::TypedUrlSpecifics new_url;

  const int64_t visits_left[] = {1024, 2065, 65534, 1237684};
  const int64_t visits_right[] = {1,    1024,  1500,    2065,
                                  6000, 65534, 1237684, 2237684};

  const int64_t visits_added[] = {1, 1500, 6000, 2237684};

  for (int64_t visit : visits_left) {
    old_visits.push_back(history::VisitRow(0,
                                           base::Time::FromInternalValue(visit),
                                           0, ui::PAGE_TRANSITION_TYPED, 0));
  }

  for (int64_t visit : visits_right) {
    new_url.add_visits(visit);
    new_url.add_visit_transitions(ui::PAGE_TRANSITION_TYPED);
  }

  std::vector<history::VisitInfo> new_visits;
  history::VisitVector removed_visits;

  DiffVisits(old_visits, new_url, &new_visits, &removed_visits);
  EXPECT_TRUE(removed_visits.empty());
  ASSERT_TRUE(new_visits.size() == arraysize(visits_added));
  for (size_t i = 0; i < arraysize(visits_added); ++i) {
    EXPECT_EQ(new_visits[i].first.ToInternalValue(), visits_added[i]);
    EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
        new_visits[i].second, ui::PAGE_TRANSITION_TYPED));
  }
}

// Create three visits, check RELOAD visit is removed by
// WriteToTypedUrlSpecifics so it won't apply to sync DB.
TEST_F(TypedUrlSyncableServiceTest, WriteTypedUrlSpecifics) {
  history::VisitVector visits;
  visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, 1));
  visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, 2));
  visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, 3));

  history::URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, 100, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // RELOAD visits should be removed.
  EXPECT_EQ(2, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  EXPECT_EQ(1, typed_url.visits(0));
  EXPECT_EQ(3, typed_url.visits(1));
  EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED),
            typed_url.visit_transitions(0));
  EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_LINK),
            typed_url.visit_transitions(1));
}

// Create 101 visits, check WriteToTypedUrlSpecifics will only keep 100 visits.
TEST_F(TypedUrlSyncableServiceTest, TooManyVisits) {
  history::VisitVector visits;
  int64_t timestamp = 1000;
  visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++));
  for (int i = 0; i < 100; ++i) {
    visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++));
  }
  history::URLRow url(
      MakeTypedUrlRow(kURL, kTitle, 0, timestamp++, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // # visits should be capped at 100.
  EXPECT_EQ(100, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  EXPECT_EQ(1000, typed_url.visits(0));
  // Visit with timestamp of 1001 should be omitted since we should have
  // skipped that visit to stay under the cap.
  EXPECT_EQ(1002, typed_url.visits(1));
  EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED),
            typed_url.visit_transitions(0));
  EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_LINK),
            typed_url.visit_transitions(1));
}

// Create 306 visits, check WriteToTypedUrlSpecifics will only keep 100 typed
// visits.
TEST_F(TypedUrlSyncableServiceTest, TooManyTypedVisits) {
  history::VisitVector visits;
  int64_t timestamp = 1000;
  for (int i = 0; i < 102; ++i) {
    visits.push_back(CreateVisit(ui::PAGE_TRANSITION_TYPED, timestamp++));
    visits.push_back(CreateVisit(ui::PAGE_TRANSITION_LINK, timestamp++));
    visits.push_back(CreateVisit(ui::PAGE_TRANSITION_RELOAD, timestamp++));
  }
  history::URLRow url(
      MakeTypedUrlRow(kURL, kTitle, 0, timestamp++, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  WriteToTypedUrlSpecifics(url, visits, &typed_url);
  // # visits should be capped at 100.
  EXPECT_EQ(100, typed_url.visits_size());
  EXPECT_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
  // First two typed visits should be skipped.
  EXPECT_EQ(1006, typed_url.visits(0));

  // Ensure there are no non-typed visits since that's all that should fit.
  for (int i = 0; i < typed_url.visits_size(); ++i) {
    EXPECT_EQ(static_cast<int32_t>(ui::PAGE_TRANSITION_TYPED),
              typed_url.visit_transitions(i));
  }
}

// Create a typed url without visit, check WriteToTypedUrlSpecifics will return
// false for it.
TEST_F(TypedUrlSyncableServiceTest, NoTypedVisits) {
  history::VisitVector visits;
  history::URLRow url(MakeTypedUrlRow(kURL, kTitle, 0, 1000, false, &visits));
  sync_pb::TypedUrlSpecifics typed_url;
  EXPECT_FALSE(WriteToTypedUrlSpecifics(url, visits, &typed_url));
  // URLs with no typed URL visits should not been written to specifics.
  EXPECT_EQ(0, typed_url.visits_size());
}

TEST_F(TypedUrlSyncableServiceTest, MergeUrls) {
  history::VisitVector visits1;
  history::URLRow row1(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits1));
  sync_pb::TypedUrlSpecifics specs1(
      MakeTypedUrlSpecifics(kURL, kTitle, 3, false));
  history::URLRow new_row1((GURL(kURL)));
  std::vector<history::VisitInfo> new_visits1;
  EXPECT_TRUE(TypedUrlSyncableServiceTest::MergeUrls(specs1, row1, &visits1,
                                                     &new_row1, &new_visits1) ==
              TypedUrlSyncableServiceTest::DIFF_NONE);

  history::VisitVector visits2;
  history::URLRow row2(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits2));
  sync_pb::TypedUrlSpecifics specs2(
      MakeTypedUrlSpecifics(kURL, kTitle, 3, true));
  history::VisitVector expected_visits2;
  history::URLRow expected2(
      MakeTypedUrlRow(kURL, kTitle, 2, 3, true, &expected_visits2));
  history::URLRow new_row2((GURL(kURL)));
  std::vector<history::VisitInfo> new_visits2;
  EXPECT_TRUE(TypedUrlSyncableServiceTest::MergeUrls(specs2, row2, &visits2,
                                                     &new_row2, &new_visits2) ==
              TypedUrlSyncableServiceTest::DIFF_LOCAL_ROW_CHANGED);
  EXPECT_TRUE(URLsEqual(new_row2, expected2));

  history::VisitVector visits3;
  history::URLRow row3(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits3));
  sync_pb::TypedUrlSpecifics specs3(
      MakeTypedUrlSpecifics(kURL, kTitle2, 3, true));
  history::VisitVector expected_visits3;
  history::URLRow expected3(
      MakeTypedUrlRow(kURL, kTitle2, 2, 3, true, &expected_visits3));
  history::URLRow new_row3((GURL(kURL)));
  std::vector<history::VisitInfo> new_visits3;
  EXPECT_EQ(TypedUrlSyncableServiceTest::DIFF_LOCAL_ROW_CHANGED |
                TypedUrlSyncableServiceTest::DIFF_NONE,
            TypedUrlSyncableServiceTest::MergeUrls(specs3, row3, &visits3,
                                                   &new_row3, &new_visits3));
  EXPECT_TRUE(URLsEqual(new_row3, expected3));

  // Create one node in history DB with timestamp of 3, and one node in sync
  // DB with timestamp of 4. Result should contain one new item (4).
  history::VisitVector visits4;
  history::URLRow row4(MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &visits4));
  sync_pb::TypedUrlSpecifics specs4(
      MakeTypedUrlSpecifics(kURL, kTitle2, 4, false));
  history::VisitVector expected_visits4;
  history::URLRow expected4(
      MakeTypedUrlRow(kURL, kTitle2, 2, 4, false, &expected_visits4));
  history::URLRow new_row4((GURL(kURL)));
  std::vector<history::VisitInfo> new_visits4;
  EXPECT_EQ(TypedUrlSyncableServiceTest::DIFF_UPDATE_NODE |
                TypedUrlSyncableServiceTest::DIFF_LOCAL_ROW_CHANGED |
                TypedUrlSyncableServiceTest::DIFF_LOCAL_VISITS_ADDED,
            TypedUrlSyncableServiceTest::MergeUrls(specs4, row4, &visits4,
                                                   &new_row4, &new_visits4));
  EXPECT_EQ(1U, new_visits4.size());
  EXPECT_EQ(specs4.visits(0), new_visits4[0].first.ToInternalValue());
  EXPECT_TRUE(URLsEqual(new_row4, expected4));
  EXPECT_EQ(2U, visits4.size());

  history::VisitVector visits5;
  history::URLRow row5(MakeTypedUrlRow(kURL, kTitle, 1, 4, false, &visits5));
  sync_pb::TypedUrlSpecifics specs5(
      MakeTypedUrlSpecifics(kURL, kTitle, 3, false));
  history::VisitVector expected_visits5;
  history::URLRow expected5(
      MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &expected_visits5));
  history::URLRow new_row5((GURL(kURL)));
  std::vector<history::VisitInfo> new_visits5;

  // UPDATE_NODE should be set because row5 has a newer last_visit timestamp.
  EXPECT_EQ(TypedUrlSyncableServiceTest::DIFF_UPDATE_NODE |
                TypedUrlSyncableServiceTest::DIFF_NONE,
            TypedUrlSyncableServiceTest::MergeUrls(specs5, row5, &visits5,
                                                   &new_row5, &new_visits5));
  EXPECT_TRUE(URLsEqual(new_row5, expected5));
  EXPECT_EQ(0U, new_visits5.size());
}

TEST_F(TypedUrlSyncableServiceTest, MergeUrlsAfterExpiration) {
  // Tests to ensure that we don't resurrect expired URLs (URLs that have been
  // deleted from the history DB but still exist in the sync DB).

  // First, create a history row that has two visits, with timestamps 2 and 3.
  history::VisitVector(history_visits);
  history_visits.push_back(history::VisitRow(
      0, base::Time::FromInternalValue(2), 0, ui::PAGE_TRANSITION_TYPED, 0));
  history::URLRow history_url(
      MakeTypedUrlRow(kURL, kTitle, 2, 3, false, &history_visits));

  // Now, create a sync node with visits at timestamps 1, 2, 3, 4.
  sync_pb::TypedUrlSpecifics node(
      MakeTypedUrlSpecifics(kURL, kTitle, 1, false));
  node.add_visits(2);
  node.add_visits(3);
  node.add_visits(4);
  node.add_visit_transitions(2);
  node.add_visit_transitions(3);
  node.add_visit_transitions(4);
  history::URLRow new_history_url(history_url.url());
  std::vector<history::VisitInfo> new_visits;
  EXPECT_EQ(
      TypedUrlSyncableServiceTest::DIFF_NONE |
          TypedUrlSyncableServiceTest::DIFF_LOCAL_VISITS_ADDED,
      TypedUrlSyncableServiceTest::MergeUrls(node, history_url, &history_visits,
                                             &new_history_url, &new_visits));
  EXPECT_TRUE(URLsEqual(history_url, new_history_url));
  EXPECT_EQ(1U, new_visits.size());
  EXPECT_EQ(4U, new_visits[0].first.ToInternalValue());
  // We should not sync the visit with timestamp #1 since it is earlier than
  // any other visit for this URL in the history DB. But we should sync visit
  // #4.
  EXPECT_EQ(3U, history_visits.size());
  EXPECT_EQ(2U, history_visits[0].visit_time.ToInternalValue());
  EXPECT_EQ(3U, history_visits[1].visit_time.ToInternalValue());
  EXPECT_EQ(4U, history_visits[2].visit_time.ToInternalValue());
}

// Create a local typed URL with one expired TYPED visit,
// MergeDataAndStartSyncing should not pass it to sync. And then add a non
// expired visit, OnURLsModified should only send the non expired visit to sync.
TEST_F(TypedUrlSyncableServiceTest, LocalExpiredTypedUrlDoNotSync) {
  URLRow row;
  URLRows changed_urls;
  VisitVector visits;

  // Add an expired typed URL to local.
  row = MakeTypedUrlRow(kURL, kTitle, 1, kExpiredVisit, false, &visits);
  fake_history_backend_->SetVisitsForUrl(row, visits);

  StartSyncing(syncer::SyncDataList());

  // Check change processor did not receive expired typed URL.
  syncer::SyncChangeList& changes = fake_change_processor_->changes();
  ASSERT_EQ(0U, changes.size());

  // Add a non expired typed URL to local.
  row = MakeTypedUrlRow(kURL, kTitle, 2, 1, false, &visits);
  fake_history_backend_->SetVisitsForUrl(row, visits);

  changed_urls.push_back(row);
  // Notify typed url sync service of the update.
  typed_url_sync_service_->OnURLsModified(fake_history_backend_.get(),
                                          changed_urls);

  // Check change processor did not receive expired typed URL.
  ASSERT_EQ(1U, changes.size());
  ASSERT_TRUE(changes[0].IsValid());
  EXPECT_EQ(syncer::TYPED_URLS, changes[0].sync_data().GetDataType());
  EXPECT_EQ(syncer::SyncChange::ACTION_ADD, changes[0].change_type());

  // Get typed url specifics. Verify only a non-expired visit received.
  sync_pb::TypedUrlSpecifics url_specifics =
      changes[0].sync_data().GetSpecifics().typed_url();

  EXPECT_TRUE(URLsEqual(row, url_specifics));
  ASSERT_EQ(1, url_specifics.visits_size());
  ASSERT_EQ(static_cast<const int>(visits.size() - 1),
            url_specifics.visits_size());
  EXPECT_EQ(visits[1].visit_time.ToInternalValue(), url_specifics.visits(0));
  EXPECT_EQ(static_cast<const int>(visits[1].transition),
            url_specifics.visit_transitions(0));
}

}  // namespace history
