1 // Copyright (c) 2011 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 "base/file_util.h" 6 #include "base/path_service.h" 7 #include "base/string_util.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/autocomplete/autocomplete.h" 10 #include "chrome/browser/autocomplete/autocomplete_match.h" 11 #include "chrome/browser/autocomplete/history_contents_provider.h" 12 #include "chrome/browser/bookmarks/bookmark_model.h" 13 #include "chrome/browser/history/history.h" 14 #include "chrome/test/testing_browser_process.h" 15 #include "chrome/test/testing_browser_process_test.h" 16 #include "chrome/test/testing_profile.h" 17 #include "content/browser/browser_thread.h" 18 #include "testing/gtest/include/gtest/gtest.h" 19 20 using base::Time; 21 using base::TimeDelta; 22 23 namespace { 24 25 struct TestEntry { 26 const char* url; 27 const char* title; 28 const char* body; 29 } test_entries[] = { 30 {"http://www.google.com/1", "PAGEONE 1", "FOO some body text"}, 31 {"http://www.google.com/2", "PAGEONE 2", "FOO some more blah blah"}, 32 {"http://www.google.com/3", "PAGETHREE 3", "BAR some hello world for you"}, 33 }; 34 35 class HistoryContentsProviderTest : public TestingBrowserProcessTest, 36 public ACProviderListener { 37 public: 38 HistoryContentsProviderTest() 39 : ui_thread_(BrowserThread::UI, &message_loop_), 40 file_thread_(BrowserThread::FILE, &message_loop_) {} 41 42 void RunQuery(const AutocompleteInput& input, 43 bool minimal_changes) { 44 provider_->Start(input, minimal_changes); 45 46 // When we're waiting for asynchronous messages, we have to spin the message 47 // loop. This will be exited in the OnProviderUpdate function when complete. 48 if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) 49 MessageLoop::current()->Run(); 50 } 51 52 const ACMatches& matches() const { return provider_->matches(); } 53 54 TestingProfile* profile() const { return profile_.get(); } 55 56 HistoryContentsProvider* provider() const { return provider_.get(); } 57 58 private: 59 // testing::Test 60 virtual void SetUp() { 61 profile_.reset(new TestingProfile()); 62 profile_->CreateHistoryService(false, false); 63 64 HistoryService* history_service = 65 profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); 66 67 // Populate history. 68 for (size_t i = 0; i < arraysize(test_entries); i++) { 69 // We need the ID scope and page ID so that the visit tracker can find it. 70 // We just use the index for the page ID below. 71 const void* id_scope = reinterpret_cast<void*>(1); 72 GURL url(test_entries[i].url); 73 74 // Add everything in order of time. We don't want to have a time that 75 // is "right now" or it will nondeterministically appear in the results. 76 Time t = Time::Now() - TimeDelta::FromDays(arraysize(test_entries) + i); 77 78 history_service->AddPage(url, t, id_scope, i, GURL(), 79 PageTransition::LINK, history::RedirectList(), 80 history::SOURCE_BROWSED, false); 81 history_service->SetPageTitle(url, UTF8ToUTF16(test_entries[i].title)); 82 history_service->SetPageContents(url, UTF8ToUTF16(test_entries[i].body)); 83 } 84 85 provider_ = new HistoryContentsProvider(this, profile_.get()); 86 } 87 88 virtual void TearDown() { 89 provider_ = NULL; 90 profile_.reset(NULL); 91 } 92 93 // ACProviderListener 94 virtual void OnProviderUpdate(bool updated_matches) { 95 // We must quit the message loop (if running) to return control to the test. 96 // Note, calling Quit() directly will checkfail if the loop isn't running, 97 // so we post a task, which is safe for either case. 98 MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); 99 } 100 101 MessageLoopForUI message_loop_; 102 BrowserThread ui_thread_; 103 BrowserThread file_thread_; 104 105 scoped_ptr<TestingProfile> profile_; 106 scoped_refptr<HistoryContentsProvider> provider_; 107 }; 108 109 TEST_F(HistoryContentsProviderTest, Body) { 110 AutocompleteInput input(ASCIIToUTF16("FOO"), string16(), true, false, true, 111 AutocompleteInput::ALL_MATCHES); 112 RunQuery(input, false); 113 114 // The results should be the first two pages, in decreasing order. 115 const ACMatches& m = matches(); 116 ASSERT_EQ(2U, m.size()); 117 EXPECT_EQ(test_entries[0].url, m[0].destination_url.spec()); 118 EXPECT_STREQ(test_entries[0].title, UTF16ToUTF8(m[0].description).c_str()); 119 EXPECT_EQ(test_entries[1].url, m[1].destination_url.spec()); 120 EXPECT_STREQ(test_entries[1].title, UTF16ToUTF8(m[1].description).c_str()); 121 } 122 123 TEST_F(HistoryContentsProviderTest, Title) { 124 AutocompleteInput input(ASCIIToUTF16("PAGEONE"), string16(), true, false, 125 true, AutocompleteInput::ALL_MATCHES); 126 RunQuery(input, false); 127 128 // The results should be the first two pages. 129 const ACMatches& m = matches(); 130 ASSERT_EQ(2U, m.size()); 131 EXPECT_EQ(test_entries[0].url, m[0].destination_url.spec()); 132 EXPECT_STREQ(test_entries[0].title, UTF16ToUTF8(m[0].description).c_str()); 133 EXPECT_EQ(test_entries[1].url, m[1].destination_url.spec()); 134 EXPECT_STREQ(test_entries[1].title, UTF16ToUTF8(m[1].description).c_str()); 135 } 136 137 // The "minimal changes" flag should mean that we don't re-query the DB. 138 TEST_F(HistoryContentsProviderTest, MinimalChanges) { 139 // A minimal changes request when there have been no real queries should 140 // give us no results. 141 AutocompleteInput sync_input(ASCIIToUTF16("PAGEONE"), string16(), true, false, 142 true, AutocompleteInput::SYNCHRONOUS_MATCHES); 143 RunQuery(sync_input, true); 144 const ACMatches& m1 = matches(); 145 EXPECT_EQ(0U, m1.size()); 146 147 // Now do a "regular" query to get the results. 148 AutocompleteInput async_input(ASCIIToUTF16("PAGEONE"), string16(), true, 149 false, true, AutocompleteInput::ALL_MATCHES); 150 RunQuery(async_input, false); 151 const ACMatches& m2 = matches(); 152 EXPECT_EQ(2U, m2.size()); 153 154 // Now do a minimal one where we want synchronous results, and the results 155 // should still be there. 156 RunQuery(sync_input, true); 157 const ACMatches& m3 = matches(); 158 EXPECT_EQ(2U, m3.size()); 159 } 160 161 // Tests that the BookmarkModel is queried correctly. 162 TEST_F(HistoryContentsProviderTest, Bookmarks) { 163 profile()->CreateBookmarkModel(false); 164 profile()->BlockUntilBookmarkModelLoaded(); 165 166 // Add a bookmark. 167 GURL bookmark_url("http://www.google.com/4"); 168 profile()->GetBookmarkModel()->SetURLStarred(bookmark_url, 169 ASCIIToUTF16("bar"), true); 170 171 // Ask for synchronous. This should only get the bookmark. 172 AutocompleteInput sync_input(ASCIIToUTF16("bar"), string16(), true, false, 173 true, AutocompleteInput::SYNCHRONOUS_MATCHES); 174 RunQuery(sync_input, false); 175 const ACMatches& m1 = matches(); 176 ASSERT_EQ(1U, m1.size()); 177 EXPECT_EQ(bookmark_url, m1[0].destination_url); 178 EXPECT_EQ(ASCIIToUTF16("bar"), m1[0].description); 179 EXPECT_TRUE(m1[0].starred); 180 181 // Ask for async. We should get the bookmark immediately. 182 AutocompleteInput async_input(ASCIIToUTF16("bar"), string16(), true, false, 183 true, AutocompleteInput::ALL_MATCHES); 184 provider()->Start(async_input, false); 185 const ACMatches& m2 = matches(); 186 ASSERT_EQ(1U, m2.size()); 187 EXPECT_EQ(bookmark_url, m2[0].destination_url); 188 189 // Run the message loop (needed for async history results). 190 MessageLoop::current()->Run(); 191 192 // We should have two urls now, bookmark_url and http://www.google.com/3. 193 const ACMatches& m3 = matches(); 194 ASSERT_EQ(2U, m3.size()); 195 if (bookmark_url == m3[0].destination_url) { 196 EXPECT_EQ("http://www.google.com/3", m3[1].destination_url.spec()); 197 } else { 198 EXPECT_EQ(bookmark_url, m3[1].destination_url); 199 EXPECT_EQ("http://www.google.com/3", m3[0].destination_url.spec()); 200 } 201 } 202 203 // Tests that history is deleted properly. 204 TEST_F(HistoryContentsProviderTest, DeleteMatch) { 205 AutocompleteInput input(ASCIIToUTF16("bar"), string16(), true, false, true, 206 AutocompleteInput::ALL_MATCHES); 207 RunQuery(input, false); 208 209 // Query; the result should be the third page. 210 const ACMatches& m = matches(); 211 ASSERT_EQ(1U, m.size()); 212 EXPECT_EQ(test_entries[2].url, m[0].destination_url.spec()); 213 214 // Now delete the match and ensure it was removed. 215 provider()->DeleteMatch(m[0]); 216 EXPECT_EQ(0U, matches().size()); 217 } 218 219 // Tests deleting starred results from history, not affecting bookmarks/matches. 220 TEST_F(HistoryContentsProviderTest, DeleteStarredMatch) { 221 profile()->CreateBookmarkModel(false); 222 profile()->BlockUntilBookmarkModelLoaded(); 223 224 // Bookmark a history item. 225 GURL bookmark_url(test_entries[2].url); 226 profile()->GetBookmarkModel()->SetURLStarred(bookmark_url, 227 ASCIIToUTF16("bar"), true); 228 229 // Get the match to delete its history 230 AutocompleteInput input(ASCIIToUTF16("bar"), string16(), true, false, true, 231 AutocompleteInput::ALL_MATCHES); 232 RunQuery(input, false); 233 const ACMatches& m = matches(); 234 ASSERT_EQ(1U, m.size()); 235 236 // Now delete the match and ensure it was *not* removed. 237 provider()->DeleteMatch(m[0]); 238 EXPECT_EQ(1U, matches().size()); 239 240 // Run a query that would only match history (but the history is deleted) 241 AutocompleteInput you_input(ASCIIToUTF16("you"), string16(), true, false, 242 true, AutocompleteInput::ALL_MATCHES); 243 RunQuery(you_input, false); 244 EXPECT_EQ(0U, matches().size()); 245 246 // Run a query that matches the bookmark 247 RunQuery(input, false); 248 EXPECT_EQ(1U, matches().size()); 249 } 250 251 } // namespace 252