root/chrome/browser/history/history_backend_unittest.cc

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

DEFINITIONS

This source file includes following definitions.
  1. IconMappingLessThan
  2. FaviconBitmapLessThan
  3. NotifyProfileError
  4. NotifyVisitDBObserversOnAddVisit
  5. MockScheduleOfRequest
  6. ui_thread_
  7. OnQueryMostVisited
  8. OnQueryFiltered
  9. get_most_visited_list
  10. get_filtered_list
  11. num_broadcasted_notifications
  12. AddRedirectChain
  13. AddRedirectChainWithTransitionAndTime
  14. AddClientRedirect
  15. getTransition
  16. getTestDir
  17. GetSizesSmall
  18. GetSizesLarge
  19. GetSizesSmallAndLarge
  20. GetSizesTinySmallAndLarge
  21. GetScaleFactors1x2x
  22. NumIconMappingsForPageURL
  23. GetSortedIconMappingsForPageURL
  24. GetSortedFaviconBitmaps
  25. GetOnlyFaviconBitmap
  26. GenerateFaviconBitmapData
  27. GenerateFaviconBitmapData
  28. BitmapDataEqual
  29. SetUp
  30. TearDown
  31. SetInMemoryBackend
  32. BroadcastNotifications
  33. SetInMemoryBackend
  34. BroadcastNotifications
  35. DBLoaded
  36. TEST_F
  37. TEST_F
  38. TEST_F
  39. TEST_F
  40. TEST_F
  41. TEST_F
  42. TEST_F
  43. TEST_F
  44. TEST_F
  45. TEST_F
  46. TEST_F
  47. TEST_F
  48. TEST_F
  49. TEST_F
  50. TEST_F
  51. TEST_F
  52. TEST_F
  53. TEST_F
  54. TEST_F
  55. TEST_F
  56. TEST_F
  57. TEST_F
  58. TEST_F
  59. TEST_F
  60. TEST_F
  61. TEST_F
  62. TEST_F
  63. TEST_F
  64. TEST_F
  65. TEST_F
  66. TEST_F
  67. TEST_F
  68. TEST_F
  69. TEST_F
  70. TEST_F
  71. TEST_F
  72. TEST_F
  73. TEST_F
  74. TEST_F
  75. TEST_F
  76. TEST_F
  77. TEST_F
  78. TEST_F
  79. TEST_F
  80. TEST_F
  81. TEST_F
  82. TEST_F
  83. TEST_F

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

#include "chrome/browser/history/history_backend.h"

#include <algorithm>
#include <set>
#include <vector>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.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 "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/bookmark_test_helpers.h"
#include "chrome/browser/bookmarks/bookmark_utils.h"
#include "chrome/browser/history/history_notifications.h"
#include "chrome/browser/history/history_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/in_memory_database.h"
#include "chrome/browser/history/in_memory_history_backend.h"
#include "chrome/browser/history/visit_filter.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/importer/imported_favicon_usage.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using base::Time;

// 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 {

static const gfx::Size kTinySize = gfx::Size(10, 10);
static const gfx::Size kSmallSize = gfx::Size(16, 16);
static const gfx::Size kLargeSize = gfx::Size(32, 32);

// Comparison functions as to make it easier to check results of
// GetFaviconBitmaps() and GetIconMappingsForPageURL().
bool IconMappingLessThan(const history::IconMapping& a,
                         const history::IconMapping& b) {
  return a.icon_url < b.icon_url;
}

bool FaviconBitmapLessThan(const history::FaviconBitmap& a,
                           const history::FaviconBitmap& b) {
  return a.pixel_size.GetArea() < b.pixel_size.GetArea();
}

}  // namespace

namespace history {

class HistoryBackendTest;

// 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(HistoryBackendTest* test) : test_(test) {}

  virtual void NotifyProfileError(sql::InitStatus init_status) OVERRIDE {}
  virtual void SetInMemoryBackend(
      scoped_ptr<InMemoryHistoryBackend> backend) OVERRIDE;
  virtual void BroadcastNotifications(
      int type,
      scoped_ptr<HistoryDetails> details) OVERRIDE;
  virtual void DBLoaded() OVERRIDE;
  virtual void NotifyVisitDBObserversOnAddVisit(
      const BriefVisitInfo& info) OVERRIDE {}

 private:
  // Not owned by us.
  HistoryBackendTest* test_;

  DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate);
};

class HistoryBackendCancelableRequest
    : public CancelableRequestProvider,
      public CancelableRequestConsumerTSimple<int> {
 public:
  HistoryBackendCancelableRequest() {}

  template<class RequestType>
  CancelableRequestProvider::Handle MockScheduleOfRequest(
      RequestType* request) {
    AddRequest(request, this);
    return request->handle();
  }
};

class HistoryBackendTest : public testing::Test {
 public:
  HistoryBackendTest()
      : bookmark_model_(NULL),
        loaded_(false),
        num_broadcasted_notifications_(0),
        ui_thread_(content::BrowserThread::UI, &message_loop_) {
  }

  virtual ~HistoryBackendTest() {
  }

  // Callback for QueryMostVisited.
  void OnQueryMostVisited(CancelableRequestProvider::Handle handle,
                          history::MostVisitedURLList data) {
    most_visited_list_.swap(data);
  }

  // Callback for QueryFiltered.
  void OnQueryFiltered(CancelableRequestProvider::Handle handle,
                       const history::FilteredURLList& data) {
    filtered_list_ = data;
  }

  const history::MostVisitedURLList& get_most_visited_list() const {
    return most_visited_list_;
  }

  const history::FilteredURLList& get_filtered_list() const {
    return filtered_list_;
  }

  int num_broadcasted_notifications() const {
    return num_broadcasted_notifications_;
  }

 protected:
  scoped_refptr<HistoryBackend> backend_;  // Will be NULL on init failure.
  scoped_ptr<InMemoryHistoryBackend> mem_backend_;

  void AddRedirectChain(const char* sequence[], int page_id) {
    AddRedirectChainWithTransitionAndTime(sequence, page_id,
                                          content::PAGE_TRANSITION_LINK,
                                          Time::Now());
  }

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

    int int_scope = 1;
    void* scope = 0;
    memcpy(&scope, &int_scope, sizeof(int_scope));
    history::HistoryAddPageArgs request(
        redirects.back(), time, scope, page_id, GURL(),
        redirects, transition, history::SOURCE_BROWSED,
        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) {
    void* const dummy_scope = reinterpret_cast<void*>(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_scope, 0, url1,
        redirects, content::PAGE_TRANSITION_CLIENT_REDIRECT,
        history::SOURCE_BROWSED, did_replace);
    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;
  }

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

  // Returns a gfx::Size vector with small size.
  const std::vector<gfx::Size> GetSizesSmall() {
    std::vector<gfx::Size> sizes_small;
    sizes_small.push_back(kSmallSize);
    return sizes_small;
  }

  // Returns a gfx::Size vector with large size.
  const std::vector<gfx::Size> GetSizesLarge() {
    std::vector<gfx::Size> sizes_large;
    sizes_large.push_back(kLargeSize);
    return sizes_large;
  }

  // Returns a gfx::Size vector with small and large sizes.
  const std::vector<gfx::Size> GetSizesSmallAndLarge() {
    std::vector<gfx::Size> sizes_small_and_large;
    sizes_small_and_large.push_back(kSmallSize);
    sizes_small_and_large.push_back(kLargeSize);
    return sizes_small_and_large;
  }

  // Returns a gfx::Size vector with tiny, small and large sizes.
  const std::vector<gfx::Size> GetSizesTinySmallAndLarge() {
    std::vector<gfx::Size> sizes_tiny_small_and_large;
    sizes_tiny_small_and_large.push_back(kTinySize);
    sizes_tiny_small_and_large.push_back(kSmallSize);
    sizes_tiny_small_and_large.push_back(kLargeSize);
    return sizes_tiny_small_and_large;
  }

  // Returns 1x and 2x scale factors.
  const std::vector<ui::ScaleFactor> GetScaleFactors1x2x() {
    std::vector<ui::ScaleFactor> scale_factors_1x_2x;
    scale_factors_1x_2x.push_back(ui::SCALE_FACTOR_100P);
    scale_factors_1x_2x.push_back(ui::SCALE_FACTOR_200P);
    return scale_factors_1x_2x;
  }

  // Returns the number of icon mappings of |icon_type| to |page_url|.
  size_t NumIconMappingsForPageURL(const GURL& page_url,
                                   chrome::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(),
              IconMappingLessThan);
    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(chrome::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(),
              FaviconBitmapLessThan);
    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 chrome::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;
  }

  // Generates |favicon_bitmap_data| with entries for the icon_urls and sizes
  // specified. The bitmap_data for entries are lowercase letters of the
  // alphabet starting at 'a' for the entry at index 0.
  void GenerateFaviconBitmapData(
      const GURL& icon_url1,
      const std::vector<gfx::Size>& icon_url1_sizes,
      std::vector<chrome::FaviconBitmapData>* favicon_bitmap_data) {
    GenerateFaviconBitmapData(icon_url1, icon_url1_sizes, GURL(),
                              std::vector<gfx::Size>(), favicon_bitmap_data);
  }

  void GenerateFaviconBitmapData(
      const GURL& icon_url1,
      const std::vector<gfx::Size>& icon_url1_sizes,
      const GURL& icon_url2,
      const std::vector<gfx::Size>& icon_url2_sizes,
      std::vector<chrome::FaviconBitmapData>* favicon_bitmap_data) {
    favicon_bitmap_data->clear();

    char bitmap_char = 'a';
    for (size_t i = 0; i < icon_url1_sizes.size(); ++i) {
      std::vector<unsigned char> data;
      data.push_back(bitmap_char);
      chrome::FaviconBitmapData bitmap_data_element;
      bitmap_data_element.bitmap_data =
          base::RefCountedBytes::TakeVector(&data);
      bitmap_data_element.pixel_size = icon_url1_sizes[i];
      bitmap_data_element.icon_url = icon_url1;
      favicon_bitmap_data->push_back(bitmap_data_element);

      ++bitmap_char;
    }

    for (size_t i = 0; i < icon_url2_sizes.size(); ++i) {
      std::vector<unsigned char> data;
      data.push_back(bitmap_char);
      chrome::FaviconBitmapData bitmap_data_element;
      bitmap_data_element.bitmap_data =
          base::RefCountedBytes::TakeVector(&data);
      bitmap_data_element.pixel_size = icon_url2_sizes[i];
      bitmap_data_element.icon_url = icon_url2;
      favicon_bitmap_data->push_back(bitmap_data_element);

      ++bitmap_char;
    }
  }

  // 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;
  }

  BookmarkModel bookmark_model_;

 protected:
  bool loaded_;

 private:
  friend class HistoryBackendTestDelegate;

  // testing::Test
  virtual void SetUp() {
    if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"),
                                      &test_dir_))
      return;
    backend_ = new HistoryBackend(test_dir_,
                                  new HistoryBackendTestDelegate(this),
                                  &bookmark_model_);
    backend_->Init(std::string(), false);
  }

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

  void SetInMemoryBackend(scoped_ptr<InMemoryHistoryBackend> backend) {
    mem_backend_.swap(backend);
  }

  void BroadcastNotifications(int type, scoped_ptr<HistoryDetails> details) {
    ++num_broadcasted_notifications_;

    // Send the notifications directly to the in-memory database.
    content::Details<HistoryDetails> det(details.get());
    mem_backend_->Observe(type, content::Source<HistoryBackendTest>(NULL), det);
  }

  // The number of notifications which were broadcasted.
  int num_broadcasted_notifications_;

  base::MessageLoop message_loop_;
  base::FilePath test_dir_;
  history::MostVisitedURLList most_visited_list_;
  history::FilteredURLList filtered_list_;
  content::TestBrowserThread ui_thread_;
};

void HistoryBackendTestDelegate::SetInMemoryBackend(
    scoped_ptr<InMemoryHistoryBackend> backend) {
  test_->SetInMemoryBackend(backend.Pass());
}

void HistoryBackendTestDelegate::BroadcastNotifications(
    int type,
    scoped_ptr<HistoryDetails> details) {
  test_->BroadcastNotifications(type, details.Pass());
}

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

// 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");
  chrome::FaviconID favicon2 = backend_->thumbnail_db_->AddFavicon(favicon_url2,
      chrome::FAVICON);
  chrome::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(favicon_url1,
      chrome::FAVICON);

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

  data[0] = 'c';
  EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2,
      new base::RefCountedBytes(data), Time::Now(), kSmallSize));
  data[0] = 'd';
  EXPECT_TRUE(backend_->thumbnail_db_->AddFaviconBitmap(favicon2,
     new base::RefCountedBytes(data), 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(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(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.
  bookmark_model_.AddURL(
      bookmark_model_.bookmark_bar_node(), 0, base::string16(), row1.url());

  // Now finally clear all history.
  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(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(Time(), 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.
  chrome::FaviconID out_favicon1 = backend_->thumbnail_db_->
      GetFaviconIDForFaviconURL(favicon_url1, chrome::FAVICON, NULL);
  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);

  chrome::FaviconID out_favicon2 = backend_->thumbnail_db_->
      GetFaviconIDForFaviconURL(favicon_url2, chrome::FAVICON, NULL);
  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(), chrome::FAVICON, &mappings));
  EXPECT_EQ(1u, mappings.size());
  EXPECT_EQ(out_favicon1, mappings[0].icon_id);

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

// 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());

  Time visit_time = Time::Now();
  GURL url("http://www.google.com/");
  HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(),
                             history::RedirectList(),
                             content::PAGE_TRANSITION_KEYWORD_GENERATED,
                             history::SOURCE_BROWSED, false);
  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(Time(), 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(Time(), 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(Time(), 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');
  chrome::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(
      favicon_url1,
      chrome::FAVICON,
      new base::RefCountedBytes(data),
      Time::Now(),
      gfx::Size());

  data[0] = '2';
  chrome::FaviconID favicon2 = backend_->thumbnail_db_->AddFavicon(
      favicon_url2,
      chrome::FAVICON,
      new base::RefCountedBytes(data),
      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(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(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.
  bookmark_utils::AddIfNotBookmarked(&bookmark_model_, row1.url(),
                                     base::string16());
  bookmark_utils::AddIfNotBookmarked(&bookmark_model_, row2.url(),
                                     base::string16());

  // Delete url 2. Because url 2 is starred this won't delete the URL, only
  // the visits.
  backend_->expirer_.DeleteURL(row2.url());

  // Make sure url 2 is still valid, but has no visits.
  URLRow tmp_url_row;
  EXPECT_EQ(row2_id, 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,
                                                         chrome::FAVICON,
                                                         NULL));

  // Unstar row2.
  bookmark_utils::RemoveAllBookmarks(&bookmark_model_, 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 no longer exist.
  EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &tmp_url_row));
  // And the favicon should be deleted.
  EXPECT_EQ(0,
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(favicon_url2,
                                                         chrome::FAVICON,
                                                         NULL));

  // Unstar row 1.
  bookmark_utils::RemoveAllBookmarks(&bookmark_model_, 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,
                                                         chrome::FAVICON,
                                                         NULL));
}

// 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");

  Time visit_time = Time::Now() - base::TimeDelta::FromDays(1);
  HistoryAddPageArgs request(url, visit_time, NULL, 0, GURL(),
                             history::RedirectList(),
                             content::PAGE_TRANSITION_KEYWORD_GENERATED,
                             history::SOURCE_BROWSED, false);
  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());

  // Expire the visits.
  std::set<GURL> restrict_urls;
  backend_->expire_backend()->ExpireHistoryBetween(restrict_urls,
                                                   visit_time, 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 & content::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 & content::PAGE_TRANSITION_CHAIN_END);
  EXPECT_TRUE(transition2 & content::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 & content::PAGE_TRANSITION_CHAIN_END);
  EXPECT_TRUE(transition2 & content::PAGE_TRANSITION_CHAIN_END);
}

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');
  chrome::FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(
      favicon_url1,
      chrome::FAVICON,
      base::RefCountedBytes::TakeVector(&data),
      Time::Now(),
      gfx::Size());
  URLRow row1(GURL("http://www.google.com/"));
  row1.set_visit_count(1);
  row1.set_last_visit(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(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(), chrome::FAVICON));
  EXPECT_EQ(0u, NumIconMappingsForPageURL(row2.url(), chrome::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.
  std::vector<ImportedFaviconUsage> favicons;
  ImportedFaviconUsage 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(), chrome::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(), chrome::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.
  bookmark_model_.AddURL(bookmark_model_.bookmark_bar_node(), 0,
                         base::string16(), url3);
  backend_->SetImportedFavicons(favicons);
  EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0);
  EXPECT_TRUE(url_row3.visit_count() == 0);
}

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,
      content::PageTransitionFromInt(
          content::PageTransitionGetQualifier(content::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, 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, content::PAGE_TRANSITION_TYPED,
      history::SOURCE_EXTENSION);
  // Assume the url is imported from Firefox.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
                         content::PAGE_TRANSITION_TYPED,
                         history::SOURCE_FIREFOX_IMPORTED);
  // Assume this url is also synced.
  backend_->AddPageVisit(url, base::Time::Now(), 0,
                         content::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,
      content::PageTransitionFromInt(
          content::PageTransitionGetQualifier(content::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,
      content::PageTransitionFromInt(
          content::PageTransitionGetQualifier(content::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, 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(),
                             content::PAGE_TRANSITION_KEYWORD_GENERATED,
                             history::SOURCE_BROWSED, false);
  backend_->AddPage(request1);
  // Assume this page is synced.
  HistoryAddPageArgs request2(url, base::Time::Now(), NULL, 0, GURL(),
                             history::RedirectList(),
                             content::PAGE_TRANSITION_LINK,
                             history::SOURCE_SYNCED, false);
  backend_->AddPage(request2);
  // Assume this page is browsed again.
  HistoryAddPageArgs request3(url, base::Time::Now(), NULL, 0, GURL(),
                             history::RedirectList(),
                             content::PAGE_TRANSITION_TYPED,
                             history::SOURCE_BROWSED, false);
  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(
      Time::Now() - base::TimeDelta::FromDays(5),
      content::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      Time::Now() - base::TimeDelta::FromDays(1),
      content::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      Time::Now(), content::PAGE_TRANSITION_LINK));

  GURL url2("http://www.example.com");
  visits2.push_back(VisitInfo(
      Time::Now() - base::TimeDelta::FromDays(10),
      content::PAGE_TRANSITION_LINK));
  visits2.push_back(VisitInfo(Time::Now(), content::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(
      Time::Now() - base::TimeDelta::FromDays(5),
      content::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      Time::Now() - base::TimeDelta::FromDays(1),
      content::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(
      Time::Now(), content::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(
      Time::Now() - base::TimeDelta::FromDays(6),
      content::PAGE_TRANSITION_TYPED);
  VisitInfo reload_visit(
      Time::Now() - base::TimeDelta::FromDays(5),
      content::PAGE_TRANSITION_RELOAD);
  VisitInfo link_visit(
      Time::Now() - base::TimeDelta::FromDays(4),
      content::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(
      Time::Now() - base::TimeDelta::FromDays(5),
      content::PAGE_TRANSITION_LINK));
  visits1.push_back(VisitInfo(Time::Now(),
    content::PAGE_TRANSITION_LINK));

  GURL url2("http://www.example.com");
  visits2.push_back(VisitInfo(
      Time::Now() - base::TimeDelta::FromDays(10),
      content::PAGE_TRANSITION_LINK));
  visits2.push_back(VisitInfo(Time::Now(), content::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(PathService::Get(chrome::DIR_TEST_DATA, &old_history_path));
  old_history_path = old_history_path.AppendASCII("History");
  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(getTestDir());
  base::DeleteFile(new_history_path, true);
  base::CreateDirectory(new_history_path);
  base::FilePath new_history_file =
      new_history_path.Append(chrome::kHistoryFilename);
  ASSERT_TRUE(base::CopyFile(old_history_path, new_history_file));

  backend_ = new HistoryBackend(new_history_path,
                                new HistoryBackendTestDelegate(this),
                                &bookmark_model_);
  backend_->Init(std::string(), false);
  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");

  // Generate bitmap data for a page with two favicons.
  std::vector<chrome::FaviconBitmapData> two_favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url1, GetSizesSmallAndLarge(),
      icon_url2, GetSizesSmallAndLarge(), &two_favicon_bitmap_data);

  // Generate bitmap data for a page with a single favicon.
  std::vector<chrome::FaviconBitmapData> one_favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url1, GetSizesSmallAndLarge(),
                            &one_favicon_bitmap_data);

  // Add two favicons
  backend_->SetFavicons(url1, chrome::FAVICON, two_favicon_bitmap_data);
  EXPECT_EQ(2u, NumIconMappingsForPageURL(url1, chrome::FAVICON));
  EXPECT_EQ(2u, NumIconMappingsForPageURL(url2, chrome::FAVICON));

  // Add one touch_icon
  backend_->SetFavicons(url1, chrome::TOUCH_ICON, one_favicon_bitmap_data);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, chrome::TOUCH_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, chrome::TOUCH_ICON));
  EXPECT_EQ(2u, NumIconMappingsForPageURL(url1, chrome::FAVICON));

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

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

  // Add a single favicon.
  backend_->SetFavicons(url1, chrome::FAVICON, one_favicon_bitmap_data);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, chrome::TOUCH_ICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, chrome::FAVICON));
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url2, chrome::FAVICON));

  // Add two favicons.
  backend_->SetFavicons(url1, chrome::FAVICON, two_favicon_bitmap_data);
  EXPECT_EQ(1u, NumIconMappingsForPageURL(url1, chrome::TOUCH_ICON));
  EXPECT_EQ(2u, NumIconMappingsForPageURL(url1, chrome::FAVICON));
}

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

  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmallAndLarge(),
                            &favicon_bitmap_data);

  backend_->SetFavicons(url, chrome::FAVICON, favicon_bitmap_data);

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

  backend_->SetFavicons(url, chrome::FAVICON, favicon_bitmap_data);

  icon_mappings.clear();
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      url, chrome::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<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmallAndLarge(),
                            &favicon_bitmap_data);
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  // 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(chrome::FAVICON, icon_mappings[0].icon_type);
  chrome::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(BitmapDataEqual('a', 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(BitmapDataEqual('b', 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.
  GenerateFaviconBitmapData(icon_url, GetSizesLarge(), &favicon_bitmap_data);
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  scoped_refptr<base::RefCountedMemory> bitmap_data_out;
  gfx::Size pixel_size_out;
  EXPECT_FALSE(backend_->thumbnail_db_->GetFaviconBitmap(small_bitmap_id,
      NULL, &bitmap_data_out, &pixel_size_out));
  EXPECT_TRUE(backend_->thumbnail_db_->GetFaviconBitmap(large_bitmap_id,
      NULL, &bitmap_data_out, &pixel_size_out));
  EXPECT_TRUE(BitmapDataEqual('a', 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);

  // Call SetFavicons() with no bitmap data. Check that the bitmaps and icon
  // mappings are deleted.
  favicon_bitmap_data.clear();
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  EXPECT_FALSE(backend_->thumbnail_db_->GetFaviconBitmap(large_bitmap_id, NULL,
      NULL, NULL));
  icon_mappings.clear();
  EXPECT_FALSE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url,
      &icon_mappings));

  // Notifications should have been broadcast for each call to SetFavicons().
  EXPECT_EQ(3, num_broadcasted_notifications());
}

// 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<unsigned char> data_initial;
  data_initial.push_back('a');

  chrome::FaviconBitmapData bitmap_data_element;
  bitmap_data_element.bitmap_data =
      base::RefCountedBytes::TakeVector(&data_initial);
  bitmap_data_element.pixel_size = kSmallSize;
  bitmap_data_element.icon_url = icon_url;
  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  favicon_bitmap_data.push_back(bitmap_data_element);

  // Add bitmap to the database.
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  chrome::FaviconID original_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
          icon_url, chrome::FAVICON, NULL);
  EXPECT_NE(0, original_favicon_id);
  FaviconBitmap original_favicon_bitmap;
  EXPECT_TRUE(
      GetOnlyFaviconBitmap(original_favicon_id, &original_favicon_bitmap));
  EXPECT_TRUE(BitmapDataEqual('a', original_favicon_bitmap.bitmap_data));

  EXPECT_EQ(1, num_broadcasted_notifications());

  // Call SetFavicons() with completely identical data.
  std::vector<unsigned char> updated_data;
  updated_data.push_back('a');
  favicon_bitmap_data[0].bitmap_data = new base::RefCountedBytes(updated_data);
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  chrome::FaviconID updated_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
          icon_url, chrome::FAVICON, NULL);
  EXPECT_NE(0, updated_favicon_id);
  FaviconBitmap updated_favicon_bitmap;
  EXPECT_TRUE(
      GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap));
  EXPECT_TRUE(BitmapDataEqual('a', updated_favicon_bitmap.bitmap_data));

  // Because the bitmap data is byte equivalent, no notifications should have
  // been broadcasted.
  EXPECT_EQ(1, num_broadcasted_notifications());

  // Call SetFavicons() with identical data but a different bitmap.
  updated_data[0] = 'b';
  favicon_bitmap_data[0].bitmap_data = new base::RefCountedBytes(updated_data);
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  updated_favicon_id =
      backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
          icon_url, chrome::FAVICON, NULL);
  EXPECT_NE(0, updated_favicon_id);
  EXPECT_TRUE(
      GetOnlyFaviconBitmap(updated_favicon_id, &updated_favicon_bitmap));
  EXPECT_TRUE(BitmapDataEqual('b', 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);

  // A notification should have been broadcasted as the favicon bitmap data has
  // changed.
  EXPECT_EQ(2, num_broadcasted_notifications());
}

// 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<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmallAndLarge(),
                            &favicon_bitmap_data);

  backend_->SetFavicons(page_url1, chrome::FAVICON, favicon_bitmap_data);

  std::vector<GURL> icon_urls;
  icon_urls.push_back(icon_url);

  std::vector<chrome::FaviconBitmapResult> bitmap_results;
  backend_->UpdateFaviconMappingsAndFetch(
      page_url2, icon_urls, chrome::FAVICON, kSmallSize.width(),
      GetScaleFactors1x2x(), &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());
  chrome::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.
  GenerateFaviconBitmapData(icon_url_new, GetSizesSmall(),
                            &favicon_bitmap_data);
  backend_->SetFavicons(page_url1, chrome::FAVICON, favicon_bitmap_data);

  // |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());

  // A notification should have been broadcast for each call to SetFavicons()
  // and each call to UpdateFaviconMappingsAndFetch().
  EXPECT_EQ(3, num_broadcasted_notifications());
}

// Test that no notifications are broadcast as a result of calling
// UpdateFaviconMappingsAndFetch() for an icon URL which is already
// mapped to the passed in page URL.
TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoChange) {
  GURL page_url("http://www.google.com");
  GURL icon_url("http://www.google.com/favicon.ico");
  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmall(), &favicon_bitmap_data);

  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  chrome::FaviconID icon_id =
    backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
        icon_url, chrome::FAVICON, NULL);
  EXPECT_NE(0, icon_id);
  EXPECT_EQ(1, num_broadcasted_notifications());

  std::vector<GURL> icon_urls;
  icon_urls.push_back(icon_url);

  std::vector<chrome::FaviconBitmapResult> bitmap_results;
  backend_->UpdateFaviconMappingsAndFetch(
      page_url, icon_urls, chrome::FAVICON, kSmallSize.width(),
      GetScaleFactors1x2x(), &bitmap_results);

  EXPECT_EQ(icon_id, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(
      icon_url, chrome::FAVICON, NULL));

  // No notification should have been broadcast as no icon mapping, favicon,
  // or favicon bitmap was updated, added or removed.
  EXPECT_EQ(1, num_broadcasted_notifications());
}

// 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, chrome::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, chrome::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_NE(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<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url1, GetSizesSmall(),
                            &favicon_bitmap_data);

  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  // 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(BitmapDataEqual('a', favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  EXPECT_EQ(1, num_broadcasted_notifications());

  // 1) Merge identical favicon bitmap.
  std::vector<unsigned char> data;
  data.push_back('a');
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));
  backend_->MergeFavicon(
      page_url, icon_url1, chrome::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(BitmapDataEqual('a', favicon_bitmap.bitmap_data));
  EXPECT_EQ(kSmallSize, favicon_bitmap.pixel_size);

  EXPECT_EQ(1, num_broadcasted_notifications());

  // 2) Merge favicon bitmap of the same size.
  data[0] = 'b';
  bitmap_data = new base::RefCountedBytes(data);
  backend_->MergeFavicon(
      page_url, icon_url1, chrome::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_NE(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, chrome::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, chrome::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);

  // A notification should have been broadcast for each call to SetFavicons()
  // and MergeFavicon().
  EXPECT_EQ(4, num_broadcasted_notifications());
}

// 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<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmall(),
                            &favicon_bitmap_data);

  backend_->SetFavicons(page_url1, chrome::FAVICON, favicon_bitmap_data);

  // 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(BitmapDataEqual('a', 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;
  data.push_back('a');
  scoped_refptr<base::RefCountedBytes> bitmap_data(
      new base::RefCountedBytes(data));

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

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

  EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_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);

  // 2) Merging a favicon bitmap with different bitmap data for the same icon
  // URL should overwrite the small favicon bitmap at |icon_url|.
  bitmap_data->data()[0] = 'b';
  backend_->MergeFavicon(
      page_url3, icon_url, chrome::FAVICON, bitmap_data, kSmallSize);

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

  EXPECT_TRUE(GetOnlyFaviconBitmap(favicon_id, &favicon_bitmap));
  EXPECT_NE(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);

  // A notification should have been broadcast for each call to SetFavicons()
  // and MergeFavicon().
  EXPECT_EQ(3, num_broadcasted_notifications());
}

// 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, chrome::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<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmallAndLarge(),
                            &favicon_bitmap_data);

  // Set some preexisting favicons for |page_url|.
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  // 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, chrome::FAVICON, bitmap_data, kSmallSize);

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

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

// 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<chrome::FaviconBitmapData> favicon_bitmap_data;
  std::vector<gfx::Size> favicon_size;
  favicon_size.push_back(gfx::Size(16, 16));
  favicon_size.push_back(gfx::Size(32, 32));
  GenerateFaviconBitmapData(icon_url, favicon_size, &favicon_bitmap_data);
  ASSERT_EQ(2u, favicon_bitmap_data.size());

  std::vector<chrome::FaviconBitmapData> touch_icon_bitmap_data;
  std::vector<gfx::Size> touch_icon_size;
  touch_icon_size.push_back(gfx::Size(64, 64));
  GenerateFaviconBitmapData(icon_url, touch_icon_size, &touch_icon_bitmap_data);
  ASSERT_EQ(1u, touch_icon_bitmap_data.size());

  // Set some preexisting favicons for |page_url|.
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);
  backend_->SetFavicons(page_url, chrome::TOUCH_ICON, touch_icon_bitmap_data);

  chrome::FaviconBitmapResult result;
  std::vector<int> icon_types;
  icon_types.push_back(chrome::FAVICON);
  icon_types.push_back(chrome::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(chrome::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(chrome::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<chrome::FaviconBitmapData> favicon_bitmap_data;
  std::vector<gfx::Size> favicon_size;
  favicon_size.push_back(gfx::Size(16, 16));
  favicon_size.push_back(gfx::Size(32, 32));
  GenerateFaviconBitmapData(icon_url, favicon_size, &favicon_bitmap_data);
  ASSERT_EQ(2u, favicon_bitmap_data.size());

  std::vector<chrome::FaviconBitmapData> touch_icon_bitmap_data;
  std::vector<gfx::Size> touch_icon_size;
  touch_icon_size.push_back(gfx::Size(32, 32));
  GenerateFaviconBitmapData(icon_url, touch_icon_size, &touch_icon_bitmap_data);
  ASSERT_EQ(1u, touch_icon_bitmap_data.size());

  // Set some preexisting favicons for |page_url|.
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);
  backend_->SetFavicons(page_url, chrome::TOUCH_ICON, touch_icon_bitmap_data);

  chrome::FaviconBitmapResult result;
  std::vector<int> icon_types;
  icon_types.push_back(chrome::FAVICON);
  icon_types.push_back(chrome::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(chrome::FAVICON, result.icon_type);

  // Change minimal size to 32x32 and verify the 32x32 favicon returned.
  chrome::FaviconBitmapResult result1;
  backend_->GetLargestFaviconForURL(page_url, icon_types, 32, &result1);
  EXPECT_EQ(gfx::Size(32, 32), result1.pixel_size);
  EXPECT_EQ(chrome::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<chrome::FaviconBitmapData> favicon_bitmap_data;
  std::vector<gfx::Size> favicon_size;
  favicon_size.push_back(gfx::Size(16, 16));
  GenerateFaviconBitmapData(icon_url, favicon_size, &favicon_bitmap_data);
  ASSERT_EQ(1u, favicon_bitmap_data.size());

  // Set preexisting favicons for |page_url|.
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  chrome::FaviconBitmapResult result;
  std::vector<int> icon_types;
  icon_types.push_back(chrome::FAVICON);
  icon_types.push_back(chrome::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(chrome::FAVICON, result.icon_type);
}

// Test UpdateFaviconMapingsAndFetch() when multiple icon types are passed in.
TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchMultipleIconTypes) {
  GURL page_url1("http://www.google.com");
  GURL page_url2("http://news.google.com");
  GURL page_url3("http://mail.google.com");
  GURL icon_urla("http://www.google.com/favicon1.ico");
  GURL icon_urlb("http://www.google.com/favicon2.ico");
  GURL icon_urlc("http://www.google.com/favicon3.ico");

  // |page_url1| is mapped to |icon_urla| which if of type TOUCH_ICON.
  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_urla, GetSizesSmall(), &favicon_bitmap_data);
  backend_->SetFavicons(page_url1, chrome::TOUCH_ICON, favicon_bitmap_data);

  // |page_url2| is mapped to |icon_urlb| and |icon_urlc| which are of type
  // TOUCH_PRECOMPOSED_ICON.
  GenerateFaviconBitmapData(icon_urlb, GetSizesSmall(), icon_urlc,
                            GetSizesSmall(), &favicon_bitmap_data);
  backend_->SetFavicons(
      page_url2, chrome::TOUCH_PRECOMPOSED_ICON, favicon_bitmap_data);

  std::vector<GURL> icon_urls;
  icon_urls.push_back(icon_urla);
  icon_urls.push_back(icon_urlb);
  icon_urls.push_back(icon_urlc);

  std::vector<chrome::FaviconBitmapResult> bitmap_results;
  backend_->UpdateFaviconMappingsAndFetch(
      page_url3,
      icon_urls,
      (chrome::TOUCH_ICON | chrome::TOUCH_PRECOMPOSED_ICON),
      kSmallSize.width(),
      GetScaleFactors1x2x(),
      &bitmap_results);

  // |page_url1| and |page_url2| should still be mapped to the same icon URLs.
  std::vector<IconMapping> icon_mappings;
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(page_url1,
      &icon_mappings));
  EXPECT_EQ(1u, icon_mappings.size());
  EXPECT_EQ(icon_urla, icon_mappings[0].icon_url);
  EXPECT_EQ(chrome::TOUCH_ICON, icon_mappings[0].icon_type);

  icon_mappings.clear();
  EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url2, &icon_mappings));
  EXPECT_EQ(2u, icon_mappings.size());
  EXPECT_EQ(icon_urlb, icon_mappings[0].icon_url);
  EXPECT_EQ(chrome::TOUCH_PRECOMPOSED_ICON, icon_mappings[0].icon_type);
  EXPECT_EQ(icon_urlc, icon_mappings[1].icon_url);
  EXPECT_EQ(chrome::TOUCH_PRECOMPOSED_ICON, icon_mappings[1].icon_type);

  // |page_url3| should be mapped only to |icon_urlb| and |icon_urlc| as
  // TOUCH_PRECOMPOSED_ICON is the largest IconType.
  icon_mappings.clear();
  EXPECT_TRUE(GetSortedIconMappingsForPageURL(page_url3, &icon_mappings));
  EXPECT_EQ(2u, icon_mappings.size());
  EXPECT_EQ(icon_urlb, icon_mappings[0].icon_url);
  EXPECT_EQ(chrome::TOUCH_PRECOMPOSED_ICON, icon_mappings[0].icon_type);
  EXPECT_EQ(icon_urlc, icon_mappings[1].icon_url);
  EXPECT_EQ(chrome::TOUCH_PRECOMPOSED_ICON, icon_mappings[1].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<chrome::FaviconBitmapResult> bitmap_results;
  EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &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");

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

  std::vector<chrome::FaviconBitmapResult> bitmap_results_out;
  EXPECT_FALSE(backend_->GetFaviconsFromDB(page_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));
  EXPECT_TRUE(bitmap_results_out.empty());
}

// Test that GetFaviconsFromDB() returns results for the bitmaps which most
// closely match the passed in desired size and scale factors.
TEST_F(HistoryBackendTest, GetFaviconsFromDBSelectClosestMatch) {
  const GURL page_url("http://www.google.com/");
  const GURL icon_url("http://www.google.com/icon1");

  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesTinySmallAndLarge(),
                            &favicon_bitmap_data);

  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  std::vector<chrome::FaviconBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url,
                                          chrome::FAVICON,
                                          kSmallSize.width(),
                                          GetScaleFactors1x2x(),
                                          &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) {
    chrome::FaviconBitmapResult 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(BitmapDataEqual('b', 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(chrome::FAVICON, bitmap_results_out[0].icon_type);

  EXPECT_FALSE(bitmap_results_out[1].expired);
  EXPECT_TRUE(BitmapDataEqual('c', 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(chrome::FAVICON, bitmap_results_out[1].icon_type);
}

// Test that GetFaviconsFromDB() returns results from the icon URL whose
// bitmaps most closely match the passed in desired size and scale factors.
TEST_F(HistoryBackendTest, GetFaviconsFromDBSingleIconURL) {
  const GURL page_url("http://www.google.com/");

  const GURL icon_url1("http://www.google.com/icon1");
  const GURL icon_url2("http://www.google.com/icon2");

  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url1, GetSizesSmall(), icon_url2,
                            GetSizesLarge(), &favicon_bitmap_data);

  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  std::vector<chrome::FaviconBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url,
                                          chrome::FAVICON,
                                          kSmallSize.width(),
                                          GetScaleFactors1x2x(),
                                          &bitmap_results_out));

  // The results should have results for the icon URL with the large bitmap as
  // downscaling is preferred to upscaling.
  EXPECT_EQ(1u, bitmap_results_out.size());
  EXPECT_EQ(kLargeSize, bitmap_results_out[0].pixel_size);
  EXPECT_EQ(icon_url2, bitmap_results_out[0].icon_url);
}

// 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<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url1, GetSizesSmall(),  &favicon_bitmap_data);
  backend_->SetFavicons(page_url, chrome::FAVICON, favicon_bitmap_data);

  GenerateFaviconBitmapData(icon_url2, GetSizesSmall(), &favicon_bitmap_data);
  backend_->SetFavicons(page_url, chrome::TOUCH_ICON, favicon_bitmap_data);

  std::vector<chrome::FaviconBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));

  EXPECT_EQ(1u, bitmap_results_out.size());
  EXPECT_EQ(chrome::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, chrome::TOUCH_ICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));

  EXPECT_EQ(1u, bitmap_results_out.size());
  EXPECT_EQ(chrome::TOUCH_ICON, bitmap_results_out[0].icon_type);
  EXPECT_EQ(icon_url2, 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);
  chrome::FaviconID icon_id =
      backend_->thumbnail_db_->AddFavicon(icon_url,
                                          chrome::FAVICON,
                                          bitmap_data,
                                          last_updated,
                                          kSmallSize);
  EXPECT_NE(0, icon_id);
  EXPECT_NE(0, backend_->thumbnail_db_->AddIconMapping(page_url, icon_id));

  std::vector<chrome::FaviconBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(page_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &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<chrome::FaviconBitmapResult> bitmap_results;

  backend_->UpdateFaviconMappingsAndFetch(
      GURL(), std::vector<GURL>(), chrome::FAVICON, kSmallSize.width(),
      GetScaleFactors1x2x(), &bitmap_results);

  EXPECT_TRUE(bitmap_results.empty());
}

TEST_F(HistoryBackendTest, CloneFaviconIsRestrictedToSameDomain) {
  const GURL url("http://www.google.com/");
  const GURL same_domain_url("http://www.google.com/subdir/index.html");
  const GURL foreign_domain_url("http://www.not-google.com/");
  const GURL icon_url("http://www.google.com/icon.png");

  // Add a favicon
  std::vector<chrome::FaviconBitmapData> favicon_bitmap_data;
  GenerateFaviconBitmapData(icon_url, GetSizesSmall(),  &favicon_bitmap_data);
  backend_->SetFavicons(url, chrome::FAVICON, favicon_bitmap_data);
  EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL(
      url, chrome::FAVICON, NULL));

  // Validate starting state.
  std::vector<chrome::FaviconBitmapResult> bitmap_results_out;
  EXPECT_TRUE(backend_->GetFaviconsFromDB(url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));
  EXPECT_FALSE(backend_->GetFaviconsFromDB(same_domain_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));
  EXPECT_FALSE(backend_->GetFaviconsFromDB(foreign_domain_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));

  // Same-domain cloning should work.
  backend_->CloneFavicons(url, same_domain_url);
  EXPECT_TRUE(backend_->GetFaviconsFromDB(same_domain_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));

  // Foreign-domain cloning is forbidden.
  backend_->CloneFavicons(url, foreign_domain_url);
  EXPECT_FALSE(backend_->GetFaviconsFromDB(foreign_domain_url, chrome::FAVICON,
      kSmallSize.width(), GetScaleFactors1x2x(), &bitmap_results_out));
}

TEST_F(HistoryBackendTest, QueryFilteredURLs) {
  const char* google = "http://www.google.com/";
  const char* yahoo = "http://www.yahoo.com/";
  const char* yahoo_sports = "http://sports.yahoo.com/";
  const char* yahoo_sports_with_article1 =
      "http://sports.yahoo.com/article1.htm";
  const char* yahoo_sports_with_article2 =
      "http://sports.yahoo.com/article2.htm";
  const char* yahoo_sports_soccer = "http://sports.yahoo.com/soccer";
  const char* apple = "http://www.apple.com/";

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

  Time tested_time = Time::Now().LocalMidnight() +
                     base::TimeDelta::FromHours(4);
  base::TimeDelta half_an_hour = base::TimeDelta::FromMinutes(30);
  base::TimeDelta one_hour = base::TimeDelta::FromHours(1);
  base::TimeDelta one_day = base::TimeDelta::FromDays(1);

  const content::PageTransition kTypedTransition =
      content::PAGE_TRANSITION_TYPED;
  const content::PageTransition kKeywordGeneratedTransition =
      content::PAGE_TRANSITION_KEYWORD_GENERATED;

  const char* redirect_sequence[2];
  redirect_sequence[1] = NULL;

  redirect_sequence[0] = google;
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0, kTypedTransition,
      tested_time - one_day - half_an_hour * 2);
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0,
      kTypedTransition, tested_time - one_day);
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0,
      kTypedTransition, tested_time - half_an_hour / 2);
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0,
      kTypedTransition, tested_time);

  // Add a visit with a transition that will make sure that no segment gets
  // created for this page (so the subsequent entries will have different URLIDs
  // and SegmentIDs).
  redirect_sequence[0] = apple;
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0, kKeywordGeneratedTransition,
      tested_time - one_day + one_hour * 6);

  redirect_sequence[0] = yahoo;
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0, kTypedTransition,
      tested_time - one_day + half_an_hour);
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0, kTypedTransition,
      tested_time - one_day + half_an_hour * 2);

  redirect_sequence[0] = yahoo_sports;
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0, kTypedTransition,
      tested_time - one_day - half_an_hour * 2);
  AddRedirectChainWithTransitionAndTime(
      redirect_sequence, 0, kTypedTransition,
      tested_time - one_day);
  int transition1, transition2;
  AddClientRedirect(GURL(yahoo_sports), GURL(yahoo_sports_with_article1), false,
                    tested_time - one_day + half_an_hour,
                    &transition1, &transition2);
  AddClientRedirect(GURL(yahoo_sports_with_article1),
                    GURL(yahoo_sports_with_article2),
                    false,
                    tested_time - one_day + half_an_hour * 2,
                    &transition1, &transition2);

  redirect_sequence[0] = yahoo_sports_soccer;
  AddRedirectChainWithTransitionAndTime(redirect_sequence, 0,
                                        kTypedTransition,
                                        tested_time - half_an_hour);
  backend_->Commit();

  scoped_refptr<QueryFilteredURLsRequest> request1 =
      new history::QueryFilteredURLsRequest(
          base::Bind(&HistoryBackendTest::OnQueryFiltered,
                     base::Unretained(static_cast<HistoryBackendTest*>(this))));
  HistoryBackendCancelableRequest cancellable_request;
  cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
      request1.get());

  VisitFilter filter;
  // Time limit is |tested_time| +/- 45 min.
  base::TimeDelta three_quarters_of_an_hour = base::TimeDelta::FromMinutes(45);
  filter.SetFilterTime(tested_time);
  filter.SetFilterWidth(three_quarters_of_an_hour);
  backend_->QueryFilteredURLs(request1, 100, filter, false);

  ASSERT_EQ(4U, get_filtered_list().size());
  EXPECT_EQ(std::string(google), get_filtered_list()[0].url.spec());
  EXPECT_EQ(std::string(yahoo_sports_soccer),
            get_filtered_list()[1].url.spec());
  EXPECT_EQ(std::string(yahoo), get_filtered_list()[2].url.spec());
  EXPECT_EQ(std::string(yahoo_sports),
            get_filtered_list()[3].url.spec());

  // Time limit is between |tested_time| and |tested_time| + 2 hours.
  scoped_refptr<QueryFilteredURLsRequest> request2 =
      new history::QueryFilteredURLsRequest(
          base::Bind(&HistoryBackendTest::OnQueryFiltered,
                     base::Unretained(static_cast<HistoryBackendTest*>(this))));
  cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
      request2.get());
  filter.SetFilterTime(tested_time + one_hour);
  filter.SetFilterWidth(one_hour);
  backend_->QueryFilteredURLs(request2, 100, filter, false);

  ASSERT_EQ(3U, get_filtered_list().size());
  EXPECT_EQ(std::string(google), get_filtered_list()[0].url.spec());
  EXPECT_EQ(std::string(yahoo), get_filtered_list()[1].url.spec());
  EXPECT_EQ(std::string(yahoo_sports), get_filtered_list()[2].url.spec());

  // Time limit is between |tested_time| - 2 hours and |tested_time|.
  scoped_refptr<QueryFilteredURLsRequest> request3 =
      new history::QueryFilteredURLsRequest(
          base::Bind(&HistoryBackendTest::OnQueryFiltered,
                     base::Unretained(static_cast<HistoryBackendTest*>(this))));
  cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
      request3.get());
  filter.SetFilterTime(tested_time - one_hour);
  filter.SetFilterWidth(one_hour);
  backend_->QueryFilteredURLs(request3, 100, filter, false);

  ASSERT_EQ(3U, get_filtered_list().size());
  EXPECT_EQ(std::string(google), get_filtered_list()[0].url.spec());
  EXPECT_EQ(std::string(yahoo_sports_soccer),
            get_filtered_list()[1].url.spec());
  EXPECT_EQ(std::string(yahoo_sports), get_filtered_list()[2].url.spec());

  filter.ClearFilters();
  base::Time::Exploded exploded_time;
  tested_time.LocalExplode(&exploded_time);

  // Today.
  scoped_refptr<QueryFilteredURLsRequest> request4 =
      new history::QueryFilteredURLsRequest(
          base::Bind(&HistoryBackendTest::OnQueryFiltered,
                     base::Unretained(static_cast<HistoryBackendTest*>(this))));
  cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
      request4.get());
  filter.SetFilterTime(tested_time);
  filter.SetDayOfTheWeekFilter(static_cast<int>(exploded_time.day_of_week));
  backend_->QueryFilteredURLs(request4, 100, filter, false);

  ASSERT_EQ(2U, get_filtered_list().size());
  EXPECT_EQ(std::string(google), get_filtered_list()[0].url.spec());
  EXPECT_EQ(std::string(yahoo_sports_soccer),
            get_filtered_list()[1].url.spec());

  // Today + time limit - only yahoo_sports_soccer should fit.
  scoped_refptr<QueryFilteredURLsRequest> request5 =
      new history::QueryFilteredURLsRequest(
          base::Bind(&HistoryBackendTest::OnQueryFiltered,
                     base::Unretained(static_cast<HistoryBackendTest*>(this))));
  cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
      request5.get());
  filter.SetFilterTime(tested_time - base::TimeDelta::FromMinutes(40));
  filter.SetFilterWidth(base::TimeDelta::FromMinutes(20));
  backend_->QueryFilteredURLs(request5, 100, filter, false);

  ASSERT_EQ(1U, get_filtered_list().size());
  EXPECT_EQ(std::string(yahoo_sports_soccer),
            get_filtered_list()[0].url.spec());

  // Make sure we get debug data if we request it.
  scoped_refptr<QueryFilteredURLsRequest> request6 =
      new history::QueryFilteredURLsRequest(
          base::Bind(&HistoryBackendTest::OnQueryFiltered,
                     base::Unretained(static_cast<HistoryBackendTest*>(this))));
  cancellable_request.MockScheduleOfRequest<QueryFilteredURLsRequest>(
      request6.get());
  filter.SetFilterTime(tested_time);
  filter.SetFilterWidth(one_hour * 2);
  backend_->QueryFilteredURLs(request6, 100, filter, true);

  // If the SegmentID is used by QueryFilteredURLs when generating the debug
  // data instead of the URLID, the |total_visits| for the |yahoo_sports_soccer|
  // entry will be zero instead of 1.
  ASSERT_GE(get_filtered_list().size(), 2U);
  EXPECT_EQ(std::string(google), get_filtered_list()[0].url.spec());
  EXPECT_EQ(std::string(yahoo_sports_soccer),
      get_filtered_list()[1].url.spec());
  EXPECT_EQ(4U, get_filtered_list()[0].extended_info.total_visits);
  EXPECT_EQ(1U, get_filtered_list()[1].extended_info.total_visits);
}

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;
  Time start_ts = Time::Now() - base::TimeDelta::FromDays(5);
  Time end_ts = start_ts + base::TimeDelta::FromDays(2);
  visit_info1.push_back(VisitInfo(start_ts, content::PAGE_TRANSITION_LINK));

  GURL url2("http://www.example.com");
  visit_info2.push_back(VisitInfo(Time::Now() - base::TimeDelta::FromDays(10),
                                  content::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, old_archived;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &old_history_path));
  old_history_path = old_history_path.AppendASCII("History");
  old_history = old_history_path.AppendASCII("HistoryNoDuration");
  old_archived = old_history_path.AppendASCII("ArchivedNoDuration");

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

  backend_ = new HistoryBackend(new_history_path,
                                new HistoryBackendTestDelegate(this),
                                &bookmark_model_);
  backend_->Init(std::string(), false);
  backend_->Closing();
  backend_ = NULL;

  // Now both history and archived_history databases 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));

  // Repeat version and visit_duration checks in archived history database
  // also.
  cur_version = ArchivedDatabase::GetCurrentVersion();
  sql::Connection archived_db;
  ASSERT_TRUE(archived_db.Open(new_archived_file));
  sql::Statement s1(archived_db.GetUniqueStatement(
      "SELECT value FROM meta WHERE key = 'version'"));
  ASSERT_TRUE(s1.Step());
  file_version = s1.ColumnInt(0);
  EXPECT_EQ(cur_version, file_version);

  // Check visit_duration column in visits table is created and set to 0.
  s1.Assign(archived_db.GetUniqueStatement(
      "SELECT visit_duration FROM visits LIMIT 1"));
  ASSERT_TRUE(s1.Step());
  EXPECT_EQ(0, s1.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);
  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::IntToString(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(Time());
  url_info1.set_hidden(false);
  const URLID url1_id = backend_->db()->AddURL(url_info1);
  EXPECT_NE(0, url1_id);

  TemplateURLID 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(Time());
  url_info2.set_hidden(false);
  const URLID url2_id = backend_->db()->AddURL(url_info2);
  EXPECT_NE(0, url2_id);

  TemplateURLID 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(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) {
  scoped_ptr<TestingProfile> profile(new TestingProfile());

  ASSERT_TRUE(profile->CreateHistoryService(false, false));
  profile->CreateBookmarkModel(true);
  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile.get());
  test::WaitForBookmarkModelToLoad(model);

  // Add a URL.
  GURL url("http://www.google.com");
  bookmark_utils::AddIfNotBookmarked(model, url, base::string16());

  HistoryService* service = HistoryServiceFactory::GetForProfile(
      profile.get(), Profile::EXPLICIT_ACCESS);

  service->AddPage(
      url, base::Time::Now(), NULL, 1, GURL(), RedirectList(),
      content::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(getTestDir());
  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_TRUE(base::WriteFile(db1, data, data_len));
  ASSERT_TRUE(base::WriteFile(db1_journal, data, data_len));
  ASSERT_TRUE(base::WriteFile(db1_wal, data, data_len));
  ASSERT_TRUE(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.
}

}  // namespace history

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