expire_history_backend_unittest.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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 <algorithm>
6#include <string>
7#include <utility>
8
9#include "base/basictypes.h"
10#include "base/compiler_specific.h"
11#include "base/file_util.h"
12#include "base/files/file_path.h"
13#include "base/files/scoped_temp_dir.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/path_service.h"
16#include "base/stl_util.h"
17#include "base/strings/string16.h"
18#include "base/strings/utf_string_conversions.h"
19#include "chrome/browser/bookmarks/bookmark_model.h"
20#include "chrome/browser/bookmarks/bookmark_utils.h"
21#include "chrome/browser/chrome_notification_types.h"
22#include "chrome/browser/history/archived_database.h"
23#include "chrome/browser/history/expire_history_backend.h"
24#include "chrome/browser/history/history_database.h"
25#include "chrome/browser/history/history_notifications.h"
26#include "chrome/browser/history/thumbnail_database.h"
27#include "chrome/browser/history/top_sites.h"
28#include "chrome/common/thumbnail_score.h"
29#include "chrome/test/base/testing_profile.h"
30#include "chrome/tools/profiles/thumbnail-inl.h"
31#include "content/public/test/test_browser_thread.h"
32#include "testing/gtest/include/gtest/gtest.h"
33#include "third_party/skia/include/core/SkBitmap.h"
34#include "ui/gfx/codec/jpeg_codec.h"
35
36using base::Time;
37using base::TimeDelta;
38using base::TimeTicks;
39using content::BrowserThread;
40
41// Filename constants.
42static const base::FilePath::CharType kHistoryFile[] =
43    FILE_PATH_LITERAL("History");
44static const base::FilePath::CharType kArchivedHistoryFile[] =
45    FILE_PATH_LITERAL("Archived History");
46static const base::FilePath::CharType kThumbnailFile[] =
47    FILE_PATH_LITERAL("Thumbnails");
48
49// The test must be in the history namespace for the gtest forward declarations
50// to work. It also eliminates a bunch of ugly "history::".
51namespace history {
52
53// ExpireHistoryTest -----------------------------------------------------------
54
55class ExpireHistoryTest : public testing::Test,
56                          public BroadcastNotificationDelegate {
57 public:
58  ExpireHistoryTest()
59      : bookmark_model_(NULL),
60        ui_thread_(BrowserThread::UI, &message_loop_),
61        db_thread_(BrowserThread::DB, &message_loop_),
62        expirer_(this, &bookmark_model_),
63        now_(Time::Now()) {
64  }
65
66 protected:
67  // Called by individual tests when they want data populated.
68  void AddExampleData(URLID url_ids[3], Time visit_times[4]);
69  // Add visits with source information.
70  void AddExampleSourceData(const GURL& url, URLID* id);
71
72  // Returns true if the given favicon/thumanil has an entry in the DB.
73  bool HasFavicon(chrome::FaviconID favicon_id);
74  bool HasThumbnail(URLID url_id);
75
76  chrome::FaviconID GetFavicon(const GURL& page_url,
77                               chrome::IconType icon_type);
78
79  // EXPECTs that each URL-specific history thing (basically, everything but
80  // favicons) is gone.
81  void EnsureURLInfoGone(const URLRow& row);
82
83  // Clears the list of notifications received.
84  void ClearLastNotifications() {
85    STLDeleteValues(&notifications_);
86  }
87
88  void StarURL(const GURL& url) {
89    bookmark_model_.AddURL(
90        bookmark_model_.bookmark_bar_node(), 0, string16(), url);
91  }
92
93  static bool IsStringInFile(const base::FilePath& filename, const char* str);
94
95  // Returns the path the db files are created in.
96  const base::FilePath& path() const { return tmp_dir_.path(); }
97
98  // This must be destroyed last.
99  base::ScopedTempDir tmp_dir_;
100
101  BookmarkModel bookmark_model_;
102
103  base::MessageLoopForUI message_loop_;
104  content::TestBrowserThread ui_thread_;
105  content::TestBrowserThread db_thread_;
106
107  ExpireHistoryBackend expirer_;
108
109  scoped_ptr<HistoryDatabase> main_db_;
110  scoped_ptr<ArchivedDatabase> archived_db_;
111  scoped_ptr<ThumbnailDatabase> thumb_db_;
112  TestingProfile profile_;
113  scoped_refptr<TopSites> top_sites_;
114
115  // Time at the beginning of the test, so everybody agrees what "now" is.
116  const Time now_;
117
118  // Notifications intended to be broadcast, we can check these values to make
119  // sure that the deletor is doing the correct broadcasts. We own the details
120  // pointers.
121  typedef std::vector< std::pair<int, HistoryDetails*> >
122      NotificationList;
123  NotificationList notifications_;
124
125 private:
126  virtual void SetUp() {
127    ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
128
129    base::FilePath history_name = path().Append(kHistoryFile);
130    main_db_.reset(new HistoryDatabase);
131    if (main_db_->Init(history_name) != sql::INIT_OK)
132      main_db_.reset();
133
134    base::FilePath archived_name = path().Append(kArchivedHistoryFile);
135    archived_db_.reset(new ArchivedDatabase);
136    if (!archived_db_->Init(archived_name))
137      archived_db_.reset();
138
139    base::FilePath thumb_name = path().Append(kThumbnailFile);
140    thumb_db_.reset(new ThumbnailDatabase);
141    if (thumb_db_->Init(thumb_name, main_db_.get()) != sql::INIT_OK)
142      thumb_db_.reset();
143
144    expirer_.SetDatabases(main_db_.get(), archived_db_.get(), thumb_db_.get());
145    profile_.CreateTopSites();
146    profile_.BlockUntilTopSitesLoaded();
147    top_sites_ = profile_.GetTopSites();
148  }
149
150  virtual void TearDown() {
151    top_sites_ = NULL;
152
153    ClearLastNotifications();
154
155    expirer_.SetDatabases(NULL, NULL, NULL);
156
157    main_db_.reset();
158    archived_db_.reset();
159    thumb_db_.reset();
160  }
161
162  // BroadcastNotificationDelegate implementation.
163  virtual void BroadcastNotifications(
164      int type,
165      HistoryDetails* details_deleted) OVERRIDE {
166    // This gets called when there are notifications to broadcast. Instead, we
167    // store them so we can tell that the correct notifications were sent.
168    notifications_.push_back(std::make_pair(type, details_deleted));
169  }
170  virtual void NotifySyncURLsDeleted(
171      bool all_history,
172      bool archived,
173      URLRows* rows) OVERRIDE {}
174};
175
176// The example data consists of 4 visits. The middle two visits are to the
177// same URL, while the first and last are for unique ones. This allows a test
178// for the oldest or newest to include both a URL that should get totally
179// deleted (the one on the end) with one that should only get a visit deleted
180// (with the one in the middle) when it picks the proper threshold time.
181//
182// Each visit has indexed data, each URL has thumbnail. The first two URLs will
183// share the same avicon, while the last one will have a unique favicon. The
184// second visit for the middle URL is typed.
185//
186// The IDs of the added URLs, and the times of the four added visits will be
187// added to the given arrays.
188void ExpireHistoryTest::AddExampleData(URLID url_ids[3], Time visit_times[4]) {
189  if (!main_db_.get())
190    return;
191
192  // Four times for each visit.
193  visit_times[3] = Time::Now();
194  visit_times[2] = visit_times[3] - TimeDelta::FromDays(1);
195  visit_times[1] = visit_times[3] - TimeDelta::FromDays(2);
196  visit_times[0] = visit_times[3] - TimeDelta::FromDays(3);
197
198  // Two favicons. The first two URLs will share the same one, while the last
199  // one will have a unique favicon.
200  chrome::FaviconID favicon1 = thumb_db_->AddFavicon(
201      GURL("http://favicon/url1"), chrome::FAVICON);
202  chrome::FaviconID favicon2 = thumb_db_->AddFavicon(
203      GURL("http://favicon/url2"), chrome::FAVICON);
204
205  // Three URLs.
206  URLRow url_row1(GURL("http://www.google.com/1"));
207  url_row1.set_last_visit(visit_times[0]);
208  url_row1.set_visit_count(1);
209  url_ids[0] = main_db_->AddURL(url_row1);
210  thumb_db_->AddIconMapping(url_row1.url(), favicon1);
211
212  URLRow url_row2(GURL("http://www.google.com/2"));
213  url_row2.set_last_visit(visit_times[2]);
214  url_row2.set_visit_count(2);
215  url_row2.set_typed_count(1);
216  url_ids[1] = main_db_->AddURL(url_row2);
217  thumb_db_->AddIconMapping(url_row2.url(), favicon1);
218
219  URLRow url_row3(GURL("http://www.google.com/3"));
220  url_row3.set_last_visit(visit_times[3]);
221  url_row3.set_visit_count(1);
222  url_ids[2] = main_db_->AddURL(url_row3);
223  thumb_db_->AddIconMapping(url_row3.url(), favicon2);
224
225  // Thumbnails for each URL. |thumbnail| takes ownership of decoded SkBitmap.
226  scoped_ptr<SkBitmap> thumbnail_bitmap(
227      gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail)));
228  gfx::Image thumbnail = gfx::Image::CreateFrom1xBitmap(*thumbnail_bitmap);
229  ThumbnailScore score(0.25, true, true, Time::Now());
230
231  Time time;
232  GURL gurl;
233  top_sites_->SetPageThumbnail(url_row1.url(), thumbnail, score);
234  top_sites_->SetPageThumbnail(url_row2.url(), thumbnail, score);
235  top_sites_->SetPageThumbnail(url_row3.url(), thumbnail, score);
236
237  // Four visits.
238  VisitRow visit_row1;
239  visit_row1.url_id = url_ids[0];
240  visit_row1.visit_time = visit_times[0];
241  main_db_->AddVisit(&visit_row1, SOURCE_BROWSED);
242
243  VisitRow visit_row2;
244  visit_row2.url_id = url_ids[1];
245  visit_row2.visit_time = visit_times[1];
246  main_db_->AddVisit(&visit_row2, SOURCE_BROWSED);
247
248  VisitRow visit_row3;
249  visit_row3.url_id = url_ids[1];
250  visit_row3.visit_time = visit_times[2];
251  visit_row3.transition = content::PAGE_TRANSITION_TYPED;
252  main_db_->AddVisit(&visit_row3, SOURCE_BROWSED);
253
254  VisitRow visit_row4;
255  visit_row4.url_id = url_ids[2];
256  visit_row4.visit_time = visit_times[3];
257  main_db_->AddVisit(&visit_row4, SOURCE_BROWSED);
258}
259
260void ExpireHistoryTest::AddExampleSourceData(const GURL& url, URLID* id) {
261  if (!main_db_)
262    return;
263
264  Time last_visit_time = Time::Now();
265  // Add one URL.
266  URLRow url_row1(url);
267  url_row1.set_last_visit(last_visit_time);
268  url_row1.set_visit_count(4);
269  URLID url_id = main_db_->AddURL(url_row1);
270  *id = url_id;
271
272  // Four times for each visit.
273  VisitRow visit_row1(url_id, last_visit_time - TimeDelta::FromDays(4), 0,
274                      content::PAGE_TRANSITION_TYPED, 0);
275  main_db_->AddVisit(&visit_row1, SOURCE_SYNCED);
276
277  VisitRow visit_row2(url_id, last_visit_time - TimeDelta::FromDays(3), 0,
278                      content::PAGE_TRANSITION_TYPED, 0);
279  main_db_->AddVisit(&visit_row2, SOURCE_BROWSED);
280
281  VisitRow visit_row3(url_id, last_visit_time - TimeDelta::FromDays(2), 0,
282                      content::PAGE_TRANSITION_TYPED, 0);
283  main_db_->AddVisit(&visit_row3, SOURCE_EXTENSION);
284
285  VisitRow visit_row4(
286      url_id, last_visit_time, 0, content::PAGE_TRANSITION_TYPED, 0);
287  main_db_->AddVisit(&visit_row4, SOURCE_FIREFOX_IMPORTED);
288}
289
290bool ExpireHistoryTest::HasFavicon(chrome::FaviconID favicon_id) {
291  if (!thumb_db_.get() || favicon_id == 0)
292    return false;
293  return thumb_db_->GetFaviconHeader(favicon_id, NULL, NULL);
294}
295
296chrome::FaviconID ExpireHistoryTest::GetFavicon(const GURL& page_url,
297                                                chrome::IconType icon_type) {
298  std::vector<IconMapping> icon_mappings;
299  if (thumb_db_->GetIconMappingsForPageURL(page_url, icon_type,
300                                           &icon_mappings)) {
301    return icon_mappings[0].icon_id;
302  }
303  return 0;
304}
305
306bool ExpireHistoryTest::HasThumbnail(URLID url_id) {
307  // TODO(sky): fix this. This test isn't really valid for TopSites. For
308  // TopSites we should be checking URL always, not the id.
309  URLRow info;
310  if (!main_db_->GetURLRow(url_id, &info))
311    return false;
312  GURL url = info.url();
313  scoped_refptr<base::RefCountedMemory> data;
314  return top_sites_->GetPageThumbnail(url, false, &data);
315}
316
317void ExpireHistoryTest::EnsureURLInfoGone(const URLRow& row) {
318  // Verify the URL no longer exists.
319  URLRow temp_row;
320  EXPECT_FALSE(main_db_->GetURLRow(row.id(), &temp_row));
321
322  // There should be no visits.
323  VisitVector visits;
324  main_db_->GetVisitsForURL(row.id(), &visits);
325  EXPECT_EQ(0U, visits.size());
326
327  // Thumbnail should be gone.
328  // TODO(sky): fix this, see comment in HasThumbnail.
329  // EXPECT_FALSE(HasThumbnail(row.id()));
330
331  bool found_delete_notification = false;
332  for (size_t i = 0; i < notifications_.size(); i++) {
333    if (notifications_[i].first == chrome::NOTIFICATION_HISTORY_URLS_DELETED) {
334      URLsDeletedDetails* details = reinterpret_cast<URLsDeletedDetails*>(
335          notifications_[i].second);
336      EXPECT_FALSE(details->archived);
337      const history::URLRows& rows(details->rows);
338      if (std::find_if(rows.begin(), rows.end(),
339          history::URLRow::URLRowHasURL(row.url())) != rows.end()) {
340        found_delete_notification = true;
341      }
342    } else {
343      EXPECT_NE(notifications_[i].first,
344                chrome::NOTIFICATION_HISTORY_URL_VISITED);
345      EXPECT_NE(notifications_[i].first,
346                chrome::NOTIFICATION_HISTORY_URLS_MODIFIED);
347    }
348  }
349  EXPECT_TRUE(found_delete_notification);
350}
351
352TEST_F(ExpireHistoryTest, DeleteFaviconsIfPossible) {
353  // Add a favicon record.
354  const GURL favicon_url("http://www.google.com/favicon.ico");
355  chrome::FaviconID icon_id = thumb_db_->AddFavicon(
356      favicon_url, chrome::FAVICON);
357  EXPECT_TRUE(icon_id);
358  EXPECT_TRUE(HasFavicon(icon_id));
359
360  // The favicon should be deletable with no users.
361  std::set<chrome::FaviconID> favicon_set;
362  std::set<GURL> expired_favicons;
363  favicon_set.insert(icon_id);
364  expirer_.DeleteFaviconsIfPossible(favicon_set, &expired_favicons);
365  EXPECT_FALSE(HasFavicon(icon_id));
366  EXPECT_EQ(1U, expired_favicons.size());
367  EXPECT_EQ(1U, expired_favicons.count(favicon_url));
368
369  // Add back the favicon.
370  icon_id = thumb_db_->AddFavicon(
371      favicon_url, chrome::TOUCH_ICON);
372  EXPECT_TRUE(icon_id);
373  EXPECT_TRUE(HasFavicon(icon_id));
374
375  // Add a page that references the favicon.
376  URLRow row(GURL("http://www.google.com/2"));
377  row.set_visit_count(1);
378  EXPECT_TRUE(main_db_->AddURL(row));
379  thumb_db_->AddIconMapping(row.url(), icon_id);
380
381  // Favicon should not be deletable.
382  favicon_set.clear();
383  favicon_set.insert(icon_id);
384  expired_favicons.clear();
385  expirer_.DeleteFaviconsIfPossible(favicon_set, &expired_favicons);
386  EXPECT_TRUE(HasFavicon(icon_id));
387  EXPECT_TRUE(expired_favicons.empty());
388}
389
390// static
391bool ExpireHistoryTest::IsStringInFile(const base::FilePath& filename,
392                                       const char* str) {
393  std::string contents;
394  EXPECT_TRUE(base::ReadFileToString(filename, &contents));
395  return contents.find(str) != std::string::npos;
396}
397
398// Deletes a URL with a favicon that it is the last referencer of, so that it
399// should also get deleted.
400// Fails near end of month. http://crbug.com/43586
401TEST_F(ExpireHistoryTest, DISABLED_DeleteURLAndFavicon) {
402  URLID url_ids[3];
403  Time visit_times[4];
404  AddExampleData(url_ids, visit_times);
405
406  // Verify things are the way we expect with a URL row, favicon, thumbnail.
407  URLRow last_row;
408  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &last_row));
409  chrome::FaviconID favicon_id = GetFavicon(last_row.url(), chrome::FAVICON);
410  EXPECT_TRUE(HasFavicon(favicon_id));
411  // TODO(sky): fix this, see comment in HasThumbnail.
412  // EXPECT_TRUE(HasThumbnail(url_ids[2]));
413
414  VisitVector visits;
415  main_db_->GetVisitsForURL(url_ids[2], &visits);
416  ASSERT_EQ(1U, visits.size());
417
418  // Delete the URL and its dependencies.
419  expirer_.DeleteURL(last_row.url());
420
421  // All the normal data + the favicon should be gone.
422  EnsureURLInfoGone(last_row);
423  EXPECT_FALSE(GetFavicon(last_row.url(), chrome::FAVICON));
424  EXPECT_FALSE(HasFavicon(favicon_id));
425}
426
427// Deletes a URL with a favicon that other URLs reference, so that the favicon
428// should not get deleted. This also tests deleting more than one visit.
429TEST_F(ExpireHistoryTest, DeleteURLWithoutFavicon) {
430  URLID url_ids[3];
431  Time visit_times[4];
432  AddExampleData(url_ids, visit_times);
433
434  // Verify things are the way we expect with a URL row, favicon, thumbnail.
435  URLRow last_row;
436  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &last_row));
437  chrome::FaviconID favicon_id = GetFavicon(last_row.url(), chrome::FAVICON);
438  EXPECT_TRUE(HasFavicon(favicon_id));
439  // TODO(sky): fix this, see comment in HasThumbnail.
440  // EXPECT_TRUE(HasThumbnail(url_ids[1]));
441
442  VisitVector visits;
443  main_db_->GetVisitsForURL(url_ids[1], &visits);
444  EXPECT_EQ(2U, visits.size());
445
446  // Delete the URL and its dependencies.
447  expirer_.DeleteURL(last_row.url());
448
449  // All the normal data except the favicon should be gone.
450  EnsureURLInfoGone(last_row);
451  EXPECT_TRUE(HasFavicon(favicon_id));
452}
453
454// DeleteURL should not delete starred urls.
455TEST_F(ExpireHistoryTest, DontDeleteStarredURL) {
456  URLID url_ids[3];
457  Time visit_times[4];
458  AddExampleData(url_ids, visit_times);
459
460  URLRow url_row;
461  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row));
462
463  // Star the last URL.
464  StarURL(url_row.url());
465
466  // Attempt to delete the url.
467  expirer_.DeleteURL(url_row.url());
468
469  // Because the url is starred, it shouldn't be deleted.
470  GURL url = url_row.url();
471  ASSERT_TRUE(main_db_->GetRowForURL(url, &url_row));
472
473  // And the favicon should exist.
474  chrome::FaviconID favicon_id = GetFavicon(url_row.url(), chrome::FAVICON);
475  EXPECT_TRUE(HasFavicon(favicon_id));
476
477  // And no visits.
478  VisitVector visits;
479  main_db_->GetVisitsForURL(url_row.id(), &visits);
480  ASSERT_EQ(0U, visits.size());
481
482  // Should still have the thumbnail.
483  // TODO(sky): fix this, see comment in HasThumbnail.
484  // ASSERT_TRUE(HasThumbnail(url_row.id()));
485
486  // Unstar the URL and delete again.
487  bookmark_utils::RemoveAllBookmarks(&bookmark_model_, url);
488  expirer_.DeleteURL(url);
489
490  // Now it should be completely deleted.
491  EnsureURLInfoGone(url_row);
492}
493
494// Deletes multiple URLs at once.  The favicon for the third one but
495// not the first two should be deleted.
496TEST_F(ExpireHistoryTest, DeleteURLs) {
497  URLID url_ids[3];
498  Time visit_times[4];
499  AddExampleData(url_ids, visit_times);
500
501  // Verify things are the way we expect with URL rows, favicons,
502  // thumbnails.
503  URLRow rows[3];
504  chrome::FaviconID favicon_ids[3];
505  std::vector<GURL> urls;
506  // Push back a bogus URL (which shouldn't change anything).
507  urls.push_back(GURL());
508  for (size_t i = 0; i < arraysize(rows); ++i) {
509    ASSERT_TRUE(main_db_->GetURLRow(url_ids[i], &rows[i]));
510    favicon_ids[i] = GetFavicon(rows[i].url(), chrome::FAVICON);
511    EXPECT_TRUE(HasFavicon(favicon_ids[i]));
512    // TODO(sky): fix this, see comment in HasThumbnail.
513    // EXPECT_TRUE(HasThumbnail(url_ids[i]));
514    urls.push_back(rows[i].url());
515  }
516
517  StarURL(rows[0].url());
518
519  // Delete the URLs and their dependencies.
520  expirer_.DeleteURLs(urls);
521
522  // First one should still be around (since it was starred).
523  ASSERT_TRUE(main_db_->GetRowForURL(rows[0].url(), &rows[0]));
524  EnsureURLInfoGone(rows[1]);
525  EnsureURLInfoGone(rows[2]);
526  EXPECT_TRUE(HasFavicon(favicon_ids[0]));
527  EXPECT_TRUE(HasFavicon(favicon_ids[1]));
528  EXPECT_FALSE(HasFavicon(favicon_ids[2]));
529}
530
531// Expires all URLs more recent than a given time, with no starred items.
532// Our time threshold is such that one URL should be updated (we delete one of
533// the two visits) and one is deleted.
534TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarred) {
535  URLID url_ids[3];
536  Time visit_times[4];
537  AddExampleData(url_ids, visit_times);
538
539  URLRow url_row1, url_row2;
540  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
541  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2));
542
543  VisitVector visits;
544  main_db_->GetVisitsForURL(url_ids[2], &visits);
545  ASSERT_EQ(1U, visits.size());
546
547  // This should delete the last two visits.
548  std::set<GURL> restrict_urls;
549  expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time());
550
551  // Verify that the middle URL had its last visit deleted only.
552  visits.clear();
553  main_db_->GetVisitsForURL(url_ids[1], &visits);
554  EXPECT_EQ(1U, visits.size());
555
556  // Verify that the middle URL visit time and visit counts were updated.
557  URLRow temp_row;
558  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row));
559  EXPECT_TRUE(visit_times[2] == url_row1.last_visit());  // Previous value.
560  EXPECT_TRUE(visit_times[1] == temp_row.last_visit());  // New value.
561  EXPECT_EQ(2, url_row1.visit_count());
562  EXPECT_EQ(1, temp_row.visit_count());
563  EXPECT_EQ(1, url_row1.typed_count());
564  EXPECT_EQ(0, temp_row.typed_count());
565
566  // Verify that the middle URL's favicon and thumbnail is still there.
567  chrome::FaviconID favicon_id = GetFavicon(url_row1.url(), chrome::FAVICON);
568  EXPECT_TRUE(HasFavicon(favicon_id));
569  // TODO(sky): fix this, see comment in HasThumbnail.
570  // EXPECT_TRUE(HasThumbnail(url_row1.id()));
571
572  // Verify that the last URL was deleted.
573  chrome::FaviconID favicon_id2 = GetFavicon(url_row2.url(), chrome::FAVICON);
574  EnsureURLInfoGone(url_row2);
575  EXPECT_FALSE(HasFavicon(favicon_id2));
576}
577
578// Expires all URLs with times in a given set.
579TEST_F(ExpireHistoryTest, FlushURLsForTimes) {
580  URLID url_ids[3];
581  Time visit_times[4];
582  AddExampleData(url_ids, visit_times);
583
584  URLRow url_row1, url_row2;
585  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
586  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2));
587
588  VisitVector visits;
589  main_db_->GetVisitsForURL(url_ids[2], &visits);
590  ASSERT_EQ(1U, visits.size());
591
592  // This should delete the last two visits.
593  std::vector<base::Time> times;
594  times.push_back(visit_times[3]);
595  times.push_back(visit_times[2]);
596  expirer_.ExpireHistoryForTimes(times);
597
598  // Verify that the middle URL had its last visit deleted only.
599  visits.clear();
600  main_db_->GetVisitsForURL(url_ids[1], &visits);
601  EXPECT_EQ(1U, visits.size());
602
603  // Verify that the middle URL visit time and visit counts were updated.
604  URLRow temp_row;
605  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row));
606  EXPECT_TRUE(visit_times[2] == url_row1.last_visit());  // Previous value.
607  EXPECT_TRUE(visit_times[1] == temp_row.last_visit());  // New value.
608  EXPECT_EQ(2, url_row1.visit_count());
609  EXPECT_EQ(1, temp_row.visit_count());
610  EXPECT_EQ(1, url_row1.typed_count());
611  EXPECT_EQ(0, temp_row.typed_count());
612
613  // Verify that the middle URL's favicon and thumbnail is still there.
614  chrome::FaviconID favicon_id = GetFavicon(url_row1.url(), chrome::FAVICON);
615  EXPECT_TRUE(HasFavicon(favicon_id));
616  // TODO(sky): fix this, see comment in HasThumbnail.
617  // EXPECT_TRUE(HasThumbnail(url_row1.id()));
618
619  // Verify that the last URL was deleted.
620  chrome::FaviconID favicon_id2 = GetFavicon(url_row2.url(), chrome::FAVICON);
621  EnsureURLInfoGone(url_row2);
622  EXPECT_FALSE(HasFavicon(favicon_id2));
623}
624
625// Expires only a specific URLs more recent than a given time, with no starred
626// items.  Our time threshold is such that the URL should be updated (we delete
627// one of the two visits).
628TEST_F(ExpireHistoryTest, FlushRecentURLsUnstarredRestricted) {
629  URLID url_ids[3];
630  Time visit_times[4];
631  AddExampleData(url_ids, visit_times);
632
633  URLRow url_row1, url_row2;
634  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
635  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2));
636
637  VisitVector visits;
638  main_db_->GetVisitsForURL(url_ids[2], &visits);
639  ASSERT_EQ(1U, visits.size());
640
641  // This should delete the last two visits.
642  std::set<GURL> restrict_urls;
643  restrict_urls.insert(url_row1.url());
644  expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time());
645
646  // Verify that the middle URL had its last visit deleted only.
647  visits.clear();
648  main_db_->GetVisitsForURL(url_ids[1], &visits);
649  EXPECT_EQ(1U, visits.size());
650
651  // Verify that the middle URL visit time and visit counts were updated.
652  URLRow temp_row;
653  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row));
654  EXPECT_TRUE(visit_times[2] == url_row1.last_visit());  // Previous value.
655  EXPECT_TRUE(visit_times[1] == temp_row.last_visit());  // New value.
656  EXPECT_EQ(2, url_row1.visit_count());
657  EXPECT_EQ(1, temp_row.visit_count());
658  EXPECT_EQ(1, url_row1.typed_count());
659  EXPECT_EQ(0, temp_row.typed_count());
660
661  // Verify that the middle URL's favicon and thumbnail is still there.
662  chrome::FaviconID favicon_id = GetFavicon(url_row1.url(), chrome::FAVICON);
663  EXPECT_TRUE(HasFavicon(favicon_id));
664  // TODO(sky): fix this, see comment in HasThumbnail.
665  // EXPECT_TRUE(HasThumbnail(url_row1.id()));
666
667  // Verify that the last URL was not touched.
668  EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row));
669  EXPECT_TRUE(HasFavicon(favicon_id));
670  // TODO(sky): fix this, see comment in HasThumbnail.
671  // EXPECT_TRUE(HasThumbnail(url_row2.id()));
672}
673
674// Expire a starred URL, it shouldn't get deleted
675TEST_F(ExpireHistoryTest, FlushRecentURLsStarred) {
676  URLID url_ids[3];
677  Time visit_times[4];
678  AddExampleData(url_ids, visit_times);
679
680  URLRow url_row1, url_row2;
681  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
682  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2));
683
684  // Star the last two URLs.
685  StarURL(url_row1.url());
686  StarURL(url_row2.url());
687
688  // This should delete the last two visits.
689  std::set<GURL> restrict_urls;
690  expirer_.ExpireHistoryBetween(restrict_urls, visit_times[2], Time());
691
692  // The URL rows should still exist.
693  URLRow new_url_row1, new_url_row2;
694  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &new_url_row1));
695  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &new_url_row2));
696
697  // The visit times should be updated.
698  EXPECT_TRUE(new_url_row1.last_visit() == visit_times[1]);
699  EXPECT_TRUE(new_url_row2.last_visit().is_null());  // No last visit time.
700
701  // Visit/typed count should not be updated for bookmarks.
702  EXPECT_EQ(0, new_url_row1.typed_count());
703  EXPECT_EQ(1, new_url_row1.visit_count());
704  EXPECT_EQ(0, new_url_row2.typed_count());
705  EXPECT_EQ(0, new_url_row2.visit_count());
706
707  // Thumbnails and favicons should still exist. Note that we keep thumbnails
708  // that may have been updated since the time threshold. Since the URL still
709  // exists in history, this should not be a privacy problem, we only update
710  // the visit counts in this case for consistency anyway.
711  chrome::FaviconID favicon_id = GetFavicon(url_row1.url(), chrome::FAVICON);
712  EXPECT_TRUE(HasFavicon(favicon_id));
713  // TODO(sky): fix this, see comment in HasThumbnail.
714  // EXPECT_TRUE(HasThumbnail(new_url_row1.id()));
715  favicon_id = GetFavicon(url_row1.url(), chrome::FAVICON);
716  EXPECT_TRUE(HasFavicon(favicon_id));
717  // TODO(sky): fix this, see comment in HasThumbnail.
718  // EXPECT_TRUE(HasThumbnail(new_url_row2.id()));
719}
720
721TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeUnstarred) {
722  URLID url_ids[3];
723  Time visit_times[4];
724  AddExampleData(url_ids, visit_times);
725
726  URLRow url_row1, url_row2;
727  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
728  ASSERT_TRUE(main_db_->GetURLRow(url_ids[2], &url_row2));
729
730  // Archive the oldest two visits. This will actually result in deleting them
731  // since their transition types are empty (not important).
732  expirer_.ArchiveHistoryBefore(visit_times[1]);
733
734  // The first URL should be deleted, the second should not be affected.
735  URLRow temp_row;
736  EXPECT_FALSE(main_db_->GetURLRow(url_ids[0], &temp_row));
737  EXPECT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row));
738  EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row));
739
740  // Make sure the archived database has nothing in it.
741  EXPECT_FALSE(archived_db_->GetRowForURL(url_row1.url(), NULL));
742  EXPECT_FALSE(archived_db_->GetRowForURL(url_row2.url(), NULL));
743
744  // Now archive one more visit so that the middle URL should be removed. This
745  // one will actually be archived instead of deleted.
746  expirer_.ArchiveHistoryBefore(visit_times[2]);
747  EXPECT_FALSE(main_db_->GetURLRow(url_ids[1], &temp_row));
748  EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row));
749
750  // Make sure the archived database has an entry for the second URL.
751  URLRow archived_row;
752  // Note that the ID is different in the archived DB, so look up by URL.
753  EXPECT_TRUE(archived_db_->GetRowForURL(url_row1.url(), &archived_row));
754  VisitVector archived_visits;
755  archived_db_->GetVisitsForURL(archived_row.id(), &archived_visits);
756  EXPECT_EQ(1U, archived_visits.size());
757}
758
759TEST_F(ExpireHistoryTest, ArchiveHistoryBeforeStarred) {
760  URLID url_ids[3];
761  Time visit_times[4];
762  AddExampleData(url_ids, visit_times);
763
764  URLRow url_row0, url_row1;
765  ASSERT_TRUE(main_db_->GetURLRow(url_ids[0], &url_row0));
766  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &url_row1));
767
768  // Star the URLs.
769  StarURL(url_row0.url());
770  StarURL(url_row1.url());
771
772  // Now archive the first three visits (first two URLs). The first two visits
773  // should be, the third deleted, but the URL records should not.
774  expirer_.ArchiveHistoryBefore(visit_times[2]);
775
776  // The first URL should have its visit deleted, but it should still be present
777  // in the main DB and not in the archived one since it is starred.
778  URLRow temp_row;
779  ASSERT_TRUE(main_db_->GetURLRow(url_ids[0], &temp_row));
780  // Note that the ID is different in the archived DB, so look up by URL.
781  EXPECT_FALSE(archived_db_->GetRowForURL(temp_row.url(), NULL));
782  VisitVector visits;
783  main_db_->GetVisitsForURL(temp_row.id(), &visits);
784  EXPECT_EQ(0U, visits.size());
785
786  // The second URL should have its first visit deleted and its second visit
787  // archived. It should be present in both the main DB (because it's starred)
788  // and the archived DB (for the archived visit).
789  ASSERT_TRUE(main_db_->GetURLRow(url_ids[1], &temp_row));
790  main_db_->GetVisitsForURL(temp_row.id(), &visits);
791  EXPECT_EQ(0U, visits.size());
792
793  // Note that the ID is different in the archived DB, so look up by URL.
794  ASSERT_TRUE(archived_db_->GetRowForURL(temp_row.url(), &temp_row));
795  archived_db_->GetVisitsForURL(temp_row.id(), &visits);
796  ASSERT_EQ(1U, visits.size());
797  EXPECT_TRUE(visit_times[2] == visits[0].visit_time);
798
799  // The third URL should be unchanged.
800  EXPECT_TRUE(main_db_->GetURLRow(url_ids[2], &temp_row));
801  EXPECT_FALSE(archived_db_->GetRowForURL(temp_row.url(), NULL));
802}
803
804// Tests the return values from ArchiveSomeOldHistory. The rest of the
805// functionality of this function is tested by the ArchiveHistoryBefore*
806// tests which use this function internally.
807TEST_F(ExpireHistoryTest, ArchiveSomeOldHistory) {
808  URLID url_ids[3];
809  Time visit_times[4];
810  AddExampleData(url_ids, visit_times);
811  const ExpiringVisitsReader* reader = expirer_.GetAllVisitsReader();
812
813  // Deleting a time range with no URLs should return false (nothing found).
814  EXPECT_FALSE(expirer_.ArchiveSomeOldHistory(
815      visit_times[0] - TimeDelta::FromDays(100), reader, 1));
816
817  // Deleting a time range with not up the the max results should also return
818  // false (there will only be one visit deleted in this range).
819  EXPECT_FALSE(expirer_.ArchiveSomeOldHistory(visit_times[0], reader, 2));
820
821  // Deleting a time range with the max number of results should return true
822  // (max deleted).
823  EXPECT_TRUE(expirer_.ArchiveSomeOldHistory(visit_times[2], reader, 1));
824}
825
826TEST_F(ExpireHistoryTest, ExpiringVisitsReader) {
827  URLID url_ids[3];
828  Time visit_times[4];
829  AddExampleData(url_ids, visit_times);
830
831  const ExpiringVisitsReader* all = expirer_.GetAllVisitsReader();
832  const ExpiringVisitsReader* auto_subframes =
833      expirer_.GetAutoSubframeVisitsReader();
834
835  VisitVector visits;
836  Time now = Time::Now();
837
838  // Verify that the early expiration threshold, stored in the meta table is
839  // initialized.
840  EXPECT_TRUE(main_db_->GetEarlyExpirationThreshold() ==
841      Time::FromInternalValue(1L));
842
843  // First, attempt reading AUTO_SUBFRAME visits. We should get none.
844  EXPECT_FALSE(auto_subframes->Read(now, main_db_.get(), &visits, 1));
845  EXPECT_EQ(0U, visits.size());
846
847  // Verify that the early expiration threshold was updated, since there are no
848  // AUTO_SUBFRAME visits in the given time range.
849  EXPECT_TRUE(now <= main_db_->GetEarlyExpirationThreshold());
850
851  // Now, read all visits and verify that there's at least one.
852  EXPECT_TRUE(all->Read(now, main_db_.get(), &visits, 1));
853  EXPECT_EQ(1U, visits.size());
854}
855
856// Tests how ArchiveSomeOldHistory treats source information.
857TEST_F(ExpireHistoryTest, ArchiveSomeOldHistoryWithSource) {
858  const GURL url("www.testsource.com");
859  URLID url_id;
860  AddExampleSourceData(url, &url_id);
861  const ExpiringVisitsReader* reader = expirer_.GetAllVisitsReader();
862
863  // Archiving all the visits we added.
864  ASSERT_FALSE(expirer_.ArchiveSomeOldHistory(Time::Now(), reader, 10));
865
866  URLRow archived_row;
867  ASSERT_TRUE(archived_db_->GetRowForURL(url, &archived_row));
868  VisitVector archived_visits;
869  archived_db_->GetVisitsForURL(archived_row.id(), &archived_visits);
870  ASSERT_EQ(4U, archived_visits.size());
871  VisitSourceMap sources;
872  archived_db_->GetVisitsSource(archived_visits, &sources);
873  ASSERT_EQ(3U, sources.size());
874  int result = 0;
875  VisitSourceMap::iterator iter;
876  for (int i = 0; i < 4; i++) {
877    iter = sources.find(archived_visits[i].visit_id);
878    if (iter == sources.end())
879      continue;
880    switch (iter->second) {
881      case history::SOURCE_EXTENSION:
882        result |= 0x1;
883        break;
884      case history::SOURCE_FIREFOX_IMPORTED:
885        result |= 0x2;
886        break;
887      case history::SOURCE_SYNCED:
888        result |= 0x4;
889      default:
890        break;
891    }
892  }
893  EXPECT_EQ(0x7, result);
894  main_db_->GetVisitsSource(archived_visits, &sources);
895  EXPECT_EQ(0U, sources.size());
896  main_db_->GetVisitsForURL(url_id, &archived_visits);
897  EXPECT_EQ(0U, archived_visits.size());
898}
899
900// TODO(brettw) add some visits with no URL to make sure everything is updated
901// properly. Have the visits also refer to nonexistent FTS rows.
902//
903// Maybe also refer to invalid favicons.
904
905}  // namespace history
906