Home | History | Annotate | Download | only in autocomplete
      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