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 19 using base::Time; 20 using base::TimeDelta; 21 22 namespace history { 23 24 namespace { 25 26 bool 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 37 class 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 72 TEST_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 99 TEST_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 139 TEST_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 160 namespace { 161 162 std::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 237 TEST_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 266 TEST_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 296 TEST_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 356 TEST_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