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