1// Copyright (c) 2011 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 "app/sql/connection.h"
6#include "base/file_path.h"
7#include "base/file_util.h"
8#include "base/path_service.h"
9#include "base/string_util.h"
10#include "base/memory/scoped_temp_dir.h"
11#include "chrome/browser/history/url_database.h"
12#include "chrome/browser/history/visit_database.h"
13#include "testing/gtest/include/gtest/gtest.h"
14#include "testing/platform_test.h"
15
16using base::Time;
17using base::TimeDelta;
18
19namespace history {
20
21namespace {
22
23bool IsVisitInfoEqual(const VisitRow& a,
24                      const VisitRow& b) {
25  return a.visit_id == b.visit_id &&
26         a.url_id == b.url_id &&
27         a.visit_time == b.visit_time &&
28         a.referring_visit == b.referring_visit &&
29         a.transition == b.transition &&
30         a.is_indexed == b.is_indexed;
31}
32
33}  // namespace
34
35class VisitDatabaseTest : public PlatformTest,
36                          public URLDatabase,
37                          public VisitDatabase {
38 public:
39  VisitDatabaseTest() {
40  }
41
42 private:
43  // Test setup.
44  void SetUp() {
45    PlatformTest::SetUp();
46    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
47    FilePath db_file = temp_dir_.path().AppendASCII("VisitTest.db");
48
49    EXPECT_TRUE(db_.Open(db_file));
50
51    // Initialize the tables for this test.
52    CreateURLTable(false);
53    CreateMainURLIndex();
54    InitVisitTable();
55  }
56  void TearDown() {
57    db_.Close();
58    PlatformTest::TearDown();
59  }
60
61  // Provided for URL/VisitDatabase.
62  virtual sql::Connection& GetDB() {
63    return db_;
64  }
65
66  ScopedTempDir temp_dir_;
67  sql::Connection db_;
68};
69
70TEST_F(VisitDatabaseTest, Add) {
71  // Add one visit.
72  VisitRow visit_info1(1, Time::Now(), 0, PageTransition::LINK, 0);
73  EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
74
75  // Add second visit for the same page.
76  VisitRow visit_info2(visit_info1.url_id,
77      visit_info1.visit_time + TimeDelta::FromSeconds(1), 1,
78      PageTransition::TYPED, 0);
79  EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
80
81  // Add third visit for a different page.
82  VisitRow visit_info3(2,
83      visit_info1.visit_time + TimeDelta::FromSeconds(2), 0,
84      PageTransition::LINK, 0);
85  EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
86
87  // Query the first two.
88  std::vector<VisitRow> matches;
89  EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
90  EXPECT_EQ(static_cast<size_t>(2), matches.size());
91
92  // Make sure we got both (order in result set is visit time).
93  EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
94              IsVisitInfoEqual(matches[1], visit_info2));
95}
96
97TEST_F(VisitDatabaseTest, Delete) {
98  // Add three visits that form a chain of navigation, and then delete the
99  // middle one. We should be left with the outer two visits, and the chain
100  // should link them.
101  static const int kTime1 = 1000;
102  VisitRow visit_info1(1, Time::FromInternalValue(kTime1), 0,
103                       PageTransition::LINK, 0);
104  EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
105
106  static const int kTime2 = kTime1 + 1;
107  VisitRow visit_info2(1, Time::FromInternalValue(kTime2),
108                       visit_info1.visit_id, PageTransition::LINK, 0);
109  EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
110
111  static const int kTime3 = kTime2 + 1;
112  VisitRow visit_info3(1, Time::FromInternalValue(kTime3),
113                       visit_info2.visit_id, PageTransition::LINK, 0);
114  EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
115
116  // First make sure all the visits are there.
117  std::vector<VisitRow> matches;
118  EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
119  EXPECT_EQ(static_cast<size_t>(3), matches.size());
120  EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
121              IsVisitInfoEqual(matches[1], visit_info2) &&
122              IsVisitInfoEqual(matches[2], visit_info3));
123
124  // Delete the middle one.
125  DeleteVisit(visit_info2);
126
127  // The outer two should be left, and the last one should have the first as
128  // the referrer.
129  visit_info3.referring_visit = visit_info1.visit_id;
130  matches.clear();
131  EXPECT_TRUE(GetVisitsForURL(visit_info1.url_id, &matches));
132  EXPECT_EQ(static_cast<size_t>(2), matches.size());
133  EXPECT_TRUE(IsVisitInfoEqual(matches[0], visit_info1) &&
134              IsVisitInfoEqual(matches[1], visit_info3));
135}
136
137TEST_F(VisitDatabaseTest, Update) {
138  // Make something in the database.
139  VisitRow original(1, Time::Now(), 23, 22, 19);
140  AddVisit(&original, SOURCE_BROWSED);
141
142  // Mutate that row.
143  VisitRow modification(original);
144  modification.url_id = 2;
145  modification.transition = PageTransition::TYPED;
146  modification.visit_time = Time::Now() + TimeDelta::FromDays(1);
147  modification.referring_visit = 9292;
148  modification.is_indexed = true;
149  UpdateVisitRow(modification);
150
151  // Check that the mutated version was written.
152  VisitRow final;
153  GetRowForVisit(original.visit_id, &final);
154  EXPECT_TRUE(IsVisitInfoEqual(modification, final));
155}
156
157// TODO(brettw) write test for GetMostRecentVisitForURL!
158
159TEST_F(VisitDatabaseTest, GetVisibleVisitsInRange) {
160  // Add one visit.
161  VisitRow visit_info1(1, Time::Now(), 0,
162      static_cast<PageTransition::Type>(PageTransition::LINK |
163                                        PageTransition::CHAIN_START |
164                                        PageTransition::CHAIN_END),
165      0);
166  visit_info1.visit_id = 1;
167  EXPECT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
168
169  // Add second visit for the same page.
170  VisitRow visit_info2(visit_info1.url_id,
171      visit_info1.visit_time + TimeDelta::FromSeconds(1), 1,
172      static_cast<PageTransition::Type>(PageTransition::TYPED |
173                                        PageTransition::CHAIN_START |
174                                        PageTransition::CHAIN_END),
175      0);
176  visit_info2.visit_id = 2;
177  EXPECT_TRUE(AddVisit(&visit_info2, SOURCE_BROWSED));
178
179  // Add third visit for a different page.
180  VisitRow visit_info3(2,
181      visit_info1.visit_time + TimeDelta::FromSeconds(2), 0,
182      static_cast<PageTransition::Type>(PageTransition::LINK |
183                                        PageTransition::CHAIN_START),
184      0);
185  visit_info3.visit_id = 3;
186  EXPECT_TRUE(AddVisit(&visit_info3, SOURCE_BROWSED));
187
188  // Add a redirect visit from the last page.
189  VisitRow visit_info4(3,
190      visit_info1.visit_time + TimeDelta::FromSeconds(3), visit_info3.visit_id,
191      static_cast<PageTransition::Type>(PageTransition::SERVER_REDIRECT |
192                                        PageTransition::CHAIN_END),
193      0);
194  visit_info4.visit_id = 4;
195  EXPECT_TRUE(AddVisit(&visit_info4, SOURCE_BROWSED));
196
197  // Add a subframe visit.
198  VisitRow visit_info5(4,
199      visit_info1.visit_time + TimeDelta::FromSeconds(4), visit_info4.visit_id,
200      static_cast<PageTransition::Type>(PageTransition::AUTO_SUBFRAME |
201                                        PageTransition::CHAIN_START |
202                                        PageTransition::CHAIN_END),
203      0);
204  visit_info5.visit_id = 5;
205  EXPECT_TRUE(AddVisit(&visit_info5, SOURCE_BROWSED));
206
207  // Query the visits for all time, we should not get the first (duplicate of
208  // the second) or the redirect or subframe visits.
209  VisitVector results;
210  GetVisibleVisitsInRange(Time(), Time(), 0, &results);
211  ASSERT_EQ(static_cast<size_t>(2), results.size());
212  EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info4) &&
213              IsVisitInfoEqual(results[1], visit_info2));
214
215  // Query a time range and make sure beginning is inclusive and ending is
216  // exclusive.
217  GetVisibleVisitsInRange(visit_info2.visit_time, visit_info4.visit_time, 0,
218                          &results);
219  ASSERT_EQ(static_cast<size_t>(1), results.size());
220  EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info2));
221
222  // Query for a max count and make sure we get only that number.
223  GetVisibleVisitsInRange(Time(), Time(), 1, &results);
224  ASSERT_EQ(static_cast<size_t>(1), results.size());
225  EXPECT_TRUE(IsVisitInfoEqual(results[0], visit_info4));
226}
227
228TEST_F(VisitDatabaseTest, VisitSource) {
229  // Add visits.
230  VisitRow visit_info1(111, Time::Now(), 0, PageTransition::LINK, 0);
231  ASSERT_TRUE(AddVisit(&visit_info1, SOURCE_BROWSED));
232
233  VisitRow visit_info2(112, Time::Now(), 1, PageTransition::TYPED, 0);
234  ASSERT_TRUE(AddVisit(&visit_info2, SOURCE_SYNCED));
235
236  VisitRow visit_info3(113, Time::Now(), 0, PageTransition::TYPED, 0);
237  ASSERT_TRUE(AddVisit(&visit_info3, SOURCE_EXTENSION));
238
239  // Query each visit.
240  std::vector<VisitRow> matches;
241  ASSERT_TRUE(GetVisitsForURL(111, &matches));
242  ASSERT_EQ(1U, matches.size());
243  VisitSourceMap sources;
244  GetVisitsSource(matches, &sources);
245  EXPECT_EQ(0U, sources.size());
246
247  ASSERT_TRUE(GetVisitsForURL(112, &matches));
248  ASSERT_EQ(1U, matches.size());
249  GetVisitsSource(matches, &sources);
250  ASSERT_EQ(1U, sources.size());
251  EXPECT_EQ(SOURCE_SYNCED, sources[matches[0].visit_id]);
252
253  ASSERT_TRUE(GetVisitsForURL(113, &matches));
254  ASSERT_EQ(1U, matches.size());
255  GetVisitsSource(matches, &sources);
256  ASSERT_EQ(1U, sources.size());
257  EXPECT_EQ(SOURCE_EXTENSION, sources[matches[0].visit_id]);
258}
259
260}  // namespace history
261