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