// 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 "components/history/core/browser/history_backend.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/favicon_base/favicon_usage_data.h"
#include "components/history/core/browser/history_backend_client.h"
#include "components/history/core/browser/history_constants.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_service_observer.h"
#include "components/history/core/browser/in_memory_database.h"
#include "components/history/core/browser/in_memory_history_backend.h"
#include "components/history/core/browser/keyword_search_term.h"
#include "components/history/core/browser/visit_delegate.h"
#include "components/history/core/test/database_test_utils.h"
#include "components/history/core/test/history_client_fake_bookmarks.h"
#include "components/history/core/test/test_history_database.h"
#include "components/prefs/pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/gurl.h"

// This file only tests functionality where it is most convenient to call the
// backend directly. Most of the history backend functions are tested by the
// history unit test. Because of the elaborate callbacks involved, this is no
// harder than calling it directly for many things.

namespace {

using ::testing::ElementsAre;
using base::HistogramBase;

const int kTinyEdgeSize = 10;
const int kSmallEdgeSize = 16;
const int kLargeEdgeSize = 32;

const gfx::Size kTinySize = gfx::Size(kTinyEdgeSize, kTinyEdgeSize);
const gfx::Size kSmallSize = gfx::Size(kSmallEdgeSize, kSmallEdgeSize);
const gfx::Size kLargeSize = gfx::Size(kLargeEdgeSize, kLargeEdgeSize);

typedef base::Callback<void(const history::URLRow*,
                            const history::URLRow*,
                            const history::URLRow*)>
    SimulateNotificationCallback;

void SimulateNotificationURLVisited(history::HistoryServiceObserver* observer,
                                    const history::URLRow* row1,
                                    const history::URLRow* row2,
                                    const history::URLRow* row3) {
  history::URLRows rows;
  rows.push_back(*row1);
  if (row2)
    rows.push_back(*row2);
  if (row3)
    rows.push_back(*row3);

  base::Time visit_time;
  history::RedirectList redirects;
  for (const history::URLRow& row : rows) {
    observer->OnURLVisited(nullptr, ui::PAGE_TRANSITION_LINK, row, redirects,
                           visit_time);
  }
}

void SimulateNotificationURLsModified(history::HistoryServiceObserver* observer,
                                      const history::URLRow* row1,
                                      const history::URLRow* row2,
                                      const history::URLRow* row3) {
  history::URLRows rows;
  rows.push_back(*row1);
  if (row2)
    rows.push_back(*row2);
  if (row3)
    rows.push_back(*row3);

  observer->OnURLsModified(nullptr, rows);
}

}  // namespace

namespace history {

class HistoryBackendTestBase;

// This must be a separate object since HistoryBackend manages its lifetime.
// This just forwards the messages we're interested in to the test object.
class HistoryBackendTestDelegate : public HistoryBackend::Delegate {
 public:
  explicit HistoryBackendTestDelegate(HistoryBackendTestBase* test)
      : test_(test) {}

  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:
  // Not owned by us.
  HistoryBackendTestBase* test_;

  DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate);
};

class HistoryBackendTestBase : public testing::Test {
 public:
  typedef std::vector<std::pair<ui::PageTransition, URLRow>> URLVisitedList;
  typedef std::vector<URLRows> URLsModifiedList;
  typedef std::vector<std::pair<bool, bool>> URLsDeletedList;

  HistoryBackendTestBase()
      : loaded_(false) {}

  ~HistoryBackendTestBase() override {}

 protected:
  std::vector<GURL> favicon_changed_notifications_page_urls() const {
    return favicon_changed_notifications_page_urls_;
  }

  std::vector<GURL> favicon_changed_notifications_icon_urls() const {
    return favicon_changed_notifications_icon_urls_;
  }

  int num_url_visited_notifications() const {
    return url_visited_notifications_.size();
  }

  const URLVisitedList& url_visited_notifications() const {
    return url_visited_notifications_;
  }

  int num_urls_modified_notifications() const {
    return urls_modified_notifications_.size();
  }

  const URLsModifiedList& urls_modified_notifications() const {
    return urls_modified_notifications_;
  }

  const URLsDeletedList& urls_deleted_notifications() const {
    return urls_deleted_notifications_;
  }

  void ClearBroadcastedNotifications() {
    url_visited_notifications_.clear();
    urls_modified_notifications_.clear();
    urls_deleted_notifications_.clear();
    favicon_changed_notifications_page_urls_.clear();
    favicon_changed_notifications_icon_urls_.clear();
  }

  base::FilePath test_dir() { return test_dir_; }

  void NotifyFaviconsChanged(const std::set<GURL>& page_urls,
                             const GURL& icon_url) {
    favicon_changed_notifications_page_urls_.insert(
        favicon_changed_notifications_page_urls_.end(), page_urls.begin(),
        page_urls.end());
    if (!icon_url.is_empty())
      favicon_changed_notifications_icon_urls_.push_back(icon_url);
  }

  void NotifyURLVisited(ui::PageTransition transition,
                        const URLRow& row,
                        const RedirectList& redirects,
                        base::Time visit_time) {
    // Send the notifications directly to the in-memory database.
    mem_backend_->OnURLVisited(nullptr, transition, row, redirects, visit_time);
    url_visited_notifications_.push_back(std::make_pair(transition, row));
  }

  void NotifyURLsModified(const URLRows& changed_urls) {
    // Send the notifications directly to the in-memory database.
    mem_backend_->OnURLsModified(nullptr, changed_urls);
    urls_modified_notifications_.push_back(changed_urls);
  }

  void NotifyURLsDeleted(bool all_history,
                         bool expired,
                         const URLRows& deleted_rows,
                         const std::set<GURL>& favicon_urls) {
    mem_backend_->OnURLsDeleted(nullptr, all_history, expired, deleted_rows,
                                favicon_urls);
    urls_deleted_notifications_.push_back(std::make_pair(all_history, expired));
  }

  void NotifyKeywordSearchTermUpdated(const URLRow& row,
                                      KeywordID keyword_id,
                                      const base::string16& term) {
    mem_backend_->OnKeywordSearchTermUpdated(nullptr, row, keyword_id, term);
  }

  void NotifyKeywordSearchTermDeleted(URLID url_id) {
    mem_backend_->OnKeywordSearchTermDeleted(nullptr, url_id);
  }

  history::HistoryClientFakeBookmarks history_client_;
  scoped_refptr<HistoryBackend> backend_;  // Will be NULL on init failure.
  std::unique_ptr<InMemoryHistoryBackend> mem_backend_;
  bool loaded_;

 private:
  friend class HistoryBackendTestDelegate;

  // testing::Test
  void SetUp() override {
    if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"),
                                      &test_dir_))
      return;
    backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this),
                                  history_client_.CreateBackendClient(),
                                  base::ThreadTaskRunnerHandle::Get());
    backend_->Init(false, TestHistoryDatabaseParamsForPath(test_dir_));
  }

  void TearDown() override {
    if (backend_.get())
      backend_->Closing();
    backend_ = NULL;
    mem_backend_.reset();
    base::DeleteFile(test_dir_, true);
    base::RunLoop().RunUntilIdle();
    history_client_.ClearAllBookmarks();
  }

  void SetInMemoryBackend(std::unique_ptr<InMemoryHistoryBackend> backend) {
    mem_backend_.swap(backend);
  }

  // The types and details of notifications which were broadcasted.
  std::vector<GURL> favicon_changed_notifications_page_urls_;
  std::vector<GURL> favicon_changed_notifications_icon_urls_;
  URLVisitedList url_visited_notifications_;
  URLsModifiedList urls_modified_notifications_;
  URLsDeletedList urls_deleted_notifications_;

  base::test::ScopedTaskEnvironment scoped_task_environment_;
  base::FilePath test_dir_;

  DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestBase);
};

void HistoryBackendTestDelegate::SetInMemoryBackend(
    std::unique_ptr<InMemoryHistoryBackend> backend) {
  test_->SetInMemoryBackend(std::move(backend));
}

void HistoryBackendTestDelegate::NotifyFaviconsChanged(
    const std::set<GURL>& page_urls,
    const GURL& icon_url) {
  test_->NotifyFaviconsChanged(page_urls, icon_url);
}

void HistoryBackendTestDelegate::NotifyURLVisited(ui::PageTransition transition,
                                                  const URLRow& row,
                                                  const RedirectList& redirects,
                                                  base::Time visit_time) {
  test_->NotifyURLVisited(transition, row, redirects, visit_time);
}

void HistoryBackendTestDelegate::NotifyURLsModified(
    const URLRows& changed_urls) {
  test_->NotifyURLsModified(changed_urls);
}

void HistoryBackendTestDelegate::NotifyURLsDeleted(
    bool all_history,
    bool expired,
    const URLRows& deleted_rows,
    const std::set<GURL>& favicon_urls) {
  test_->NotifyURLsDeleted(all_history, expired, deleted_rows, favicon_urls);
}

void HistoryBackendTestDelegate::NotifyKeywordSearchTermUpdated(
    const URLRow& row,
    KeywordID keyword_id,
    const base::string16& term) {
  test_->NotifyKeywordSearchTermUpdated(row, keyword_id, term);
}

void HistoryBackendTestDelegate::NotifyKeywordSearchTermDeleted(URLID url_id) {
  test_->NotifyKeywordSearchTermDeleted(url_id);
}

void HistoryBackendTestDelegate::DBLoaded() {
  test_->loaded_ = true;
}

class HistoryBackendTest : public HistoryBackendTestBase {
 public:
  HistoryBackendTest() {}
  ~HistoryBackendTest() override {}

 protected:
  void AddRedirectChain(const char* sequence[], int nav_entry_id) {
    AddRedirectChainWithTransitionAndTime(
        sequence, nav_entry_id, ui::PAGE_TRANSITION_LINK, base::Time::Now());
  }

  void AddRedirectChainWithTransitionAndTime(const char* sequence[],
                                             int nav_entry_id,
                                             ui::PageTransition transition,
                                             base::Time time) {
    history::RedirectList redirects;
    for (int i = 0; sequence[i] != NULL; ++i)
      redirects.push_back(GURL(sequence[i]));

    ContextID context_id = reinterpret_cast<ContextID>(1);
    history::HistoryAddPageArgs request(
        redirects.back(), time, context_id, nav_entry_id, GURL(),
        redirects, transition, history::SOURCE_BROWSED,
        true, true);
    backend_->AddPage(request);
  }

  // Adds CLIENT_REDIRECT page transition.
  // |url1| is the source URL and |url2| is the destination.
  // |did_replace| is true if the transition is non-user initiated and the
  // navigation entry for |url2| has replaced that for |url1|. The possibly
  // updated transition code of the visit records for |url1| and |url2| is
  // returned by filling in |*transition1| and |*transition2|, respectively.
  // |time| is a time of the redirect.
  void AddClientRedirect(const GURL& url1,
                         const GURL& url2,
                         bool did_replace,
                         base::Time time,
                         int* transition1,
                         int* transition2) {
    ContextID dummy_context_id = reinterpret_cast<ContextID>(0x87654321);
    history::RedirectList redirects;
    if (url1.is_valid())
      redirects.push_back(url1);
    if (url2.is_valid())
      redirects.push_back(url2);
    HistoryAddPageArgs request(
        url2, time, dummy_context_id, 0, url1,
        redirects, ui::PAGE_TRANSITION_CLIENT_REDIRECT,
        history::SOURCE_BROWSED, did_replace, true);
    backend_->AddPage(request);

    *transition1 = GetTransition(url1);
    *transition2 = GetTransition(url2);
  }

  int GetTransition(const GURL& url) {
    if (!url.is_valid())
      return 0;
    URLRow row;
    URLID id = backend_->db()->GetRowForURL(url, &row);
    VisitVector visits;
    EXPECT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
    return visits[0].transition;
  }

  // Returns a vector with the small and large edge sizes.
  const std::vector<int> GetEdgeSizesSmallAndLarge() {
    std::vector<int> sizes_small_and_large;
    sizes_small_and_large.push_back(kSmallEdgeSize);
    sizes_small_and_large.push_back(kLargeEdgeSize);
    return sizes_small_and_large;
  }

  // Returns the number of icon mappings of |icon_type| to |page_url|.
  size_t NumIconMappingsForPageURL(const GURL& page_url,
                                   favicon_base::IconType icon_type) {
    std::vector<IconMapping> icon_mappings;
    backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url, icon_type,
                                                       &icon_mappings);
    return icon_mappings.size();
  }

  // Returns the icon mappings for |page_url| sorted alphabetically by icon
  // URL in ascending order. Returns true if there is at least one icon
  // mapping.
  bool GetSortedIconMappingsForPageURL(
      const GURL& page_url,
      std::vector<IconMapping>* icon_mappings) {
    if (!backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
                                                            icon_mappings)) {
      return false;
    }
    std::sort(icon_mappings->begin(), icon_mappings->end(),
              [](const history::IconMapping& a, const history::IconMapping& b) {
                return a.icon_url < b.icon_url;
              });
    return true;
  }

  // Returns the favicon bitmaps for |icon_id| sorted by pixel size in
  // ascending order. Returns true if there is at least one favicon bitmap.
  bool GetSortedFaviconBitmaps(favicon_base::FaviconID icon_id,
                               std::vector<FaviconBitmap>* favicon_bitmaps) {
    if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, favicon_bitmaps))
      return false;
    std::sort(
        favicon_bitmaps->begin(), favicon_bitmaps->end(),
        [](const history::FaviconBitmap& a, const history::FaviconBitmap& b) {
          return a.pixel_size.GetArea() < b.pixel_size.GetArea();
        });
    return true;
  }

  // Returns true if there is exactly one favicon bitmap associated to
  // |favicon_id|. If true, returns favicon bitmap in output parameter.
  bool GetOnlyFaviconBitmap(const favicon_base::FaviconID icon_id,
                            FaviconBitmap* favicon_bitmap) {
    std::vector<FaviconBitmap> favicon_bitmaps;
    if (!backend_->thumbnail_db_->GetFaviconBitmaps(icon_id, &favicon_bitmaps))
      return false;
    if (favicon_bitmaps.size() != 1)
      return false;
    *favicon_bitmap = favicon_bitmaps[0];
    return true;
  }

  // Creates an |edge_size|x|edge_size| bitmap of |color|.
  SkBitmap CreateBitmap(SkColor color, int edge_size) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(edge_size, edge_size);
    bitmap.eraseColor(color);
    return bitmap;
  }

  // Returns true if |bitmap_data| is equal to |expected_data|.
  bool BitmapDataEqual(char expected_data,
                       scoped_refptr<base::RefCountedMemory> bitmap_data) {
    return bitmap_data.get() &&
           bitmap_data->size() == 1u &&
           *bitmap_data->front() == expected_data;
  }

  // Returns true if |bitmap_data| is of |color|.
  bool BitmapColorEqual(SkColor expected_color,
                        scoped_refptr<base::RefCountedMemory> bitmap_data) {
    SkBitmap bitmap;
    if (!gfx::PNGCodec::Decode(bitmap_data->front(), bitmap_data->size(),
                               &bitmap))
      return false;
    return expected_color == bitmap.getColor(0, 0);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(HistoryBackendTest);
};

class InMemoryHistoryBackendTest : public HistoryBackendTestBase {
 public:
  InMemoryHistoryBackendTest() {}
  ~InMemoryHistoryBackendTest() override {}

 protected:
  void SimulateNotificationURLsDeleted(const URLRow* row1,
                                       const URLRow* row2 = NULL,
                                       const URLRow* row3 = NULL) {
    URLRows rows;
    rows.push_back(*row1);
    if (row2) rows.push_back(*row2);
    if (row3) rows.push_back(*row3);

    NotifyURLsDeleted(false, false, rows, std::set<GURL>());
  }

  size_t GetNumberOfMatchingSearchTerms(const int keyword_id,
                                        const base::string16& prefix) {
    std::vector<KeywordSearchTermVisit> matching_terms;
    mem_backend_->db()->GetMostRecentKeywordSearchTerms(
        keyword_id, prefix, 1, &matching_terms);
    return matching_terms.size();
  }

  static URLRow CreateTestTypedURL() {
    URLRow url_row(GURL("https://www.google.com/"));
    url_row.set_id(10);
    url_row.set_title(base::UTF8ToUTF16("Google Search"));
    url_row.set_typed_count(1);
    url_row.set_visit_count(1);
    url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(1));
    return url_row;
  }

  static URLRow CreateAnotherTestTypedURL() {
    URLRow url_row(GURL("https://maps.google.com/"));
    url_row.set_id(20);
    url_row.set_title(base::UTF8ToUTF16("Google Maps"));
    url_row.set_typed_count(2);
    url_row.set_visit_count(3);
    url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(2));
    return url_row;
  }

  static URLRow CreateTestNonTypedURL() {
    URLRow url_row(GURL("https://news.google.com/"));
    url_row.set_id(30);
    url_row.set_title(base::UTF8ToUTF16("Google News"));
    url_row.set_visit_count(5);
    url_row.set_last_visit(base::Time::Now() - base::TimeDelta::FromHours(3));
    return url_row;
  }

  void PopulateTestURLsAndSearchTerms(URLRow* row1,
                                      URLRow* row2,
                                      const base::string16& term1,
                                      const base::string16& term2);

  void TestAddingAndChangingURLRows(
      const SimulateNotificationCallback& callback);

  static const KeywordID kTestKeywordId;
  static const char kTestSearchTerm1[];
  static const char kTestSearchTerm2[];

 private:
  DISALLOW_COPY_AND_ASSIGN(InMemoryHistoryBackendTest);
};

const KeywordID InMemoryHistoryBackendTest::kTestKeywordId = 42;
const char InMemoryHistoryBackendTest::kTestSearchTerm1[] = "banana";
const char InMemoryHistoryBackendTest::kTestSearchTerm2[] = "orange";

// http://crbug.com/114287
#if defined(OS_WIN)
#define MAYBE_Loaded DISABLED_Loaded
#else
#define MAYBE_Loaded Loaded
#endif  // defined(OS_WIN)
TEST_F(HistoryBackendTest, MAYBE_Loaded) {
  ASSERT_TRUE(backend_.get());
  ASSERT_TRUE(loaded_);
}

TEST_F(HistoryBackendTest, DeleteAll) {
  ASSERT_TRUE(backend_.get());

  // Add two favicons, each with two bitmaps. Note that we add favicon2 before
  // adding favicon1. This is so that favicon1 one gets ID 2 autoassigned to
  // the database, which will change when the other one is deleted. This way
  // we can test that updating works properly.
  GURL favicon_url1("http://www.google.com/favicon.ico");
  GURL favicon_url2("http://news.google.com/favicon.ico");
  favicon_base::FaviconID favicon2 =
      backend_->thumbnail_db_->AddFavicon(favicon_url2, favicon_base::FAVICON);
  favicon_base::FaviconID favicon1 =
      backend_->thumbnail_db_->AddFavicon(favicon_url1, favicon_base::FAVICON);

  std::vector<unsigned char> data;
  data.push_back('a');
  EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(
      favicon1, new base::RefCountedBytes(data), FaviconBitmapType::ON_VISIT,
      base::Time::Now(), kSmallSize));
  data[0] = 'b';
  EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(
      favicon1, new base::RefCountedBytes(data), FaviconBitmapType::ON_VISIT,
      base::Time::Now(), kLargeSize));

  data[0] = 'c';
  EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(
      favicon2, new base::RefCountedBytes(data), FaviconBitmapType::ON_VISIT,
      base::Time::Now(), kSmallSize));
  data[0] = 'd';
  EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(
      favicon2, new base::RefCountedBytes(data), FaviconBitmapType::ON_VISIT,
      base::Time::Now(), kLargeSize));

  // First visit two URLs.
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_visit_count(2);
  row1.set_typed_count(1);
  row1.set_last_visit(base::Time::Now());
  backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1);

  URLRow row2(GURL("http://news.google.com/"));
  row2.set_visit_count(1);
  row2.set_last_visit(base::Time::Now());
  backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2);

  URLRows rows;
  rows.push_back(row2);  // Reversed order for the same reason as favicons.
  rows.push_back(row1);
  backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);

  URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
  URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);

  // Get the two visits for the URLs we just added.
  VisitVector visits;
  backend_->db_->GetVisitsForURL(row1_id, &visits);
  ASSERT_EQ(1U, visits.size());

  visits.clear();
  backend_->db_->GetVisitsForURL(row2_id, &visits);
  ASSERT_EQ(1U, visits.size());

  // The in-memory backend should have been set and it should have gotten the
  // typed URL.
  ASSERT_TRUE(mem_backend_.get());
  URLRow outrow1;
  EXPECT_TRUE(mem_backend_->db_->GetRowForURL(row1.url(), NULL));

  // Star row1.
  history_client_.AddBookmark(row1.url());

  // Now finally clear all history.
  ClearBroadcastedNotifications();
  backend_->DeleteAllHistory();

  // The first URL should be preserved but the time should be cleared.
  EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1));
  EXPECT_EQ(row1.url(), outrow1.url());
  EXPECT_EQ(0, outrow1.visit_count());
  EXPECT_EQ(0, outrow1.typed_count());
  EXPECT_TRUE(base::Time() == outrow1.last_visit());

  // The second row should be deleted.
  URLRow outrow2;
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &outrow2));

  // All visits should be deleted for both URLs.
  VisitVector all_visits;
  backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
                                     &all_visits);
  ASSERT_EQ(0U, all_visits.size());

  // We should have a favicon and favicon bitmaps for the first URL only. We
  // look them up by favicon URL since the IDs may have changed.
  favicon_base::FaviconID out_favicon1 =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(favicon_url1,
                                                         favicon_base::FAVICON);
  EXPECT_TRUE(out_favicon1);

  std::vector<FaviconBitmap> favicon_bitmaps;
  EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
      out_favicon1, &favicon_bitmaps));
  ASSERT_EQ(2u, favicon_bitmaps.size());

  FaviconBitmap favicon_bitmap1 = favicon_bitmaps[0];
  FaviconBitmap favicon_bitmap2 = favicon_bitmaps[1];

  // Favicon bitmaps do not need to be in particular order.
  if (favicon_bitmap1.pixel_size == kLargeSize) {
    FaviconBitmap tmp_favicon_bitmap = favicon_bitmap1;
    favicon_bitmap1 = favicon_bitmap2;
    favicon_bitmap2 = tmp_favicon_bitmap;
  }

  EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap1.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap1.pixel_size);

  EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap2.bitmap_data));
  EXPECT_EQ(kLargeSize, favicon_bitmap2.pixel_size);

  favicon_base::FaviconID out_favicon2 =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(favicon_url2,
                                                         favicon_base::FAVICON);
  EXPECT_FALSE(out_favicon2) << "Favicon not deleted";

  // The remaining URL should still reference the same favicon, even if its
  // ID has changed.
  std::vector<IconMapping> mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      outrow1.url(), favicon_base::FAVICON, &mappings));
  EXPECT_EQ(1u, mappings.size());
  EXPECT_EQ(out_favicon1, mappings[0].icon_id);

  // The first URL should still be bookmarked.
  EXPECT_TRUE(history_client_.IsBookmarked(row1.url()));

  // Check that we fire the notification about all history having been deleted.
  ASSERT_EQ(1u, urls_deleted_notifications().size());
  EXPECT_TRUE(urls_deleted_notifications()[0].first);
  EXPECT_FALSE(urls_deleted_notifications()[0].second);
}

// Test that clearing all history does not delete bookmark favicons in the
// special case that the bookmark page URL is no longer present in the History
// database's urls table.
TEST_F(HistoryBackendTest, DeleteAllURLPreviouslyDeleted) {
  ASSERT_TRUE(backend_.get());

  GURL kPageURL("http://www.google.com");
  GURL kFaviconURL("http://www.google.com/favicon.ico");

  // Setup: Add visit for |kPageURL|.
  URLRow row(kPageURL);
  row.set_visit_count(2);
  row.set_typed_count(1);
  row.set_last_visit(base::Time::Now());
  backend_->AddPagesWithDetails(std::vector<URLRow>(1u, row),
                                history::SOURCE_BROWSED);

  // Setup: Add favicon for |kPageURL|.
  std::vector<unsigned char> data;
  data.push_back('a');
  favicon_base::FaviconID favicon = backend_->thumbnail_db_->AddFavicon(
      kFaviconURL, favicon_base::FAVICON, new base::RefCountedBytes(data),
      FaviconBitmapType::ON_VISIT, base::Time::Now(), kSmallSize);
  backend_->thumbnail_db_->AddIconMapping(row.url(), favicon);

  history_client_.AddBookmark(kPageURL);

  // Test initial state.
  URLID row_id = backend_->db_->GetRowForURL(kPageURL, NULL);
  ASSERT_NE(0, row_id);
  VisitVector visits;
  backend_->db_->GetVisitsForURL(row_id, &visits);
  ASSERT_EQ(1U, visits.size());

  std::vector<IconMapping> icon_mappings;
  ASSERT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      kPageURL, favicon_base::FAVICON, &icon_mappings));
  ASSERT_EQ(1u, icon_mappings.size());

  // Delete information for |kPageURL|, then clear all browsing data.
  backend_->DeleteURL(kPageURL);
  backend_->DeleteAllHistory();

  // Test that the entry in the url table for the bookmark is gone but that the
  // favicon data for the bookmark is still there.
  ASSERT_EQ(0, backend_->db_->GetRowForURL(kPageURL, NULL));

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      kPageURL, favicon_base::FAVICON, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
}

// Checks that adding a visit, then calling DeleteAll, and then trying to add
// data for the visited page works.  This can happen when clearing the history
// immediately after visiting a page.
TEST_F(HistoryBackendTest, DeleteAllThenAddData) {
  ASSERT_TRUE(backend_.get());

  base::Time visit_time = base::Time::Now();
  GURL url("http://www.google.com/");
  HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(),
                             history::RedirectList(),
                             ui::PAGE_TRANSITION_KEYWORD_GENERATED,
                             history::SOURCE_BROWSED, false, true);
  backend_->AddPage(request);

  // Check that a row was added.
  URLRow outrow;
  EXPECT_TRUE(backend_->db_->GetRowForURL(url, &outrow));

  // Check that the visit was added.
  VisitVector all_visits;
  backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
                                     &all_visits);
  ASSERT_EQ(1U, all_visits.size());

  // Clear all history.
  backend_->DeleteAllHistory();

  // The row should be deleted.
  EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow));

  // The visit should be deleted.
  backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
                                     &all_visits);
  ASSERT_EQ(0U, all_visits.size());

  // Try and set the title.
  backend_->SetPageTitle(url, base::UTF8ToUTF16("Title"));

  // The row should still be deleted.
  EXPECT_FALSE(backend_->db_->GetRowForURL(url, &outrow));

  // The visit should still be deleted.
  backend_->db_->GetAllVisitsInRange(base::Time(), base::Time(), 0,
                                     &all_visits);
  ASSERT_EQ(0U, all_visits.size());
}

TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) {
  GURL favicon_url1("http://www.google.com/favicon.ico");
  GURL favicon_url2("http://news.google.com/favicon.ico");

  std::vector<unsigned char> data;
  data.push_back('1');
  favicon_base::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(
      favicon_url1, favicon_base::FAVICON, new base::RefCountedBytes(data),
      FaviconBitmapType::ON_VISIT, base::Time::Now(), gfx::Size());

  data[0] = '2';
  favicon_base::FaviconID favicon2 = backend_->thumbnail_db_->AddFavicon(
      favicon_url2, favicon_base::FAVICON, new base::RefCountedBytes(data),
      FaviconBitmapType::ON_VISIT, base::Time::Now(), gfx::Size());

  // First visit two URLs.
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_visit_count(2);
  row1.set_typed_count(1);
  row1.set_last_visit(base::Time::Now());
  EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1));

  URLRow row2(GURL("http://news.google.com/"));
  row2.set_visit_count(1);
  row2.set_last_visit(base::Time::Now());
  EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2));

  URLRows rows;
  rows.push_back(row2);  // Reversed order for the same reason as favicons.
  rows.push_back(row1);
  backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);

  URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL);
  URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL);

  // Star the two URLs.
  history_client_.AddBookmark(row1.url());
  history_client_.AddBookmark(row2.url());

  // Delete url 2.
  backend_->expirer_.DeleteURL(row2.url());
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL));
  VisitVector visits;
  backend_->db_->GetVisitsForURL(row2_id, &visits);
  EXPECT_EQ(0U, visits.size());
  // The favicon should still be valid.
  EXPECT_EQ(favicon2, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
                          favicon_url2, favicon_base::FAVICON));

  // Unstar row2.
  history_client_.DelBookmark(row2.url());

  // Tell the backend it was unstarred. We have to explicitly do this as
  // BookmarkModel isn't wired up to the backend during testing.
  std::set<GURL> unstarred_urls;
  unstarred_urls.insert(row2.url());
  backend_->URLsNoLongerBookmarked(unstarred_urls);

  // The URL should still not exist.
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), NULL));
  // And the favicon should be deleted.
  EXPECT_EQ(0, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
                   favicon_url2, favicon_base::FAVICON));

  // Unstar row 1.
  history_client_.DelBookmark(row1.url());

  // Tell the backend it was unstarred. We have to explicitly do this as
  // BookmarkModel isn't wired up to the backend during testing.
  unstarred_urls.clear();
  unstarred_urls.insert(row1.url());
  backend_->URLsNoLongerBookmarked(unstarred_urls);

  // The URL should still exist (because there were visits).
  EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL));

  // There should still be visits.
  visits.clear();
  backend_->db_->GetVisitsForURL(row1_id, &visits);
  EXPECT_EQ(1U, visits.size());

  // The favicon should still be valid.
  EXPECT_EQ(favicon1, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
                          favicon_url1, favicon_base::FAVICON));
}

// Tests a handful of assertions for a navigation with a type of
// KEYWORD_GENERATED.
TEST_F(HistoryBackendTest, KeywordGenerated) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://google.com");

  base::Time visit_time = base::Time::Now() - base::TimeDelta::FromDays(1);
  HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(),
                             history::RedirectList(),
                             ui::PAGE_TRANSITION_KEYWORD_GENERATED,
                             history::SOURCE_BROWSED, false, true);
  backend_->AddPage(request);

  // A row should have been added for the url.
  URLRow row;
  URLID url_id = backend_->db()->GetRowForURL(url, &row);
  ASSERT_NE(0, url_id);

  // The typed count should be 1.
  ASSERT_EQ(1, row.typed_count());

  // KEYWORD_GENERATED urls should not be added to the segment db.
  std::string segment_name = VisitSegmentDatabase::ComputeSegmentName(url);
  EXPECT_EQ(0, backend_->db()->GetSegmentNamed(segment_name));

  // One visit should be added.
  VisitVector visits;
  EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits));
  EXPECT_EQ(1U, visits.size());

  // But no visible visits.
  visits.clear();
  QueryOptions query_options;
  query_options.max_count = 1;
  backend_->db()->GetVisibleVisitsInRange(query_options, &visits);
  EXPECT_TRUE(visits.empty());

  // Going back to the same entry should not increment the typed count.
  ui::PageTransition back_transition = ui::PageTransitionFromInt(
      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK);
  HistoryAddPageArgs back_request(url, visit_time, NULL, 0, GURL(),
                                  history::RedirectList(), back_transition,
                                  history::SOURCE_BROWSED, false, true);
  backend_->AddPage(back_request);
  url_id = backend_->db()->GetRowForURL(url, &row);
  ASSERT_NE(0, url_id);
  ASSERT_EQ(1, row.typed_count());

  // Expire the visits.
  std::set<GURL> restrict_urls;
  backend_->expire_backend()->ExpireHistoryBetween(restrict_urls, visit_time,
                                                   base::Time::Now());

  // The visit should have been nuked.
  visits.clear();
  EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits));
  EXPECT_TRUE(visits.empty());

  // As well as the url.
  ASSERT_EQ(0, backend_->db()->GetRowForURL(url, &row));
}

TEST_F(HistoryBackendTest, ClientRedirect) {
  ASSERT_TRUE(backend_.get());

  int transition1;
  int transition2;

  // Initial transition to page A.
  GURL url_a("http://google.com/a");
  AddClientRedirect(GURL(), url_a, false, base::Time(),
                    &transition1, &transition2);
  EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END);

  // User initiated redirect to page B.
  GURL url_b("http://google.com/b");
  AddClientRedirect(url_a, url_b, false, base::Time(),
                    &transition1, &transition2);
  EXPECT_TRUE(transition1 & ui::PAGE_TRANSITION_CHAIN_END);
  EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END);

  // Non-user initiated redirect to page C.
  GURL url_c("http://google.com/c");
  AddClientRedirect(url_b, url_c, true, base::Time(),
                    &transition1, &transition2);
  EXPECT_FALSE(transition1 & ui::PAGE_TRANSITION_CHAIN_END);
  EXPECT_TRUE(transition2 & ui::PAGE_TRANSITION_CHAIN_END);
}

TEST_F(HistoryBackendTest, AddPagesWithDetails) {
  ASSERT_TRUE(backend_.get());

  // Import one non-typed URL, and two recent and one expired typed URLs.
  URLRow row1(GURL("https://news.google.com/"));
  row1.set_visit_count(1);
  row1.set_last_visit(base::Time::Now());
  URLRow row2(GURL("https://www.google.com/"));
  row2.set_typed_count(1);
  row2.set_last_visit(base::Time::Now());
  URLRow row3(GURL("https://mail.google.com/"));
  row3.set_visit_count(1);
  row3.set_typed_count(1);
  row3.set_last_visit(base::Time::Now() - base::TimeDelta::FromDays(7 - 1));
  URLRow row4(GURL("https://maps.google.com/"));
  row4.set_visit_count(1);
  row4.set_typed_count(1);
  row4.set_last_visit(base::Time::Now() - base::TimeDelta::FromDays(365 + 2));

  URLRows rows;
  rows.push_back(row1);
  rows.push_back(row2);
  rows.push_back(row3);
  rows.push_back(row4);
  backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);

  // Verify that recent URLs have ended up in the main |db_|, while the already
  // expired URL has been ignored.
  URLRow stored_row1, stored_row2, stored_row3, stored_row4;
  EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1));
  EXPECT_NE(0, backend_->db_->GetRowForURL(row2.url(), &stored_row2));
  EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3));
  EXPECT_EQ(0, backend_->db_->GetRowForURL(row4.url(), &stored_row4));

  // Ensure that a notification was fired for both typed and non-typed URLs.
  // Further verify that the IDs in the notification are set to those that are
  // in effect in the main database. The InMemoryHistoryBackend relies on this
  // for caching.
  ASSERT_EQ(1, num_urls_modified_notifications());

  const URLRows& changed_urls = urls_modified_notifications()[0];
  EXPECT_EQ(3u, changed_urls.size());

  URLRows::const_iterator it_row1 =
      std::find_if(changed_urls.begin(), changed_urls.end(),
                   history::URLRow::URLRowHasURL(row1.url()));
  ASSERT_NE(changed_urls.end(), it_row1);
  EXPECT_EQ(stored_row1.id(), it_row1->id());

  URLRows::const_iterator it_row2 =
      std::find_if(changed_urls.begin(), changed_urls.end(),
                   history::URLRow::URLRowHasURL(row2.url()));
  ASSERT_NE(changed_urls.end(), it_row2);
  EXPECT_EQ(stored_row2.id(), it_row2->id());

  URLRows::const_iterator it_row3 =
      std::find_if(changed_urls.begin(), changed_urls.end(),
                   history::URLRow::URLRowHasURL(row3.url()));
  ASSERT_NE(changed_urls.end(), it_row3);
  EXPECT_EQ(stored_row3.id(), it_row3->id());
}

TEST_F(HistoryBackendTest, UpdateURLs) {
  ASSERT_TRUE(backend_.get());

  // Add three pages directly to the database.
  URLRow row1(GURL("https://news.google.com/"));
  row1.set_visit_count(1);
  row1.set_last_visit(base::Time::Now());
  URLRow row2(GURL("https://maps.google.com/"));
  row2.set_visit_count(2);
  row2.set_last_visit(base::Time::Now());
  URLRow row3(GURL("https://www.google.com/"));
  row3.set_visit_count(3);
  row3.set_last_visit(base::Time::Now());

  backend_->db_->AddURL(row1);
  backend_->db_->AddURL(row2);
  backend_->db_->AddURL(row3);

  // Now create changed versions of all URLRows by incrementing their visit
  // counts, and in the meantime, also delete the second row from the database.
  URLRow altered_row1, altered_row2, altered_row3;
  backend_->db_->GetRowForURL(row1.url(), &altered_row1);
  altered_row1.set_visit_count(42);
  backend_->db_->GetRowForURL(row2.url(), &altered_row2);
  altered_row2.set_visit_count(43);
  backend_->db_->GetRowForURL(row3.url(), &altered_row3);
  altered_row3.set_visit_count(44);

  backend_->db_->DeleteURLRow(altered_row2.id());

  // Now try to update all three rows at once. The change to the second URLRow
  // should be ignored, as it is no longer present in the DB.
  URLRows rows;
  rows.push_back(altered_row1);
  rows.push_back(altered_row2);
  rows.push_back(altered_row3);
  EXPECT_EQ(2u, backend_->UpdateURLs(rows));

  URLRow stored_row1, stored_row3;
  EXPECT_NE(0, backend_->db_->GetRowForURL(row1.url(), &stored_row1));
  EXPECT_NE(0, backend_->db_->GetRowForURL(row3.url(), &stored_row3));
  EXPECT_EQ(altered_row1.visit_count(), stored_row1.visit_count());
  EXPECT_EQ(altered_row3.visit_count(), stored_row3.visit_count());

  // Ensure that a notification was fired, and further verify that the IDs in
  // the notification are set to those that are in effect in the main database.
  // The InMemoryHistoryBackend relies on this for caching.
  ASSERT_EQ(1, num_urls_modified_notifications());

  const URLRows& changed_urls = urls_modified_notifications()[0];
  EXPECT_EQ(2u, changed_urls.size());

  URLRows::const_iterator it_row1 =
      std::find_if(changed_urls.begin(), changed_urls.end(),
                   history::URLRow::URLRowHasURL(row1.url()));
  ASSERT_NE(changed_urls.end(), it_row1);
  EXPECT_EQ(altered_row1.id(), it_row1->id());
  EXPECT_EQ(altered_row1.visit_count(), it_row1->visit_count());

  URLRows::const_iterator it_row3 =
      std::find_if(changed_urls.begin(), changed_urls.end(),
                   history::URLRow::URLRowHasURL(row3.url()));
  ASSERT_NE(changed_urls.end(), it_row3);
  EXPECT_EQ(altered_row3.id(), it_row3->id());
  EXPECT_EQ(altered_row3.visit_count(), it_row3->visit_count());
}

// This verifies that a notification is fired. In-depth testing of logic should
// be done in HistoryTest.SetTitle.
TEST_F(HistoryBackendTest, SetPageTitleFiresNotificationWithCorrectDetails) {
  const char kTestUrlTitle[] = "Google Search";

  ASSERT_TRUE(backend_.get());

  // Add two pages, then change the title of the second one.
  URLRow row1(GURL("https://news.google.com/"));
  row1.set_typed_count(1);
  row1.set_last_visit(base::Time::Now());
  URLRow row2(GURL("https://www.google.com/"));
  row2.set_visit_count(2);
  row2.set_last_visit(base::Time::Now());

  URLRows rows;
  rows.push_back(row1);
  rows.push_back(row2);
  backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);

  ClearBroadcastedNotifications();
  backend_->SetPageTitle(row2.url(), base::UTF8ToUTF16(kTestUrlTitle));

  // Ensure that a notification was fired, and further verify that the IDs in
  // the notification are set to those that are in effect in the main database.
  // The InMemoryHistoryBackend relies on this for caching.
  URLRow stored_row2;
  EXPECT_TRUE(backend_->GetURL(row2.url(), &stored_row2));
  ASSERT_EQ(1, num_urls_modified_notifications());

  const URLRows& changed_urls = urls_modified_notifications()[0];
  ASSERT_EQ(1u, changed_urls.size());
  EXPECT_EQ(base::UTF8ToUTF16(kTestUrlTitle), changed_urls[0].title());
  EXPECT_EQ(stored_row2.id(), changed_urls[0].id());
}

// There's no importer on Android.
#if !defined(OS_ANDROID)
TEST_F(HistoryBackendTest, ImportedFaviconsTest) {
  // Setup test data - two Urls in the history, one with favicon assigned and
  // one without.
  GURL favicon_url1("http://www.google.com/favicon.ico");
  std::vector<unsigned char> data;
  data.push_back('1');
  favicon_base::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(
      favicon_url1, favicon_base::FAVICON,
      base::RefCountedBytes::TakeVector(&data), FaviconBitmapType::ON_VISIT,
      base::Time::Now(), gfx::Size());
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_visit_count(1);
  row1.set_last_visit(base::Time::Now());
  EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1));

  URLRow row2(GURL("http://news.google.com/"));
  row2.set_visit_count(1);
  row2.set_last_visit(base::Time::Now());
  URLRows rows;
  rows.push_back(row1);
  rows.push_back(row2);
  backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
  URLRow url_row1, url_row2;
  EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0);
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(row1.url(), favicon_base::FAVICON));
  EXPECT_EQ(0u, NumIconMappingsForPageURL(row2.url(), favicon_base::FAVICON));

  // Now provide one imported favicon for both URLs already in the registry.
  // The new favicon should only be used with the URL that doesn't already have
  // a favicon.
  favicon_base::FaviconUsageDataList favicons;
  favicon_base::FaviconUsageData favicon;
  favicon.favicon_url = GURL("http://news.google.com/favicon.ico");
  favicon.png_data.push_back('2');
  favicon.urls.insert(row1.url());
  favicon.urls.insert(row2.url());
  favicons.push_back(favicon);
  backend_->SetImportedFavicons(favicons);
  EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0);
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0);

  std::vector<IconMapping> mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      row1.url(), favicon_base::FAVICON, &mappings));
  EXPECT_EQ(1u, mappings.size());
  EXPECT_EQ(favicon1, mappings[0].icon_id);
  EXPECT_EQ(favicon_url1, mappings[0].icon_url);

  mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      row2.url(), favicon_base::FAVICON, &mappings));
  EXPECT_EQ(1u, mappings.size());
  EXPECT_EQ(favicon.favicon_url, mappings[0].icon_url);

  // A URL should not be added to history (to store favicon), if
  // the URL is not bookmarked.
  GURL url3("http://mail.google.com");
  favicons.clear();
  favicon.favicon_url = GURL("http://mail.google.com/favicon.ico");
  favicon.png_data.push_back('3');
  favicon.urls.insert(url3);
  favicons.push_back(favicon);
  backend_->SetImportedFavicons(favicons);
  URLRow url_row3;
  EXPECT_TRUE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);

  // If the URL is bookmarked, it should get added to history with 0 visits.
  history_client_.AddBookmark(url3);
  backend_->SetImportedFavicons(favicons);
  EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);
  EXPECT_TRUE(url_row3.visit_count() == 0);
}
#endif  // !defined(OS_ANDROID)

TEST_F(HistoryBackendTest, StripUsernamePasswordTest) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://anyuser:anypass@www.google.com");
  GURL stripped_url("http://www.google.com");

  // Clear all history.
  backend_->DeleteAllHistory();

  // Visit the url with username, password.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
      ui::PageTransitionFromInt(
          ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)),
      history::SOURCE_BROWSED);

  // Fetch the row information about stripped url from history db.
  VisitVector visits;
  URLID row_id = backend_->db_->GetRowForURL(stripped_url, NULL);
  backend_->db_->GetVisitsForURL(row_id, &visits);

  // Check if stripped url is stored in database.
  ASSERT_EQ(1U, visits.size());
}

TEST_F(HistoryBackendTest, AddPageVisitBackForward) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://www.google.com");

  // Clear all history.
  backend_->DeleteAllHistory();

  // Visit the url after typing it.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
      ui::PAGE_TRANSITION_TYPED,
      history::SOURCE_BROWSED);

  // Ensure both the typed count and visit count are 1.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  EXPECT_EQ(1, row.typed_count());
  EXPECT_EQ(1, row.visit_count());

  // Visit the url again via back/forward.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
      ui::PageTransitionFromInt(
          ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FORWARD_BACK),
      history::SOURCE_BROWSED);

  // Ensure the typed count is still 1 but the visit count is 2.
  id = backend_->db()->GetRowForURL(url, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  EXPECT_EQ(1, row.typed_count());
  EXPECT_EQ(2, row.visit_count());
}

TEST_F(HistoryBackendTest, AddPageVisitRedirectBackForward) {
  ASSERT_TRUE(backend_.get());

  GURL url1("http://www.google.com");
  GURL url2("http://www.chromium.org");

  // Clear all history.
  backend_->DeleteAllHistory();

  // Visit a typed URL with a redirect.
  backend_->AddPageVisit(url1, base::Time::Now(), 0,
      ui::PAGE_TRANSITION_TYPED,
      history::SOURCE_BROWSED);
  backend_->AddPageVisit(url2, base::Time::Now(), 0,
      ui::PageTransitionFromInt(
          ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_CLIENT_REDIRECT),
      history::SOURCE_BROWSED);

  // Ensure the redirected URL does not count as typed.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url2, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  EXPECT_EQ(0, row.typed_count());
  EXPECT_EQ(1, row.visit_count());

  // Visit the redirected url again via back/forward.
  backend_->AddPageVisit(url2, base::Time::Now(), 0,
      ui::PageTransitionFromInt(
          ui::PAGE_TRANSITION_TYPED |
          ui::PAGE_TRANSITION_FORWARD_BACK |
          ui::PAGE_TRANSITION_CLIENT_REDIRECT),
      history::SOURCE_BROWSED);

  // Ensure the typed count is still 1 but the visit count is 2.
  id = backend_->db()->GetRowForURL(url2, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  EXPECT_EQ(0, row.typed_count());
  EXPECT_EQ(2, row.visit_count());
}

TEST_F(HistoryBackendTest, AddPageVisitSource) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://www.google.com");

  // Clear all history.
  backend_->DeleteAllHistory();

  // Assume visiting the url from an externsion.
  backend_->AddPageVisit(
      url, base::Time::Now(), 0, ui::PAGE_TRANSITION_TYPED,
      history::SOURCE_EXTENSION);
  // Assume the url is imported from Firefox.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
                         ui::PAGE_TRANSITION_TYPED,
                         history::SOURCE_FIREFOX_IMPORTED);
  // Assume this url is also synced.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
                         ui::PAGE_TRANSITION_TYPED,
                         history::SOURCE_SYNCED);

  // Fetch the row information about the url from history db.
  VisitVector visits;
  URLID row_id = backend_->db_->GetRowForURL(url, NULL);
  backend_->db_->GetVisitsForURL(row_id, &visits);

  // Check if all the visits to the url are stored in database.
  ASSERT_EQ(3U, visits.size());
  VisitSourceMap visit_sources;
  ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
  ASSERT_EQ(3U, visit_sources.size());
  int sources = 0;
  for (int i = 0; i < 3; i++) {
    switch (visit_sources[visits[i].visit_id]) {
      case history::SOURCE_EXTENSION:
        sources |= 0x1;
        break;
      case history::SOURCE_FIREFOX_IMPORTED:
        sources |= 0x2;
        break;
      case history::SOURCE_SYNCED:
        sources |= 0x4;
      default:
        break;
    }
  }
  EXPECT_EQ(0x7, sources);
}

TEST_F(HistoryBackendTest, AddPageVisitNotLastVisit) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://www.google.com");

  // Clear all history.
  backend_->DeleteAllHistory();

  // Create visit times
  base::Time recent_time = base::Time::Now();
  base::TimeDelta visit_age = base::TimeDelta::FromDays(3);
  base::Time older_time = recent_time - visit_age;

  // Visit the url with recent time.
  backend_->AddPageVisit(url, recent_time, 0,
      ui::PageTransitionFromInt(
          ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)),
      history::SOURCE_BROWSED);

  // Add to the url a visit with older time (could be syncing from another
  // client, etc.).
  backend_->AddPageVisit(url, older_time, 0,
      ui::PageTransitionFromInt(
          ui::PageTransitionGetQualifier(ui::PAGE_TRANSITION_TYPED)),
      history::SOURCE_SYNCED);

  // Fetch the row information about url from history db.
  VisitVector visits;
  URLRow row;
  URLID row_id = backend_->db_->GetRowForURL(url, &row);
  backend_->db_->GetVisitsForURL(row_id, &visits);

  // Last visit time should be the most recent time, not the most recently added
  // visit.
  ASSERT_EQ(2U, visits.size());
  ASSERT_EQ(recent_time, row.last_visit());
}

TEST_F(HistoryBackendTest, AddPageVisitFiresNotificationWithCorrectDetails) {
  ASSERT_TRUE(backend_.get());

  GURL url1("http://www.google.com");
  GURL url2("http://maps.google.com");

  // Clear all history.
  backend_->DeleteAllHistory();
  ClearBroadcastedNotifications();

  // Visit two distinct URLs, the second one twice.
  backend_->AddPageVisit(url1, base::Time::Now(), 0,
                         ui::PAGE_TRANSITION_LINK,
                         history::SOURCE_BROWSED);
  for (int i = 0; i < 2; ++i) {
    backend_->AddPageVisit(url2, base::Time::Now(), 0,
                           ui::PAGE_TRANSITION_TYPED,
                           history::SOURCE_BROWSED);
  }

  URLRow stored_row1, stored_row2;
  EXPECT_NE(0, backend_->db_->GetRowForURL(url1, &stored_row1));
  EXPECT_NE(0, backend_->db_->GetRowForURL(url2, &stored_row2));

  // Expect that HistoryServiceObserver::OnURLVisited has been called 3 times,
  // and that each time the URLRows have the correct URLs and IDs set.
  ASSERT_EQ(3, num_url_visited_notifications());
  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[0].first,
                                           ui::PAGE_TRANSITION_LINK));
  EXPECT_EQ(stored_row1.id(), url_visited_notifications()[0].second.id());
  EXPECT_EQ(stored_row1.url(), url_visited_notifications()[0].second.url());

  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[1].first,
                                           ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(stored_row2.id(), url_visited_notifications()[1].second.id());
  EXPECT_EQ(stored_row2.url(), url_visited_notifications()[1].second.url());

  EXPECT_TRUE(ui::PageTransitionCoreTypeIs(url_visited_notifications()[2].first,
                                           ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(stored_row2.id(), url_visited_notifications()[2].second.id());
  EXPECT_EQ(stored_row2.url(), url_visited_notifications()[2].second.url());
}

TEST_F(HistoryBackendTest, AddPageArgsSource) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://testpageargs.com");

  // Assume this page is browsed by user.
  HistoryAddPageArgs request1(url, base::Time::Now(), NULL, 0, GURL(),
                             history::RedirectList(),
                             ui::PAGE_TRANSITION_KEYWORD_GENERATED,
                             history::SOURCE_BROWSED, false, true);
  backend_->AddPage(request1);
  // Assume this page is synced.
  HistoryAddPageArgs request2(url, base::Time::Now(), NULL, 0, GURL(),
                             history::RedirectList(),
                             ui::PAGE_TRANSITION_LINK,
                             history::SOURCE_SYNCED, false, true);
  backend_->AddPage(request2);
  // Assume this page is browsed again.
  HistoryAddPageArgs request3(url, base::Time::Now(), NULL, 0, GURL(),
                             history::RedirectList(),
                             ui::PAGE_TRANSITION_TYPED,
                             history::SOURCE_BROWSED, false, true);
  backend_->AddPage(request3);

  // Three visits should be added with proper sources.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(3U, visits.size());
  VisitSourceMap visit_sources;
  ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
  ASSERT_EQ(1U, visit_sources.size());
  EXPECT_EQ(history::SOURCE_SYNCED, visit_sources.begin()->second);
}

TEST_F(HistoryBackendTest, AddVisitsSource) {
  ASSERT_TRUE(backend_.get());

  GURL url1("http://www.cnn.com");
  std::vector<VisitInfo> visits1, visits2;
  visits1.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(5),
      ui::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(1),
      ui::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      base::Time::Now(), ui::PAGE_TRANSITION_LINK));

  GURL url2("http://www.example.com");
  visits2.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(10),
      ui::PAGE_TRANSITION_LINK));
  visits2.push_back(VisitInfo(base::Time::Now(), ui::PAGE_TRANSITION_LINK));

  // Clear all history.
  backend_->DeleteAllHistory();

  // Add the visits.
  backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED);
  backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED);

  // Verify the visits were added with their sources.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(3U, visits.size());
  VisitSourceMap visit_sources;
  ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
  ASSERT_EQ(3U, visit_sources.size());
  for (int i = 0; i < 3; i++)
    EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_sources[visits[i].visit_id]);
  id = backend_->db()->GetRowForURL(url2, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(2U, visits.size());
  ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
  ASSERT_EQ(2U, visit_sources.size());
  for (int i = 0; i < 2; i++)
    EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]);
}

TEST_F(HistoryBackendTest, GetMostRecentVisits) {
  ASSERT_TRUE(backend_.get());

  GURL url1("http://www.cnn.com");
  std::vector<VisitInfo> visits1;
  visits1.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(5),
      ui::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(1),
      ui::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      base::Time::Now(), ui::PAGE_TRANSITION_LINK));

  // Clear all history.
  backend_->DeleteAllHistory();

  // Add the visits.
  backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED);

  // Verify the visits were added with their sources.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetMostRecentVisitsForURL(id, 1, &visits));
  ASSERT_EQ(1U, visits.size());
  EXPECT_EQ(visits1[2].first, visits[0].visit_time);
}

TEST_F(HistoryBackendTest, RemoveVisitsTransitions) {
  ASSERT_TRUE(backend_.get());

  // Clear all history.
  backend_->DeleteAllHistory();

  GURL url1("http://www.cnn.com");
  VisitInfo typed_visit(
      base::Time::Now() - base::TimeDelta::FromDays(6),
      ui::PAGE_TRANSITION_TYPED);
  VisitInfo reload_visit(
      base::Time::Now() - base::TimeDelta::FromDays(5),
      ui::PAGE_TRANSITION_RELOAD);
  VisitInfo link_visit(
      base::Time::Now() - base::TimeDelta::FromDays(4),
      ui::PAGE_TRANSITION_LINK);
  std::vector<VisitInfo> visits_to_add;
  visits_to_add.push_back(typed_visit);
  visits_to_add.push_back(reload_visit);
  visits_to_add.push_back(link_visit);

  // Add the visits.
  backend_->AddVisits(url1, visits_to_add, history::SOURCE_SYNCED);

  // Verify that the various counts are what we expect.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(3U, visits.size());
  ASSERT_EQ(1, row.typed_count());
  ASSERT_EQ(2, row.visit_count());

  // Now, delete the typed visit and verify that typed_count is updated.
  ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0])));
  id = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(2U, visits.size());
  ASSERT_EQ(0, row.typed_count());
  ASSERT_EQ(1, row.visit_count());

  // Delete the reload visit now and verify that none of the counts have
  // changed.
  ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0])));
  id = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(1U, visits.size());
  ASSERT_EQ(0, row.typed_count());
  ASSERT_EQ(1, row.visit_count());

  // Delete the last visit and verify that we delete the URL.
  ASSERT_TRUE(backend_->RemoveVisits(VisitVector(1, visits[0])));
  ASSERT_EQ(0, backend_->db()->GetRowForURL(url1, &row));
}

TEST_F(HistoryBackendTest, RemoveVisitsSource) {
  ASSERT_TRUE(backend_.get());

  GURL url1("http://www.cnn.com");
  std::vector<VisitInfo> visits1, visits2;
  visits1.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(5),
      ui::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(base::Time::Now(),
    ui::PAGE_TRANSITION_LINK));

  GURL url2("http://www.example.com");
  visits2.push_back(VisitInfo(
      base::Time::Now() - base::TimeDelta::FromDays(10),
      ui::PAGE_TRANSITION_LINK));
  visits2.push_back(VisitInfo(base::Time::Now(), ui::PAGE_TRANSITION_LINK));

  // Clear all history.
  backend_->DeleteAllHistory();

  // Add the visits.
  backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED);
  backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED);

  // Verify the visits of url1 were added.
  VisitVector visits;
  URLRow row;
  URLID id = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(2U, visits.size());
  // Remove these visits.
  ASSERT_TRUE(backend_->RemoveVisits(visits));

  // Now check only url2's source in visit_source table.
  VisitSourceMap visit_sources;
  ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
  ASSERT_EQ(0U, visit_sources.size());
  id = backend_->db()->GetRowForURL(url2, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits));
  ASSERT_EQ(2U, visits.size());
  ASSERT_TRUE(backend_->GetVisitsSource(visits, &visit_sources));
  ASSERT_EQ(2U, visit_sources.size());
  for (int i = 0; i < 2; i++)
    EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]);
}

// Test for migration of adding visit_source table.
TEST_F(HistoryBackendTest, MigrationVisitSource) {
  ASSERT_TRUE(backend_.get());
  backend_->Closing();
  backend_ = NULL;

  base::FilePath old_history_path;
  ASSERT_TRUE(GetTestDataHistoryDir(&old_history_path));
  old_history_path = old_history_path.AppendASCII("HistoryNoSource");

  // Copy history database file to current directory so that it will be deleted
  // in Teardown.
  base::FilePath new_history_path(test_dir());
  base::DeleteFile(new_history_path, true);
  base::CreateDirectory(new_history_path);
  base::FilePath new_history_file = new_history_path.Append(kHistoryFilename);
  ASSERT_TRUE(base::CopyFile(old_history_path, new_history_file));

  backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this),
                                history_client_.CreateBackendClient(),
                                base::ThreadTaskRunnerHandle::Get());
  backend_->Init(false, TestHistoryDatabaseParamsForPath(new_history_path));
  backend_->Closing();
  backend_ = NULL;

  // Now the database should already be migrated.
  // Check version first.
  int cur_version = HistoryDatabase::GetCurrentVersion();
  sql::Connection db;
  ASSERT_TRUE(db.Open(new_history_file));
  sql::Statement s(db.GetUniqueStatement(
      "SELECT value FROM meta WHERE key = 'version'"));
  ASSERT_TRUE(s.Step());
  int file_version = s.ColumnInt(0);
  EXPECT_EQ(cur_version, file_version);

  // Check visit_source table is created and empty.
  s.Assign(db.GetUniqueStatement(
      "SELECT name FROM sqlite_master WHERE name=\"visit_source\""));
  ASSERT_TRUE(s.Step());
  s.Assign(db.GetUniqueStatement("SELECT * FROM visit_source LIMIT 10"));
  EXPECT_FALSE(s.Step());
}

// Test that SetFaviconMappingsForPageAndRedirects correctly updates icon
// mappings based on redirects, icon URLs and icon types.
TEST_F(HistoryBackendTest, SetFaviconMappingsForPageAndRedirects) {
  // Init recent_redirects_
  const GURL url1("http://www.google.com");
  const GURL url2("http://www.google.com/m");
  URLRow url_info1(url1);
  url_info1.set_visit_count(0);
  url_info1.set_typed_count(0);
  url_info1.set_last_visit(base::Time());
  url_info1.set_hidden(false);
  backend_->db_->AddURL(url_info1);

  URLRow url_info2(url2);
  url_info2.set_visit_count(0);
  url_info2.set_typed_count(0);
  url_info2.set_last_visit(base::Time());
  url_info2.set_hidden(false);
  backend_->db_->AddURL(url_info2);

  history::RedirectList redirects;
  redirects.push_back(url2);
  redirects.push_back(url1);
  backend_->recent_redirects_.Put(url1, redirects);

  const GURL icon_url1("http://www.google.com/icon");
  const GURL icon_url2("http://www.google.com/icon2");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));

  // Add a favicon.
  backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));

  // Add one touch_icon
  backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::TOUCH_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));

  // Add one TOUCH_PRECOMPOSED_ICON
  backend_->SetFavicons(
      url1, favicon_base::TOUCH_PRECOMPOSED_ICON, icon_url1, bitmaps);
  // The touch_icon was replaced.
  EXPECT_EQ(0u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  EXPECT_EQ(
      1u,
      NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON));
  EXPECT_EQ(
      1u,
      NumIconMappingsForPageURL(url2, favicon_base::TOUCH_PRECOMPOSED_ICON));

  // Add a touch_icon.
  backend_->SetFavicons(url1, favicon_base::TOUCH_ICON, icon_url1, bitmaps);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  // The TOUCH_PRECOMPOSED_ICON was replaced.
  EXPECT_EQ(
      0u,
      NumIconMappingsForPageURL(url1, favicon_base::TOUCH_PRECOMPOSED_ICON));

  // Add a web manifest_icon.
  backend_->SetFavicons(url1, favicon_base::WEB_MANIFEST_ICON, icon_url2,
                        bitmaps);
  EXPECT_EQ(1u,
            NumIconMappingsForPageURL(url1, favicon_base::WEB_MANIFEST_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  // The TOUCH_ICON_ICON was replaced.
  EXPECT_EQ(0u, NumIconMappingsForPageURL(url1, favicon_base::TOUCH_ICON));

  // The TOUCH_PRECOMPOSED_ICON was replaced.
  EXPECT_EQ(0u, NumIconMappingsForPageURL(
                    url1, favicon_base::TOUCH_PRECOMPOSED_ICON));

  // Add a different favicon.
  backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url2, bitmaps);
  EXPECT_EQ(1u,
            NumIconMappingsForPageURL(url1, favicon_base::WEB_MANIFEST_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
}


// Test that SetFaviconMappingsForPageAndRedirects correctly updates icon
// mappings when the final URL has a fragment.
TEST_F(HistoryBackendTest, SetFaviconMappingsForPageAndRedirectsWithFragment) {
  // URLs used for recent_redirects_
  const GURL url1("http://www.google.com#abc");
  const GURL url2("http://www.google.com");
  const GURL url3("http://www.google.com/m");
  URLRow url_info1(url1);
  url_info1.set_visit_count(0);
  url_info1.set_typed_count(0);
  url_info1.set_last_visit(base::Time());
  url_info1.set_hidden(false);
  backend_->db_->AddURL(url_info1);

  URLRow url_info2(url2);
  url_info2.set_visit_count(0);
  url_info2.set_typed_count(0);
  url_info2.set_last_visit(base::Time());
  url_info2.set_hidden(false);
  backend_->db_->AddURL(url_info2);

  URLRow url_info3(url3);
  url_info3.set_visit_count(0);
  url_info3.set_typed_count(0);
  url_info3.set_last_visit(base::Time());
  url_info3.set_hidden(false);
  backend_->db_->AddURL(url_info3);

  // Icon URL.
  const GURL icon_url1("http://www.google.com/icon");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  // Page URL has a fragment, but redirect is keyed to the same URL without a
  // fragment.
  history::RedirectList redirects;
  redirects.push_back(url3);
  redirects.push_back(url2);
  backend_->recent_redirects_.Put(url2, redirects);

  backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url3, favicon_base::FAVICON));

  // Both page and redirect key have a fragment.
  redirects.clear();
  redirects.push_back(url3);
  redirects.push_back(url2);
  redirects.push_back(url1);
  backend_->recent_redirects_.Clear();
  backend_->recent_redirects_.Put(url1, redirects);

  backend_->SetFavicons(url1, favicon_base::FAVICON, icon_url1, bitmaps);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, favicon_base::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, favicon_base::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url3, favicon_base::FAVICON));
}

// Test that there is no churn in icon mappings from calling
// SetFavicons() twice with the same |bitmaps| parameter.
TEST_F(HistoryBackendTest, SetFaviconMappingsForPageDuplicates) {
  const GURL url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon");

  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));

  backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps);

  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      url, favicon_base::FAVICON, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  IconMappingID mapping_id = icon_mappings[0].mapping_id;

  backend_->SetFavicons(url, favicon_base::FAVICON, icon_url, bitmaps);

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      url, favicon_base::FAVICON, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());

  // The same row in the icon_mapping table should be used for the mapping as
  // before.
  EXPECT_EQ(mapping_id, icon_mappings[0].mapping_id);
}

// Test that calling SetFavicons() with FaviconBitmapData of different pixel
// sizes than the initially passed in FaviconBitmapData deletes the no longer
// used favicon bitmaps.
TEST_F(HistoryBackendTest, SetFaviconsDeleteBitmaps) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon");

  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  // Test initial state.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url, icon_mappings[0].icon_url);
  EXPECT_EQ(favicon_base::FAVICON, icon_mappings[0].icon_type);
  favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id;

  std::vector<FaviconBitmap> favicon_bitmaps;
  EXPECT_TRUE(GetSortedFaviconBitmaps(favicon_id, &favicon_bitmaps));
  EXPECT_EQ(2u, favicon_bitmaps.size());
  FaviconBitmapID small_bitmap_id = favicon_bitmaps[0].bitmap_id;
  EXPECT_NE(0, small_bitmap_id);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmaps[0].bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmaps[0].pixel_size);
  FaviconBitmapID large_bitmap_id = favicon_bitmaps[1].bitmap_id;
  EXPECT_NE(0, large_bitmap_id);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, favicon_bitmaps[1].bitmap_data));
  EXPECT_EQ(kLargeSize, favicon_bitmaps[1].pixel_size);

  // Call SetFavicons() with bitmap data for only the large bitmap. Check that
  // the small bitmap is in fact deleted.
  bitmaps.clear();
  bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kLargeEdgeSize));
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  scoped_refptr<base::RefCountedMemory> bitmap_data_out;
  gfx::Size pixel_size_out;
  EXPECT_FALSE(backend_->thumbnail_db_->GetFaviconBitmap(small_bitmap_id,
      NULL, NULL, &bitmap_data_out, &pixel_size_out));
  EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmap(large_bitmap_id,
      NULL, NULL, &bitmap_data_out, &pixel_size_out));
  EXPECT_TRUE(BitmapColorEqual(SK_ColorWHITE, bitmap_data_out));
  EXPECT_EQ(kLargeSize, pixel_size_out);

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
}

// Test updating a single favicon bitmap's data via SetFavicons.
TEST_F(HistoryBackendTest, SetFaviconsReplaceBitmapData) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  // Add bitmap to the database.
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  favicon_base::FaviconID original_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(icon_url,
                                                         favicon_base::FAVICON);
  EXPECT_NE(0, original_favicon_id);
  FaviconBitmap original_favicon_bitmap;
  EXPECT_TRUE(
      GetOnlyFaviconBitmap(original_favicon_id, &original_favicon_bitmap));
  EXPECT_TRUE(
      BitmapColorEqual(SK_ColorBLUE, original_favicon_bitmap.bitmap_data));
  EXPECT_NE(base::Time(), original_favicon_bitmap.last_updated);

  // Call SetFavicons() with completely identical data.
  bitmaps[0] = CreateBitmap(SK_ColorBLUE, kSmallEdgeSize);
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  favicon_base::FaviconID updated_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(icon_url,
                                                         favicon_base::FAVICON);
  EXPECT_NE(0, updated_favicon_id);
  FaviconBitmap updated_favicon_bitmap;
  EXPECT_TRUE(
      GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap));
  EXPECT_TRUE(
      BitmapColorEqual(SK_ColorBLUE, updated_favicon_bitmap.bitmap_data));
  EXPECT_NE(base::Time(), updated_favicon_bitmap.last_updated);

  // Call SetFavicons() with a different bitmap of the same size.
  bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize);
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  updated_favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
      icon_url, favicon_base::FAVICON);
  EXPECT_NE(0, updated_favicon_id);
  EXPECT_TRUE(
      GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap));
  EXPECT_TRUE(
      BitmapColorEqual(SK_ColorWHITE, updated_favicon_bitmap.bitmap_data));

  // There should be no churn in FaviconIDs or FaviconBitmapIds even though
  // the bitmap data changed.
  EXPECT_EQ(original_favicon_bitmap.icon_id, updated_favicon_bitmap.icon_id);
  EXPECT_EQ(original_favicon_bitmap.bitmap_id,
            updated_favicon_bitmap.bitmap_id);
}

// Test that if two pages share the same FaviconID, changing the favicon for
// one page does not affect the other.
TEST_F(HistoryBackendTest, SetFaviconsSameFaviconURLForTwoPages) {
  GURL icon_url("http://www.google.com/favicon.ico");
  GURL icon_url_new("http://www.google.com/favicon2.ico");
  GURL page_url1("http://www.google.com");
  GURL page_url2("http://www.google.ca");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));

  backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps);

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
  backend_->UpdateFaviconMappingsAndFetch(
      page_url2, icon_url, favicon_base::FAVICON, GetEdgeSizesSmallAndLarge(),
      &bitmap_results);

  // Check that the same FaviconID is mapped to both page URLs.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      page_url1, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  favicon_base::FaviconID favicon_id = icon_mappings[0].icon_id;
  EXPECT_NE(0, favicon_id);

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      page_url2, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);

  // Change the icon URL that |page_url1| is mapped to.
  bitmaps.clear();
  bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize));
  backend_->SetFavicons(
      page_url1, favicon_base::FAVICON, icon_url_new, bitmaps);

  // |page_url1| should map to a new FaviconID and have valid bitmap data.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      page_url1, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url_new, icon_mappings[0].icon_url);
  EXPECT_NE(favicon_id, icon_mappings[0].icon_id);

  std::vector<FaviconBitmap> favicon_bitmaps;
  EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
      icon_mappings[0].icon_id, &favicon_bitmaps));
  EXPECT_EQ(1u, favicon_bitmaps.size());

  // |page_url2| should still map to the same FaviconID and have valid bitmap
  // data.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      page_url2, &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);

  favicon_bitmaps.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(favicon_id,
                                                         &favicon_bitmaps));
  EXPECT_EQ(2u, favicon_bitmaps.size());
}

// Tests calling SetOnDemandFavicons(). Neither |page_url| nor |icon_url| are
// known to the database.
TEST_F(HistoryBackendTest, SetOnDemandFaviconsForEmptyDB) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http:/www.google.com/favicon.ico");

  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kSmallEdgeSize));

  // Call SetOnDemandFavicons() with a different icon URL and bitmap data.
  EXPECT_TRUE(backend_->SetOnDemandFavicons(page_url, favicon_base::FAVICON,
                                            icon_url, bitmaps));

  favicon_base::FaviconID favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(icon_url,
                                                         favicon_base::FAVICON);
  EXPECT_NE(0, favicon_id);

  FaviconBitmap favicon_bitmap;
  ASSERT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
  // The original bitmap should have been retrieved.
  EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, favicon_bitmap.bitmap_data));
  // The favicon should not be marked as expired.
  EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
}

// Tests calling SetOnDemandFavicons(). |page_url| is known to the database
// but |icon_url| is not (the second should be irrelevant though).
TEST_F(HistoryBackendTest, SetOnDemandFaviconsForPageInDB) {
  GURL page_url("http://www.google.com");
  GURL icon_url1("http:/www.google.com/favicon1.ico");
  GURL icon_url2("http:/www.google.com/favicon2.ico");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  // Add bitmap to the database.
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps);
  favicon_base::FaviconID original_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(icon_url1,
                                                         favicon_base::FAVICON);
  ASSERT_NE(0, original_favicon_id);

  // Call SetOnDemandFavicons() with a different icon URL and bitmap data.
  bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize);
  EXPECT_FALSE(backend_->SetOnDemandFavicons(page_url, favicon_base::FAVICON,
                                             icon_url2, bitmaps));
  EXPECT_EQ(0, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
                   icon_url2, favicon_base::FAVICON));

  FaviconBitmap favicon_bitmap;
  ASSERT_TRUE(GetOnlyFaviconBitmap(original_favicon_id, &favicon_bitmap));
  // The original bitmap should have been retrieved.
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
  // The favicon should not be marked as expired.
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
}

// Tests calling SetOnDemandFavicons(). |page_url| is not known to the
// database but |icon_url| is.
TEST_F(HistoryBackendTest, SetOnDemandFaviconsForIconInDB) {
  const GURL old_page_url("http://www.google.com/old");
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  // Add bitmap to the database.
  backend_->SetFavicons(old_page_url, favicon_base::FAVICON, icon_url, bitmaps);
  favicon_base::FaviconID original_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(icon_url,
                                                         favicon_base::FAVICON);
  ASSERT_NE(0, original_favicon_id);

  // Call SetOnDemandFavicons() with a different bitmap.
  bitmaps[0] = CreateBitmap(SK_ColorWHITE, kSmallEdgeSize);
  EXPECT_FALSE(backend_->SetOnDemandFavicons(page_url, favicon_base::FAVICON,
                                             icon_url, bitmaps));

  EXPECT_EQ(original_favicon_id,
            backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
                icon_url, favicon_base::FAVICON));

  FaviconBitmap favicon_bitmap;
  ASSERT_TRUE(GetOnlyFaviconBitmap(original_favicon_id, &favicon_bitmap));
  // The original bitmap should have been retrieved.
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
  // The favicon should not be marked as expired.
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
}

// Test repeatedly calling MergeFavicon(). |page_url| is initially not known
// to the database.
TEST_F(HistoryBackendTest, MergeFaviconPageURLNotInDB) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http:/www.google.com/favicon.ico");

  std::vector<unsigned char> data;
  data.push_back('a');
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));

  backend_->MergeFavicon(
      page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);

  // |page_url| should now be mapped to |icon_url| and the favicon bitmap should
  // not be expired.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url, icon_mappings[0].icon_url);

  FaviconBitmap favicon_bitmap;
  EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapDataEqual('a', favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  data[0] = 'b';
  bitmap_data = new base::RefCountedBytes(data);
  backend_->MergeFavicon(
      page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);

  // |page_url| should still have a single favicon bitmap. The bitmap data
  // should be updated.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url, icon_mappings[0].icon_url);

  EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
  EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);
}

// Test calling MergeFavicon() when |page_url| is known to the database.
TEST_F(HistoryBackendTest, MergeFaviconPageURLInDB) {
  GURL page_url("http://www.google.com");
  GURL icon_url1("http:/www.google.com/favicon.ico");
  GURL icon_url2("http://www.google.com/favicon2.ico");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps);

  // Test initial state.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);

  FaviconBitmap favicon_bitmap;
  EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  // 1) Merge identical favicon bitmap.
  std::vector<unsigned char> data;
  gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data);
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));
  backend_->MergeFavicon(
      page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize);

  // All the data should stay the same and no notifications should have been
  // sent.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);

  EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  // 2) Merge favicon bitmap of the same size.
  data.clear();
  data.push_back('b');
  bitmap_data = new base::RefCountedBytes(data);
  backend_->MergeFavicon(
      page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize);

  // The small favicon bitmap at |icon_url1| should be overwritten.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);

  EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
  EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  // 3) Merge favicon for the same icon URL, but a pixel size for which there is
  // no favicon bitmap.
  data[0] = 'c';
  bitmap_data = new base::RefCountedBytes(data);
  backend_->MergeFavicon(
      page_url, icon_url1, favicon_base::FAVICON, bitmap_data, kTinySize);

  // A new favicon bitmap should be created and the preexisting favicon bitmap
  // ('b') should be expired.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url1, icon_mappings[0].icon_url);

  std::vector<FaviconBitmap> favicon_bitmaps;
  EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id,
                                      &favicon_bitmaps));
  EXPECT_NE(base::Time(), favicon_bitmaps[0].last_updated);
  EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data));
  EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size);
  EXPECT_EQ(base::Time(), favicon_bitmaps[1].last_updated);
  EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmaps[1].bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size);

  // 4) Merge favicon for an icon URL different from the icon URLs already
  // mapped to page URL.
  data[0] = 'd';
  bitmap_data = new base::RefCountedBytes(data);
  backend_->MergeFavicon(
      page_url, icon_url2, favicon_base::FAVICON, bitmap_data, kSmallSize);

  // The existing favicon bitmaps should be copied over to the newly created
  // favicon at |icon_url2|. |page_url| should solely be mapped to |icon_url2|.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url2, icon_mappings[0].icon_url);

  favicon_bitmaps.clear();
  EXPECT_TRUE(GetSortedFaviconBitmaps(icon_mappings[0].icon_id,
                                      &favicon_bitmaps));
  EXPECT_EQ(base::Time(), favicon_bitmaps[0].last_updated);
  EXPECT_TRUE(BitmapDataEqual('c', favicon_bitmaps[0].bitmap_data));
  EXPECT_EQ(kTinySize, favicon_bitmaps[0].pixel_size);
  // The favicon being merged should take precedence over the preexisting
  // favicon bitmaps.
  EXPECT_NE(base::Time(), favicon_bitmaps[1].last_updated);
  EXPECT_TRUE(BitmapDataEqual('d', favicon_bitmaps[1].bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmaps[1].pixel_size);
}

// Test calling MergeFavicon() when |icon_url| is known to the database but not
// mapped to |page_url|.
TEST_F(HistoryBackendTest, MergeFaviconIconURLMappedToDifferentPageURL) {
  GURL page_url1("http://www.google.com");
  GURL page_url2("http://news.google.com");
  GURL page_url3("http://maps.google.com");
  GURL icon_url("http:/www.google.com/favicon.ico");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url, bitmaps);

  // Test initial state.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_url, icon_mappings[0].icon_url);

  FaviconBitmap favicon_bitmap;
  EXPECT_TRUE(GetOnlyFaviconBitmap(icon_mappings[0].icon_id, &favicon_bitmap));
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  // 1) Merge in an identical favicon bitmap data but for a different page URL.
  std::vector<unsigned char> data;
  gfx::PNGCodec::EncodeBGRASkBitmap(bitmaps[0], false, &data);
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));

  backend_->MergeFavicon(
      page_url2, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);

  favicon_base::FaviconID favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(icon_url,
                                                         favicon_base::FAVICON);
  EXPECT_NE(0, favicon_id);

  EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
  EXPECT_NE(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorBLUE, favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  // 2) Merging a favicon bitmap with different bitmap data for the same icon
  // URL should overwrite the small favicon bitmap at |icon_url|.
  data.clear();
  data.push_back('b');
  bitmap_data = new base::RefCountedBytes(data);
  backend_->MergeFavicon(
      page_url3, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);

  favicon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
      icon_url, favicon_base::FAVICON);
  EXPECT_NE(0, favicon_id);

  EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
  EXPECT_EQ(base::Time(), favicon_bitmap.last_updated);
  EXPECT_TRUE(BitmapDataEqual('b', favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  // |icon_url| should be mapped to all three page URLs.
  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url2,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url3,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(favicon_id, icon_mappings[0].icon_id);
}

// Test that MergeFavicon() does not add more than
// |kMaxFaviconBitmapsPerIconURL| to a favicon.
TEST_F(HistoryBackendTest, MergeFaviconMaxFaviconBitmapsPerIconURL) {
  GURL page_url("http://www.google.com");
  std::string icon_url_string("http://www.google.com/favicon.ico");
  size_t replace_index = icon_url_string.size() - 1;

  std::vector<unsigned char> data;
  data.push_back('a');
  scoped_refptr<base::RefCountedMemory> bitmap_data =
      base::RefCountedBytes::TakeVector(&data);

  int pixel_size = 1;
  for (size_t i = 0; i < kMaxFaviconBitmapsPerIconURL + 1; ++i) {
    icon_url_string[replace_index] = '0' + i;
    GURL icon_url(icon_url_string);

    backend_->MergeFavicon(page_url,
                           icon_url,
                           favicon_base::FAVICON,
                           bitmap_data,
                           gfx::Size(pixel_size, pixel_size));
    ++pixel_size;
  }

  // There should be a single favicon mapped to |page_url| with exactly
  // kMaxFaviconBitmapsPerIconURL favicon bitmaps.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  std::vector<FaviconBitmap> favicon_bitmaps;
  EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
      icon_mappings[0].icon_id, &favicon_bitmaps));
  EXPECT_EQ(kMaxFaviconBitmapsPerIconURL, favicon_bitmaps.size());
}

// Tests that the favicon set by MergeFavicon() shows up in the result of
// GetFaviconsForURL().
TEST_F(HistoryBackendTest, MergeFaviconShowsUpInGetFaviconsForURLResult) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");
  GURL merged_icon_url("http://wwww.google.com/favicon2.ico");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));

  // Set some preexisting favicons for |page_url|.
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  // Merge small favicon.
  std::vector<unsigned char> data;
  data.push_back('c');
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));
  backend_->MergeFavicon(page_url,
                         merged_icon_url,
                         favicon_base::FAVICON,
                         bitmap_data,
                         kSmallSize);

  // Request favicon bitmaps for both 1x and 2x to simulate request done by
  // BookmarkModel::GetFavicon().
  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
  backend_->GetFaviconsForURL(page_url,
                              favicon_base::FAVICON,
                              GetEdgeSizesSmallAndLarge(),
                              &bitmap_results);

  EXPECT_EQ(2u, bitmap_results.size());
  const favicon_base::FaviconRawBitmapResult& first_result = bitmap_results[0];
  const favicon_base::FaviconRawBitmapResult& result =
      (first_result.pixel_size == kSmallSize) ? first_result
                                              : bitmap_results[1];
  EXPECT_TRUE(BitmapDataEqual('c', result.bitmap_data));
}

// Test that adding a favicon for a new icon URL:
// - Sends a notification that the favicon for the page URL has changed.
// - Does not send a notification that the favicon for the icon URL has changed
//   as there are no other page URLs which use the icon URL.
TEST_F(HistoryBackendTest, FaviconChangedNotificationNewFavicon) {
  GURL page_url1("http://www.google.com/a");
  GURL icon_url1("http://www.google.com/favicon1.ico");
  GURL page_url2("http://www.google.com/b");
  GURL icon_url2("http://www.google.com/favicon2.ico");

  // SetFavicons()
  {
    std::vector<SkBitmap> bitmaps;
    bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
    backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url1, bitmaps);
    ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
    EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
    EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
    ClearBroadcastedNotifications();
  }

  // MergeFavicon()
  {
    std::vector<unsigned char> data;
    data.push_back('a');
    scoped_refptr<base::RefCountedBytes> bitmap_data(
        new base::RefCountedBytes(data));
    backend_->MergeFavicon(
        page_url2, icon_url2, favicon_base::FAVICON, bitmap_data, kSmallSize);
    ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
    EXPECT_EQ(page_url2, favicon_changed_notifications_page_urls()[0]);
    EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
  }
}

// Test that changing the favicon bitmap data for an icon URL:
// - Does not send a notification that the favicon for the page URL has changed.
// - Sends a notification that the favicon for the icon URL has changed (Several
//   page URLs may be mapped to the icon URL).
TEST_F(HistoryBackendTest, FaviconChangedNotificationBitmapDataChanged) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");

  // Setup
  {
    std::vector<SkBitmap> bitmaps;
    bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
    backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
    ClearBroadcastedNotifications();
  }

  // SetFavicons()
  {
    std::vector<SkBitmap> bitmaps;
    bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize));
    backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
    EXPECT_EQ(0u, favicon_changed_notifications_page_urls().size());
    ASSERT_EQ(1u, favicon_changed_notifications_icon_urls().size());
    EXPECT_EQ(icon_url, favicon_changed_notifications_icon_urls()[0]);
    ClearBroadcastedNotifications();
  }

  // MergeFavicon()
  {
    std::vector<unsigned char> data;
    data.push_back('a');
    scoped_refptr<base::RefCountedBytes> bitmap_data(
        new base::RefCountedBytes(data));
    backend_->MergeFavicon(
        page_url, icon_url, favicon_base::FAVICON, bitmap_data, kSmallSize);
    EXPECT_EQ(0u, favicon_changed_notifications_page_urls().size());
    ASSERT_EQ(1u, favicon_changed_notifications_icon_urls().size());
    EXPECT_EQ(icon_url, favicon_changed_notifications_icon_urls()[0]);
  }
}

// Test that changing the page URL -> icon URL mapping:
// - Sends a notification that the favicon for the page URL has changed.
// - Does not send a notification that the favicon for the icon URL has changed.
TEST_F(HistoryBackendTest, FaviconChangedNotificationIconMappingChanged) {
  GURL page_url1("http://www.google.com/a");
  GURL page_url2("http://www.google.com/b");
  GURL page_url3("http://www.google.com/c");
  GURL page_url4("http://www.google.com/d");
  GURL icon_url1("http://www.google.com/favicon1.ico");
  GURL icon_url2("http://www.google.com/favicon2.ico");

  SkBitmap bitmap(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(bitmap);
  std::vector<unsigned char> png_bytes;
  ASSERT_TRUE(gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_bytes));

  // Setup
  {
    std::vector<SkBitmap> bitmaps;
    bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
    backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url1, bitmaps);
    backend_->SetFavicons(page_url2, favicon_base::FAVICON, icon_url2, bitmaps);

    // Map |page_url3| to |icon_url1| so that the test does not delete the
    // favicon at |icon_url1|.
    std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
    backend_->UpdateFaviconMappingsAndFetch(
        page_url3, icon_url1, favicon_base::FAVICON,
        GetEdgeSizesSmallAndLarge(), &bitmap_results);
    ClearBroadcastedNotifications();
  }

  // SetFavicons()
  backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url2, bitmaps);
  ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
  EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
  EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
  ClearBroadcastedNotifications();

  // MergeFavicon()
  backend_->MergeFavicon(page_url1, icon_url1, favicon_base::FAVICON,
                         new base::RefCountedBytes(png_bytes), kSmallSize);
  ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
  EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
  EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
  ClearBroadcastedNotifications();

  // UpdateFaviconMappingsAndFetch()
  {
    std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
    backend_->UpdateFaviconMappingsAndFetch(
        page_url1, icon_url2, favicon_base::FAVICON,
        GetEdgeSizesSmallAndLarge(), &bitmap_results);
    ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
    EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
    EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
  }
}

// Test that changing both:
// - The page URL -> icon URL mapping
// - The favicon's bitmap data
// sends notifications that the favicon data for both the page URL and the icon
// URL have changed.
TEST_F(HistoryBackendTest,
       FaviconChangedNotificationIconMappingAndBitmapDataChanged) {
  GURL page_url1("http://www.google.com/a");
  GURL page_url2("http://www.google.com/b");
  GURL page_url3("http://www.google.com/c");
  GURL icon_url1("http://www.google.com/favicon1.ico");
  GURL icon_url2("http://www.google.com/favicon2.ico");

  // Setup
  {
    std::vector<SkBitmap> bitmaps;
    bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
    backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url1, bitmaps);
    backend_->SetFavicons(page_url2, favicon_base::FAVICON, icon_url2, bitmaps);

    // Map |page_url3| to |icon_url1| so that the test does not delete the
    // favicon at |icon_url1|.
    std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
    backend_->UpdateFaviconMappingsAndFetch(
        page_url3, icon_url1, favicon_base::FAVICON,
        GetEdgeSizesSmallAndLarge(), &bitmap_results);
    ClearBroadcastedNotifications();
  }

  // SetFavicons()
  {
    std::vector<SkBitmap> bitmaps;
    bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kSmallEdgeSize));
    backend_->SetFavicons(page_url1, favicon_base::FAVICON, icon_url2, bitmaps);
    ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
    EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
    ASSERT_EQ(1u, favicon_changed_notifications_icon_urls().size());
    EXPECT_EQ(icon_url2, favicon_changed_notifications_icon_urls()[0]);
    ClearBroadcastedNotifications();
  }

  // MergeFavicon()
  {
    std::vector<unsigned char> data;
    data.push_back('a');
    scoped_refptr<base::RefCountedBytes> bitmap_data(
        new base::RefCountedBytes(data));
    backend_->MergeFavicon(
        page_url1, icon_url1, favicon_base::FAVICON, bitmap_data, kSmallSize);
    ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
    EXPECT_EQ(page_url1, favicon_changed_notifications_page_urls()[0]);
    ASSERT_EQ(1u, favicon_changed_notifications_icon_urls().size());
    EXPECT_EQ(icon_url1, favicon_changed_notifications_icon_urls()[0]);
  }
}

// Test that if MergeFavicon() copies favicon bitmaps from one favicon to
// another that a notification is sent that the favicon at the destination
// icon URL has changed.
TEST_F(HistoryBackendTest, FaviconChangedNotificationsMergeCopy) {
  GURL page_url1("http://www.google.com/a");
  GURL icon_url1("http://www.google.com/favicon1.ico");
  GURL page_url2("http://www.google.com/b");
  GURL icon_url2("http://www.google.com/favicon2.ico");
  std::vector<unsigned char> png_bytes1;
  png_bytes1.push_back('a');
  std::vector<unsigned char> png_bytes2;
  png_bytes2.push_back('b');

  // Setup
  backend_->MergeFavicon(page_url1, icon_url1, favicon_base::FAVICON,
                         new base::RefCountedBytes(png_bytes1), kSmallSize);
  backend_->MergeFavicon(page_url2, icon_url2, favicon_base::FAVICON,
                         new base::RefCountedBytes(png_bytes2), kSmallSize);
  backend_->MergeFavicon(page_url2, icon_url2, favicon_base::FAVICON,
                         new base::RefCountedBytes(png_bytes2), kLargeSize);
  ClearBroadcastedNotifications();

  // Calling MergeFavicon() with |page_url2|, |icon_url1|, |png_bytes1| and
  // |kSmallSize| should cause the large favicon bitmap from |icon_url2| to
  // be copied to |icon_url1|.
  backend_->MergeFavicon(page_url2, icon_url1, favicon_base::FAVICON,
                         new base::RefCountedBytes(png_bytes1), kSmallSize);

  ASSERT_EQ(1u, favicon_changed_notifications_page_urls().size());
  EXPECT_EQ(page_url2, favicon_changed_notifications_page_urls()[0]);

  // A favicon bitmap was copied to the favicon at |icon_url1|. A notification
  // that the favicon at |icon_url1| has changed should be sent.
  ASSERT_EQ(1u, favicon_changed_notifications_icon_urls().size());
  EXPECT_EQ(icon_url1, favicon_changed_notifications_icon_urls()[0]);
}

// Tests that calling MergeFavicon() with identical favicon data does not affect
// the favicon bitmap's "last updated" time. This is important because sync
// calls MergeFavicon() for all of the favicons that it manages at startup.
TEST_F(HistoryBackendTest, MergeIdenticalFaviconDoesNotChangeLastUpdatedTime) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");
  std::vector<unsigned char> data;
  data.push_back('a');

  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));
  backend_->MergeFavicon(page_url,
                         icon_url,
                         favicon_base::FAVICON,
                         bitmap_data,
                         kSmallSize);

  // Find the ID of the add favicon bitmap.
  std::vector<IconMapping> icon_mappings;
  ASSERT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  ASSERT_EQ(1u, icon_mappings.size());
  std::vector<FaviconBitmap> favicon_bitmaps;
  ASSERT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
      icon_mappings[0].icon_id, &favicon_bitmaps));

  // Change the last updated time of the just added favicon bitmap.
  const base::Time kLastUpdateTime =
      base::Time::Now() - base::TimeDelta::FromDays(314);
  backend_->thumbnail_db_->SetFaviconBitmapLastUpdateTime(
      favicon_bitmaps[0].bitmap_id, kLastUpdateTime);

  // Call MergeFavicon() with identical data.
  backend_->MergeFavicon(page_url,
                         icon_url,
                         favicon_base::FAVICON,
                         bitmap_data,
                         kSmallSize);

  // Check that the "last updated" time did not change.
  icon_mappings.clear();
  ASSERT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));
  ASSERT_EQ(1u, icon_mappings.size());
  favicon_bitmaps.clear();
  ASSERT_TRUE(backend_->thumbnail_db_->GetFaviconBitmaps(
      icon_mappings[0].icon_id, &favicon_bitmaps));
  EXPECT_EQ(kLastUpdateTime, favicon_bitmaps[0].last_updated);
}

// Test that no notifications are broadcast if calling SetFavicons() /
// MergeFavicon() / UpdateFaviconMappingsAndFetch() did not alter the Favicon
// database data (with the exception of the "last updated time").
TEST_F(HistoryBackendTest, NoFaviconChangedNotifications) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");

  SkBitmap bitmap(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(bitmap);
  std::vector<unsigned char> png_bytes;
  ASSERT_TRUE(gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_bytes));

  // Setup
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);
  ClearBroadcastedNotifications();

  // SetFavicons()
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  // MergeFavicon()
  backend_->MergeFavicon(page_url, icon_url, favicon_base::FAVICON,
                         new base::RefCountedBytes(png_bytes), kSmallSize);

  // UpdateFaviconMappingsAndFetch()
  {
    std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
    backend_->UpdateFaviconMappingsAndFetch(
        page_url, icon_url, favicon_base::FAVICON, GetEdgeSizesSmallAndLarge(),
        &bitmap_results);
  }

  EXPECT_EQ(0u, favicon_changed_notifications_page_urls().size());
  EXPECT_EQ(0u, favicon_changed_notifications_icon_urls().size());
}

// Tests GetFaviconsForURL with icon_types priority,
TEST_F(HistoryBackendTest, TestGetFaviconsForURLWithIconTypesPriority) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");
  GURL touch_icon_url("http://wwww.google.com/touch_icon.ico");

  std::vector<SkBitmap> favicon_bitmaps;
  favicon_bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16));
  favicon_bitmaps.push_back(CreateBitmap(SK_ColorRED, 32));

  std::vector<SkBitmap> touch_bitmaps;
  touch_bitmaps.push_back(CreateBitmap(SK_ColorWHITE, 64));

  // Set some preexisting favicons for |page_url|.
  backend_->SetFavicons(
      page_url, favicon_base::FAVICON, icon_url, favicon_bitmaps);
  backend_->SetFavicons(
      page_url, favicon_base::TOUCH_ICON, touch_icon_url, touch_bitmaps);

  favicon_base::FaviconRawBitmapResult result;
  std::vector<int> icon_types;
  icon_types.push_back(favicon_base::FAVICON);
  icon_types.push_back(favicon_base::TOUCH_ICON);

  backend_->GetLargestFaviconForURL(page_url, icon_types, 16, &result);

  // Verify the result icon is 32x32 favicon.
  EXPECT_EQ(gfx::Size(32, 32), result.pixel_size);
  EXPECT_EQ(favicon_base::FAVICON, result.icon_type);

  // Change Minimal size to 32x32 and verify the 64x64 touch icon returned.
  backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result);
  EXPECT_EQ(gfx::Size(64, 64), result.pixel_size);
  EXPECT_EQ(favicon_base::TOUCH_ICON, result.icon_type);
}

// Test the the first types of icon is returned if its size equal to the
// second types icon.
TEST_F(HistoryBackendTest, TestGetFaviconsForURLReturnFavicon) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");
  GURL touch_icon_url("http://wwww.google.com/touch_icon.ico");

  std::vector<SkBitmap> favicon_bitmaps;
  favicon_bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16));
  favicon_bitmaps.push_back(CreateBitmap(SK_ColorRED, 32));

  std::vector<SkBitmap> touch_bitmaps;
  touch_bitmaps.push_back(CreateBitmap(SK_ColorWHITE, 32));

  // Set some preexisting favicons for |page_url|.
  backend_->SetFavicons(
      page_url, favicon_base::FAVICON, icon_url, favicon_bitmaps);
  backend_->SetFavicons(
      page_url, favicon_base::TOUCH_ICON, touch_icon_url, touch_bitmaps);

  favicon_base::FaviconRawBitmapResult result;
  std::vector<int> icon_types;
  icon_types.push_back(favicon_base::FAVICON);
  icon_types.push_back(favicon_base::TOUCH_ICON);

  backend_->GetLargestFaviconForURL(page_url, icon_types, 16, &result);

  // Verify the result icon is 32x32 favicon.
  EXPECT_EQ(gfx::Size(32, 32), result.pixel_size);
  EXPECT_EQ(favicon_base::FAVICON, result.icon_type);

  // Change minimal size to 32x32 and verify the 32x32 favicon returned.
  favicon_base::FaviconRawBitmapResult result1;
  backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result1);
  EXPECT_EQ(gfx::Size(32, 32), result1.pixel_size);
  EXPECT_EQ(favicon_base::FAVICON, result1.icon_type);
}

// Test the favicon is returned if its size is smaller than minimal size,
// because it is only one available.
TEST_F(HistoryBackendTest, TestGetFaviconsForURLReturnFaviconEvenItSmaller) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");

  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, 16));

  // Set preexisting favicons for |page_url|.
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  favicon_base::FaviconRawBitmapResult result;
  std::vector<int> icon_types;
  icon_types.push_back(favicon_base::FAVICON);
  icon_types.push_back(favicon_base::TOUCH_ICON);

  backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result);

  // Verify 16x16 icon is returned, even it small than minimal_size.
  EXPECT_EQ(gfx::Size(16, 16), result.pixel_size);
  EXPECT_EQ(favicon_base::FAVICON, result.icon_type);
}

// Test the results of GetFaviconsFromDB() when there are no found favicons.
TEST_F(HistoryBackendTest, GetFaviconsFromDBEmpty) {
  const GURL page_url("http://www.google.com/");

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;
  EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url,
                                           favicon_base::FAVICON,
                                           GetEdgeSizesSmallAndLarge(),
                                           &bitmap_results));
  EXPECT_TRUE(bitmap_results.empty());
}

// Test the results of GetFaviconsFromDB() when there are matching favicons
// but there are no associated favicon bitmaps.
TEST_F(HistoryBackendTest, GetFaviconsFromDBNoFaviconBitmaps) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon1");

  favicon_base::FaviconID icon_id =
      backend_->thumbnail_db_->AddFavicon(icon_url, favicon_base::FAVICON);
  EXPECT_NE(0, icon_id);
  EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id));

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out;
  EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url,
                                           favicon_base::FAVICON,
                                           GetEdgeSizesSmallAndLarge(),
                                           &bitmap_results_out));
  EXPECT_TRUE(bitmap_results_out.empty());
}

// Test that GetFaviconsFromDB() returns results for the bitmaps which most
// closely match the passed in the desired pixel sizes.
TEST_F(HistoryBackendTest, GetFaviconsFromDBSelectClosestMatch) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon1");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorWHITE, kTinyEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));
  bitmaps.push_back(CreateBitmap(SK_ColorRED, kLargeEdgeSize));

  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url, bitmaps);

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url,
                                          favicon_base::FAVICON,
                                          GetEdgeSizesSmallAndLarge(),
                                          &bitmap_results_out));

  // The bitmap data for the small and large bitmaps should be returned as their
  // sizes match exactly.
  EXPECT_EQ(2u, bitmap_results_out.size());
  // No required order for results.
  if (bitmap_results_out[0].pixel_size == kLargeSize) {
    favicon_base::FaviconRawBitmapResult tmp_result = bitmap_results_out[0];
    bitmap_results_out[0] = bitmap_results_out[1];
    bitmap_results_out[1] = tmp_result;
  }

  EXPECT_FALSE(bitmap_results_out[0].expired);
  EXPECT_TRUE(
      BitmapColorEqual(SK_ColorBLUE, bitmap_results_out[0].bitmap_data));
  EXPECT_EQ(kSmallSize, bitmap_results_out[0].pixel_size);
  EXPECT_EQ(icon_url, bitmap_results_out[0].icon_url);
  EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[0].icon_type);

  EXPECT_FALSE(bitmap_results_out[1].expired);
  EXPECT_TRUE(BitmapColorEqual(SK_ColorRED, bitmap_results_out[1].bitmap_data));
  EXPECT_EQ(kLargeSize, bitmap_results_out[1].pixel_size);
  EXPECT_EQ(icon_url, bitmap_results_out[1].icon_url);
  EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[1].icon_type);
}

// Test the results of GetFaviconsFromDB() when called with different
// |icon_types|.
TEST_F(HistoryBackendTest, GetFaviconsFromDBIconType) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url1("http://www.google.com/icon1.png");
  const GURL icon_url2("http://www.google.com/icon2.png");
  std::vector<SkBitmap> bitmaps;
  bitmaps.push_back(CreateBitmap(SK_ColorBLUE, kSmallEdgeSize));

  std::vector<favicon_base::FaviconRawBitmapData> favicon_bitmap_data;
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1, bitmaps);
  backend_->SetFavicons(page_url, favicon_base::TOUCH_ICON, icon_url2, bitmaps);

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url,
                                          favicon_base::FAVICON,
                                          GetEdgeSizesSmallAndLarge(),
                                          &bitmap_results_out));

  EXPECT_EQ(1u, bitmap_results_out.size());
  EXPECT_EQ(favicon_base::FAVICON, bitmap_results_out[0].icon_type);
  EXPECT_EQ(icon_url1, bitmap_results_out[0].icon_url);

  bitmap_results_out.clear();
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url,
                                          favicon_base::TOUCH_ICON,
                                          GetEdgeSizesSmallAndLarge(),
                                          &bitmap_results_out));

  EXPECT_EQ(1u, bitmap_results_out.size());
  EXPECT_EQ(favicon_base::TOUCH_ICON, bitmap_results_out[0].icon_type);
  EXPECT_EQ(icon_url2, bitmap_results_out[0].icon_url);
}

// Test that when GetFaviconsFromDB() is called with multiple icon types that
// the best favicon bitmap is selected from among all of the icon types.
TEST_F(HistoryBackendTest, GetFaviconsFromDBMultipleIconTypes) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url1("http://www.google.com/icon1.png");
  const GURL icon_url2("http://www.google.com/icon2.png");

  std::vector<favicon_base::FaviconRawBitmapData> favicon_bitmap_data;
  backend_->SetFavicons(page_url, favicon_base::FAVICON, icon_url1,
                        {CreateBitmap(SK_ColorBLUE, kSmallEdgeSize)});
  backend_->SetFavicons(page_url, favicon_base::TOUCH_ICON, icon_url2,
                        {CreateBitmap(SK_ColorBLUE, kLargeEdgeSize)});

  struct TestCase {
    int desired_edge_size;
    GURL expected_icon_url;
  } kTestCases[]{{kSmallEdgeSize, icon_url1}, {kLargeEdgeSize, icon_url2}};

  for (const TestCase& test_case : kTestCases) {
    std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out;
    backend_->GetFaviconsForURL(
        page_url, favicon_base::FAVICON | favicon_base::TOUCH_ICON,
        {test_case.desired_edge_size}, &bitmap_results_out);

    ASSERT_EQ(1u, bitmap_results_out.size());
    EXPECT_EQ(test_case.expected_icon_url, bitmap_results_out[0].icon_url);
  }
}

// Test that GetFaviconsFromDB() correctly sets the expired flag for bitmap
// reults.
TEST_F(HistoryBackendTest, GetFaviconsFromDBExpired) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon.png");

  std::vector<unsigned char> data;
  data.push_back('a');
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      base::RefCountedBytes::TakeVector(&data));
  base::Time last_updated = base::Time::FromTimeT(0);
  favicon_base::FaviconID icon_id = backend_->thumbnail_db_->AddFavicon(
      icon_url, favicon_base::FAVICON, bitmap_data, FaviconBitmapType::ON_VISIT,
      last_updated, kSmallSize);
  EXPECT_NE(0, icon_id);
  EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id));

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url,
                                          favicon_base::FAVICON,
                                          GetEdgeSizesSmallAndLarge(),
                                          &bitmap_results_out));

  EXPECT_EQ(1u, bitmap_results_out.size());
  EXPECT_TRUE(bitmap_results_out[0].expired);
}

// Check that UpdateFaviconMappingsAndFetch() call back to the UI when there is
// no valid thumbnail database.
TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoDB) {
  // Make the thumbnail database invalid.
  backend_->thumbnail_db_.reset();

  std::vector<favicon_base::FaviconRawBitmapResult> bitmap_results;

  backend_->UpdateFaviconMappingsAndFetch(GURL(), GURL(), favicon_base::FAVICON,
                                          GetEdgeSizesSmallAndLarge(),
                                          &bitmap_results);

  EXPECT_TRUE(bitmap_results.empty());
}

TEST_F(HistoryBackendTest, TopHosts) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("http://cnn.com/intl"));
  urls.push_back(GURL("http://dogtopia.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  EXPECT_THAT(backend_->TopHosts(3),
              ElementsAre(std::make_pair("cnn.com", 2),
                          std::make_pair("dogtopia.com", 1)));
}

TEST_F(HistoryBackendTest, TopHosts_ElidePortAndScheme) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("https://cnn.com/intl"));
  urls.push_back(GURL("http://cnn.com:567/sports"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  EXPECT_THAT(backend_->TopHosts(3), ElementsAre(std::make_pair("cnn.com", 3)));
}

TEST_F(HistoryBackendTest, TopHosts_ElideWWW) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://www.cnn.com/us"));
  urls.push_back(GURL("http://cnn.com/intl"));
  urls.push_back(GURL("http://www.dogtopia.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  EXPECT_THAT(backend_->TopHosts(3),
              ElementsAre(std::make_pair("cnn.com", 2),
                          std::make_pair("dogtopia.com", 1)));
}

TEST_F(HistoryBackendTest, TopHosts_OnlyLast30Days) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("http://cnn.com/intl"));
  urls.push_back(GURL("http://dogtopia.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }
  backend_->AddPageVisit(
      GURL("http://www.oracle.com/"),
      base::Time::Now() - base::TimeDelta::FromDays(31), 0,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                ui::PAGE_TRANSITION_CHAIN_START |
                                ui::PAGE_TRANSITION_CHAIN_END),
      history::SOURCE_BROWSED);

  EXPECT_THAT(backend_->TopHosts(3),
              ElementsAre(std::make_pair("cnn.com", 2),
                          std::make_pair("dogtopia.com", 1)));
}

TEST_F(HistoryBackendTest, TopHosts_MaxNumHosts) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("http://cnn.com/intl"));
  urls.push_back(GURL("http://cnn.com/sports"));
  urls.push_back(GURL("http://dogtopia.com/"));
  urls.push_back(GURL("http://dogtopia.com/webcam"));
  urls.push_back(GURL("http://www.gardenweb.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  EXPECT_THAT(backend_->TopHosts(2),
              ElementsAre(std::make_pair("cnn.com", 3),
                          std::make_pair("dogtopia.com", 2)));
}

TEST_F(HistoryBackendTest, TopHosts_IgnoreUnusualURLs) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("ftp://cnn.com/intl"));
  urls.push_back(GURL("https://cnn.com/sports"));
  urls.push_back(
      GURL("chrome-extension://nghiiepjnjgjeolabmjjceablnkpkjde/options.html"));
  urls.push_back(GURL("file:///home/foobar/tmp/baz.html"));
  urls.push_back(GURL("data:text/plain,Hello%20world%21"));
  urls.push_back(GURL("chrome://version"));
  urls.push_back(GURL("about:mammon"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  EXPECT_THAT(backend_->TopHosts(5), ElementsAre(std::make_pair("cnn.com", 3)));
}

TEST_F(HistoryBackendTest, TopHosts_IgnoreRedirects) {
  const char* redirect1[] = {"http://foo.com/page1.html",
                             "http://mobile.foo.com/page1.html", NULL};
  const char* redirect2[] = {"http://bar.com/page1.html",
                             "https://bar.com/page1.html",
                             "https://mobile.bar.com/page1.html", NULL};
  AddRedirectChain(redirect1, 0);
  AddRedirectChain(redirect2, 1);
  EXPECT_THAT(backend_->TopHosts(5),
              ElementsAre(std::make_pair("mobile.bar.com", 1),
                          std::make_pair("mobile.foo.com", 1)));
}

TEST_F(HistoryBackendTest, HostRankIfAvailable) {
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("http://cnn.com/intl"));
  urls.push_back(GURL("http://dogtopia.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  EXPECT_EQ(kMaxTopHosts,
            backend_->HostRankIfAvailable(GURL("http://cnn.com/")));

  backend_->TopHosts(3);

  EXPECT_EQ(0, backend_->HostRankIfAvailable(GURL("http://cnn.com/")));
  EXPECT_EQ(1, backend_->HostRankIfAvailable(GURL("http://dogtopia.com/")));
  EXPECT_EQ(kMaxTopHosts,
            backend_->HostRankIfAvailable(GURL("http://catsylvania.com/")));
}

TEST_F(HistoryBackendTest, RecordTopHostsMetrics) {
  base::HistogramTester histogram;

  // Load initial URLs for the purpose of populating host_ranks_.
  std::vector<GURL> urls;
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("http://cnn.com/intl"));
  urls.push_back(GURL("http://dogtopia.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(
        url, base::Time::Now(), 0,
        ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
                                  ui::PAGE_TRANSITION_CHAIN_START |
                                  ui::PAGE_TRANSITION_CHAIN_END),
        history::SOURCE_BROWSED);
  }

  // Compute host_ranks_ for RecordTopHostsMetrics.
  EXPECT_THAT(backend_->TopHosts(3),
              ElementsAre(std::make_pair("cnn.com", 2),
                          std::make_pair("dogtopia.com", 1)));

  // Load URLs to record top-hosts metrics for.
  urls.clear();
  urls.push_back(GURL("http://cnn.com/us"));
  urls.push_back(GURL("http://www.unipresse.com/"));
  for (const GURL& url : urls) {
    backend_->AddPageVisit(url, base::Time::Now(), 0,
                           ui::PAGE_TRANSITION_CHAIN_END,
                           history::SOURCE_BROWSED);
  }

  EXPECT_THAT(histogram.GetAllSamples("History.TopHostsVisitsByRank"),
              ElementsAre(base::Bucket(1, 1), base::Bucket(51, 1)));
}

TEST_F(HistoryBackendTest, GetCountsAndLastVisitForOrigins) {
  base::Time now = base::Time::Now();
  base::Time tomorrow = now + base::TimeDelta::FromDays(1);
  base::Time yesterday = now - base::TimeDelta::FromDays(1);
  base::Time last_week = now - base::TimeDelta::FromDays(7);

  backend_->AddPageVisit(GURL("http://cnn.com/intl"), yesterday, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);
  backend_->AddPageVisit(GURL("http://cnn.com/us"), last_week, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);
  backend_->AddPageVisit(GURL("http://cnn.com/ny"), now, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);
  backend_->AddPageVisit(GURL("https://cnn.com/intl"), yesterday, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);
  backend_->AddPageVisit(GURL("http://cnn.com:8080/path"), yesterday, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);
  backend_->AddPageVisit(GURL("http://dogtopia.com/pups?q=poods"), now, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);

  std::set<GURL> origins;
  origins.insert(GURL("http://cnn.com/"));
  EXPECT_THAT(backend_->GetCountsAndLastVisitForOrigins(origins),
              ElementsAre(std::make_pair(GURL("http://cnn.com/"),
                                         std::make_pair(3, now))));

  origins.insert(GURL("http://dogtopia.com/"));
  origins.insert(GURL("http://cnn.com:8080/"));
  origins.insert(GURL("https://cnn.com/"));
  origins.insert(GURL("http://notpresent.com/"));
  backend_->AddPageVisit(GURL("http://cnn.com/"), tomorrow, 0,
                         ui::PAGE_TRANSITION_LINK, history::SOURCE_BROWSED);

  EXPECT_THAT(
      backend_->GetCountsAndLastVisitForOrigins(origins),
      ElementsAre(
          std::make_pair(GURL("http://cnn.com/"), std::make_pair(4, tomorrow)),
          std::make_pair(GURL("http://cnn.com:8080/"),
                         std::make_pair(1, yesterday)),
          std::make_pair(GURL("http://dogtopia.com/"), std::make_pair(1, now)),
          std::make_pair(GURL("http://notpresent.com/"),
                         std::make_pair(0, base::Time())),
          std::make_pair(GURL("https://cnn.com/"),
                         std::make_pair(1, yesterday))));
}

TEST_F(HistoryBackendTest, UpdateVisitDuration) {
  // This unit test will test adding and deleting visit details information.
  ASSERT_TRUE(backend_.get());

  GURL url1("http://www.cnn.com");
  std::vector<VisitInfo> visit_info1, visit_info2;
  base::Time start_ts = base::Time::Now() - base::TimeDelta::FromDays(5);
  base::Time end_ts = start_ts + base::TimeDelta::FromDays(2);
  visit_info1.push_back(VisitInfo(start_ts, ui::PAGE_TRANSITION_LINK));

  GURL url2("http://www.example.com");
  visit_info2.push_back(
      VisitInfo(base::Time::Now() - base::TimeDelta::FromDays(10),
                ui::PAGE_TRANSITION_LINK));

  // Clear all history.
  backend_->DeleteAllHistory();

  // Add the visits.
  backend_->AddVisits(url1, visit_info1, history::SOURCE_BROWSED);
  backend_->AddVisits(url2, visit_info2, history::SOURCE_BROWSED);

  // Verify the entries for both visits were added in visit_details.
  VisitVector visits1, visits2;
  URLRow row;
  URLID url_id1 = backend_->db()->GetRowForURL(url1, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id1, &visits1));
  ASSERT_EQ(1U, visits1.size());
  EXPECT_EQ(0, visits1[0].visit_duration.ToInternalValue());

  URLID url_id2 = backend_->db()->GetRowForURL(url2, &row);
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id2, &visits2));
  ASSERT_EQ(1U, visits2.size());
  EXPECT_EQ(0, visits2[0].visit_duration.ToInternalValue());

  // Update the visit to cnn.com.
  backend_->UpdateVisitDuration(visits1[0].visit_id, end_ts);

  // Check the duration for visiting cnn.com was correctly updated.
  ASSERT_TRUE(backend_->db()->GetVisitsForURL(url_id1, &visits1));
  ASSERT_EQ(1U, visits1.size());
  base::TimeDelta expected_duration = end_ts - start_ts;
  EXPECT_EQ(expected_duration.ToInternalValue(),
            visits1[0].visit_duration.ToInternalValue());

  // Remove the visit to cnn.com.
  ASSERT_TRUE(backend_->RemoveVisits(visits1));
}

// Test for migration of adding visit_duration column.
TEST_F(HistoryBackendTest, MigrationVisitDuration) {
  ASSERT_TRUE(backend_.get());
  backend_->Closing();
  backend_ = NULL;

  base::FilePath old_history_path, old_history;
  ASSERT_TRUE(GetTestDataHistoryDir(&old_history_path));
  old_history = old_history_path.AppendASCII("HistoryNoDuration");

  // Copy history database file to current directory so that it will be deleted
  // in Teardown.
  base::FilePath new_history_path(test_dir());
  base::DeleteFile(new_history_path, true);
  base::CreateDirectory(new_history_path);
  base::FilePath new_history_file = new_history_path.Append(kHistoryFilename);
  ASSERT_TRUE(base::CopyFile(old_history, new_history_file));

  backend_ = new HistoryBackend(new HistoryBackendTestDelegate(this),
                                history_client_.CreateBackendClient(),
                                base::ThreadTaskRunnerHandle::Get());
  backend_->Init(false, TestHistoryDatabaseParamsForPath(new_history_path));
  backend_->Closing();
  backend_ = NULL;

  // Now the history database should already be migrated.

  // Check version in history database first.
  int cur_version = HistoryDatabase::GetCurrentVersion();
  sql::Connection db;
  ASSERT_TRUE(db.Open(new_history_file));
  sql::Statement s(db.GetUniqueStatement(
      "SELECT value FROM meta WHERE key = 'version'"));
  ASSERT_TRUE(s.Step());
  int file_version = s.ColumnInt(0);
  EXPECT_EQ(cur_version, file_version);

  // Check visit_duration column in visits table is created and set to 0.
  s.Assign(db.GetUniqueStatement(
      "SELECT visit_duration FROM visits LIMIT 1"));
  ASSERT_TRUE(s.Step());
  EXPECT_EQ(0, s.ColumnInt(0));
}

TEST_F(HistoryBackendTest, AddPageNoVisitForBookmark) {
  ASSERT_TRUE(backend_.get());

  GURL url("http://www.google.com");
  base::string16 title(base::UTF8ToUTF16("Bookmark title"));
  backend_->AddPageNoVisitForBookmark(url, title);

  URLRow row;
  backend_->GetURL(url, &row);
  EXPECT_EQ(url, row.url());
  EXPECT_EQ(title, row.title());
  EXPECT_EQ(0, row.visit_count());

  backend_->DeleteURL(url);
  backend_->AddPageNoVisitForBookmark(url, base::string16());
  backend_->GetURL(url, &row);
  EXPECT_EQ(url, row.url());
  EXPECT_EQ(base::UTF8ToUTF16(url.spec()), row.title());
  EXPECT_EQ(0, row.visit_count());
}

TEST_F(HistoryBackendTest, ExpireHistoryForTimes) {
  ASSERT_TRUE(backend_.get());

  HistoryAddPageArgs args[10];
  for (size_t i = 0; i < arraysize(args); ++i) {
    args[i].url = GURL("http://example" +
                       std::string((i % 2 == 0 ? ".com" : ".net")));
    args[i].time = base::Time::FromInternalValue(i);
    backend_->AddPage(args[i]);
  }
  EXPECT_EQ(base::Time(), backend_->GetFirstRecordedTimeForTest());

  URLRow row;
  for (size_t i = 0; i < arraysize(args); ++i) {
    EXPECT_TRUE(backend_->GetURL(args[i].url, &row));
  }

  std::set<base::Time> times;
  times.insert(args[5].time);
  // Invalid time (outside range), should have no effect.
  times.insert(base::Time::FromInternalValue(10));
  backend_->ExpireHistoryForTimes(times,
                                  base::Time::FromInternalValue(2),
                                  base::Time::FromInternalValue(8));

  EXPECT_EQ(base::Time::FromInternalValue(0),
            backend_->GetFirstRecordedTimeForTest());

  // Visits to http://example.com are untouched.
  VisitVector visit_vector;
  EXPECT_TRUE(backend_->GetVisitsForURL(
      backend_->db_->GetRowForURL(GURL("http://example.com"), NULL),
      &visit_vector));
  ASSERT_EQ(5u, visit_vector.size());
  EXPECT_EQ(base::Time::FromInternalValue(0), visit_vector[0].visit_time);
  EXPECT_EQ(base::Time::FromInternalValue(2), visit_vector[1].visit_time);
  EXPECT_EQ(base::Time::FromInternalValue(4), visit_vector[2].visit_time);
  EXPECT_EQ(base::Time::FromInternalValue(6), visit_vector[3].visit_time);
  EXPECT_EQ(base::Time::FromInternalValue(8), visit_vector[4].visit_time);

  // Visits to http://example.net between [2,8] are removed.
  visit_vector.clear();
  EXPECT_TRUE(backend_->GetVisitsForURL(
      backend_->db_->GetRowForURL(GURL("http://example.net"), NULL),
      &visit_vector));
  ASSERT_EQ(2u, visit_vector.size());
  EXPECT_EQ(base::Time::FromInternalValue(1), visit_vector[0].visit_time);
  EXPECT_EQ(base::Time::FromInternalValue(9), visit_vector[1].visit_time);

  EXPECT_EQ(base::Time::FromInternalValue(0),
            backend_->GetFirstRecordedTimeForTest());
}

TEST_F(HistoryBackendTest, ExpireHistory) {
  ASSERT_TRUE(backend_.get());
  // Since history operations are dependent on the local timezone, make all
  // entries relative to a fixed, local reference time.
  base::Time reference_time = base::Time::UnixEpoch().LocalMidnight() +
                              base::TimeDelta::FromHours(12);

  // Insert 4 entries into the database.
  HistoryAddPageArgs args[4];
  for (size_t i = 0; i < arraysize(args); ++i) {
    args[i].url = GURL("http://example" + base::SizeTToString(i) + ".com");
    args[i].time = reference_time + base::TimeDelta::FromDays(i);
    backend_->AddPage(args[i]);
  }

  URLRow url_rows[4];
  for (unsigned int i = 0; i < arraysize(args); ++i)
    ASSERT_TRUE(backend_->GetURL(args[i].url, &url_rows[i]));

  std::vector<ExpireHistoryArgs> expire_list;
  VisitVector visits;

  // Passing an empty map should be a no-op.
  backend_->ExpireHistory(expire_list);
  backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits);
  EXPECT_EQ(4U, visits.size());

  // Trying to delete an unknown URL with the time of the first visit should
  // also be a no-op.
  expire_list.resize(expire_list.size() + 1);
  expire_list[0].SetTimeRangeForOneDay(args[0].time);
  expire_list[0].urls.insert(GURL("http://google.does-not-exist"));
  backend_->ExpireHistory(expire_list);
  backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits);
  EXPECT_EQ(4U, visits.size());

  // Now add the first URL with the same time -- it should get deleted.
  expire_list.back().urls.insert(url_rows[0].url());
  backend_->ExpireHistory(expire_list);

  backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits);
  ASSERT_EQ(3U, visits.size());
  EXPECT_EQ(visits[0].url_id, url_rows[1].id());
  EXPECT_EQ(visits[1].url_id, url_rows[2].id());
  EXPECT_EQ(visits[2].url_id, url_rows[3].id());

  // The first recorded time should also get updated.
  EXPECT_EQ(backend_->GetFirstRecordedTimeForTest(), args[1].time);

  // Now delete the rest of the visits in one call.
  for (unsigned int i = 1; i < arraysize(args); ++i) {
    expire_list.resize(expire_list.size() + 1);
    expire_list[i].SetTimeRangeForOneDay(args[i].time);
    expire_list[i].urls.insert(args[i].url);
  }
  backend_->ExpireHistory(expire_list);

  backend_->db()->GetAllVisitsInRange(base::Time(), base::Time(), 0, &visits);
  ASSERT_EQ(0U, visits.size());
}

TEST_F(HistoryBackendTest, DeleteMatchingUrlsForKeyword) {
  // Set up urls and keyword_search_terms
  GURL url1("https://www.bing.com/?q=bar");
  URLRow url_info1(url1);
  url_info1.set_visit_count(0);
  url_info1.set_typed_count(0);
  url_info1.set_last_visit(base::Time());
  url_info1.set_hidden(false);
  const URLID url1_id = backend_->db()->AddURL(url_info1);
  EXPECT_NE(0, url1_id);

  KeywordID keyword_id = 1;
  base::string16 keyword = base::UTF8ToUTF16("bar");
  ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL(
      url1_id, keyword_id, keyword));

  GURL url2("https://www.google.com/?q=bar");
  URLRow url_info2(url2);
  url_info2.set_visit_count(0);
  url_info2.set_typed_count(0);
  url_info2.set_last_visit(base::Time());
  url_info2.set_hidden(false);
  const URLID url2_id = backend_->db()->AddURL(url_info2);
  EXPECT_NE(0, url2_id);

  KeywordID keyword_id2 = 2;
  ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL(
      url2_id, keyword_id2, keyword));

  // Add another visit to the same URL
  URLRow url_info3(url2);
  url_info3.set_visit_count(0);
  url_info3.set_typed_count(0);
  url_info3.set_last_visit(base::Time());
  url_info3.set_hidden(false);
  const URLID url3_id = backend_->db()->AddURL(url_info3);
  EXPECT_NE(0, url3_id);
  ASSERT_TRUE(backend_->db()->SetKeywordSearchTermsForURL(
      url3_id, keyword_id2, keyword));

  // Test that deletion works correctly
  backend_->DeleteMatchingURLsForKeyword(keyword_id2, keyword);

  // Test that rows 2 and 3 are deleted, while 1 is intact
  URLRow row;
  EXPECT_TRUE(backend_->db()->GetURLRow(url1_id, &row));
  EXPECT_EQ(url1.spec(), row.url().spec());
  EXPECT_FALSE(backend_->db()->GetURLRow(url2_id, &row));
  EXPECT_FALSE(backend_->db()->GetURLRow(url3_id, &row));

  // Test that corresponding keyword search terms are deleted for rows 2 & 3,
  // but not for row 1
  EXPECT_TRUE(backend_->db()->GetKeywordSearchTermRow(url1_id, NULL));
  EXPECT_FALSE(backend_->db()->GetKeywordSearchTermRow(url2_id, NULL));
  EXPECT_FALSE(backend_->db()->GetKeywordSearchTermRow(url3_id, NULL));
}

// Simple test that removes a bookmark. This test exercises the code paths in
// History that block till bookmark bar model is loaded.
TEST_F(HistoryBackendTest, RemoveNotification) {
  base::ScopedTempDir scoped_temp_dir;
  EXPECT_TRUE(scoped_temp_dir.CreateUniqueTempDirUnderPath(test_dir()));

  // Add a URL.
  GURL url("http://www.google.com");
  std::unique_ptr<HistoryService> service(
      new HistoryService(base::WrapUnique(new HistoryClientFakeBookmarks),
                         std::unique_ptr<history::VisitDelegate>()));
  EXPECT_TRUE(service->Init(
      TestHistoryDatabaseParamsForPath(scoped_temp_dir.GetPath())));

  service->AddPage(
      url, base::Time::Now(), NULL, 1, GURL(), RedirectList(),
      ui::PAGE_TRANSITION_TYPED, SOURCE_BROWSED, false);

  // This won't actually delete the URL, rather it'll empty out the visits.
  // This triggers blocking on the BookmarkModel.
  service->DeleteURL(url);
}

// Test DeleteFTSIndexDatabases deletes expected files.
TEST_F(HistoryBackendTest, DeleteFTSIndexDatabases) {
  ASSERT_TRUE(backend_.get());

  base::FilePath history_path(test_dir());
  base::FilePath db1(history_path.AppendASCII("History Index 2013-05"));
  base::FilePath db1_journal(db1.InsertBeforeExtensionASCII("-journal"));
  base::FilePath db1_wal(db1.InsertBeforeExtensionASCII("-wal"));
  base::FilePath db2_symlink(history_path.AppendASCII("History Index 2013-06"));
  base::FilePath db2_actual(history_path.AppendASCII("Underlying DB"));

  // Setup dummy index database files.
  const char* data = "Dummy";
  const size_t data_len = 5;
  ASSERT_EQ(static_cast<int>(data_len), base::WriteFile(db1, data, data_len));
  ASSERT_EQ(static_cast<int>(data_len),
            base::WriteFile(db1_journal, data, data_len));
  ASSERT_EQ(static_cast<int>(data_len),
            base::WriteFile(db1_wal, data, data_len));
  ASSERT_EQ(static_cast<int>(data_len),
            base::WriteFile(db2_actual, data, data_len));
#if defined(OS_POSIX)
  EXPECT_TRUE(base::CreateSymbolicLink(db2_actual, db2_symlink));
#endif

  // Delete all DTS index databases.
  backend_->DeleteFTSIndexDatabases();
  EXPECT_FALSE(base::PathExists(db1));
  EXPECT_FALSE(base::PathExists(db1_wal));
  EXPECT_FALSE(base::PathExists(db1_journal));
  EXPECT_FALSE(base::PathExists(db2_symlink));
  EXPECT_TRUE(base::PathExists(db2_actual));  // Symlinks shouldn't be followed.
}

// Common implementation for the two tests below, given that the only difference
// between them is the type of the notification sent out.
void InMemoryHistoryBackendTest::TestAddingAndChangingURLRows(
    const SimulateNotificationCallback& callback) {
  const char kTestTypedURLAlternativeTitle[] = "Google Search Again";
  const char kTestNonTypedURLAlternativeTitle[] = "Google News Again";

  // Notify the in-memory database that a typed and non-typed URLRow (which were
  // never before seen by the cache) have been modified.
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateTestNonTypedURL());
  callback.Run(&row1, &row2, nullptr);

  // The in-memory database should only pick up the typed URL, and should ignore
  // the non-typed one. The typed URL should retain the ID that was present in
  // the notification.
  URLRow cached_row1, cached_row2;
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2));
  EXPECT_EQ(row1.id(), cached_row1.id());

  // Try changing attributes (other than typed_count) for existing URLRows.
  row1.set_title(base::UTF8ToUTF16(kTestTypedURLAlternativeTitle));
  row2.set_title(base::UTF8ToUTF16(kTestNonTypedURLAlternativeTitle));
  callback.Run(&row1, &row2, nullptr);

  // URLRows that are cached by the in-memory database should be updated.
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2));
  EXPECT_EQ(base::UTF8ToUTF16(kTestTypedURLAlternativeTitle),
            cached_row1.title());

  // Now decrease the typed count for the typed URLRow, and increase it for the
  // previously non-typed URLRow.
  row1.set_typed_count(0);
  row2.set_typed_count(2);
  callback.Run(&row1, &row2, nullptr);

  // The in-memory database should stop caching the first URLRow, and start
  // caching the second URLRow.
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2));
  EXPECT_EQ(row2.id(), cached_row2.id());
  EXPECT_EQ(base::UTF8ToUTF16(kTestNonTypedURLAlternativeTitle),
            cached_row2.title());
}

TEST_F(InMemoryHistoryBackendTest, OnURLsModified) {
  TestAddingAndChangingURLRows(base::Bind(
      &SimulateNotificationURLsModified, base::Unretained(mem_backend_.get())));
}

TEST_F(InMemoryHistoryBackendTest, OnURLsVisisted) {
  TestAddingAndChangingURLRows(base::Bind(
      &SimulateNotificationURLVisited, base::Unretained(mem_backend_.get())));
}

TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedPiecewise) {
  // Add two typed and one non-typed URLRow to the in-memory database.
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateAnotherTestTypedURL());
  URLRow row3(CreateTestNonTypedURL());
  SimulateNotificationURLsModified(mem_backend_.get(), &row1, &row2, &row3);

  // Notify the in-memory database that the second typed URL and the non-typed
  // URL has been deleted.
  SimulateNotificationURLsDeleted(&row2, &row3);

  // Expect that the first typed URL remains intact, the second typed URL is
  // correctly removed, and the non-typed URL does not magically appear.
  URLRow cached_row1;
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), NULL));
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row3.url(), NULL));
  EXPECT_EQ(row1.id(), cached_row1.id());
}

TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedEnMasse) {
  // Add two typed and one non-typed URLRow to the in-memory database.
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateAnotherTestTypedURL());
  URLRow row3(CreateTestNonTypedURL());
  SimulateNotificationURLsModified(mem_backend_.get(), &row1, &row2, &row3);

  // Now notify the in-memory database that all history has been deleted.
  mem_backend_->OnURLsDeleted(nullptr, true, false, URLRows(),
                              std::set<GURL>());

  // Expect that everything goes away.
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row1.url(), NULL));
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row2.url(), NULL));
  EXPECT_EQ(0, mem_backend_->db()->GetRowForURL(row3.url(), NULL));
}

void InMemoryHistoryBackendTest::PopulateTestURLsAndSearchTerms(
    URLRow* row1,
    URLRow* row2,
    const base::string16& term1,
    const base::string16& term2) {
  // Add a typed and a non-typed URLRow to the in-memory database. This time,
  // though, do it through the history backend...
  URLRows rows;
  rows.push_back(*row1);
  rows.push_back(*row2);
  backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED);
  backend_->db()->GetRowForURL(row1->url(), row1);  // Get effective IDs from
  backend_->db()->GetRowForURL(row2->url(), row2);  // the database.

  // ... so that we can also use that for adding the search terms. This way, we
  // not only test that the notifications involved are handled correctly, but
  // also that they are fired correctly (in the history backend).
  backend_->SetKeywordSearchTermsForURL(row1->url(), kTestKeywordId, term1);
  backend_->SetKeywordSearchTermsForURL(row2->url(), kTestKeywordId, term2);
}

TEST_F(InMemoryHistoryBackendTest, SetKeywordSearchTerms) {
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateTestNonTypedURL());
  base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1));
  base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2));
  PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2);

  // Both URLs now have associated search terms, so the in-memory database
  // should cache both of them, regardless whether they have been typed or not.
  URLRow cached_row1, cached_row2;
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row2.url(), &cached_row2));
  EXPECT_EQ(row1.id(), cached_row1.id());
  EXPECT_EQ(row2.id(), cached_row2.id());

  // Verify that lookups will actually return both search terms; and also check
  // at the low level that the rows are there.
  EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1));
  EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2));
  EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL));
  EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL));
}

TEST_F(InMemoryHistoryBackendTest, DeleteKeywordSearchTerms) {
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateTestNonTypedURL());
  base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1));
  base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2));
  PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2);

  // Delete both search terms. This should be reflected in the in-memory DB.
  backend_->DeleteKeywordSearchTermForURL(row1.url());
  backend_->DeleteKeywordSearchTermForURL(row2.url());

  // The typed URL should remain intact.
  // Note: we do not need to guarantee anything about the non-typed URL.
  URLRow cached_row1;
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_EQ(row1.id(), cached_row1.id());

  // Verify that the search terms are no longer returned as results, and also
  // check at the low level that they are gone for good.
  EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1));
  EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2));
  EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL));
  EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL));
}

TEST_F(InMemoryHistoryBackendTest, DeleteAllSearchTermsForKeyword) {
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateTestNonTypedURL());
  base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1));
  base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2));
  PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2);

  // Delete all corresponding search terms from the in-memory database.
  KeywordID id = kTestKeywordId;
  mem_backend_->DeleteAllSearchTermsForKeyword(id);

  // The typed URL should remain intact.
  // Note: we do not need to guarantee anything about the non-typed URL.
  URLRow cached_row1;
  EXPECT_NE(0, mem_backend_->db()->GetRowForURL(row1.url(), &cached_row1));
  EXPECT_EQ(row1.id(), cached_row1.id());

  // Verify that the search terms are no longer returned as results, and also
  // check at the low level that they are gone for good.
  EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1));
  EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2));
  EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL));
  EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL));
}

TEST_F(InMemoryHistoryBackendTest, OnURLsDeletedWithSearchTerms) {
  URLRow row1(CreateTestTypedURL());
  URLRow row2(CreateTestNonTypedURL());
  base::string16 term1(base::UTF8ToUTF16(kTestSearchTerm1));
  base::string16 term2(base::UTF8ToUTF16(kTestSearchTerm2));
  PopulateTestURLsAndSearchTerms(&row1, &row2, term1, term2);

  // Notify the in-memory database that the second typed URL has been deleted.
  SimulateNotificationURLsDeleted(&row2);

  // Verify that the second term is no longer returned as result, and also check
  // at the low level that it is gone for good. The term corresponding to the
  // first URLRow should not be affected.
  EXPECT_EQ(1u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term1));
  EXPECT_EQ(0u, GetNumberOfMatchingSearchTerms(kTestKeywordId, term2));
  EXPECT_TRUE(mem_backend_->db()->GetKeywordSearchTermRow(row1.id(), NULL));
  EXPECT_FALSE(mem_backend_->db()->GetKeywordSearchTermRow(row2.id(), NULL));
}

TEST_F(HistoryBackendTest, QueryMostVisitedURLs) {
  ASSERT_TRUE(backend_.get());

  // Pairs from page transitions to consider_for_ntp_most_visited.
  std::vector<std::pair<ui::PageTransition, bool>> pages;
  pages.emplace_back(ui::PAGE_TRANSITION_AUTO_BOOKMARK, true);   // good.
  pages.emplace_back(ui::PAGE_TRANSITION_AUTO_BOOKMARK, false);  // bad.
  pages.emplace_back(ui::PAGE_TRANSITION_LINK, true);            // bad.
  pages.emplace_back(ui::PAGE_TRANSITION_TYPED, false);          // bad.
  pages.emplace_back(ui::PAGE_TRANSITION_TYPED, true);           // good.

  for (size_t i = 0; i < pages.size(); ++i) {
    HistoryAddPageArgs args;
    args.url = GURL("http://example" + base::SizeTToString(i + 1) + ".com");
    args.time = base::Time::Now() - base::TimeDelta::FromDays(i + 1);
    args.transition = pages[i].first;
    args.consider_for_ntp_most_visited = pages[i].second;
    backend_->AddPage(args);
  }

  MostVisitedURLList most_visited;
  backend_->QueryMostVisitedURLs(100, 100, &most_visited);

  const base::string16 kSomeTitle;  // Ignored by equality operator.
  EXPECT_THAT(
      most_visited,
      ElementsAre(MostVisitedURL(GURL("http://example1.com"), kSomeTitle),
                  MostVisitedURL(GURL("http://example5.com"), kSomeTitle)));
}

}  // namespace history
