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.
4
5#include <set>
6#include <vector>
7
8#include "base/files/file_path.h"
9#include "base/files/scoped_temp_dir.h"
10#include "base/path_service.h"
11#include "base/strings/string_util.h"
12#include "base/time/time.h"
13#include "chrome/browser/history/url_database.h"
14#include "chrome/browser/history/visit_database.h"
15#include "sql/connection.h"
16#include "testing/gtest/include/gtest/gtest.h"
17#include "testing/platform_test.h"
18
19using base::Time;
20using base::TimeDelta;
21
22namespace history {
23
24namespace {
25
26bool IsVisitInfoEqual(const VisitRow& a,
27                      const VisitRow& b) {
28  return a.visit_id == b.visit_id &&
29         a.url_id == b.url_id &&
30         a.visit_time == b.visit_time &&
31         a.referring_visit == b.referring_visit &&
32         a.transition == b.transition;
33}
34
35}  // namespace
36
37class VisitDatabaseTest : public PlatformTest,
38                          public URLDatabase,
39                          public VisitDatabase {
40 public:
41  VisitDatabaseTest() {
42  }
43
44 private:
45  // Test setup.
46  virtual void SetUp() {
47    PlatformTest::SetUp();
48    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
49    base::FilePath db_file = temp_dir_.path().AppendASCII("VisitTest.db");
50
51    EXPECT_TRUE(db_.Open(db_file));
52
53    // Initialize the tables for this test.
54    CreateURLTable(false);
55    CreateMainURLIndex();
56    InitVisitTable();
57  }
58  virtual void TearDown() {
59    db_.Close();
60    PlatformTest::TearDown();
61  }
62
63  // Provided for URL/VisitDatabase.
64  virtual sql::Connection& GetDB() OVERRIDE {
65    return db_;
66  }
67
68  base::ScopedTempDir temp_dir_;
69  sql::Connection db_;
70};
71
72TEST_F(VisitDatabaseTest, Add) {
73  // Add one visit.
74  VisitRow visit_info1(1, Time::Now(), 0, content::PAGE_TRANSITION_LINK, 0);
75  EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
76
77  // Add second visit for the same page.
78  VisitRow visit_info2(visit_info1.url_id,
79      visit_info1.visit_time + TimeDelta::FromSeconds(1), 1,
80      content::PAGE_TRANSITION_TYPED, 0);
81  EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
82
83  // Add third visit for a different page.
84  VisitRow visit_info3(2,
85      visit_info1.visit_time + TimeDelta::FromSeconds(2), 0,
86      content::PAGE_TRANSITION_LINK, 0);
87  EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
88
89  // Query the first two.
90  std::vector<VisitRow> matches;
91  EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
92  EXPECT_EQ(static_cast<size_t>(2), matches.size());
93
94  // Make sure we got both (order in result set is visit time).
95  EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
96              IsVisitInfoEqual(matches[1], visit_info2));
97}
98
99TEST_F(VisitDatabaseTest, Delete) {
100  // Add three visits that form a chain of navigation, and then delete the
101  // middle one. We should be left with the outer two visits, and the chain
102  // should link them.
103  static const int kTime1 = 1000;
104  VisitRow visit_info1(1, Time::FromInternalValue(kTime1), 0,
105                       content::PAGE_TRANSITION_LINK, 0);
106  EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
107
108  static const int kTime2 = kTime1 + 1;
109  VisitRow visit_info2(1, Time::FromInternalValue(kTime2),
110                       visit_info1.visit_id, content::PAGE_TRANSITION_LINK, 0);
111  EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
112
113  static const int kTime3 = kTime2 + 1;
114  VisitRow visit_info3(1, Time::FromInternalValue(kTime3),
115                       visit_info2.visit_id, content::PAGE_TRANSITION_LINK, 0);
116  EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
117
118  // First make sure all the visits are there.
119  std::vector<VisitRow> matches;
120  EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
121  EXPECT_EQ(static_cast<size_t>(3), matches.size());
122  EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
123              IsVisitInfoEqual(matches[1], visit_info2) &&
124              IsVisitInfoEqual(matches[2], visit_info3));
125
126  // Delete the middle one.
127  DeleteVisit(visit_info2);
128
129  // The outer two should be left, and the last one should have the first as
130  // the referrer.
131  visit_info3.referring_visit = visit_info1.visit_id;
132  matches.clear();
133  EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
134  EXPECT_EQ(static_cast<size_t>(2), matches.size());
135  EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
136              IsVisitInfoEqual(matches[1], visit_info3));
137}
138
139TEST_F(VisitDatabaseTest, Update) {
140  // Make something in the database.
141  VisitRow original(1, Time::Now(), 23, content::PageTransitionFromInt(0), 19);
142  AddVisit(&original, SOURCE_BROWSED);
143
144  // Mutate that row.
145  VisitRow modification(original);
146  modification.url_id = 2;
147  modification.transition = content::PAGE_TRANSITION_TYPED;
148  modification.visit_time = Time::Now() + TimeDelta::FromDays(1);
149  modification.referring_visit = 9292;
150  UpdateVisitRow(modification);
151
152  // Check that the mutated version was written.
153  VisitRow final;
154  GetRowForVisit(original.visit_id, &final);
155  EXPECT_TRUE(IsVisitInfoEqual(modification, final));
156}
157
158// TODO(brettw) write test for GetMostRecentVisitForURL!
159
160namespace {
161
162std::vector<VisitRow> GetTestVisitRows() {
163  // Tests can be sensitive to the local timezone, so use a local time as the
164  // basis for all visit times.
165  base::Time base_time = Time::UnixEpoch().LocalMidnight();
166
167  // Add one visit.
168  VisitRow visit_info1(1, base_time + TimeDelta::FromMinutes(1), 0,
169      static_cast<content::PageTransition>(
170          content::PAGE_TRANSITION_LINK |
171          content::PAGE_TRANSITION_CHAIN_START |
172          content::PAGE_TRANSITION_CHAIN_END),
173      0);
174  visit_info1.visit_id = 1;
175
176  // Add second visit for the same page.
177  VisitRow visit_info2(visit_info1.url_id,
178      visit_info1.visit_time + TimeDelta::FromSeconds(1), 1,
179      static_cast<content::PageTransition>(
180          content::PAGE_TRANSITION_TYPED |
181          content::PAGE_TRANSITION_CHAIN_START |
182          content::PAGE_TRANSITION_CHAIN_END),
183      0);
184  visit_info2.visit_id = 2;
185
186  // Add third visit for a different page.
187  VisitRow visit_info3(2,
188      visit_info1.visit_time + TimeDelta::FromSeconds(2), 0,
189      static_cast<content::PageTransition>(
190          content::PAGE_TRANSITION_LINK |
191          content::PAGE_TRANSITION_CHAIN_START),
192      0);
193  visit_info3.visit_id = 3;
194
195  // Add a redirect visit from the last page.
196  VisitRow visit_info4(3,
197      visit_info1.visit_time + TimeDelta::FromSeconds(3), visit_info3.visit_id,
198      static_cast<content::PageTransition>(
199          content::PAGE_TRANSITION_SERVER_REDIRECT |
200          content::PAGE_TRANSITION_CHAIN_END),
201      0);
202  visit_info4.visit_id = 4;
203
204  // Add a subframe visit.
205  VisitRow visit_info5(4,
206      visit_info1.visit_time + TimeDelta::FromSeconds(4), visit_info4.visit_id,
207      static_cast<content::PageTransition>(
208          content::PAGE_TRANSITION_AUTO_SUBFRAME |
209          content::PAGE_TRANSITION_CHAIN_START |
210          content::PAGE_TRANSITION_CHAIN_END),
211      0);
212  visit_info5.visit_id = 5;
213
214  // Add third visit for the same URL as visit 1 and 2, but exactly a day
215  // later than visit 2.
216  VisitRow visit_info6(visit_info1.url_id,
217      visit_info2.visit_time + TimeDelta::FromDays(1), 1,
218      static_cast<content::PageTransition>(
219          content::PAGE_TRANSITION_TYPED |
220          content::PAGE_TRANSITION_CHAIN_START |
221          content::PAGE_TRANSITION_CHAIN_END),
222      0);
223  visit_info6.visit_id = 6;
224
225  std::vector<VisitRow> test_visit_rows;
226  test_visit_rows.push_back(visit_info1);
227  test_visit_rows.push_back(visit_info2);
228  test_visit_rows.push_back(visit_info3);
229  test_visit_rows.push_back(visit_info4);
230  test_visit_rows.push_back(visit_info5);
231  test_visit_rows.push_back(visit_info6);
232  return test_visit_rows;
233}
234
235}  // namespace
236
237TEST_F(VisitDatabaseTest, GetVisitsForTimes) {
238  std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
239
240  for (size_t i = 0; i < test_visit_rows.size(); ++i) {
241    EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
242  }
243
244  // Query the visits for all our times.  We should get all visits.
245  {
246    std::vector<base::Time> times;
247    for (size_t i = 0; i < test_visit_rows.size(); ++i) {
248      times.push_back(test_visit_rows[i].visit_time);
249    }
250    VisitVector results;
251    GetVisitsForTimes(times, &results);
252    EXPECT_EQ(test_visit_rows.size(), results.size());
253  }
254
255  // Query the visits for a single time.
256  for (size_t i = 0; i < test_visit_rows.size(); ++i) {
257    std::vector<base::Time> times;
258    times.push_back(test_visit_rows[i].visit_time);
259    VisitVector results;
260    GetVisitsForTimes(times, &results);
261    ASSERT_EQ(static_cast<size_t>(1), results.size());
262    EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[i]));
263  }
264}
265
266TEST_F(VisitDatabaseTest, GetAllVisitsInRange) {
267  std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
268
269  for (size_t i = 0; i < test_visit_rows.size(); ++i) {
270    EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
271  }
272
273  // Query the visits for all time.  We should get all visits.
274  VisitVector results;
275  GetAllVisitsInRange(Time(), Time(), 0, &results);
276  ASSERT_EQ(test_visit_rows.size(), results.size());
277  for (size_t i = 0; i < test_visit_rows.size(); ++i) {
278    EXPECT_TRUE(IsVisitInfoEqual(results[i], test_visit_rows[i]));
279  }
280
281  // Query a time range and make sure beginning is inclusive and ending is
282  // exclusive.
283  GetAllVisitsInRange(test_visit_rows[1].visit_time,
284                      test_visit_rows[3].visit_time, 0,
285                      &results);
286  ASSERT_EQ(static_cast<size_t>(2), results.size());
287  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1]));
288  EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[2]));
289
290  // Query for a max count and make sure we get only that number.
291  GetAllVisitsInRange(Time(), Time(), 1, &results);
292  ASSERT_EQ(static_cast<size_t>(1), results.size());
293  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0]));
294}
295
296TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) {
297  std::vector<VisitRow> test_visit_rows = GetTestVisitRows();
298
299  for (size_t i = 0; i < test_visit_rows.size(); ++i) {
300    EXPECT_TRUE(AddVisit(&test_visit_rows[i], SOURCE_BROWSED));
301  }
302
303  // Query the visits for all time.  We should not get the first or the second
304  // visit (duplicates of the sixth) or the redirect or subframe visits.
305  VisitVector results;
306  QueryOptions options;
307  GetVisibleVisitsInRange(options, &results);
308  ASSERT_EQ(static_cast<size_t>(2), results.size());
309  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
310  EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
311
312  // Now try with only per-day de-duping -- the second visit should appear,
313  // since it's a duplicate of visit6 but on a different day.
314  options.duplicate_policy = QueryOptions::REMOVE_DUPLICATES_PER_DAY;
315  GetVisibleVisitsInRange(options, &results);
316  ASSERT_EQ(static_cast<size_t>(3), results.size());
317  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
318  EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
319  EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1]));
320
321  // Now try without de-duping, expect to see all visible visits.
322  options.duplicate_policy = QueryOptions::KEEP_ALL_DUPLICATES;
323  GetVisibleVisitsInRange(options, &results);
324  ASSERT_EQ(static_cast<size_t>(4), results.size());
325  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
326  EXPECT_TRUE(IsVisitInfoEqual(results[1], test_visit_rows[3]));
327  EXPECT_TRUE(IsVisitInfoEqual(results[2], test_visit_rows[1]));
328  EXPECT_TRUE(IsVisitInfoEqual(results[3], test_visit_rows[0]));
329
330  // Set the end time to exclude the second visit. The first visit should be
331  // returned. Even though the second is a more recent visit, it's not in the
332  // query range.
333  options.end_time = test_visit_rows[1].visit_time;
334  GetVisibleVisitsInRange(options, &results);
335  ASSERT_EQ(static_cast<size_t>(1), results.size());
336  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[0]));
337
338  options = QueryOptions();  // Reset to options to default.
339
340  // Query for a max count and make sure we get only that number.
341  options.max_count = 1;
342  GetVisibleVisitsInRange(options, &results);
343  ASSERT_EQ(static_cast<size_t>(1), results.size());
344  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[5]));
345
346  // Query a time range and make sure beginning is inclusive and ending is
347  // exclusive.
348  options.begin_time = test_visit_rows[1].visit_time;
349  options.end_time = test_visit_rows[3].visit_time;
350  options.max_count = 0;
351  GetVisibleVisitsInRange(options, &results);
352  ASSERT_EQ(static_cast<size_t>(1), results.size());
353  EXPECT_TRUE(IsVisitInfoEqual(results[0], test_visit_rows[1]));
354}
355
356TEST_F(VisitDatabaseTest, VisitSource) {
357  // Add visits.
358  VisitRow visit_info1(111, Time::Now(), 0, content::PAGE_TRANSITION_LINK, 0);
359  ASSERT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
360
361  VisitRow visit_info2(112, Time::Now(), 1, content::PAGE_TRANSITION_TYPED, 0);
362  ASSERT_TRUE(AddVisit(&visit_info2, SOURCE_SYNCED));
363
364  VisitRow visit_info3(113, Time::Now(), 0, content::PAGE_TRANSITION_TYPED, 0);
365  ASSERT_TRUE(AddVisit(&visit_info3, SOURCE_EXTENSION));
366
367  // Query each visit.
368  std::vector<VisitRow> matches;
369  ASSERT_TRUE(GetVisitsForURL(111, &matches));
370  ASSERT_EQ(1U, matches.size());
371  VisitSourceMap sources;
372  GetVisitsSource(matches, &sources);
373  EXPECT_EQ(0U, sources.size());
374
375  ASSERT_TRUE(GetVisitsForURL(112, &matches));
376  ASSERT_EQ(1U, matches.size());
377  GetVisitsSource(matches, &sources);
378  ASSERT_EQ(1U, sources.size());
379  EXPECT_EQ(SOURCE_SYNCED, sources[matches[0].visit_id]);
380
381  ASSERT_TRUE(GetVisitsForURL(113, &matches));
382  ASSERT_EQ(1U, matches.size());
383  GetVisitsSource(matches, &sources);
384  ASSERT_EQ(1U, sources.size());
385  EXPECT_EQ(SOURCE_EXTENSION, sources[matches[0].visit_id]);
386}
387
388}  // namespace history
389