blob: 58074479b785a8194a77a56a93da6ce0b00e0f98 [file] [log] [blame]
// 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.
// 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 "components/history/core/browser/history_backend.h"
#include <stdint.h>
#include <string>
#include <unordered_set>
#include "base/format_macros.h"
#include "base/guid.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/history/core/browser/download_constants.h"
#include "components/history/core/browser/download_row.h"
#include "components/history/core/browser/history_constants.h"
#include "components/history/core/browser/history_database.h"
#include "components/history/core/browser/page_usage_data.h"
#include "components/history/core/test/history_backend_db_base_test.h"
#include "components/history/core/test/test_history_database.h"
namespace history {
namespace {
// This must be outside the anonymous namespace for the friend statement in
// HistoryBackend to work.
class HistoryBackendDBTest : public HistoryBackendDBBaseTest {
public:
HistoryBackendDBTest() {}
~HistoryBackendDBTest() override {}
};
TEST_F(HistoryBackendDBTest, ClearBrowsingData_Downloads) {
CreateBackendAndDatabase();
// Initially there should be nothing in the downloads database.
std::vector<DownloadRow> downloads;
db_->QueryDownloads(&downloads);
EXPECT_EQ(0U, downloads.size());
// Add a download, test that it was added correctly, remove it, test that it
// was removed.
base::Time now = base::Time();
uint32_t id = 1;
EXPECT_TRUE(AddDownload(id,
"BC5E3854-7B1D-4DE0-B619-B0D99C8B18B4",
DownloadState::COMPLETE,
base::Time()));
db_->QueryDownloads(&downloads);
EXPECT_EQ(1U, downloads.size());
EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("current-path")),
downloads[0].current_path);
EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("target-path")),
downloads[0].target_path);
EXPECT_EQ(1UL, downloads[0].url_chain.size());
EXPECT_EQ(GURL("foo-url"), downloads[0].url_chain[0]);
EXPECT_EQ(std::string("http://referrer.com/"),
std::string(downloads[0].referrer_url.spec()));
EXPECT_EQ(now, downloads[0].start_time);
EXPECT_EQ(now, downloads[0].end_time);
EXPECT_EQ(0, downloads[0].received_bytes);
EXPECT_EQ(512, downloads[0].total_bytes);
EXPECT_EQ(DownloadState::COMPLETE, downloads[0].state);
EXPECT_EQ(DownloadDangerType::NOT_DANGEROUS, downloads[0].danger_type);
EXPECT_EQ(kTestDownloadInterruptReasonNone, downloads[0].interrupt_reason);
EXPECT_FALSE(downloads[0].opened);
EXPECT_EQ("by_ext_id", downloads[0].by_ext_id);
EXPECT_EQ("by_ext_name", downloads[0].by_ext_name);
EXPECT_EQ("application/vnd.oasis.opendocument.text", downloads[0].mime_type);
EXPECT_EQ("application/octet-stream", downloads[0].original_mime_type);
db_->QueryDownloads(&downloads);
EXPECT_EQ(1U, downloads.size());
db_->RemoveDownload(id);
db_->QueryDownloads(&downloads);
EXPECT_EQ(0U, downloads.size());
}
TEST_F(HistoryBackendDBTest, MigrateDownloadsState) {
// Create the db we want.
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
{
// Open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// Manually insert corrupted rows; there's infrastructure in place now to
// make this impossible, at least according to the test above.
for (int state = 0; state < 5; ++state) {
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads (id, full_path, url, start_time, "
"received_bytes, total_bytes, state, end_time, opened) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?)"));
s.BindInt64(0, 1 + state);
s.BindString(1, "path");
s.BindString(2, "url");
s.BindInt64(3, base::Time::Now().ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, state);
s.BindInt64(7, base::Time::Now().ToTimeT());
s.BindInt(8, state % 2);
ASSERT_TRUE(s.Run());
}
}
// Re-open the db using the HistoryDatabase, which should migrate from version
// 22 to the current version, fixing just the row whose state was 3.
// Then close the db so that we can re-open it directly.
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LT(22, cur_version);
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
sql::Statement statement(db.GetUniqueStatement(
"SELECT id, state, opened "
"FROM downloads "
"ORDER BY id"));
int counter = 0;
while (statement.Step()) {
EXPECT_EQ(1 + counter, statement.ColumnInt64(0));
// The only thing that migration should have changed was state from 3 to
// 4.
EXPECT_EQ(((counter == 3) ? 4 : counter), statement.ColumnInt(1));
EXPECT_EQ(counter % 2, statement.ColumnInt(2));
++counter;
}
EXPECT_EQ(5, counter);
}
}
}
TEST_F(HistoryBackendDBTest, MigrateDownloadsReasonPathsAndDangerType) {
base::Time now(base::Time::Now());
// Create the db we want. The schema didn't change from 22->23, so just
// re-use the v22 file.
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// Manually insert some rows.
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads (id, full_path, url, start_time, "
"received_bytes, total_bytes, state, end_time, opened) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?)"));
int64_t id = 0;
// Null path.
s.BindInt64(0, ++id);
s.BindString(1, std::string());
s.BindString(2, "http://whatever.com/index.html");
s.BindInt64(3, now.ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, 1);
s.BindInt64(7, now.ToTimeT());
s.BindInt(8, 1);
ASSERT_TRUE(s.Run());
s.Reset(true);
// Non-null path.
s.BindInt64(0, ++id);
s.BindString(1, "/path/to/some/file");
s.BindString(2, "http://whatever.com/index1.html");
s.BindInt64(3, now.ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, 1);
s.BindInt64(7, now.ToTimeT());
s.BindInt(8, 1);
ASSERT_TRUE(s.Run());
}
// Re-open the db using the HistoryDatabase, which should migrate from version
// 23 to 24, creating the new tables and creating the new path, reason,
// and danger columns.
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LT(23, cur_version);
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
base::Time nowish(base::Time::FromTimeT(now.ToTimeT()));
// Confirm downloads table is valid.
sql::Statement statement(db.GetUniqueStatement(
"SELECT id, interrupt_reason, current_path, target_path, "
" danger_type, start_time, end_time "
"FROM downloads ORDER BY id"));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(1, statement.ColumnInt64(0));
EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone),
statement.ColumnInt(1));
EXPECT_EQ("", statement.ColumnString(2));
EXPECT_EQ("", statement.ColumnString(3));
// Implicit dependence on value of kDangerTypeNotDangerous from
// download_database.cc.
EXPECT_EQ(0, statement.ColumnInt(4));
EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5));
EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(2, statement.ColumnInt64(0));
EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone),
statement.ColumnInt(1));
EXPECT_EQ("/path/to/some/file", statement.ColumnString(2));
EXPECT_EQ("/path/to/some/file", statement.ColumnString(3));
EXPECT_EQ(0, statement.ColumnInt(4));
EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(5));
EXPECT_EQ(nowish.ToInternalValue(), statement.ColumnInt64(6));
EXPECT_FALSE(statement.Step());
}
{
// Confirm downloads_url_chains table is valid.
sql::Statement statement(db.GetUniqueStatement(
"SELECT id, chain_index, url FROM downloads_url_chains "
" ORDER BY id, chain_index"));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(1, statement.ColumnInt64(0));
EXPECT_EQ(0, statement.ColumnInt(1));
EXPECT_EQ("http://whatever.com/index.html", statement.ColumnString(2));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(2, statement.ColumnInt64(0));
EXPECT_EQ(0, statement.ColumnInt(1));
EXPECT_EQ("http://whatever.com/index1.html", statement.ColumnString(2));
EXPECT_FALSE(statement.Step());
}
}
}
TEST_F(HistoryBackendDBTest, MigrateReferrer) {
base::Time now(base::Time::Now());
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads (id, full_path, url, start_time, "
"received_bytes, total_bytes, state, end_time, opened) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?)"));
int64_t db_handle = 0;
s.BindInt64(0, ++db_handle);
s.BindString(1, "full_path");
s.BindString(2, "http://whatever.com/index.html");
s.BindInt64(3, now.ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, 1);
s.BindInt64(7, now.ToTimeT());
s.BindInt(8, 1);
ASSERT_TRUE(s.Run());
}
// Re-open the db using the HistoryDatabase, which should migrate to version
// 26, creating the referrer column.
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LE(26, cur_version);
{
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
sql::Statement s(db.GetUniqueStatement(
"SELECT referrer from downloads"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(std::string(), s.ColumnString(0));
}
}
}
TEST_F(HistoryBackendDBTest, MigrateDownloadedByExtension) {
base::Time now(base::Time::Now());
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(26));
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads (id, current_path, target_path, start_time, "
"received_bytes, total_bytes, state, danger_type, interrupt_reason, "
"end_time, opened, referrer) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
s.BindInt64(0, 1);
s.BindString(1, "current_path");
s.BindString(2, "target_path");
s.BindInt64(3, now.ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, 1);
s.BindInt(7, 0);
s.BindInt(8, 0);
s.BindInt64(9, now.ToTimeT());
s.BindInt(10, 1);
s.BindString(11, "referrer");
ASSERT_TRUE(s.Run());
}
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads_url_chains (id, chain_index, url) VALUES "
"(?, ?, ?)"));
s.BindInt64(0, 4);
s.BindInt64(1, 0);
s.BindString(2, "url");
ASSERT_TRUE(s.Run());
}
}
// Re-open the db using the HistoryDatabase, which should migrate to version
// 27, creating the by_ext_id and by_ext_name columns.
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LE(27, cur_version);
{
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
sql::Statement s(db.GetUniqueStatement(
"SELECT by_ext_id, by_ext_name from downloads"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(std::string(), s.ColumnString(0));
EXPECT_EQ(std::string(), s.ColumnString(1));
}
}
}
TEST_F(HistoryBackendDBTest, MigrateDownloadValidators) {
base::Time now(base::Time::Now());
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(27));
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads (id, current_path, target_path, start_time, "
"received_bytes, total_bytes, state, danger_type, interrupt_reason, "
"end_time, opened, referrer, by_ext_id, by_ext_name) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
s.BindInt64(0, 1);
s.BindString(1, "current_path");
s.BindString(2, "target_path");
s.BindInt64(3, now.ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, 1);
s.BindInt(7, 0);
s.BindInt(8, 0);
s.BindInt64(9, now.ToTimeT());
s.BindInt(10, 1);
s.BindString(11, "referrer");
s.BindString(12, "by extension ID");
s.BindString(13, "by extension name");
ASSERT_TRUE(s.Run());
}
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads_url_chains (id, chain_index, url) VALUES "
"(?, ?, ?)"));
s.BindInt64(0, 4);
s.BindInt64(1, 0);
s.BindString(2, "url");
ASSERT_TRUE(s.Run());
}
}
// Re-open the db using the HistoryDatabase, which should migrate to the
// current version, creating the etag and last_modified columns.
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LE(28, cur_version);
{
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
sql::Statement s(db.GetUniqueStatement(
"SELECT etag, last_modified from downloads"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(std::string(), s.ColumnString(0));
EXPECT_EQ(std::string(), s.ColumnString(1));
}
}
}
TEST_F(HistoryBackendDBTest, MigrateDownloadMimeType) {
base::Time now(base::Time::Now());
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(28));
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads (id, current_path, target_path, start_time, "
"received_bytes, total_bytes, state, danger_type, interrupt_reason, "
"end_time, opened, referrer, by_ext_id, by_ext_name, etag, "
"last_modified) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
s.BindInt64(0, 1);
s.BindString(1, "current_path");
s.BindString(2, "target_path");
s.BindInt64(3, now.ToTimeT());
s.BindInt64(4, 100);
s.BindInt64(5, 100);
s.BindInt(6, 1);
s.BindInt(7, 0);
s.BindInt(8, 0);
s.BindInt64(9, now.ToTimeT());
s.BindInt(10, 1);
s.BindString(11, "referrer");
s.BindString(12, "by extension ID");
s.BindString(13, "by extension name");
s.BindString(14, "etag");
s.BindInt64(15, now.ToTimeT());
ASSERT_TRUE(s.Run());
}
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO downloads_url_chains (id, chain_index, url) VALUES "
"(?, ?, ?)"));
s.BindInt64(0, 4);
s.BindInt64(1, 0);
s.BindString(2, "url");
ASSERT_TRUE(s.Run());
}
}
// Re-open the db using the HistoryDatabase, which should migrate to the
// current version, creating the mime_type abd original_mime_type columns.
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LE(29, cur_version);
{
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
sql::Statement s(db.GetUniqueStatement(
"SELECT mime_type, original_mime_type from downloads"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(std::string(), s.ColumnString(0));
EXPECT_EQ(std::string(), s.ColumnString(1));
}
}
}
bool IsValidRFC4122Ver4GUID(const std::string& guid) {
// base::IsValidGUID() doesn't restrict its validation to version (or subtype)
// 4 GUIDs as described in RFC 4122. So we check if base::IsValidGUID() thinks
// it's a valid GUID first, and then check the additional constraints.
//
// * Bits 4-7 of time_hi_and_version should be set to 0b0100 == 4
// => guid[14] == '4'
//
// * Bits 6-7 of clk_seq_hi_res should be set to 0b10
// => guid[19] in {'8','9','A','B'}
//
// * All other bits should be random or pseudo random.
// => http://dilbert.com/strip/2001-10-25
return base::IsValidGUID(guid) && guid[14] == '4' &&
(guid[19] == '8' || guid[19] == '9' || guid[19] == 'A' ||
guid[19] == 'B');
}
TEST_F(HistoryBackendDBTest, MigrateHashHttpMethodAndGenerateGuids) {
const size_t kDownloadCount = 100;
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(29));
base::Time now(base::Time::Now());
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// In testing, it appeared that constructing a query where all rows are
// specified (i.e. looks like "INSERT INTO foo (...) VALUES (...),(...)")
// performs much better than executing a cached query multiple times where
// the query inserts only a single row per run (i.e. looks like "INSERT INTO
// (...) VALUES (...)"). For 100 records, the latter took 19s on a
// developer machine while the former inserted 100 records in ~400ms.
std::string download_insert_query =
"INSERT INTO downloads (id, current_path, target_path, start_time, "
"received_bytes, total_bytes, state, danger_type, interrupt_reason, "
"end_time, opened, referrer, by_ext_id, by_ext_name, etag, "
"last_modified, mime_type, original_mime_type) VALUES ";
std::string url_insert_query =
"INSERT INTO downloads_url_chains (id, chain_index, url) VALUES ";
for (uint32_t i = 0; i < kDownloadCount; ++i) {
uint32_t download_id = i * 13321;
if (i != 0)
download_insert_query += ",";
download_insert_query += base::StringPrintf(
"(%" PRId64 ", 'current_path', 'target_path', %" PRId64
", 100, 100, 1, 0, 0, %" PRId64
", 1, 'referrer', 'by extension ID','by extension name', 'etag', "
"'last modified', 'mime/type', 'original/mime-type')",
static_cast<int64_t>(download_id),
static_cast<int64_t>(now.ToTimeT()),
static_cast<int64_t>(now.ToTimeT()));
if (i != 0)
url_insert_query += ",";
url_insert_query += base::StringPrintf("(%" PRId64 ", 0, 'url')",
static_cast<int64_t>(download_id));
}
ASSERT_TRUE(db.Execute(download_insert_query.c_str()));
ASSERT_TRUE(db.Execute(url_insert_query.c_str()));
}
CreateBackendAndDatabase();
DeleteBackend();
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// The version should have been updated.
int cur_version = HistoryDatabase::GetCurrentVersion();
ASSERT_LE(30, cur_version);
{
sql::Statement s(db.GetUniqueStatement(
"SELECT value FROM meta WHERE key = 'version'"));
EXPECT_TRUE(s.Step());
EXPECT_EQ(cur_version, s.ColumnInt(0));
}
{
sql::Statement s(db.GetUniqueStatement("SELECT guid from downloads"));
std::unordered_set<std::string> guids;
while (s.Step()) {
std::string guid = s.ColumnString(0);
EXPECT_TRUE(IsValidRFC4122Ver4GUID(guid));
EXPECT_EQ(guid, base::ToUpperASCII(guid));
guids.insert(guid);
}
EXPECT_TRUE(s.Succeeded());
EXPECT_EQ(kDownloadCount, guids.size());
}
}
}
TEST_F(HistoryBackendDBTest, DownloadCreateAndQuery) {
CreateBackendAndDatabase();
ASSERT_EQ(0u, db_->CountDownloads());
std::vector<GURL> url_chain;
url_chain.push_back(GURL("http://example.com/a"));
url_chain.push_back(GURL("http://example.com/b"));
url_chain.push_back(GURL("http://example.com/c"));
base::Time start_time(base::Time::Now());
base::Time end_time(start_time + base::TimeDelta::FromHours(1));
DownloadRow download_A(base::FilePath(FILE_PATH_LITERAL("/path/1")),
base::FilePath(FILE_PATH_LITERAL("/path/2")),
url_chain,
GURL("http://example.com/referrer"),
"GET",
"mime/type",
"original/mime-type",
start_time,
end_time,
"etag1",
"last_modified_1",
100,
1000,
DownloadState::INTERRUPTED,
DownloadDangerType::NOT_DANGEROUS,
kTestDownloadInterruptReasonCrash,
"hash-value1",
1,
"FE672168-26EF-4275-A149-FEC25F6A75F9",
false,
"extension-id",
"extension-name");
ASSERT_TRUE(db_->CreateDownload(download_A));
url_chain.push_back(GURL("http://example.com/d"));
DownloadRow download_B(base::FilePath(FILE_PATH_LITERAL("/path/3")),
base::FilePath(FILE_PATH_LITERAL("/path/4")),
url_chain,
GURL("http://example.com/referrer2"),
"POST",
"mime/type2",
"original/mime-type2",
start_time,
end_time,
"etag2",
"last_modified_2",
1001,
1001,
DownloadState::COMPLETE,
DownloadDangerType::DANGEROUS_FILE,
kTestDownloadInterruptReasonNone,
std::string(),
2,
"b70f3869-7d75-4878-acb4-4caf7026d12b",
false,
"extension-id",
"extension-name");
ASSERT_TRUE(db_->CreateDownload(download_B));
EXPECT_EQ(2u, db_->CountDownloads());
std::vector<DownloadRow> results;
db_->QueryDownloads(&results);
ASSERT_EQ(2u, results.size());
const DownloadRow& retrieved_download_A =
results[0].id == 1 ? results[0] : results[1];
const DownloadRow& retrieved_download_B =
results[0].id == 1 ? results[1] : results[0];
EXPECT_EQ(download_A, retrieved_download_A);
EXPECT_EQ(download_B, retrieved_download_B);
}
TEST_F(HistoryBackendDBTest, DownloadCreateAndUpdate_VolatileFields) {
CreateBackendAndDatabase();
std::vector<GURL> url_chain;
url_chain.push_back(GURL("http://example.com/a"));
url_chain.push_back(GURL("http://example.com/b"));
url_chain.push_back(GURL("http://example.com/c"));
base::Time start_time(base::Time::Now());
base::Time end_time(start_time + base::TimeDelta::FromHours(1));
DownloadRow download(base::FilePath(FILE_PATH_LITERAL("/path/1")),
base::FilePath(FILE_PATH_LITERAL("/path/2")),
url_chain,
GURL("http://example.com/referrer"),
"GET",
"mime/type",
"original/mime-type",
start_time,
end_time,
"etag1",
"last_modified_1",
100,
1000,
DownloadState::INTERRUPTED,
DownloadDangerType::NOT_DANGEROUS,
3,
"some-hash-value",
1,
"FE672168-26EF-4275-A149-FEC25F6A75F9",
false,
"extension-id",
"extension-name");
db_->CreateDownload(download);
download.current_path =
base::FilePath(FILE_PATH_LITERAL("/new/current_path"));
download.target_path = base::FilePath(FILE_PATH_LITERAL("/new/target_path"));
download.mime_type = "new/mime/type";
download.original_mime_type = "new/original/mime/type";
download.received_bytes += 1000;
download.state = DownloadState::CANCELLED;
download.danger_type = DownloadDangerType::USER_VALIDATED;
download.interrupt_reason = 4;
download.end_time += base::TimeDelta::FromHours(1);
download.total_bytes += 1;
download.hash = "some-other-hash";
download.opened = !download.opened;
download.by_ext_id = "by-new-extension-id";
download.by_ext_name = "by-new-extension-name";
download.etag = "new-etag";
download.last_modified = "new-last-modified";
ASSERT_TRUE(db_->UpdateDownload(download));
std::vector<DownloadRow> results;
db_->QueryDownloads(&results);
ASSERT_EQ(1u, results.size());
EXPECT_EQ(download, results[0]);
}
TEST_F(HistoryBackendDBTest, ConfirmDownloadRowCreateAndDelete) {
// Create the DB.
CreateBackendAndDatabase();
base::Time now(base::Time::Now());
// Add some downloads.
uint32_t id1 = 1, id2 = 2, id3 = 3;
AddDownload(id1,
"05AF6C8E-E4E0-45D7-B5CE-BC99F7019918",
DownloadState::COMPLETE,
now);
AddDownload(id2,
"05AF6C8E-E4E0-45D7-B5CE-BC99F7019919",
DownloadState::COMPLETE,
now + base::TimeDelta::FromDays(2));
AddDownload(id3,
"05AF6C8E-E4E0-45D7-B5CE-BC99F701991A",
DownloadState::COMPLETE,
now - base::TimeDelta::FromDays(2));
// Confirm that resulted in the correct number of rows in the DB.
DeleteBackend();
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
sql::Statement statement(db.GetUniqueStatement(
"Select Count(*) from downloads"));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(3, statement.ColumnInt(0));
sql::Statement statement1(db.GetUniqueStatement(
"Select Count(*) from downloads_url_chains"));
EXPECT_TRUE(statement1.Step());
EXPECT_EQ(3, statement1.ColumnInt(0));
}
// Delete some rows and make sure the results are still correct.
CreateBackendAndDatabase();
db_->RemoveDownload(id2);
db_->RemoveDownload(id3);
DeleteBackend();
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
sql::Statement statement(db.GetUniqueStatement(
"Select Count(*) from downloads"));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(1, statement.ColumnInt(0));
sql::Statement statement1(db.GetUniqueStatement(
"Select Count(*) from downloads_url_chains"));
EXPECT_TRUE(statement1.Step());
EXPECT_EQ(1, statement1.ColumnInt(0));
}
}
TEST_F(HistoryBackendDBTest, DownloadNukeRecordsMissingURLs) {
CreateBackendAndDatabase();
base::Time now(base::Time::Now());
std::vector<GURL> url_chain;
DownloadRow download(base::FilePath(FILE_PATH_LITERAL("foo-path")),
base::FilePath(FILE_PATH_LITERAL("foo-path")),
url_chain,
GURL(std::string()),
std::string(),
"application/octet-stream",
"application/octet-stream",
now,
now,
std::string(),
std::string(),
0,
512,
DownloadState::COMPLETE,
DownloadDangerType::NOT_DANGEROUS,
kTestDownloadInterruptReasonNone,
std::string(),
1,
"05AF6C8E-E4E0-45D7-B5CE-BC99F7019918",
0,
"by_ext_id",
"by_ext_name");
// Creating records without any urls should fail.
EXPECT_FALSE(db_->CreateDownload(download));
download.url_chain.push_back(GURL("foo-url"));
EXPECT_TRUE(db_->CreateDownload(download));
// Pretend that the URLs were dropped.
DeleteBackend();
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
sql::Statement statement(db.GetUniqueStatement(
"DELETE FROM downloads_url_chains WHERE id=1"));
ASSERT_TRUE(statement.Run());
}
CreateBackendAndDatabase();
std::vector<DownloadRow> downloads;
db_->QueryDownloads(&downloads);
EXPECT_EQ(0U, downloads.size());
// QueryDownloads should have nuked the corrupt record.
DeleteBackend();
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
sql::Statement statement(db.GetUniqueStatement(
"SELECT count(*) from downloads"));
ASSERT_TRUE(statement.Step());
EXPECT_EQ(0, statement.ColumnInt(0));
}
}
}
TEST_F(HistoryBackendDBTest, ConfirmDownloadInProgressCleanup) {
// Create the DB.
CreateBackendAndDatabase();
base::Time now(base::Time::Now());
// Put an IN_PROGRESS download in the DB.
AddDownload(1,
"05AF6C8E-E4E0-45D7-B5CE-BC99F7019918",
DownloadState::IN_PROGRESS,
now);
// Confirm that they made it into the DB unchanged.
DeleteBackend();
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
sql::Statement statement(db.GetUniqueStatement(
"Select Count(*) from downloads"));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(1, statement.ColumnInt(0));
sql::Statement statement1(db.GetUniqueStatement(
"Select state, interrupt_reason from downloads"));
EXPECT_TRUE(statement1.Step());
EXPECT_EQ(DownloadStateToInt(DownloadState::IN_PROGRESS),
statement1.ColumnInt(0));
EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonNone),
statement1.ColumnInt(1));
EXPECT_FALSE(statement1.Step());
}
// Read in the DB through query downloads, then test that the
// right transformation was returned.
CreateBackendAndDatabase();
std::vector<DownloadRow> results;
db_->QueryDownloads(&results);
ASSERT_EQ(1u, results.size());
EXPECT_EQ(DownloadState::INTERRUPTED, results[0].state);
EXPECT_EQ(kTestDownloadInterruptReasonCrash, results[0].interrupt_reason);
// Allow the update to propagate, shut down the DB, and confirm that
// the query updated the on disk database as well.
base::MessageLoop::current()->RunUntilIdle();
DeleteBackend();
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
sql::Statement statement(db.GetUniqueStatement(
"Select Count(*) from downloads"));
EXPECT_TRUE(statement.Step());
EXPECT_EQ(1, statement.ColumnInt(0));
sql::Statement statement1(db.GetUniqueStatement(
"Select state, interrupt_reason from downloads"));
EXPECT_TRUE(statement1.Step());
EXPECT_EQ(DownloadStateToInt(DownloadState::INTERRUPTED),
statement1.ColumnInt(0));
EXPECT_EQ(DownloadInterruptReasonToInt(kTestDownloadInterruptReasonCrash),
statement1.ColumnInt(1));
EXPECT_FALSE(statement1.Step());
}
}
TEST_F(HistoryBackendDBTest, MigratePresentations) {
// Create the db we want. Use 22 since segments didn't change in that time
// frame.
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(22));
const SegmentID segment_id = 2;
const URLID url_id = 3;
const GURL url("http://www.foo.com");
const std::string url_name(VisitSegmentDatabase::ComputeSegmentName(url));
const base::string16 title(base::ASCIIToUTF16("Title1"));
const base::Time segment_time(base::Time::Now());
{
// Re-open the db for manual manipulation.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
// Add an entry to urls.
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO urls "
"(id, url, title, last_visit_time) VALUES "
"(?, ?, ?, ?)"));
s.BindInt64(0, url_id);
s.BindString(1, url.spec());
s.BindString16(2, title);
s.BindInt64(3, segment_time.ToInternalValue());
ASSERT_TRUE(s.Run());
}
// Add an entry to segments.
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO segments "
"(id, name, url_id, pres_index) VALUES "
"(?, ?, ?, ?)"));
s.BindInt64(0, segment_id);
s.BindString(1, url_name);
s.BindInt64(2, url_id);
s.BindInt(3, 4); // pres_index
ASSERT_TRUE(s.Run());
}
// And one to segment_usage.
{
sql::Statement s(db.GetUniqueStatement(
"INSERT INTO segment_usage "
"(id, segment_id, time_slot, visit_count) VALUES "
"(?, ?, ?, ?)"));
s.BindInt64(0, 4); // id.
s.BindInt64(1, segment_id);
s.BindInt64(2, segment_time.ToInternalValue());
s.BindInt(3, 5); // visit count.
ASSERT_TRUE(s.Run());
}
}
// Re-open the db, triggering migration.
CreateBackendAndDatabase();
std::vector<PageUsageData*> results;
db_->QuerySegmentUsage(segment_time, 10, &results);
ASSERT_EQ(1u, results.size());
EXPECT_EQ(url, results[0]->GetURL());
EXPECT_EQ(segment_id, results[0]->GetID());
EXPECT_EQ(title, results[0]->GetTitle());
STLDeleteElements(&results);
}
TEST_F(HistoryBackendDBTest, CheckLastCompatibleVersion) {
ASSERT_NO_FATAL_FAILURE(CreateDBVersion(28));
{
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
// Manually set last compatible version to one higher
// than current version.
sql::MetaTable meta;
meta.Init(&db, 1, 1);
meta.SetCompatibleVersionNumber(HistoryDatabase::GetCurrentVersion() + 1);
}
}
// Try to create and init backend for non compatible db.
// Allow failure in backend creation.
CreateBackendAndDatabaseAllowFail();
DeleteBackend();
// Check that error delegate was called with correct init error status.
EXPECT_EQ(sql::INIT_TOO_NEW, last_profile_error_);
{
// Re-open the db to check that it was not migrated.
// Non compatible DB must be ignored.
// Check that DB version in file remains the same.
sql::Connection db;
ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
{
sql::MetaTable meta;
meta.Init(&db, 1, 1);
// Current browser version must be already higher than 28.
ASSERT_LT(28, HistoryDatabase::GetCurrentVersion());
// Expect that version in DB remains the same.
EXPECT_EQ(28, meta.GetVersionNumber());
}
}
}
} // namespace
} // namespace history