// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <set> #include <vector> #include "base/command_line.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/string16.h" #include "base/utf_string_conversions.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_notifications.h" #include "chrome/browser/history/in_memory_database.h" #include "chrome/browser/history/in_memory_history_backend.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/thumbnail_score.h" #include "chrome/tools/profiles/thumbnail-inl.h" #include "content/common/notification_details.h" #include "content/common/notification_source.h" #include "googleurl/src/gurl.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/codec/jpeg_codec.h" using base::Time; // This file only tests functionality where it is most convenient to call the // backend directly. Most of the history backend functions are tested by the // history unit test. Because of the elaborate callbacks involved, this is no // harder than calling it directly for many things. namespace { // data we'll put into the thumbnail database static const unsigned char blob1[] = "12346102356120394751634516591348710478123649165419234519234512349134"; } // namepace namespace history { class HistoryBackendTest; // This must be a separate object since HistoryBackend manages its lifetime. // This just forwards the messages we're interested in to the test object. class HistoryBackendTestDelegate : public HistoryBackend::Delegate { public: explicit HistoryBackendTestDelegate(HistoryBackendTest* test) : test_(test) {} virtual void NotifyProfileError(sql::InitStatus init_status) OVERRIDE {} virtual void SetInMemoryBackend(InMemoryHistoryBackend* backend) OVERRIDE; virtual void BroadcastNotifications(NotificationType type, HistoryDetails* details) OVERRIDE; virtual void DBLoaded() OVERRIDE; virtual void StartTopSitesMigration() OVERRIDE; private: // Not owned by us. HistoryBackendTest* test_; DISALLOW_COPY_AND_ASSIGN(HistoryBackendTestDelegate); }; class HistoryBackendTest : public testing::Test { public: HistoryBackendTest() : bookmark_model_(NULL), loaded_(false) {} virtual ~HistoryBackendTest() { } protected: scoped_refptr<HistoryBackend> backend_; // Will be NULL on init failure. scoped_ptr<InMemoryHistoryBackend> mem_backend_; void AddRedirectChain(const char* sequence[], int page_id) { history::RedirectList redirects; for (int i = 0; sequence[i] != NULL; ++i) redirects.push_back(GURL(sequence[i])); int int_scope = 1; void* scope = 0; memcpy(&scope, &int_scope, sizeof(int_scope)); scoped_refptr<history::HistoryAddPageArgs> request( new history::HistoryAddPageArgs( redirects.back(), Time::Now(), scope, page_id, GURL(), redirects, PageTransition::LINK, history::SOURCE_BROWSED, true)); backend_->AddPage(request); } // Adds CLIENT_REDIRECT page transition. // |url1| is the source URL and |url2| is the destination. // |did_replace| is true if the transition is non-user initiated and the // navigation entry for |url2| has replaced that for |url1|. The possibly // updated transition code of the visit records for |url1| and |url2| is // returned by filling in |*transition1| and |*transition2|, respectively. void AddClientRedirect(const GURL& url1, const GURL& url2, bool did_replace, int* transition1, int* transition2) { void* const dummy_scope = reinterpret_cast<void*>(0x87654321); history::RedirectList redirects; if (url1.is_valid()) redirects.push_back(url1); if (url2.is_valid()) redirects.push_back(url2); scoped_refptr<HistoryAddPageArgs> request( new HistoryAddPageArgs(url2, base::Time(), dummy_scope, 0, url1, redirects, PageTransition::CLIENT_REDIRECT, history::SOURCE_BROWSED, did_replace)); backend_->AddPage(request); *transition1 = getTransition(url1); *transition2 = getTransition(url2); } int getTransition(const GURL& url) { if (!url.is_valid()) return 0; URLRow row; URLID id = backend_->db()->GetRowForURL(url, &row); VisitVector visits; EXPECT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); return visits[0].transition; } FilePath getTestDir() { return test_dir_; } FaviconID GetFavicon(const GURL& url, IconType icon_type) { IconMapping icon_mapping; if (backend_->thumbnail_db_->GetIconMappingForPageURL(url, icon_type, &icon_mapping)) return icon_mapping.icon_id; else return 0; } BookmarkModel bookmark_model_; protected: bool loaded_; private: friend class HistoryBackendTestDelegate; // testing::Test virtual void SetUp() { if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("BackendTest"), &test_dir_)) return; backend_ = new HistoryBackend(test_dir_, new HistoryBackendTestDelegate(this), &bookmark_model_); backend_->Init(std::string(), false); } virtual void TearDown() { if (backend_.get()) backend_->Closing(); backend_ = NULL; mem_backend_.reset(); file_util::Delete(test_dir_, true); } void SetInMemoryBackend(InMemoryHistoryBackend* backend) { mem_backend_.reset(backend); } void BroadcastNotifications(NotificationType type, HistoryDetails* details) { // Send the notifications directly to the in-memory database. Details<HistoryDetails> det(details); mem_backend_->Observe(type, Source<HistoryBackendTest>(NULL), det); // The backend passes ownership of the details pointer to us. delete details; } MessageLoop message_loop_; FilePath test_dir_; }; void HistoryBackendTestDelegate::SetInMemoryBackend( InMemoryHistoryBackend* backend) { test_->SetInMemoryBackend(backend); } void HistoryBackendTestDelegate::BroadcastNotifications( NotificationType type, HistoryDetails* details) { test_->BroadcastNotifications(type, details); } void HistoryBackendTestDelegate::DBLoaded() { test_->loaded_ = true; } void HistoryBackendTestDelegate::StartTopSitesMigration() { test_->backend_->MigrateThumbnailsDatabase(); } TEST_F(HistoryBackendTest, Loaded) { ASSERT_TRUE(backend_.get()); ASSERT_TRUE(loaded_); } TEST_F(HistoryBackendTest, DeleteAll) { ASSERT_TRUE(backend_.get()); // Add two favicons, use the characters '1' and '2' for the image data. Note // that we do these in the opposite order. This is so the first one gets ID // 2 autoassigned to the database, which will change when the other one is // deleted. This way we can test that updating works properly. GURL favicon_url1("http://www.google.com/favicon.ico"); GURL favicon_url2("http://news.google.com/favicon.ico"); FaviconID favicon2 = backend_->thumbnail_db_->AddFavicon(favicon_url2, FAVICON); FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(favicon_url1, FAVICON); std::vector<unsigned char> data; data.push_back('1'); EXPECT_TRUE(backend_->thumbnail_db_->SetFavicon(favicon1, new RefCountedBytes(data), Time::Now())); data[0] = '2'; EXPECT_TRUE(backend_->thumbnail_db_->SetFavicon( favicon2, new RefCountedBytes(data), Time::Now())); // First visit two URLs. URLRow row1(GURL("http://www.google.com/")); row1.set_visit_count(2); row1.set_typed_count(1); row1.set_last_visit(Time::Now()); backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1); URLRow row2(GURL("http://news.google.com/")); row2.set_visit_count(1); row2.set_last_visit(Time::Now()); backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2); std::vector<URLRow> rows; rows.push_back(row2); // Reversed order for the same reason as favicons. rows.push_back(row1); backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL); URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL); // Get the two visits for the URLs we just added. VisitVector visits; backend_->db_->GetVisitsForURL(row1_id, &visits); ASSERT_EQ(1U, visits.size()); VisitID visit1_id = visits[0].visit_id; visits.clear(); backend_->db_->GetVisitsForURL(row2_id, &visits); ASSERT_EQ(1U, visits.size()); VisitID visit2_id = visits[0].visit_id; // The in-memory backend should have been set and it should have gotten the // typed URL. ASSERT_TRUE(mem_backend_.get()); URLRow outrow1; EXPECT_TRUE(mem_backend_->db_->GetRowForURL(row1.url(), NULL)); // Add thumbnails for each page. ThumbnailScore score(0.25, true, true); scoped_ptr<SkBitmap> google_bitmap( gfx::JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); Time time; GURL gurl; backend_->thumbnail_db_->SetPageThumbnail(gurl, row1_id, *google_bitmap, score, time); scoped_ptr<SkBitmap> weewar_bitmap( gfx::JPEGCodec::Decode(kWeewarThumbnail, sizeof(kWeewarThumbnail))); backend_->thumbnail_db_->SetPageThumbnail(gurl, row2_id, *weewar_bitmap, score, time); // Star row1. bookmark_model_.AddURL( bookmark_model_.GetBookmarkBarNode(), 0, string16(), row1.url()); // Set full text index for each one. backend_->text_database_->AddPageData(row1.url(), row1_id, visit1_id, row1.last_visit(), UTF8ToUTF16("Title 1"), UTF8ToUTF16("Body 1")); backend_->text_database_->AddPageData(row2.url(), row2_id, visit2_id, row2.last_visit(), UTF8ToUTF16("Title 2"), UTF8ToUTF16("Body 2")); // Now finally clear all history. backend_->DeleteAllHistory(); // The first URL should be preserved but the time should be cleared. EXPECT_TRUE(backend_->db_->GetRowForURL(row1.url(), &outrow1)); EXPECT_EQ(row1.url(), outrow1.url()); EXPECT_EQ(0, outrow1.visit_count()); EXPECT_EQ(0, outrow1.typed_count()); EXPECT_TRUE(Time() == outrow1.last_visit()); // The second row should be deleted. URLRow outrow2; EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &outrow2)); // All visits should be deleted for both URLs. VisitVector all_visits; backend_->db_->GetAllVisitsInRange(Time(), Time(), 0, &all_visits); ASSERT_EQ(0U, all_visits.size()); // All thumbnails should be deleted. std::vector<unsigned char> out_data; EXPECT_FALSE(backend_->thumbnail_db_->GetPageThumbnail(outrow1.id(), &out_data)); EXPECT_FALSE(backend_->thumbnail_db_->GetPageThumbnail(row2_id, &out_data)); // We should have a favicon for the first URL only. We look them up by favicon // URL since the IDs may hav changed. FaviconID out_favicon1 = backend_->thumbnail_db_-> GetFaviconIDForFaviconURL(favicon_url1, FAVICON, NULL); EXPECT_TRUE(out_favicon1); FaviconID out_favicon2 = backend_->thumbnail_db_-> GetFaviconIDForFaviconURL(favicon_url2, FAVICON, NULL); EXPECT_FALSE(out_favicon2) << "Favicon not deleted"; // The remaining URL should still reference the same favicon, even if its // ID has changed. EXPECT_EQ(out_favicon1, GetFavicon(outrow1.url(), FAVICON)); // The first URL should still be bookmarked. EXPECT_TRUE(bookmark_model_.IsBookmarked(row1.url())); // The full text database should have no data. std::vector<TextDatabase::Match> text_matches; Time first_time_searched; backend_->text_database_->GetTextMatches(UTF8ToUTF16("Body"), QueryOptions(), &text_matches, &first_time_searched); EXPECT_EQ(0U, text_matches.size()); } TEST_F(HistoryBackendTest, URLsNoLongerBookmarked) { GURL favicon_url1("http://www.google.com/favicon.ico"); GURL favicon_url2("http://news.google.com/favicon.ico"); FaviconID favicon2 = backend_->thumbnail_db_->AddFavicon(favicon_url2, FAVICON); FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(favicon_url1, FAVICON); std::vector<unsigned char> data; data.push_back('1'); EXPECT_TRUE(backend_->thumbnail_db_->SetFavicon( favicon1, new RefCountedBytes(data), Time::Now())); data[0] = '2'; EXPECT_TRUE(backend_->thumbnail_db_->SetFavicon( favicon2, new RefCountedBytes(data), Time::Now())); // First visit two URLs. URLRow row1(GURL("http://www.google.com/")); row1.set_visit_count(2); row1.set_typed_count(1); row1.set_last_visit(Time::Now()); EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1)); URLRow row2(GURL("http://news.google.com/")); row2.set_visit_count(1); row2.set_last_visit(Time::Now()); EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row2.url(), favicon2)); std::vector<URLRow> rows; rows.push_back(row2); // Reversed order for the same reason as favicons. rows.push_back(row1); backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); URLID row1_id = backend_->db_->GetRowForURL(row1.url(), NULL); URLID row2_id = backend_->db_->GetRowForURL(row2.url(), NULL); // Star the two URLs. bookmark_model_.SetURLStarred(row1.url(), string16(), true); bookmark_model_.SetURLStarred(row2.url(), string16(), true); // Delete url 2. Because url 2 is starred this won't delete the URL, only // the visits. backend_->expirer_.DeleteURL(row2.url()); // Make sure url 2 is still valid, but has no visits. URLRow tmp_url_row; EXPECT_EQ(row2_id, backend_->db_->GetRowForURL(row2.url(), NULL)); VisitVector visits; backend_->db_->GetVisitsForURL(row2_id, &visits); EXPECT_EQ(0U, visits.size()); // The favicon should still be valid. EXPECT_EQ(favicon2, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(favicon_url2, FAVICON, NULL)); // Unstar row2. bookmark_model_.SetURLStarred(row2.url(), string16(), false); // Tell the backend it was unstarred. We have to explicitly do this as // BookmarkModel isn't wired up to the backend during testing. std::set<GURL> unstarred_urls; unstarred_urls.insert(row2.url()); backend_->URLsNoLongerBookmarked(unstarred_urls); // The URL should no longer exist. EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &tmp_url_row)); // And the favicon should be deleted. EXPECT_EQ(0, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(favicon_url2, FAVICON, NULL)); // Unstar row 1. bookmark_model_.SetURLStarred(row1.url(), string16(), false); // Tell the backend it was unstarred. We have to explicitly do this as // BookmarkModel isn't wired up to the backend during testing. unstarred_urls.clear(); unstarred_urls.insert(row1.url()); backend_->URLsNoLongerBookmarked(unstarred_urls); // The URL should still exist (because there were visits). EXPECT_EQ(row1_id, backend_->db_->GetRowForURL(row1.url(), NULL)); // There should still be visits. visits.clear(); backend_->db_->GetVisitsForURL(row1_id, &visits); EXPECT_EQ(1U, visits.size()); // The favicon should still be valid. EXPECT_EQ(favicon1, backend_->thumbnail_db_->GetFaviconIDForFaviconURL(favicon_url1, FAVICON, NULL)); } // Tests a handful of assertions for a navigation with a type of // KEYWORD_GENERATED. TEST_F(HistoryBackendTest, KeywordGenerated) { ASSERT_TRUE(backend_.get()); GURL url("http://google.com"); Time visit_time = Time::Now() - base::TimeDelta::FromDays(1); scoped_refptr<HistoryAddPageArgs> request( new HistoryAddPageArgs(url, visit_time, NULL, 0, GURL(), history::RedirectList(), PageTransition::KEYWORD_GENERATED, history::SOURCE_BROWSED, false)); backend_->AddPage(request); // A row should have been added for the url. URLRow row; URLID url_id = backend_->db()->GetRowForURL(url, &row); ASSERT_NE(0, url_id); // The typed count should be 1. ASSERT_EQ(1, row.typed_count()); // KEYWORD_GENERATED urls should not be added to the segment db. std::string segment_name = VisitSegmentDatabase::ComputeSegmentName(url); EXPECT_EQ(0, backend_->db()->GetSegmentNamed(segment_name)); // One visit should be added. VisitVector visits; EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits)); EXPECT_EQ(1U, visits.size()); // But no visible visits. visits.clear(); backend_->db()->GetVisibleVisitsInRange(base::Time(), base::Time(), 1, &visits); EXPECT_TRUE(visits.empty()); // Expire the visits. std::set<GURL> restrict_urls; backend_->expire_backend()->ExpireHistoryBetween(restrict_urls, visit_time, Time::Now()); // The visit should have been nuked. visits.clear(); EXPECT_TRUE(backend_->db()->GetVisitsForURL(url_id, &visits)); EXPECT_TRUE(visits.empty()); // As well as the url. ASSERT_EQ(0, backend_->db()->GetRowForURL(url, &row)); } TEST_F(HistoryBackendTest, ClientRedirect) { ASSERT_TRUE(backend_.get()); int transition1; int transition2; // Initial transition to page A. GURL url_a("http://google.com/a"); AddClientRedirect(GURL(), url_a, false, &transition1, &transition2); EXPECT_TRUE(transition2 & PageTransition::CHAIN_END); // User initiated redirect to page B. GURL url_b("http://google.com/b"); AddClientRedirect(url_a, url_b, false, &transition1, &transition2); EXPECT_TRUE(transition1 & PageTransition::CHAIN_END); EXPECT_TRUE(transition2 & PageTransition::CHAIN_END); // Non-user initiated redirect to page C. GURL url_c("http://google.com/c"); AddClientRedirect(url_b, url_c, true, &transition1, &transition2); EXPECT_FALSE(transition1 & PageTransition::CHAIN_END); EXPECT_TRUE(transition2 & PageTransition::CHAIN_END); } TEST_F(HistoryBackendTest, ImportedFaviconsTest) { // Setup test data - two Urls in the history, one with favicon assigned and // one without. GURL favicon_url1("http://www.google.com/favicon.ico"); FaviconID favicon1 = backend_->thumbnail_db_->AddFavicon(favicon_url1, FAVICON); std::vector<unsigned char> data; data.push_back('1'); EXPECT_TRUE(backend_->thumbnail_db_->SetFavicon(favicon1, RefCountedBytes::TakeVector(&data), Time::Now())); URLRow row1(GURL("http://www.google.com/")); row1.set_visit_count(1); row1.set_last_visit(Time::Now()); EXPECT_TRUE(backend_->thumbnail_db_->AddIconMapping(row1.url(), favicon1)); URLRow row2(GURL("http://news.google.com/")); row2.set_visit_count(1); row2.set_last_visit(Time::Now()); std::vector<URLRow> rows; rows.push_back(row1); rows.push_back(row2); backend_->AddPagesWithDetails(rows, history::SOURCE_BROWSED); URLRow url_row1, url_row2; EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0); EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0); EXPECT_FALSE(GetFavicon(row1.url(), FAVICON) == 0); EXPECT_TRUE(GetFavicon(row2.url(), FAVICON) == 0); // Now provide one imported favicon for both URLs already in the registry. // The new favicon should only be used with the URL that doesn't already have // a favicon. std::vector<history::ImportedFaviconUsage> favicons; history::ImportedFaviconUsage favicon; favicon.favicon_url = GURL("http://news.google.com/favicon.ico"); favicon.png_data.push_back('2'); favicon.urls.insert(row1.url()); favicon.urls.insert(row2.url()); favicons.push_back(favicon); backend_->SetImportedFavicons(favicons); EXPECT_FALSE(backend_->db_->GetRowForURL(row1.url(), &url_row1) == 0); EXPECT_FALSE(backend_->db_->GetRowForURL(row2.url(), &url_row2) == 0); EXPECT_FALSE(GetFavicon(row1.url(), FAVICON) == 0); EXPECT_FALSE(GetFavicon(row2.url(), FAVICON) == 0); EXPECT_FALSE(GetFavicon(row1.url(), FAVICON) == GetFavicon(row2.url(), FAVICON)); // A URL should not be added to history (to store favicon), if // the URL is not bookmarked. GURL url3("http://mail.google.com"); favicons.clear(); favicon.favicon_url = GURL("http://mail.google.com/favicon.ico"); favicon.png_data.push_back('3'); favicon.urls.insert(url3); favicons.push_back(favicon); backend_->SetImportedFavicons(favicons); URLRow url_row3; EXPECT_TRUE(backend_->db_->GetRowForURL(url3, &url_row3) == 0); // If the URL is bookmarked, it should get added to history with 0 visits. bookmark_model_.AddURL(bookmark_model_.GetBookmarkBarNode(), 0, string16(), url3); backend_->SetImportedFavicons(favicons); EXPECT_FALSE(backend_->db_->GetRowForURL(url3, &url_row3) == 0); EXPECT_TRUE(url_row3.visit_count() == 0); } TEST_F(HistoryBackendTest, StripUsernamePasswordTest) { ASSERT_TRUE(backend_.get()); GURL url("http://anyuser:anypass@www.google.com"); GURL stripped_url("http://www.google.com"); // Clear all history. backend_->DeleteAllHistory(); // Visit the url with username, password. backend_->AddPageVisit(url, base::Time::Now(), 0, PageTransition::GetQualifier(PageTransition::TYPED), history::SOURCE_BROWSED); // Fetch the row information about stripped url from history db. VisitVector visits; URLID row_id = backend_->db_->GetRowForURL(stripped_url, NULL); backend_->db_->GetVisitsForURL(row_id, &visits); // Check if stripped url is stored in database. ASSERT_EQ(1U, visits.size()); } TEST_F(HistoryBackendTest, AddPageVisitSource) { ASSERT_TRUE(backend_.get()); GURL url("http://www.google.com"); // Clear all history. backend_->DeleteAllHistory(); // Assume visiting the url from an externsion. backend_->AddPageVisit(url, base::Time::Now(), 0, PageTransition::TYPED, history::SOURCE_EXTENSION); // Assume the url is imported from Firefox. backend_->AddPageVisit(url, base::Time::Now(), 0, PageTransition::TYPED, history::SOURCE_FIREFOX_IMPORTED); // Assume this url is also synced. backend_->AddPageVisit(url, base::Time::Now(), 0, PageTransition::TYPED, history::SOURCE_SYNCED); // Fetch the row information about the url from history db. VisitVector visits; URLID row_id = backend_->db_->GetRowForURL(url, NULL); backend_->db_->GetVisitsForURL(row_id, &visits); // Check if all the visits to the url are stored in database. ASSERT_EQ(3U, visits.size()); VisitSourceMap visit_sources; backend_->db_->GetVisitsSource(visits, &visit_sources); ASSERT_EQ(3U, visit_sources.size()); int sources = 0; for (int i = 0; i < 3; i++) { switch (visit_sources[visits[i].visit_id]) { case history::SOURCE_EXTENSION: sources |= 0x1; break; case history::SOURCE_FIREFOX_IMPORTED: sources |= 0x2; break; case history::SOURCE_SYNCED: sources |= 0x4; default: break; } } EXPECT_EQ(0x7, sources); } TEST_F(HistoryBackendTest, AddPageArgsSource) { ASSERT_TRUE(backend_.get()); GURL url("http://testpageargs.com"); // Assume this page is browsed by user. scoped_refptr<HistoryAddPageArgs> request1( new HistoryAddPageArgs(url, base::Time::Now(), NULL, 0, GURL(), history::RedirectList(), PageTransition::KEYWORD_GENERATED, history::SOURCE_BROWSED, false)); backend_->AddPage(request1); // Assume this page is synced. scoped_refptr<HistoryAddPageArgs> request2( new HistoryAddPageArgs(url, base::Time::Now(), NULL, 0, GURL(), history::RedirectList(), PageTransition::LINK, history::SOURCE_SYNCED, false)); backend_->AddPage(request2); // Assume this page is browsed again. scoped_refptr<HistoryAddPageArgs> request3( new HistoryAddPageArgs(url, base::Time::Now(), NULL, 0, GURL(), history::RedirectList(), PageTransition::TYPED, history::SOURCE_BROWSED, false)); backend_->AddPage(request3); // Three visits should be added with proper sources. VisitVector visits; URLRow row; URLID id = backend_->db()->GetRowForURL(url, &row); ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); ASSERT_EQ(3U, visits.size()); VisitSourceMap visit_sources; backend_->db_->GetVisitsSource(visits, &visit_sources); ASSERT_EQ(1U, visit_sources.size()); EXPECT_EQ(history::SOURCE_SYNCED, visit_sources.begin()->second); } TEST_F(HistoryBackendTest, AddVisitsSource) { ASSERT_TRUE(backend_.get()); GURL url1("http://www.cnn.com"); std::vector<base::Time> visits1; visits1.push_back(Time::Now() - base::TimeDelta::FromDays(5)); visits1.push_back(Time::Now() - base::TimeDelta::FromDays(1)); visits1.push_back(Time::Now()); GURL url2("http://www.example.com"); std::vector<base::Time> visits2; visits2.push_back(Time::Now() - base::TimeDelta::FromDays(10)); visits2.push_back(Time::Now()); // Clear all history. backend_->DeleteAllHistory(); // Add the visits. backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED); // Verify the visits were added with their sources. VisitVector visits; URLRow row; URLID id = backend_->db()->GetRowForURL(url1, &row); ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); ASSERT_EQ(3U, visits.size()); VisitSourceMap visit_sources; backend_->db_->GetVisitsSource(visits, &visit_sources); ASSERT_EQ(3U, visit_sources.size()); for (int i = 0; i < 3; i++) EXPECT_EQ(history::SOURCE_IE_IMPORTED, visit_sources[visits[i].visit_id]); id = backend_->db()->GetRowForURL(url2, &row); ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); ASSERT_EQ(2U, visits.size()); backend_->db_->GetVisitsSource(visits, &visit_sources); ASSERT_EQ(2U, visit_sources.size()); for (int i = 0; i < 2; i++) EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]); } TEST_F(HistoryBackendTest, RemoveVisitsSource) { ASSERT_TRUE(backend_.get()); GURL url1("http://www.cnn.com"); std::vector<base::Time> visits1; visits1.push_back(Time::Now() - base::TimeDelta::FromDays(5)); visits1.push_back(Time::Now()); GURL url2("http://www.example.com"); std::vector<base::Time> visits2; visits2.push_back(Time::Now() - base::TimeDelta::FromDays(10)); visits2.push_back(Time::Now()); // Clear all history. backend_->DeleteAllHistory(); // Add the visits. backend_->AddVisits(url1, visits1, history::SOURCE_IE_IMPORTED); backend_->AddVisits(url2, visits2, history::SOURCE_SYNCED); // Verify the visits of url1 were added. VisitVector visits; URLRow row; URLID id = backend_->db()->GetRowForURL(url1, &row); ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); ASSERT_EQ(2U, visits.size()); // Remove these visits. ASSERT_TRUE(backend_->RemoveVisits(visits)); // Now check only url2's source in visit_source table. VisitSourceMap visit_sources; backend_->db_->GetVisitsSource(visits, &visit_sources); ASSERT_EQ(0U, visit_sources.size()); id = backend_->db()->GetRowForURL(url2, &row); ASSERT_TRUE(backend_->db()->GetVisitsForURL(id, &visits)); ASSERT_EQ(2U, visits.size()); backend_->db_->GetVisitsSource(visits, &visit_sources); ASSERT_EQ(2U, visit_sources.size()); for (int i = 0; i < 2; i++) EXPECT_EQ(history::SOURCE_SYNCED, visit_sources[visits[i].visit_id]); } // Test for migration of adding visit_source table. TEST_F(HistoryBackendTest, MigrationVisitSource) { ASSERT_TRUE(backend_.get()); backend_->Closing(); backend_ = NULL; FilePath old_history_path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &old_history_path)); old_history_path = old_history_path.AppendASCII("History"); old_history_path = old_history_path.AppendASCII("HistoryNoSource"); // Copy history database file to current directory so that it will be deleted // in Teardown. FilePath new_history_path(getTestDir()); file_util::Delete(new_history_path, true); file_util::CreateDirectory(new_history_path); FilePath new_history_file = new_history_path.Append(chrome::kHistoryFilename); ASSERT_TRUE(file_util::CopyFile(old_history_path, new_history_file)); backend_ = new HistoryBackend(new_history_path, new HistoryBackendTestDelegate(this), &bookmark_model_); backend_->Init(std::string(), false); backend_->Closing(); backend_ = NULL; // Now the database should already be migrated. // Check version first. int cur_version = HistoryDatabase::GetCurrentVersion(); sql::Connection db; ASSERT_TRUE(db.Open(new_history_file)); sql::Statement s(db.GetUniqueStatement( "SELECT value FROM meta WHERE key = 'version'")); ASSERT_TRUE(s.Step()); int file_version = s.ColumnInt(0); EXPECT_EQ(cur_version, file_version); // Check visit_source table is created and empty. s.Assign(db.GetUniqueStatement( "SELECT name FROM sqlite_master WHERE name=\"visit_source\"")); ASSERT_TRUE(s.Step()); s.Assign(db.GetUniqueStatement("SELECT * FROM visit_source LIMIT 10")); EXPECT_FALSE(s.Step()); } TEST_F(HistoryBackendTest, SetFaviconMapping) { // Init recent_redirects_ const GURL url1("http://www.google.com"); const GURL url2("http://www.google.com/m"); URLRow url_info1(url1); url_info1.set_visit_count(0); url_info1.set_typed_count(0); url_info1.set_last_visit(base::Time()); url_info1.set_hidden(false); backend_->db_->AddURL(url_info1); URLRow url_info2(url2); url_info2.set_visit_count(0); url_info2.set_typed_count(0); url_info2.set_last_visit(base::Time()); url_info2.set_hidden(false); backend_->db_->AddURL(url_info2); history::RedirectList redirects; redirects.push_back(url2); redirects.push_back(url1); backend_->recent_redirects_.Put(url1, redirects); const GURL icon_url("http://www.google.com/icon"); std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1)); // Add a favicon backend_->SetFavicon( url1, icon_url, RefCountedBytes::TakeVector(&data), FAVICON); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, FAVICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url2, FAVICON, NULL)); // Add a touch_icon backend_->SetFavicon( url1, icon_url, RefCountedBytes::TakeVector(&data), TOUCH_ICON); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, TOUCH_ICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url2, TOUCH_ICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, FAVICON, NULL)); // Add a TOUCH_PRECOMPOSED_ICON backend_->SetFavicon(url1, icon_url, RefCountedBytes::TakeVector(&data), TOUCH_PRECOMPOSED_ICON); // The touch_icon was replaced. EXPECT_FALSE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, TOUCH_ICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, FAVICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, TOUCH_PRECOMPOSED_ICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url2, TOUCH_PRECOMPOSED_ICON, NULL)); // Add a touch_icon backend_->SetFavicon( url1, icon_url, RefCountedBytes::TakeVector(&data), TOUCH_ICON); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, TOUCH_ICON, NULL)); EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, FAVICON, NULL)); // The TOUCH_PRECOMPOSED_ICON was replaced. EXPECT_FALSE(backend_->thumbnail_db_->GetIconMappingForPageURL( url1, TOUCH_PRECOMPOSED_ICON, NULL)); // Add a favicon const GURL icon_url2("http://www.google.com/icon2"); backend_->SetFavicon( url1, icon_url2, RefCountedBytes::TakeVector(&data), FAVICON); FaviconID icon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL( icon_url2, FAVICON, NULL); EXPECT_NE(0, icon_id); std::vector<IconMapping> icon_mapping; EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( url1, &icon_mapping)); // The old icon was replaced. EXPECT_TRUE(icon_mapping.size() > 1); EXPECT_EQ(icon_id, icon_mapping[1].icon_id); } TEST_F(HistoryBackendTest, AddOrUpdateIconMapping) { // Test the same icon and page mapping will not be added twice. other case // should be covered in TEST_F(HistoryBackendTest, SetFaviconMapping) const GURL url("http://www.google.com/"); const GURL icon_url("http://www.google.com/icon"); std::vector<unsigned char> data(blob1, blob1 + sizeof(blob1)); backend_->SetFavicon( url, icon_url, RefCountedBytes::TakeVector(&data), FAVICON); FaviconID icon_id = backend_->thumbnail_db_->GetFaviconIDForFaviconURL( icon_url, FAVICON, NULL); // Add the same mapping FaviconID replaced; EXPECT_FALSE(backend_->AddOrUpdateIconMapping( url, icon_id, FAVICON, &replaced)); EXPECT_EQ(0, replaced); std::vector<IconMapping> icon_mapping; EXPECT_TRUE(backend_->thumbnail_db_->GetIconMappingsForPageURL( url, &icon_mapping)); EXPECT_EQ(1u, icon_mapping.size()); } } // namespace history