1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/files/file_path.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/strings/string_util.h"
11 #include "base/time/time.h"
12 #include "chrome/browser/history/visit_database.h"
13 #include "components/history/core/browser/url_database.h"
14 #include "sql/connection.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "testing/platform_test.h"
19 using base::TimeDelta;
25 bool IsVisitInfoEqual(const VisitRow& a,
27 return a.visit_id == b.visit_id &&
28 a.url_id == b.url_id &&
29 a.visit_time == b.visit_time &&
30 a.referring_visit == b.referring_visit &&
31 a.transition == b.transition;
36 class VisitDatabaseTest : public PlatformTest,
38 public VisitDatabase {
45 void SetUp() override {
46 PlatformTest::SetUp();
47 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
48 base::FilePath db_file = temp_dir_.path().AppendASCII("VisitTest.db");
50 EXPECT_TRUE(db_.Open(db_file));
52 // Initialize the tables for this test.
53 CreateURLTable(false);
57 void TearDown() override {
59 PlatformTest::TearDown();
62 // Provided for URL/VisitDatabase.
63 sql::Connection& GetDB() override { return db_; }
65 base::ScopedTempDir temp_dir_;
69 TEST_F(VisitDatabaseTest, Add) {
71 VisitRow visit_info1(1, Time::Now(), 0, ui::PAGE_TRANSITION_LINK, 0);
72 EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
74 // Add second visit for the same page.
75 VisitRow visit_info2(visit_info1.url_id,
76 visit_info1.visit_time + TimeDelta::FromSeconds(1), 1,
77 ui::PAGE_TRANSITION_TYPED, 0);
78 EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
80 // Add third visit for a different page.
81 VisitRow visit_info3(2,
82 visit_info1.visit_time + TimeDelta::FromSeconds(2), 0,
83 ui::PAGE_TRANSITION_LINK, 0);
84 EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
86 // Query the first two.
87 std::vector<VisitRow> matches;
88 EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
89 EXPECT_EQ(static_cast<size_t>(2), matches.size());
91 // Make sure we got both (order in result set is visit time).
92 EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
93 IsVisitInfoEqual(matches[1], visit_info2));
96 TEST_F(VisitDatabaseTest, Delete) {
97 // Add three visits that form a chain of navigation, and then delete the
98 // middle one. We should be left with the outer two visits, and the chain
100 static const int kTime1 = 1000;
101 VisitRow visit_info1(1, Time::FromInternalValue(kTime1), 0,
102 ui::PAGE_TRANSITION_LINK, 0);
103 EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
105 static const int kTime2 = kTime1 + 1;
106 VisitRow visit_info2(1, Time::FromInternalValue(kTime2),
107 visit_info1.visit_id, ui::PAGE_TRANSITION_LINK, 0);
108 EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
110 static const int kTime3 = kTime2 + 1;
111 VisitRow visit_info3(1, Time::FromInternalValue(kTime3),
112 visit_info2.visit_id, ui::PAGE_TRANSITION_LINK, 0);
113 EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
115 // First make sure all the visits are there.
116 std::vector<VisitRow> matches;
117 EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
118 EXPECT_EQ(static_cast<size_t>(3), matches.size());
119 EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
120 IsVisitInfoEqual(matches[1], visit_info2) &&
121 IsVisitInfoEqual(matches[2], visit_info3));
123 // Delete the middle one.
124 DeleteVisit(visit_info2);
126 // The outer two should be left, and the last one should have the first as
128 visit_info3.referring_visit = visit_info1.visit_id;
130 EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
131 EXPECT_EQ(static_cast<size_t>(2), matches.size());
132 EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
133 IsVisitInfoEqual(matches[1], visit_info3));
136 TEST_F(VisitDatabaseTest, Update) {
137 // Make something in the database.
138 VisitRow original(1, Time::Now(), 23, ui::PageTransitionFromInt(0), 19);
139 AddVisit(&original, SOURCE_BROWSED);
142 VisitRow modification(original);
143 modification.url_id = 2;
144 modification.transition = ui::PAGE_TRANSITION_TYPED;
145 modification.visit_time = Time::Now() + TimeDelta::FromDays(1);
146 modification.referring_visit = 9292;
147 UpdateVisitRow(modification);
149 // Check that the mutated version was written.
151 GetRowForVisit(original.visit_id, &final);
152 EXPECT_TRUE(IsVisitInfoEqual(modification, final));
155 // TODO(brettw) write test for GetMostRecentVisitForURL!
159 std::vector<VisitRow> GetTestVisitRows() {
160 // Tests can be sensitive to the local timezone, so use a local time as the
161 // basis for all visit times.
162 base::Time base_time = Time::UnixEpoch().LocalMidnight();
165 VisitRow visit_info1(1, base_time + TimeDelta::FromMinutes(1), 0,
166 ui::PageTransitionFromInt(
167 ui::PAGE_TRANSITION_LINK |
168 ui::PAGE_TRANSITION_CHAIN_START |
169 ui::PAGE_TRANSITION_CHAIN_END),
171 visit_info1.visit_id = 1;
173 // Add second visit for the same page.
174 VisitRow visit_info2(visit_info1.url_id,
175 visit_info1.visit_time + TimeDelta::FromSeconds(1), 1,
176 ui::PageTransitionFromInt(
177 ui::PAGE_TRANSITION_TYPED |
178 ui::PAGE_TRANSITION_CHAIN_START |
179 ui::PAGE_TRANSITION_CHAIN_END),
181 visit_info2.visit_id = 2;
183 // Add third visit for a different page.
184 VisitRow visit_info3(2,
185 visit_info1.visit_time + TimeDelta::FromSeconds(2), 0,
186 ui::PageTransitionFromInt(
187 ui::PAGE_TRANSITION_LINK |
188 ui::PAGE_TRANSITION_CHAIN_START),
190 visit_info3.visit_id = 3;
192 // Add a redirect visit from the last page.
193 VisitRow visit_info4(3,
194 visit_info1.visit_time + TimeDelta::FromSeconds(3), visit_info3.visit_id,
195 ui::PageTransitionFromInt(
196 ui::PAGE_TRANSITION_SERVER_REDIRECT |
197 ui::PAGE_TRANSITION_CHAIN_END),
199 visit_info4.visit_id = 4;
201 // Add a subframe visit.
202 VisitRow visit_info5(4,
203 visit_info1.visit_time + TimeDelta::FromSeconds(4), visit_info4.visit_id,
204 ui::PageTransitionFromInt(
205 ui::PAGE_TRANSITION_AUTO_SUBFRAME |
206 ui::PAGE_TRANSITION_CHAIN_START |
207 ui::PAGE_TRANSITION_CHAIN_END),
209 visit_info5.visit_id = 5;
211 // Add third visit for the same URL as visit 1 and 2, but exactly a day
212 // later than visit 2.
213 VisitRow visit_info6(visit_info1.url_id,
214 visit_info2.visit_time + TimeDelta::FromDays(1), 1,
215 ui::PageTransitionFromInt(
216 ui::PAGE_TRANSITION_TYPED |
217 ui::PAGE_TRANSITION_CHAIN_START |
218 ui::PAGE_TRANSITION_CHAIN_END),
220 visit_info6.visit_id = 6;
222 std::vector<VisitRow> test_visit_rows;
223 test_visit_rows.push_back(visit_info1);
224 test_visit_rows.push_back(visit_info2);
225 test_visit_rows.push_back(visit_info3);
226 test_visit_rows.push_back(visit_info4);
227 test_visit_rows.push_back(visit_info5);
228 test_visit_rows.push_back(visit_info6);
229 return test_visit_rows;
234 TEST_F(VisitDatabaseTest, GetVisitsForTimes) {
235 std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
237 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
238 EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
241 // Query the visits for all our times. We should get all visits.
243 std::vector<base::Time> times;
244 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
245 times.push_back(test_visit_rows[i].visit_time);
248 GetVisitsForTimes(times, &results);
249 EXPECT_EQ(test_visit_rows.size(), results.size());
252 // Query the visits for a single time.
253 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
254 std::vector<base::Time> times;
255 times.push_back(test_visit_rows[i].visit_time);
257 GetVisitsForTimes(times, &results);
258 ASSERT_EQ(static_cast<size_t>(1), results.size());
259 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[i]));
263 TEST_F(VisitDatabaseTest, GetAllVisitsInRange) {
264 std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
266 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
267 EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
270 // Query the visits for all time. We should get all visits.
272 GetAllVisitsInRange(Time(), Time(), 0, &results);
273 ASSERT_EQ(test_visit_rows.size(), results.size());
274 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
275 EXPECT_TRUE(IsVisitInfoEqual(results[i], test_visit_rows[i]));
278 // Query a time range and make sure beginning is inclusive and ending is
280 GetAllVisitsInRange(test_visit_rows[1].visit_time,
281 test_visit_rows[3].visit_time, 0,
283 ASSERT_EQ(static_cast<size_t>(2), results.size());
284 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1]));
285 EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[2]));
287 // Query for a max count and make sure we get only that number.
288 GetAllVisitsInRange(Time(), Time(), 1, &results);
289 ASSERT_EQ(static_cast<size_t>(1), results.size());
290 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0]));
293 TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) {
294 std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
296 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
297 EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
300 // Query the visits for all time. We should not get the first or the second
301 // visit (duplicates of the sixth) or the redirect or subframe visits.
303 QueryOptions options;
304 GetVisibleVisitsInRange(options, &results);
305 ASSERT_EQ(static_cast<size_t>(2), results.size());
306 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
307 EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
309 // Now try with only per-day de-duping -- the second visit should appear,
310 // since it's a duplicate of visit6 but on a different day.
311 options.duplicate_policy = QueryOptions::REMOVE_DUPLICATES_PER_DAY;
312 GetVisibleVisitsInRange(options, &results);
313 ASSERT_EQ(static_cast<size_t>(3), results.size());
314 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
315 EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
316 EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1]));
318 // Now try without de-duping, expect to see all visible visits.
319 options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES;
320 GetVisibleVisitsInRange(options, &results);
321 ASSERT_EQ(static_cast<size_t>(4), results.size());
322 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
323 EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
324 EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1]));
325 EXPECT_TRUE(IsVisitInfoEqual(results[3], test_visit_rows[0]));
327 // Set the end time to exclude the second visit. The first visit should be
328 // returned. Even though the second is a more recent visit, it's not in the
330 options.end_time = test_visit_rows[1].visit_time;
331 GetVisibleVisitsInRange(options, &results);
332 ASSERT_EQ(static_cast<size_t>(1), results.size());
333 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0]));
335 options = QueryOptions(); // Reset to options to default.
337 // Query for a max count and make sure we get only that number.
338 options.max_count = 1;
339 GetVisibleVisitsInRange(options, &results);
340 ASSERT_EQ(static_cast<size_t>(1), results.size());
341 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
343 // Query a time range and make sure beginning is inclusive and ending is
345 options.begin_time = test_visit_rows[1].visit_time;
346 options.end_time = test_visit_rows[3].visit_time;
347 options.max_count = 0;
348 GetVisibleVisitsInRange(options, &results);
349 ASSERT_EQ(static_cast<size_t>(1), results.size());
350 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1]));
353 TEST_F(VisitDatabaseTest, VisitSource) {
355 VisitRow visit_info1(111, Time::Now(), 0, ui::PAGE_TRANSITION_LINK, 0);
356 ASSERT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
358 VisitRow visit_info2(112, Time::Now(), 1, ui::PAGE_TRANSITION_TYPED, 0);
359 ASSERT_TRUE(AddVisit(&visit_info2, SOURCE_SYNCED));
361 VisitRow visit_info3(113, Time::Now(), 0, ui::PAGE_TRANSITION_TYPED, 0);
362 ASSERT_TRUE(AddVisit(&visit_info3, SOURCE_EXTENSION));
365 std::vector<VisitRow> matches;
366 ASSERT_TRUE(GetVisitsForURL(111, &matches));
367 ASSERT_EQ(1U, matches.size());
368 VisitSourceMap sources;
369 GetVisitsSource(matches, &sources);
370 EXPECT_EQ(0U, sources.size());
372 ASSERT_TRUE(GetVisitsForURL(112, &matches));
373 ASSERT_EQ(1U, matches.size());
374 GetVisitsSource(matches, &sources);
375 ASSERT_EQ(1U, sources.size());
376 EXPECT_EQ(SOURCE_SYNCED, sources[matches[0].visit_id]);
378 ASSERT_TRUE(GetVisitsForURL(113, &matches));
379 ASSERT_EQ(1U, matches.size());
380 GetVisitsSource(matches, &sources);
381 ASSERT_EQ(1U, sources.size());
382 EXPECT_EQ(SOURCE_EXTENSION, sources[matches[0].visit_id]);
385 TEST_F(VisitDatabaseTest, GetVisibleVisitsForURL) {
386 std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
388 for (size_t i = 0; i < test_visit_rows.size(); ++i) {
389 EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
392 // Query the visits for the first url id. We should not get the first or the
393 // second visit (duplicates of the sixth) or any other urls, redirects or
396 QueryOptions options;
397 int url_id = test_visit_rows[0].url_id;
398 GetVisibleVisitsForURL(url_id, options, &results);
399 ASSERT_EQ(static_cast<size_t>(1), results.size());
400 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
402 // Now try with only per-day de-duping -- the second visit should appear,
403 // since it's a duplicate of visit6 but on a different day.
404 options.duplicate_policy = QueryOptions::REMOVE_DUPLICATES_PER_DAY;
405 GetVisibleVisitsForURL(url_id, options, &results);
406 ASSERT_EQ(static_cast<size_t>(2), results.size());
407 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
408 EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1]));
410 // Now try without de-duping, expect to see all visible visits to url id 1.
411 options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES;
412 GetVisibleVisitsForURL(url_id, options, &results);
413 ASSERT_EQ(static_cast<size_t>(3), results.size());
414 EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
415 EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[1]));
416 EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[0]));
419 } // namespace history