// Copyright (c) 2011 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.

// History unit tests come in two flavors:
//
// 1. The more complicated style is that the unit test creates a full history
//    service. This spawns a background thread for the history backend, and
//    all communication is asynchronous. This is useful for testing more
//    complicated things or end-to-end behavior.
//
// 2. The simpler style is to create a history backend on this thread and
//    access it directly without a HistoryService object. This is much simpler
//    because communication is synchronous. Generally, sets should go through
//    the history backend (since there is a lot of logic) but gets can come
//    directly from the HistoryDatabase. This is because the backend generally
//    has no logic in the getter except threading stuff, which we don't want
//    to run.

#include <time.h>

#include <algorithm>
#include <string>

#include "app/sql/connection.h"
#include "app/sql/statement.h"
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/scoped_temp_dir.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/task.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/download/download_item.h"
#include "chrome/browser/history/download_create_info.h"
#include "chrome/browser/history/history.h"
#include "chrome/browser/history/history_backend.h"
#include "chrome/browser/history/history_database.h"
#include "chrome/browser/history/history_notifications.h"
#include "chrome/browser/history/in_memory_database.h"
#include "chrome/browser/history/in_memory_history_backend.h"
#include "chrome/browser/history/page_usage_data.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/thumbnail_score.h"
#include "chrome/tools/profiles/thumbnail-inl.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"

using base::Time;
using base::TimeDelta;

namespace history {
class HistoryTest;
}

// Specialize RunnableMethodTraits for HistoryTest so we can create callbacks.
// None of these callbacks can outlast the test, so there is not need to retain
// the HistoryTest object.
DISABLE_RUNNABLE_METHOD_REFCOUNT(history::HistoryTest);

namespace history {

namespace {

// The tracker uses RenderProcessHost pointers for scoping but never
// dereferences them. We use ints because it's easier. This function converts
// between the two.
static void* MakeFakeHost(int id) {
  void* host = 0;
  memcpy(&host, &id, sizeof(id));
  return host;
}

}  // namespace

// Delegate class for when we create a backend without a HistoryService.
class BackendDelegate : public HistoryBackend::Delegate {
 public:
  explicit BackendDelegate(HistoryTest* history_test)
      : history_test_(history_test) {
  }

  virtual void NotifyProfileError(sql::InitStatus init_status) OVERRIDE {}
  virtual void SetInMemoryBackend(InMemoryHistoryBackend* backend) OVERRIDE;
  virtual void BroadcastNotifications(NotificationType type,
                                      HistoryDetails* details) OVERRIDE;
  virtual void DBLoaded() OVERRIDE {}
  virtual void StartTopSitesMigration() OVERRIDE {}
 private:
  HistoryTest* history_test_;
};

// This must be outside the anonymous namespace for the friend statement in
// HistoryBackend to work.
class HistoryTest : public testing::Test {
 public:
  HistoryTest()
      : history_service_(NULL),
        got_thumbnail_callback_(false),
        redirect_query_success_(false),
        query_url_success_(false),
        db_(NULL) {
  }
  ~HistoryTest() {
  }

  // Creates the HistoryBackend and HistoryDatabase on the current thread,
  // assigning the values to backend_ and db_.
  void CreateBackendAndDatabase() {
    backend_ =
        new HistoryBackend(history_dir_, new BackendDelegate(this), NULL);
    backend_->Init(std::string(), false);
    db_ = backend_->db_.get();
    DCHECK(in_mem_backend_.get()) << "Mem backend should have been set by "
        "HistoryBackend::Init";
  }

  void OnSegmentUsageAvailable(CancelableRequestProvider::Handle handle,
                               std::vector<PageUsageData*>* data) {
    page_usage_data_->swap(*data);
    MessageLoop::current()->Quit();
  }

  void OnDeleteURLsDone(CancelableRequestProvider::Handle handle) {
    MessageLoop::current()->Quit();
  }

  void OnMostVisitedURLsAvailable(CancelableRequestProvider::Handle handle,
                                  MostVisitedURLList url_list) {
    most_visited_urls_.swap(url_list);
    MessageLoop::current()->Quit();
  }

 protected:
  friend class BackendDelegate;

  // testing::Test
  virtual void SetUp() {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    history_dir_ = temp_dir_.path().AppendASCII("HistoryTest");
    ASSERT_TRUE(file_util::CreateDirectory(history_dir_));
  }

  void DeleteBackend() {
    if (backend_) {
      backend_->Closing();
      backend_ = NULL;
    }
  }

  virtual void TearDown() {
    DeleteBackend();

    if (history_service_)
      CleanupHistoryService();

    // Make sure we don't have any event pending that could disrupt the next
    // test.
    MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
    MessageLoop::current()->Run();
  }

  void CleanupHistoryService() {
    DCHECK(history_service_.get());

    history_service_->NotifyRenderProcessHostDestruction(0);
    history_service_->SetOnBackendDestroyTask(new MessageLoop::QuitTask);
    history_service_->Cleanup();
    history_service_ = NULL;

    // Wait for the backend class to terminate before deleting the files and
    // moving to the next test. Note: if this never terminates, somebody is
    // probably leaking a reference to the history backend, so it never calls
    // our destroy task.
    MessageLoop::current()->Run();
  }

  int64 AddDownload(int32 state, const Time& time) {
    DownloadCreateInfo download(FilePath(FILE_PATH_LITERAL("foo-path")),
                                GURL("foo-url"), time, 0, 512, state, 0, false);
    return db_->CreateDownload(download);
  }

  // Fills the query_url_row_ and query_url_visits_ structures with the
  // information about the given URL and returns true. If the URL was not
  // found, this will return false and those structures will not be changed.
  bool QueryURL(HistoryService* history, const GURL& url) {
    history->QueryURL(url, true, &consumer_,
                      NewCallback(this, &HistoryTest::SaveURLAndQuit));
    MessageLoop::current()->Run();  // Will be exited in SaveURLAndQuit.
    return query_url_success_;
  }

  // Callback for HistoryService::QueryURL.
  void SaveURLAndQuit(HistoryService::Handle handle,
                      bool success,
                      const URLRow* url_row,
                      VisitVector* visit_vector) {
    query_url_success_ = success;
    if (query_url_success_) {
      query_url_row_ = *url_row;
      query_url_visits_.swap(*visit_vector);
    } else {
      query_url_row_ = URLRow();
      query_url_visits_.clear();
    }
    MessageLoop::current()->Quit();
  }

  // Fills in saved_redirects_ with the redirect information for the given URL,
  // returning true on success. False means the URL was not found.
  bool QueryRedirectsFrom(HistoryService* history, const GURL& url) {
    history->QueryRedirectsFrom(url, &consumer_,
        NewCallback(this, &HistoryTest::OnRedirectQueryComplete));
    MessageLoop::current()->Run();  // Will be exited in *QueryComplete.
    return redirect_query_success_;
  }

  // Callback for QueryRedirects.
  void OnRedirectQueryComplete(HistoryService::Handle handle,
                               GURL url,
                               bool success,
                               history::RedirectList* redirects) {
    redirect_query_success_ = success;
    if (redirect_query_success_)
      saved_redirects_.swap(*redirects);
    else
      saved_redirects_.clear();
    MessageLoop::current()->Quit();
  }

  ScopedTempDir temp_dir_;

  MessageLoopForUI message_loop_;

  // PageUsageData vector to test segments.
  ScopedVector<PageUsageData> page_usage_data_;

  MostVisitedURLList most_visited_urls_;

  // When non-NULL, this will be deleted on tear down and we will block until
  // the backend thread has completed. This allows tests for the history
  // service to use this feature, but other tests to ignore this.
  scoped_refptr<HistoryService> history_service_;

  // names of the database files
  FilePath history_dir_;

  // Set by the thumbnail callback when we get data, you should be sure to
  // clear this before issuing a thumbnail request.
  bool got_thumbnail_callback_;
  std::vector<unsigned char> thumbnail_data_;

  // Set by the redirect callback when we get data. You should be sure to
  // clear this before issuing a redirect request.
  history::RedirectList saved_redirects_;
  bool redirect_query_success_;

  // For history requests.
  CancelableRequestConsumer consumer_;

  // For saving URL info after a call to QueryURL
  bool query_url_success_;
  URLRow query_url_row_;
  VisitVector query_url_visits_;

  // Created via CreateBackendAndDatabase.
  scoped_refptr<HistoryBackend> backend_;
  scoped_ptr<InMemoryHistoryBackend> in_mem_backend_;
  HistoryDatabase* db_;  // Cached reference to the backend's database.
};

void BackendDelegate::SetInMemoryBackend(InMemoryHistoryBackend* backend) {
  // Save the in-memory backend to the history test object, this happens
  // synchronously, so we don't have to do anything fancy.
  history_test_->in_mem_backend_.reset(backend);
}

void BackendDelegate::BroadcastNotifications(NotificationType type,
                                             HistoryDetails* details) {
  // Currently, just send the notifications directly to the in-memory database.
  // We may want do do something more fancy in the future.
  Details<HistoryDetails> det(details);
  history_test_->in_mem_backend_->Observe(type,
      Source<HistoryTest>(NULL), det);

  // The backend passes ownership of the details pointer to us.
  delete details;
}

TEST_F(HistoryTest, ClearBrowsingData_Downloads) {
  CreateBackendAndDatabase();

  Time now = Time::Now();
  TimeDelta one_day = TimeDelta::FromDays(1);
  Time month_ago = now - TimeDelta::FromDays(30);

  // Initially there should be nothing in the downloads database.
  std::vector<DownloadCreateInfo> downloads;
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(0U, downloads.size());

  // Keep track of these as we need to update them later during the test.
  DownloadID in_progress, removing;

  // Create one with a 0 time.
  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, Time()));
  // Create one for now and +/- 1 day.
  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now - one_day));
  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now));
  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, now + one_day));
  // Try the other four states.
  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE, month_ago));
  EXPECT_NE(0, in_progress = AddDownload(DownloadItem::IN_PROGRESS, month_ago));
  EXPECT_NE(0, AddDownload(DownloadItem::CANCELLED, month_ago));
  EXPECT_NE(0, AddDownload(DownloadItem::INTERRUPTED, month_ago));
  EXPECT_NE(0, removing = AddDownload(DownloadItem::REMOVING, month_ago));

  // Test to see if inserts worked.
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(9U, downloads.size());

  // Try removing from current timestamp. This should delete the one in the
  // future and one very recent one.
  db_->RemoveDownloadsBetween(now, Time());
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(7U, downloads.size());

  // Try removing from two months ago. This should not delete items that are
  // 'in progress' or in 'removing' state.
  db_->RemoveDownloadsBetween(now - TimeDelta::FromDays(60), Time());
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(3U, downloads.size());

  // Download manager converts to TimeT, which is lossy, so we do the same
  // for comparison.
  Time month_ago_lossy = Time::FromTimeT(month_ago.ToTimeT());

  // Make sure the right values remain.
  EXPECT_EQ(DownloadItem::COMPLETE, downloads[0].state);
  EXPECT_EQ(0, downloads[0].start_time.ToInternalValue());
  EXPECT_EQ(DownloadItem::IN_PROGRESS, downloads[1].state);
  EXPECT_EQ(month_ago_lossy.ToInternalValue(),
            downloads[1].start_time.ToInternalValue());
  EXPECT_EQ(DownloadItem::REMOVING, downloads[2].state);
  EXPECT_EQ(month_ago_lossy.ToInternalValue(),
            downloads[2].start_time.ToInternalValue());

  // Change state so we can delete the downloads.
  EXPECT_TRUE(db_->UpdateDownload(512, DownloadItem::COMPLETE, in_progress));
  EXPECT_TRUE(db_->UpdateDownload(512, DownloadItem::CANCELLED, removing));

  // Try removing from Time=0. This should delete all.
  db_->RemoveDownloadsBetween(Time(), Time());
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(0U, downloads.size());

  // Check removal of downloads stuck in IN_PROGRESS state.
  EXPECT_NE(0, AddDownload(DownloadItem::COMPLETE,    month_ago));
  EXPECT_NE(0, AddDownload(DownloadItem::IN_PROGRESS, month_ago));
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(2U, downloads.size());
  db_->RemoveDownloadsBetween(Time(), Time());
  db_->QueryDownloads(&downloads);
  // IN_PROGRESS download should remain. It it indicated as "Canceled"
  EXPECT_EQ(1U, downloads.size());
  db_->CleanUpInProgressEntries();
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(1U, downloads.size());
  db_->RemoveDownloadsBetween(Time(), Time());
  db_->QueryDownloads(&downloads);
  EXPECT_EQ(0U, downloads.size());
}

TEST_F(HistoryTest, AddPage) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;
  ASSERT_TRUE(history->Init(history_dir_, NULL));

  // Add the page once from a child frame.
  const GURL test_url("http://www.google.com/");
  history->AddPage(test_url, NULL, 0, GURL(),
                   PageTransition::MANUAL_SUBFRAME,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_url));
  EXPECT_EQ(1, query_url_row_.visit_count());
  EXPECT_EQ(0, query_url_row_.typed_count());
  EXPECT_TRUE(query_url_row_.hidden());  // Hidden because of child frame.

  // Add the page once from the main frame (should unhide it).
  history->AddPage(test_url, NULL, 0, GURL(), PageTransition::LINK,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_url));
  EXPECT_EQ(2, query_url_row_.visit_count());  // Added twice.
  EXPECT_EQ(0, query_url_row_.typed_count());  // Never typed.
  EXPECT_FALSE(query_url_row_.hidden());  // Because loaded in main frame.
}

TEST_F(HistoryTest, AddPageSameTimes) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;
  ASSERT_TRUE(history->Init(history_dir_, NULL));

  Time now = Time::Now();
  const GURL test_urls[] = {
    GURL("http://timer.first.page/"),
    GURL("http://timer.second.page/"),
    GURL("http://timer.third.page/"),
  };

  // Make sure that two pages added at the same time with no intervening
  // additions have different timestamps.
  history->AddPage(test_urls[0], now, NULL, 0, GURL(),
                   PageTransition::LINK,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_urls[0]));
  EXPECT_EQ(1, query_url_row_.visit_count());
  EXPECT_TRUE(now == query_url_row_.last_visit());  // gtest doesn't like Time

  history->AddPage(test_urls[1], now, NULL, 0, GURL(),
                   PageTransition::LINK,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_urls[1]));
  EXPECT_EQ(1, query_url_row_.visit_count());
  EXPECT_TRUE(now + TimeDelta::FromMicroseconds(1) ==
      query_url_row_.last_visit());

  // Make sure the next page, at a different time, is also correct.
  history->AddPage(test_urls[2], now + TimeDelta::FromMinutes(1),
                   NULL, 0, GURL(),
                   PageTransition::LINK,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_urls[2]));
  EXPECT_EQ(1, query_url_row_.visit_count());
  EXPECT_TRUE(now + TimeDelta::FromMinutes(1) ==
      query_url_row_.last_visit());
}

TEST_F(HistoryTest, AddRedirect) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;
  ASSERT_TRUE(history->Init(history_dir_, NULL));

  const char* first_sequence[] = {
    "http://first.page/",
    "http://second.page/"};
  int first_count = arraysize(first_sequence);
  history::RedirectList first_redirects;
  for (int i = 0; i < first_count; i++)
    first_redirects.push_back(GURL(first_sequence[i]));

  // Add the sequence of pages as a server with no referrer. Note that we need
  // to have a non-NULL page ID scope.
  history->AddPage(first_redirects.back(), MakeFakeHost(1), 0, GURL(),
                   PageTransition::LINK, first_redirects,
                   history::SOURCE_BROWSED,  true);

  // The first page should be added once with a link visit type (because we set
  // LINK when we added the original URL, and a referrer of nowhere (0).
  EXPECT_TRUE(QueryURL(history, first_redirects[0]));
  EXPECT_EQ(1, query_url_row_.visit_count());
  ASSERT_EQ(1U, query_url_visits_.size());
  int64 first_visit = query_url_visits_[0].visit_id;
  EXPECT_EQ(PageTransition::LINK |
            PageTransition::CHAIN_START, query_url_visits_[0].transition);
  EXPECT_EQ(0, query_url_visits_[0].referring_visit);  // No referrer.

  // The second page should be a server redirect type with a referrer of the
  // first page.
  EXPECT_TRUE(QueryURL(history, first_redirects[1]));
  EXPECT_EQ(1, query_url_row_.visit_count());
  ASSERT_EQ(1U, query_url_visits_.size());
  int64 second_visit = query_url_visits_[0].visit_id;
  EXPECT_EQ(PageTransition::SERVER_REDIRECT |
            PageTransition::CHAIN_END, query_url_visits_[0].transition);
  EXPECT_EQ(first_visit, query_url_visits_[0].referring_visit);

  // Check that the redirect finding function successfully reports it.
  saved_redirects_.clear();
  QueryRedirectsFrom(history, first_redirects[0]);
  ASSERT_EQ(1U, saved_redirects_.size());
  EXPECT_EQ(first_redirects[1], saved_redirects_[0]);

  // Now add a client redirect from that second visit to a third, client
  // redirects are tracked by the RenderView prior to updating history,
  // so we pass in a CLIENT_REDIRECT qualifier to mock that behavior.
  history::RedirectList second_redirects;
  second_redirects.push_back(first_redirects[1]);
  second_redirects.push_back(GURL("http://last.page/"));
  history->AddPage(second_redirects[1], MakeFakeHost(1), 1,
                   second_redirects[0],
                   static_cast<PageTransition::Type>(PageTransition::LINK |
                       PageTransition::CLIENT_REDIRECT),
                   second_redirects, history::SOURCE_BROWSED, true);

  // The last page (source of the client redirect) should NOT have an
  // additional visit added, because it was a client redirect (normally it
  // would). We should only have 1 left over from the first sequence.
  EXPECT_TRUE(QueryURL(history, second_redirects[0]));
  EXPECT_EQ(1, query_url_row_.visit_count());

  // The final page should be set as a client redirect from the previous visit.
  EXPECT_TRUE(QueryURL(history, second_redirects[1]));
  EXPECT_EQ(1, query_url_row_.visit_count());
  ASSERT_EQ(1U, query_url_visits_.size());
  EXPECT_EQ(PageTransition::CLIENT_REDIRECT |
            PageTransition::CHAIN_END, query_url_visits_[0].transition);
  EXPECT_EQ(second_visit, query_url_visits_[0].referring_visit);
}

TEST_F(HistoryTest, Typed) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;
  ASSERT_TRUE(history->Init(history_dir_, NULL));

  // Add the page once as typed.
  const GURL test_url("http://www.google.com/");
  history->AddPage(test_url, NULL, 0, GURL(), PageTransition::TYPED,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_url));

  // We should have the same typed & visit count.
  EXPECT_EQ(1, query_url_row_.visit_count());
  EXPECT_EQ(1, query_url_row_.typed_count());

  // Add the page again not typed.
  history->AddPage(test_url, NULL, 0, GURL(), PageTransition::LINK,
                   history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_url));

  // The second time should not have updated the typed count.
  EXPECT_EQ(2, query_url_row_.visit_count());
  EXPECT_EQ(1, query_url_row_.typed_count());

  // Add the page again as a generated URL.
  history->AddPage(test_url, NULL, 0, GURL(),
                   PageTransition::GENERATED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_url));

  // This should have worked like a link click.
  EXPECT_EQ(3, query_url_row_.visit_count());
  EXPECT_EQ(1, query_url_row_.typed_count());

  // Add the page again as a reload.
  history->AddPage(test_url, NULL, 0, GURL(),
                   PageTransition::RELOAD, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  EXPECT_TRUE(QueryURL(history, test_url));

  // This should not have incremented any visit counts.
  EXPECT_EQ(3, query_url_row_.visit_count());
  EXPECT_EQ(1, query_url_row_.typed_count());
}

TEST_F(HistoryTest, SetTitle) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;
  ASSERT_TRUE(history->Init(history_dir_, NULL));

  // Add a URL.
  const GURL existing_url("http://www.google.com/");
  history->AddPage(existing_url, history::SOURCE_BROWSED);

  // Set some title.
  const string16 existing_title = UTF8ToUTF16("Google");
  history->SetPageTitle(existing_url, existing_title);

  // Make sure the title got set.
  EXPECT_TRUE(QueryURL(history, existing_url));
  EXPECT_EQ(existing_title, query_url_row_.title());

  // set a title on a nonexistent page
  const GURL nonexistent_url("http://news.google.com/");
  const string16 nonexistent_title = UTF8ToUTF16("Google News");
  history->SetPageTitle(nonexistent_url, nonexistent_title);

  // Make sure nothing got written.
  EXPECT_FALSE(QueryURL(history, nonexistent_url));
  EXPECT_EQ(string16(), query_url_row_.title());

  // TODO(brettw) this should also test redirects, which get the title of the
  // destination page.
}

TEST_F(HistoryTest, Segments) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;

  ASSERT_TRUE(history->Init(history_dir_, NULL));

  static const void* scope = static_cast<void*>(this);

  // Add a URL.
  const GURL existing_url("http://www.google.com/");
  history->AddPage(existing_url, scope, 0, GURL(),
                   PageTransition::TYPED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);

  // Make sure a segment was created.
  history->QuerySegmentUsageSince(
      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
      NewCallback(static_cast<HistoryTest*>(this),
                  &HistoryTest::OnSegmentUsageAvailable));

  // Wait for processing.
  MessageLoop::current()->Run();

  ASSERT_EQ(1U, page_usage_data_->size());
  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);
  EXPECT_DOUBLE_EQ(3.0, page_usage_data_[0]->GetScore());

  // Add a URL which doesn't create a segment.
  const GURL link_url("http://yahoo.com/");
  history->AddPage(link_url, scope, 0, GURL(),
                   PageTransition::LINK, history::RedirectList(),
                   history::SOURCE_BROWSED, false);

  // Query again
  history->QuerySegmentUsageSince(
      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
      NewCallback(static_cast<HistoryTest*>(this),
                  &HistoryTest::OnSegmentUsageAvailable));

  // Wait for processing.
  MessageLoop::current()->Run();

  // Make sure we still have one segment.
  ASSERT_EQ(1U, page_usage_data_->size());
  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);

  // Add a page linked from existing_url.
  history->AddPage(GURL("http://www.google.com/foo"), scope, 3, existing_url,
                   PageTransition::LINK, history::RedirectList(),
                   history::SOURCE_BROWSED, false);

  // Query again
  history->QuerySegmentUsageSince(
      &consumer_, Time::Now() - TimeDelta::FromDays(1), 10,
      NewCallback(static_cast<HistoryTest*>(this),
                  &HistoryTest::OnSegmentUsageAvailable));

  // Wait for processing.
  MessageLoop::current()->Run();

  // Make sure we still have one segment.
  ASSERT_EQ(1U, page_usage_data_->size());
  EXPECT_TRUE(page_usage_data_[0]->GetURL() == existing_url);

  // However, the score should have increased.
  EXPECT_GT(page_usage_data_[0]->GetScore(), 5.0);
}

TEST_F(HistoryTest, MostVisitedURLs) {
  scoped_refptr<HistoryService> history(new HistoryService);
  history_service_ = history;
  ASSERT_TRUE(history->Init(history_dir_, NULL));

  const GURL url0("http://www.google.com/url0/");
  const GURL url1("http://www.google.com/url1/");
  const GURL url2("http://www.google.com/url2/");
  const GURL url3("http://www.google.com/url3/");
  const GURL url4("http://www.google.com/url4/");

  static const void* scope = static_cast<void*>(this);

  // Add two pages.
  history->AddPage(url0, scope, 0, GURL(),
                   PageTransition::TYPED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  history->AddPage(url1, scope, 0, GURL(),
                   PageTransition::TYPED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  history->QueryMostVisitedURLs(20, 90, &consumer_,
                                NewCallback(static_cast<HistoryTest*>(this),
                                    &HistoryTest::OnMostVisitedURLsAvailable));
  MessageLoop::current()->Run();

  EXPECT_EQ(2U, most_visited_urls_.size());
  EXPECT_EQ(url0, most_visited_urls_[0].url);
  EXPECT_EQ(url1, most_visited_urls_[1].url);

  // Add another page.
  history->AddPage(url2, scope, 0, GURL(),
                   PageTransition::TYPED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  history->QueryMostVisitedURLs(20, 90, &consumer_,
                                NewCallback(static_cast<HistoryTest*>(this),
                                    &HistoryTest::OnMostVisitedURLsAvailable));
  MessageLoop::current()->Run();

  EXPECT_EQ(3U, most_visited_urls_.size());
  EXPECT_EQ(url0, most_visited_urls_[0].url);
  EXPECT_EQ(url1, most_visited_urls_[1].url);
  EXPECT_EQ(url2, most_visited_urls_[2].url);

  // Revisit url2, making it the top URL.
  history->AddPage(url2, scope, 0, GURL(),
                   PageTransition::TYPED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  history->QueryMostVisitedURLs(20, 90, &consumer_,
                                NewCallback(static_cast<HistoryTest*>(this),
                                    &HistoryTest::OnMostVisitedURLsAvailable));
  MessageLoop::current()->Run();

  EXPECT_EQ(3U, most_visited_urls_.size());
  EXPECT_EQ(url2, most_visited_urls_[0].url);
  EXPECT_EQ(url0, most_visited_urls_[1].url);
  EXPECT_EQ(url1, most_visited_urls_[2].url);

  // Revisit url1, making it the top URL.
  history->AddPage(url1, scope, 0, GURL(),
                   PageTransition::TYPED, history::RedirectList(),
                   history::SOURCE_BROWSED, false);
  history->QueryMostVisitedURLs(20, 90, &consumer_,
                                NewCallback(static_cast<HistoryTest*>(this),
                                    &HistoryTest::OnMostVisitedURLsAvailable));
  MessageLoop::current()->Run();

  EXPECT_EQ(3U, most_visited_urls_.size());
  EXPECT_EQ(url1, most_visited_urls_[0].url);
  EXPECT_EQ(url2, most_visited_urls_[1].url);
  EXPECT_EQ(url0, most_visited_urls_[2].url);

  // Redirects
  history::RedirectList redirects;
  redirects.push_back(url3);
  redirects.push_back(url4);

  // Visit url4 using redirects.
  history->AddPage(url4, scope, 0, GURL(),
                   PageTransition::TYPED, redirects,
                   history::SOURCE_BROWSED, false);
  history->QueryMostVisitedURLs(20, 90, &consumer_,
                                NewCallback(static_cast<HistoryTest*>(this),
                                    &HistoryTest::OnMostVisitedURLsAvailable));
  MessageLoop::current()->Run();

  EXPECT_EQ(4U, most_visited_urls_.size());
  EXPECT_EQ(url1, most_visited_urls_[0].url);
  EXPECT_EQ(url2, most_visited_urls_[1].url);
  EXPECT_EQ(url0, most_visited_urls_[2].url);
  EXPECT_EQ(url3, most_visited_urls_[3].url);
  EXPECT_EQ(2U, most_visited_urls_[3].redirects.size());
}

// The version of the history database should be current in the "typical
// history" example file or it will be imported on startup, throwing off timing
// measurements.
//
// See test/data/profiles/typical_history/README.txt for instructions on
// how to up the version.
TEST(HistoryProfileTest, TypicalProfileVersion) {
  FilePath file;
  ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &file));
  file = file.AppendASCII("profiles");
  file = file.AppendASCII("typical_history");
  file = file.AppendASCII("Default");
  file = file.AppendASCII("History");

  int cur_version = HistoryDatabase::GetCurrentVersion();

  sql::Connection db;
  ASSERT_TRUE(db.Open(file));

  {
    sql::Statement s(db.GetUniqueStatement(
        "SELECT value FROM meta WHERE key = 'version'"));
    EXPECT_TRUE(s.Step());
    int file_version = s.ColumnInt(0);
    EXPECT_EQ(cur_version, file_version);
  }
}

namespace {

// A HistoryDBTask implementation. Each time RunOnDBThread is invoked
// invoke_count is increment. When invoked kWantInvokeCount times, true is
// returned from RunOnDBThread which should stop RunOnDBThread from being
// invoked again. When DoneRunOnMainThread is invoked, done_invoked is set to
// true.
class HistoryDBTaskImpl : public HistoryDBTask {
 public:
  static const int kWantInvokeCount;

  HistoryDBTaskImpl() : invoke_count(0), done_invoked(false) {}

  virtual bool RunOnDBThread(HistoryBackend* backend, HistoryDatabase* db) {
    return (++invoke_count == kWantInvokeCount);
  }

  virtual void DoneRunOnMainThread() {
    done_invoked = true;
    MessageLoop::current()->Quit();
  }

  int invoke_count;
  bool done_invoked;

 private:
  virtual ~HistoryDBTaskImpl() {}

  DISALLOW_COPY_AND_ASSIGN(HistoryDBTaskImpl);
};

// static
const int HistoryDBTaskImpl::kWantInvokeCount = 2;

}  // namespace

TEST_F(HistoryTest, HistoryDBTask) {
  CancelableRequestConsumerT<int, 0> request_consumer;
  HistoryService* history = new HistoryService();
  ASSERT_TRUE(history->Init(history_dir_, NULL));
  scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
  history_service_ = history;
  history->ScheduleDBTask(task.get(), &request_consumer);
  // Run the message loop. When HistoryDBTaskImpl::DoneRunOnMainThread runs,
  // it will stop the message loop. If the test hangs here, it means
  // DoneRunOnMainThread isn't being invoked correctly.
  MessageLoop::current()->Run();
  CleanupHistoryService();
  // WARNING: history has now been deleted.
  history = NULL;
  ASSERT_EQ(HistoryDBTaskImpl::kWantInvokeCount, task->invoke_count);
  ASSERT_TRUE(task->done_invoked);
}

TEST_F(HistoryTest, HistoryDBTaskCanceled) {
  CancelableRequestConsumerT<int, 0> request_consumer;
  HistoryService* history = new HistoryService();
  ASSERT_TRUE(history->Init(history_dir_, NULL));
  scoped_refptr<HistoryDBTaskImpl> task(new HistoryDBTaskImpl());
  history_service_ = history;
  history->ScheduleDBTask(task.get(), &request_consumer);
  request_consumer.CancelAllRequests();
  CleanupHistoryService();
  // WARNING: history has now been deleted.
  history = NULL;
  ASSERT_FALSE(task->done_invoked);
}

}  // namespace history