| // 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 <set> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/history/visit_database.h" |
| #include "components/history/core/browser/url_database.h" |
| #include "sql/connection.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/platform_test.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace history { |
| |
| namespace { |
| |
| bool IsVisitInfoEqual(const VisitRow& a, |
| const VisitRow& b) { |
| return a.visit_id == b.visit_id && |
| a.url_id == b.url_id && |
| a.visit_time == b.visit_time && |
| a.referring_visit == b.referring_visit && |
| a.transition == b.transition; |
| } |
| |
| } // namespace |
| |
| class VisitDatabaseTest : public PlatformTest, |
| public URLDatabase, |
| public VisitDatabase { |
| public: |
| VisitDatabaseTest() { |
| } |
| |
| private: |
| // Test setup. |
| void SetUp() override { |
| PlatformTest::SetUp(); |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| base::FilePath db_file = temp_dir_.path().AppendASCII("VisitTest.db"); |
| |
| EXPECT_TRUE(db_.Open(db_file)); |
| |
| // Initialize the tables for this test. |
| CreateURLTable(false); |
| CreateMainURLIndex(); |
| InitVisitTable(); |
| } |
| void TearDown() override { |
| db_.Close(); |
| PlatformTest::TearDown(); |
| } |
| |
| // Provided for URL/VisitDatabase. |
| sql::Connection& GetDB() override { return db_; } |
| |
| base::ScopedTempDir temp_dir_; |
| sql::Connection db_; |
| }; |
| |
| TEST_F(VisitDatabaseTest, Add) { |
| // Add one visit. |
| VisitRow visit_info1(1, Time::Now(), 0, ui::PAGE_TRANSITION_LINK, 0); |
| EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED)); |
| |
| // Add second visit for the same page. |
| VisitRow visit_info2(visit_info1.url_id, |
| visit_info1.visit_time + TimeDelta::FromSeconds(1), 1, |
| ui::PAGE_TRANSITION_TYPED, 0); |
| EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED)); |
| |
| // Add third visit for a different page. |
| VisitRow visit_info3(2, |
| visit_info1.visit_time + TimeDelta::FromSeconds(2), 0, |
| ui::PAGE_TRANSITION_LINK, 0); |
| EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED)); |
| |
| // Query the first two. |
| std::vector<VisitRow> matches; |
| EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches)); |
| EXPECT_EQ(static_cast<size_t>(2), matches.size()); |
| |
| // Make sure we got both (order in result set is visit time). |
| EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) && |
| IsVisitInfoEqual(matches[1], visit_info2)); |
| } |
| |
| TEST_F(VisitDatabaseTest, Delete) { |
| // Add three visits that form a chain of navigation, and then delete the |
| // middle one. We should be left with the outer two visits, and the chain |
| // should link them. |
| static const int kTime1 = 1000; |
| VisitRow visit_info1(1, Time::FromInternalValue(kTime1), 0, |
| ui::PAGE_TRANSITION_LINK, 0); |
| EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED)); |
| |
| static const int kTime2 = kTime1 + 1; |
| VisitRow visit_info2(1, Time::FromInternalValue(kTime2), |
| visit_info1.visit_id, ui::PAGE_TRANSITION_LINK, 0); |
| EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED)); |
| |
| static const int kTime3 = kTime2 + 1; |
| VisitRow visit_info3(1, Time::FromInternalValue(kTime3), |
| visit_info2.visit_id, ui::PAGE_TRANSITION_LINK, 0); |
| EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED)); |
| |
| // First make sure all the visits are there. |
| std::vector<VisitRow> matches; |
| EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches)); |
| EXPECT_EQ(static_cast<size_t>(3), matches.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) && |
| IsVisitInfoEqual(matches[1], visit_info2) && |
| IsVisitInfoEqual(matches[2], visit_info3)); |
| |
| // Delete the middle one. |
| DeleteVisit(visit_info2); |
| |
| // The outer two should be left, and the last one should have the first as |
| // the referrer. |
| visit_info3.referring_visit = visit_info1.visit_id; |
| matches.clear(); |
| EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches)); |
| EXPECT_EQ(static_cast<size_t>(2), matches.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) && |
| IsVisitInfoEqual(matches[1], visit_info3)); |
| } |
| |
| TEST_F(VisitDatabaseTest, Update) { |
| // Make something in the database. |
| VisitRow original(1, Time::Now(), 23, ui::PageTransitionFromInt(0), 19); |
| AddVisit(&original, SOURCE_BROWSED); |
| |
| // Mutate that row. |
| VisitRow modification(original); |
| modification.url_id = 2; |
| modification.transition = ui::PAGE_TRANSITION_TYPED; |
| modification.visit_time = Time::Now() + TimeDelta::FromDays(1); |
| modification.referring_visit = 9292; |
| UpdateVisitRow(modification); |
| |
| // Check that the mutated version was written. |
| VisitRow final; |
| GetRowForVisit(original.visit_id, &final); |
| EXPECT_TRUE(IsVisitInfoEqual(modification, final)); |
| } |
| |
| // TODO(brettw) write test for GetMostRecentVisitForURL! |
| |
| namespace { |
| |
| std::vector<VisitRow> GetTestVisitRows() { |
| // Tests can be sensitive to the local timezone, so use a local time as the |
| // basis for all visit times. |
| base::Time base_time = Time::UnixEpoch().LocalMidnight(); |
| |
| // Add one visit. |
| VisitRow visit_info1(1, base_time + TimeDelta::FromMinutes(1), 0, |
| ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_LINK | |
| ui::PAGE_TRANSITION_CHAIN_START | |
| ui::PAGE_TRANSITION_CHAIN_END), |
| 0); |
| visit_info1.visit_id = 1; |
| |
| // Add second visit for the same page. |
| VisitRow visit_info2(visit_info1.url_id, |
| visit_info1.visit_time + TimeDelta::FromSeconds(1), 1, |
| ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_CHAIN_START | |
| ui::PAGE_TRANSITION_CHAIN_END), |
| 0); |
| visit_info2.visit_id = 2; |
| |
| // Add third visit for a different page. |
| VisitRow visit_info3(2, |
| visit_info1.visit_time + TimeDelta::FromSeconds(2), 0, |
| ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_LINK | |
| ui::PAGE_TRANSITION_CHAIN_START), |
| 0); |
| visit_info3.visit_id = 3; |
| |
| // Add a redirect visit from the last page. |
| VisitRow visit_info4(3, |
| visit_info1.visit_time + TimeDelta::FromSeconds(3), visit_info3.visit_id, |
| ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_SERVER_REDIRECT | |
| ui::PAGE_TRANSITION_CHAIN_END), |
| 0); |
| visit_info4.visit_id = 4; |
| |
| // Add a subframe visit. |
| VisitRow visit_info5(4, |
| visit_info1.visit_time + TimeDelta::FromSeconds(4), visit_info4.visit_id, |
| ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_AUTO_SUBFRAME | |
| ui::PAGE_TRANSITION_CHAIN_START | |
| ui::PAGE_TRANSITION_CHAIN_END), |
| 0); |
| visit_info5.visit_id = 5; |
| |
| // Add third visit for the same URL as visit 1 and 2, but exactly a day |
| // later than visit 2. |
| VisitRow visit_info6(visit_info1.url_id, |
| visit_info2.visit_time + TimeDelta::FromDays(1), 1, |
| ui::PageTransitionFromInt( |
| ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_CHAIN_START | |
| ui::PAGE_TRANSITION_CHAIN_END), |
| 0); |
| visit_info6.visit_id = 6; |
| |
| std::vector<VisitRow> test_visit_rows; |
| test_visit_rows.push_back(visit_info1); |
| test_visit_rows.push_back(visit_info2); |
| test_visit_rows.push_back(visit_info3); |
| test_visit_rows.push_back(visit_info4); |
| test_visit_rows.push_back(visit_info5); |
| test_visit_rows.push_back(visit_info6); |
| return test_visit_rows; |
| } |
| |
| } // namespace |
| |
| TEST_F(VisitDatabaseTest, GetVisitsForTimes) { |
| std::vector<VisitRow> test_visit_rows = GetTestVisitRows(); |
| |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED)); |
| } |
| |
| // Query the visits for all our times. We should get all visits. |
| { |
| std::vector<base::Time> times; |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| times.push_back(test_visit_rows[i].visit_time); |
| } |
| VisitVector results; |
| GetVisitsForTimes(times, &results); |
| EXPECT_EQ(test_visit_rows.size(), results.size()); |
| } |
| |
| // Query the visits for a single time. |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| std::vector<base::Time> times; |
| times.push_back(test_visit_rows[i].visit_time); |
| VisitVector results; |
| GetVisitsForTimes(times, &results); |
| ASSERT_EQ(static_cast<size_t>(1), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[i])); |
| } |
| } |
| |
| TEST_F(VisitDatabaseTest, GetAllVisitsInRange) { |
| std::vector<VisitRow> test_visit_rows = GetTestVisitRows(); |
| |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED)); |
| } |
| |
| // Query the visits for all time. We should get all visits. |
| VisitVector results; |
| GetAllVisitsInRange(Time(), Time(), 0, &results); |
| ASSERT_EQ(test_visit_rows.size(), results.size()); |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| EXPECT_TRUE(IsVisitInfoEqual(results[i], test_visit_rows[i])); |
| } |
| |
| // Query a time range and make sure beginning is inclusive and ending is |
| // exclusive. |
| GetAllVisitsInRange(test_visit_rows[1].visit_time, |
| test_visit_rows[3].visit_time, 0, |
| &results); |
| ASSERT_EQ(static_cast<size_t>(2), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[2])); |
| |
| // Query for a max count and make sure we get only that number. |
| GetAllVisitsInRange(Time(), Time(), 1, &results); |
| ASSERT_EQ(static_cast<size_t>(1), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0])); |
| } |
| |
| TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) { |
| std::vector<VisitRow> test_visit_rows = GetTestVisitRows(); |
| |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED)); |
| } |
| |
| // Query the visits for all time. We should not get the first or the second |
| // visit (duplicates of the sixth) or the redirect or subframe visits. |
| VisitVector results; |
| QueryOptions options; |
| GetVisibleVisitsInRange(options, &results); |
| ASSERT_EQ(static_cast<size_t>(2), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3])); |
| |
| // Now try with only per-day de-duping -- the second visit should appear, |
| // since it's a duplicate of visit6 but on a different day. |
| options.duplicate_policy = QueryOptions::REMOVE_DUPLICATES_PER_DAY; |
| GetVisibleVisitsInRange(options, &results); |
| ASSERT_EQ(static_cast<size_t>(3), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1])); |
| |
| // Now try without de-duping, expect to see all visible visits. |
| options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES; |
| GetVisibleVisitsInRange(options, &results); |
| ASSERT_EQ(static_cast<size_t>(4), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[3], test_visit_rows[0])); |
| |
| // Set the end time to exclude the second visit. The first visit should be |
| // returned. Even though the second is a more recent visit, it's not in the |
| // query range. |
| options.end_time = test_visit_rows[1].visit_time; |
| GetVisibleVisitsInRange(options, &results); |
| ASSERT_EQ(static_cast<size_t>(1), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0])); |
| |
| options = QueryOptions(); // Reset to options to default. |
| |
| // Query for a max count and make sure we get only that number. |
| options.max_count = 1; |
| GetVisibleVisitsInRange(options, &results); |
| ASSERT_EQ(static_cast<size_t>(1), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| |
| // Query a time range and make sure beginning is inclusive and ending is |
| // exclusive. |
| options.begin_time = test_visit_rows[1].visit_time; |
| options.end_time = test_visit_rows[3].visit_time; |
| options.max_count = 0; |
| GetVisibleVisitsInRange(options, &results); |
| ASSERT_EQ(static_cast<size_t>(1), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1])); |
| } |
| |
| TEST_F(VisitDatabaseTest, VisitSource) { |
| // Add visits. |
| VisitRow visit_info1(111, Time::Now(), 0, ui::PAGE_TRANSITION_LINK, 0); |
| ASSERT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED)); |
| |
| VisitRow visit_info2(112, Time::Now(), 1, ui::PAGE_TRANSITION_TYPED, 0); |
| ASSERT_TRUE(AddVisit(&visit_info2, SOURCE_SYNCED)); |
| |
| VisitRow visit_info3(113, Time::Now(), 0, ui::PAGE_TRANSITION_TYPED, 0); |
| ASSERT_TRUE(AddVisit(&visit_info3, SOURCE_EXTENSION)); |
| |
| // Query each visit. |
| std::vector<VisitRow> matches; |
| ASSERT_TRUE(GetVisitsForURL(111, &matches)); |
| ASSERT_EQ(1U, matches.size()); |
| VisitSourceMap sources; |
| GetVisitsSource(matches, &sources); |
| EXPECT_EQ(0U, sources.size()); |
| |
| ASSERT_TRUE(GetVisitsForURL(112, &matches)); |
| ASSERT_EQ(1U, matches.size()); |
| GetVisitsSource(matches, &sources); |
| ASSERT_EQ(1U, sources.size()); |
| EXPECT_EQ(SOURCE_SYNCED, sources[matches[0].visit_id]); |
| |
| ASSERT_TRUE(GetVisitsForURL(113, &matches)); |
| ASSERT_EQ(1U, matches.size()); |
| GetVisitsSource(matches, &sources); |
| ASSERT_EQ(1U, sources.size()); |
| EXPECT_EQ(SOURCE_EXTENSION, sources[matches[0].visit_id]); |
| } |
| |
| TEST_F(VisitDatabaseTest, GetVisibleVisitsForURL) { |
| std::vector<VisitRow> test_visit_rows = GetTestVisitRows(); |
| |
| for (size_t i = 0; i < test_visit_rows.size(); ++i) { |
| EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED)); |
| } |
| |
| // Query the visits for the first url id. We should not get the first or the |
| // second visit (duplicates of the sixth) or any other urls, redirects or |
| // subframe visits. |
| VisitVector results; |
| QueryOptions options; |
| int url_id = test_visit_rows[0].url_id; |
| GetVisibleVisitsForURL(url_id, options, &results); |
| ASSERT_EQ(static_cast<size_t>(1), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| |
| // Now try with only per-day de-duping -- the second visit should appear, |
| // since it's a duplicate of visit6 but on a different day. |
| options.duplicate_policy = QueryOptions::REMOVE_DUPLICATES_PER_DAY; |
| GetVisibleVisitsForURL(url_id, options, &results); |
| ASSERT_EQ(static_cast<size_t>(2), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1])); |
| |
| // Now try without de-duping, expect to see all visible visits to url id 1. |
| options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES; |
| GetVisibleVisitsForURL(url_id, options, &results); |
| ASSERT_EQ(static_cast<size_t>(3), results.size()); |
| EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1])); |
| EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[0])); |
| } |
| |
| } // namespace history |