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/string_util.h" 6 #include "base/time.h" 7 #include "base/utf_string_conversions.h" 8 #include "build/build_config.h" 9 #include "chrome/browser/autocomplete/autocomplete_match.h" 10 #include "chrome/browser/autocomplete/search_provider.h" 11 #include "chrome/browser/history/history.h" 12 #include "chrome/browser/prefs/pref_service.h" 13 #include "chrome/browser/search_engines/template_url.h" 14 #include "chrome/browser/search_engines/template_url_model.h" 15 #include "chrome/common/net/test_url_fetcher_factory.h" 16 #include "chrome/common/pref_names.h" 17 #include "chrome/test/testing_browser_process.h" 18 #include "chrome/test/testing_browser_process_test.h" 19 #include "chrome/test/testing_profile.h" 20 #include "content/browser/browser_thread.h" 21 #include "net/url_request/url_request_status.h" 22 #include "testing/gtest/include/gtest/gtest.h" 23 24 // The following environment is configured for these tests: 25 // . The TemplateURL default_t_url_ is set as the default provider. 26 // . The TemplateURL keyword_t_url_ is added to the TemplateURLModel. This 27 // TemplateURL has a valid suggest and search URL. 28 // . The URL created by using the search term term1_ with default_t_url_ is 29 // added to history. 30 // . The URL created by using the search term keyword_term_ with keyword_t_url_ 31 // is added to history. 32 // . test_factory_ is set as the URLFetcher::Factory. 33 class SearchProviderTest : public TestingBrowserProcessTest, 34 public AutocompleteProvider::ACProviderListener { 35 public: 36 SearchProviderTest() 37 : default_t_url_(NULL), 38 term1_(UTF8ToUTF16("term1")), 39 keyword_t_url_(NULL), 40 keyword_term_(UTF8ToUTF16("keyword")), 41 io_thread_(BrowserThread::IO), 42 quit_when_done_(false) { 43 io_thread_.Start(); 44 } 45 46 // See description above class for what this registers. 47 virtual void SetUp(); 48 49 virtual void TearDown(); 50 51 protected: 52 // Returns an AutocompleteMatch in provider_'s set of matches that matches 53 // |url|. If there is no matching URL, an empty match is returned. 54 AutocompleteMatch FindMatchWithDestination(const GURL& url); 55 56 // ACProviderListener method. If we're waiting for the provider to finish, 57 // this exits the message loop. 58 virtual void OnProviderUpdate(bool updated_matches); 59 60 // Runs a nested message loop until provider_ is done. The message loop is 61 // exited by way of OnProviderUPdate. 62 void RunTillProviderDone(); 63 64 // Invokes Start on provider_, then runs all pending tasks. 65 void QueryForInput(const string16& text, 66 bool prevent_inline_autocomplete, 67 bool minimal_changes); 68 69 // Notifies the URLFetcher for the suggest query corresponding to the default 70 // search provider that it's done. 71 // Be sure and wrap calls to this in ASSERT_NO_FATAL_FAILURE. 72 void FinishDefaultSuggestQuery(); 73 74 // See description above class for details of these fields. 75 TemplateURL* default_t_url_; 76 const string16 term1_; 77 GURL term1_url_; 78 TemplateURL* keyword_t_url_; 79 const string16 keyword_term_; 80 GURL keyword_url_; 81 82 MessageLoopForUI message_loop_; 83 BrowserThread io_thread_; 84 85 // URLFetcher::Factory implementation registered. 86 TestURLFetcherFactory test_factory_; 87 88 // Profile we use. 89 TestingProfile profile_; 90 91 // The provider. 92 scoped_refptr<SearchProvider> provider_; 93 94 // If true, OnProviderUpdate exits out of the current message loop. 95 bool quit_when_done_; 96 97 DISALLOW_COPY_AND_ASSIGN(SearchProviderTest); 98 }; 99 100 void SearchProviderTest::SetUp() { 101 SearchProvider::set_query_suggest_immediately(true); 102 103 // We need both the history service and template url model loaded. 104 profile_.CreateHistoryService(true, false); 105 profile_.CreateTemplateURLModel(); 106 107 TemplateURLModel* turl_model = profile_.GetTemplateURLModel(); 108 109 // Reset the default TemplateURL. 110 default_t_url_ = new TemplateURL(); 111 default_t_url_->SetURL("http://defaultturl/{searchTerms}", 0, 0); 112 default_t_url_->SetSuggestionsURL("http://defaultturl2/{searchTerms}", 0, 0); 113 turl_model->Add(default_t_url_); 114 turl_model->SetDefaultSearchProvider(default_t_url_); 115 TemplateURLID default_provider_id = default_t_url_->id(); 116 ASSERT_NE(0, default_provider_id); 117 118 // Add url1, with search term term1_. 119 HistoryService* history = 120 profile_.GetHistoryService(Profile::EXPLICIT_ACCESS); 121 term1_url_ = GURL(default_t_url_->url()->ReplaceSearchTerms( 122 *default_t_url_, term1_, 0, string16())); 123 history->AddPageWithDetails(term1_url_, string16(), 1, 1, 124 base::Time::Now(), false, 125 history::SOURCE_BROWSED); 126 history->SetKeywordSearchTermsForURL(term1_url_, default_t_url_->id(), 127 term1_); 128 129 // Create another TemplateURL. 130 keyword_t_url_ = new TemplateURL(); 131 keyword_t_url_->set_keyword(ASCIIToUTF16("k")); 132 keyword_t_url_->SetURL("http://keyword/{searchTerms}", 0, 0); 133 keyword_t_url_->SetSuggestionsURL("http://suggest_keyword/{searchTerms}", 0, 134 0); 135 profile_.GetTemplateURLModel()->Add(keyword_t_url_); 136 ASSERT_NE(0, keyword_t_url_->id()); 137 138 // Add a page and search term for keyword_t_url_. 139 keyword_url_ = GURL(keyword_t_url_->url()->ReplaceSearchTerms( 140 *keyword_t_url_, keyword_term_, 0, string16())); 141 history->AddPageWithDetails(keyword_url_, string16(), 1, 1, 142 base::Time::Now(), false, 143 history::SOURCE_BROWSED); 144 history->SetKeywordSearchTermsForURL(keyword_url_, keyword_t_url_->id(), 145 keyword_term_); 146 147 // Keywords are updated by the InMemoryHistoryBackend only after the message 148 // has been processed on the history thread. Block until history processes all 149 // requests to ensure the InMemoryDatabase is the state we expect it. 150 profile_.BlockUntilHistoryProcessesPendingRequests(); 151 152 provider_ = new SearchProvider(this, &profile_); 153 154 URLFetcher::set_factory(&test_factory_); 155 } 156 157 void SearchProviderTest::OnProviderUpdate(bool updated_matches) { 158 if (quit_when_done_ && provider_->done()) { 159 quit_when_done_ = false; 160 message_loop_.Quit(); 161 } 162 } 163 164 void SearchProviderTest::RunTillProviderDone() { 165 if (provider_->done()) 166 return; 167 168 quit_when_done_ = true; 169 #if defined(OS_MACOSX) 170 message_loop_.Run(); 171 #else 172 message_loop_.Run(NULL); 173 #endif 174 } 175 176 void SearchProviderTest::QueryForInput(const string16& text, 177 bool prevent_inline_autocomplete, 178 bool minimal_changes) { 179 // Start a query. 180 AutocompleteInput input(text, string16(), prevent_inline_autocomplete, 181 false, true, AutocompleteInput::ALL_MATCHES); 182 provider_->Start(input, minimal_changes); 183 184 // RunAllPending so that the task scheduled by SearchProvider to create the 185 // URLFetchers runs. 186 message_loop_.RunAllPending(); 187 } 188 189 void SearchProviderTest::TearDown() { 190 message_loop_.RunAllPending(); 191 192 URLFetcher::set_factory(NULL); 193 194 // Shutdown the provider before the profile. 195 provider_ = NULL; 196 } 197 198 AutocompleteMatch SearchProviderTest::FindMatchWithDestination( 199 const GURL& url) { 200 for (ACMatches::const_iterator i = provider_->matches().begin(); 201 i != provider_->matches().end(); ++i) { 202 if (i->destination_url == url) 203 return *i; 204 } 205 return AutocompleteMatch(NULL, 1, false, AutocompleteMatch::HISTORY_URL); 206 } 207 208 void SearchProviderTest::FinishDefaultSuggestQuery() { 209 TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID( 210 SearchProvider::kDefaultProviderURLFetcherID); 211 ASSERT_TRUE(default_fetcher); 212 213 // Tell the SearchProvider the default suggest query is done. 214 default_fetcher->delegate()->OnURLFetchComplete( 215 default_fetcher, GURL(), net::URLRequestStatus(), 200, ResponseCookies(), 216 std::string()); 217 } 218 219 // Tests ----------------------------------------------------------------------- 220 221 // Make sure we query history for the default provider and a URLFetcher is 222 // created for the default provider suggest results. 223 TEST_F(SearchProviderTest, QueryDefaultProvider) { 224 string16 term = term1_.substr(0, term1_.size() - 1); 225 QueryForInput(term, false, false); 226 227 // Make sure the default providers suggest service was queried. 228 TestURLFetcher* fetcher = test_factory_.GetFetcherByID( 229 SearchProvider::kDefaultProviderURLFetcherID); 230 ASSERT_TRUE(fetcher); 231 232 // And the URL matches what we expected. 233 GURL expected_url = GURL(default_t_url_->suggestions_url()-> 234 ReplaceSearchTerms(*default_t_url_, term, 0, string16())); 235 ASSERT_TRUE(fetcher->original_url() == expected_url); 236 237 // Tell the SearchProvider the suggest query is done. 238 fetcher->delegate()->OnURLFetchComplete( 239 fetcher, GURL(), net::URLRequestStatus(), 200, ResponseCookies(), 240 std::string()); 241 fetcher = NULL; 242 243 // Run till the history results complete. 244 RunTillProviderDone(); 245 246 // The SearchProvider is done. Make sure it has a result for the history 247 // term term1. 248 AutocompleteMatch term1_match = FindMatchWithDestination(term1_url_); 249 EXPECT_TRUE(!term1_match.destination_url.is_empty()); 250 // Term1 should have a description. 251 EXPECT_FALSE(term1_match.description.empty()); 252 253 GURL what_you_typed_url = GURL(default_t_url_->url()->ReplaceSearchTerms( 254 *default_t_url_, term, 0, string16())); 255 AutocompleteMatch what_you_typed_match = 256 FindMatchWithDestination(what_you_typed_url); 257 EXPECT_TRUE(!what_you_typed_match.destination_url.is_empty()); 258 EXPECT_TRUE(what_you_typed_match.description.empty()); 259 260 // The match for term1 should be more relevant than the what you typed result. 261 EXPECT_GT(term1_match.relevance, what_you_typed_match.relevance); 262 } 263 264 TEST_F(SearchProviderTest, HonorPreventInlineAutocomplete) { 265 string16 term = term1_.substr(0, term1_.size() - 1); 266 QueryForInput(term, true, false); 267 268 ASSERT_FALSE(provider_->matches().empty()); 269 ASSERT_EQ(AutocompleteMatch::SEARCH_WHAT_YOU_TYPED, 270 provider_->matches()[0].type); 271 } 272 273 // Issues a query that matches the registered keyword and makes sure history 274 // is queried as well as URLFetchers getting created. 275 TEST_F(SearchProviderTest, QueryKeywordProvider) { 276 string16 term = keyword_term_.substr(0, keyword_term_.size() - 1); 277 QueryForInput(keyword_t_url_->keyword() + UTF8ToUTF16(" ") + term, false, 278 false); 279 280 // Make sure the default providers suggest service was queried. 281 TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID( 282 SearchProvider::kDefaultProviderURLFetcherID); 283 ASSERT_TRUE(default_fetcher); 284 285 // Tell the SearchProvider the default suggest query is done. 286 default_fetcher->delegate()->OnURLFetchComplete( 287 default_fetcher, GURL(), net::URLRequestStatus(), 200, ResponseCookies(), 288 std::string()); 289 default_fetcher = NULL; 290 291 // Make sure the keyword providers suggest service was queried. 292 TestURLFetcher* keyword_fetcher = test_factory_.GetFetcherByID( 293 SearchProvider::kKeywordProviderURLFetcherID); 294 ASSERT_TRUE(keyword_fetcher); 295 296 // And the URL matches what we expected. 297 GURL expected_url = GURL(keyword_t_url_->suggestions_url()-> 298 ReplaceSearchTerms(*keyword_t_url_, term, 0, string16())); 299 ASSERT_TRUE(keyword_fetcher->original_url() == expected_url); 300 301 // Tell the SearchProvider the keyword suggest query is done. 302 keyword_fetcher->delegate()->OnURLFetchComplete( 303 keyword_fetcher, GURL(), net::URLRequestStatus(), 200, ResponseCookies(), 304 std::string()); 305 keyword_fetcher = NULL; 306 307 // Run till the history results complete. 308 RunTillProviderDone(); 309 310 // The SearchProvider is done. Make sure it has a result for the history 311 // term keyword. 312 AutocompleteMatch match = FindMatchWithDestination(keyword_url_); 313 ASSERT_TRUE(!match.destination_url.is_empty()); 314 315 // The match should have a TemplateURL. 316 EXPECT_TRUE(match.template_url); 317 318 // The fill into edit should contain the keyword. 319 EXPECT_EQ(keyword_t_url_->keyword() + char16(' ') + keyword_term_, 320 match.fill_into_edit); 321 } 322 323 TEST_F(SearchProviderTest, DontSendPrivateDataToSuggest) { 324 // None of the following input strings should be sent to the suggest server, 325 // because they may contain private data. 326 const char* inputs[] = { 327 "username:password", 328 "http://username:password", 329 "https://username:password", 330 "username:password@hostname", 331 "http://username:password@hostname/", 332 "file://filename", 333 "data://data", 334 "unknownscheme:anything", 335 "http://hostname/?query=q", 336 "http://hostname/path#ref", 337 "https://hostname/path", 338 }; 339 340 for (size_t i = 0; i < arraysize(inputs); ++i) { 341 QueryForInput(ASCIIToUTF16(inputs[i]), false, false); 342 // Make sure the default providers suggest service was not queried. 343 ASSERT_TRUE(test_factory_.GetFetcherByID( 344 SearchProvider::kDefaultProviderURLFetcherID) == NULL); 345 // Run till the history results complete. 346 RunTillProviderDone(); 347 } 348 } 349 350 // Make sure FinalizeInstantQuery works. 351 TEST_F(SearchProviderTest, FinalizeInstantQuery) { 352 PrefService* service = profile_.GetPrefs(); 353 service->SetBoolean(prefs::kInstantEnabled, true); 354 355 QueryForInput(ASCIIToUTF16("foo"), false, false); 356 357 // Wait until history and the suggest query complete. 358 profile_.BlockUntilHistoryProcessesPendingRequests(); 359 ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); 360 361 // When instant is enabled the provider isn't done until it hears from 362 // instant. 363 EXPECT_FALSE(provider_->done()); 364 365 // Tell the provider instant is done. 366 provider_->FinalizeInstantQuery(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); 367 368 // The provider should now be done. 369 EXPECT_TRUE(provider_->done()); 370 371 // There should be two matches, one for what you typed, the other for 372 // 'foobar'. 373 EXPECT_EQ(2u, provider_->matches().size()); 374 GURL instant_url = GURL(default_t_url_->url()->ReplaceSearchTerms( 375 *default_t_url_, ASCIIToUTF16("foobar"), 0, string16())); 376 AutocompleteMatch instant_match = FindMatchWithDestination(instant_url); 377 EXPECT_TRUE(!instant_match.destination_url.is_empty()); 378 379 // And the 'foobar' match should have a description. 380 EXPECT_FALSE(instant_match.description.empty()); 381 382 // Make sure the what you typed match has no description. 383 GURL what_you_typed_url = GURL(default_t_url_->url()->ReplaceSearchTerms( 384 *default_t_url_, ASCIIToUTF16("foo"), 0, string16())); 385 AutocompleteMatch what_you_typed_match = 386 FindMatchWithDestination(what_you_typed_url); 387 EXPECT_TRUE(!what_you_typed_match.destination_url.is_empty()); 388 EXPECT_TRUE(what_you_typed_match.description.empty()); 389 390 // The instant search should be more relevant. 391 EXPECT_GT(instant_match.relevance, what_you_typed_match.relevance); 392 } 393 394 // Make sure that if FinalizeInstantQuery is invoked before suggest results 395 // return, the suggest text from FinalizeInstantQuery is remembered. 396 TEST_F(SearchProviderTest, RememberInstantQuery) { 397 PrefService* service = profile_.GetPrefs(); 398 service->SetBoolean(prefs::kInstantEnabled, true); 399 400 QueryForInput(ASCIIToUTF16("foo"), false, false); 401 402 // Finalize the instant query immediately. 403 provider_->FinalizeInstantQuery(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); 404 405 // There should be two matches, one for what you typed, the other for 406 // 'foobar'. 407 EXPECT_EQ(2u, provider_->matches().size()); 408 GURL instant_url = GURL(default_t_url_->url()->ReplaceSearchTerms( 409 *default_t_url_, ASCIIToUTF16("foobar"), 0, string16())); 410 AutocompleteMatch instant_match = FindMatchWithDestination(instant_url); 411 EXPECT_FALSE(instant_match.destination_url.is_empty()); 412 413 // Wait until history and the suggest query complete. 414 profile_.BlockUntilHistoryProcessesPendingRequests(); 415 ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); 416 417 // Provider should be done. 418 EXPECT_TRUE(provider_->done()); 419 420 // There should be two matches, one for what you typed, the other for 421 // 'foobar'. 422 EXPECT_EQ(2u, provider_->matches().size()); 423 instant_match = FindMatchWithDestination(instant_url); 424 EXPECT_FALSE(instant_match.destination_url.is_empty()); 425 426 // And the 'foobar' match should have a description. 427 EXPECT_FALSE(instant_match.description.empty()); 428 } 429 430 // Make sure that if trailing whitespace is added to the text supplied to 431 // AutocompleteInput the default suggest text is cleared. 432 TEST_F(SearchProviderTest, DifferingText) { 433 PrefService* service = profile_.GetPrefs(); 434 service->SetBoolean(prefs::kInstantEnabled, true); 435 436 QueryForInput(ASCIIToUTF16("foo"), false, false); 437 438 // Wait until history and the suggest query complete. 439 profile_.BlockUntilHistoryProcessesPendingRequests(); 440 ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); 441 442 // Finalize the instant query immediately. 443 provider_->FinalizeInstantQuery(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); 444 445 // Query with input that ends up getting trimmed to be the same as was 446 // originally supplied. 447 QueryForInput(ASCIIToUTF16("foo "), false, true); 448 449 // There should only one match, for what you typed. 450 EXPECT_EQ(1u, provider_->matches().size()); 451 GURL instant_url = GURL(default_t_url_->url()->ReplaceSearchTerms( 452 *default_t_url_, ASCIIToUTF16("foo"), 0, string16())); 453 AutocompleteMatch instant_match = FindMatchWithDestination(instant_url); 454 EXPECT_FALSE(instant_match.destination_url.is_empty()); 455 } 456 457 TEST_F(SearchProviderTest, DontAutocompleteURLLikeTerms) { 458 profile_.CreateAutocompleteClassifier(); 459 string16 term(ASCIIToUTF16("docs.google.com")); 460 HistoryService* history = 461 profile_.GetHistoryService(Profile::EXPLICIT_ACCESS); 462 GURL url = GURL(default_t_url_->url()->ReplaceSearchTerms( 463 *default_t_url_, term, 0, string16())); 464 history->AddPageWithDetails( 465 url, string16(), 1, 1, base::Time::Now(), false, history::SOURCE_BROWSED); 466 history->SetKeywordSearchTermsForURL(url, default_t_url_->id(), term); 467 468 // Add the term as a url. 469 history->AddPageWithDetails( 470 GURL("http://docs.google.com"), string16(), 1, 1, base::Time::Now(), 471 false, history::SOURCE_BROWSED); 472 473 profile_.BlockUntilHistoryProcessesPendingRequests(); 474 475 QueryForInput(ASCIIToUTF16("docs"), false, false); 476 477 // Wait until history and the suggest query complete. 478 profile_.BlockUntilHistoryProcessesPendingRequests(); 479 ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); 480 481 // Provider should be done. 482 EXPECT_TRUE(provider_->done()); 483 484 // There should be two matches, one for what you typed, the other for 485 // 'docs.google.com'. The search term should have a lower priority than the 486 // what you typed match. 487 ASSERT_EQ(2u, provider_->matches().size()); 488 AutocompleteMatch term_match = FindMatchWithDestination(url); 489 GURL what_you_typed_url = GURL(default_t_url_->url()->ReplaceSearchTerms( 490 *default_t_url_, ASCIIToUTF16("docs"), 0, string16())); 491 AutocompleteMatch what_you_typed_match = 492 FindMatchWithDestination(what_you_typed_url); 493 EXPECT_GT(what_you_typed_match.relevance, term_match.relevance); 494 } 495