search_provider_unittest.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 "chrome/browser/autocomplete/search_provider.h"
6
7#include "base/run_loop.h"
8#include "base/string_util.h"
9#include "base/time.h"
10#include "base/utf_string_conversions.h"
11#include "build/build_config.h"
12#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
13#include "chrome/browser/autocomplete/autocomplete_controller.h"
14#include "chrome/browser/autocomplete/autocomplete_input.h"
15#include "chrome/browser/autocomplete/autocomplete_match.h"
16#include "chrome/browser/autocomplete/autocomplete_provider.h"
17#include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
18#include "chrome/browser/history/history.h"
19#include "chrome/browser/history/history_service_factory.h"
20#include "chrome/browser/instant/instant_controller.h"
21#include "chrome/browser/prefs/pref_service.h"
22#include "chrome/browser/search_engines/template_url.h"
23#include "chrome/browser/search_engines/template_url_service.h"
24#include "chrome/browser/search_engines/template_url_service_factory.h"
25#include "chrome/common/pref_names.h"
26#include "chrome/test/base/testing_browser_process.h"
27#include "chrome/test/base/testing_profile.h"
28#include "content/public/test/test_browser_thread.h"
29#include "net/url_request/test_url_fetcher_factory.h"
30#include "net/url_request/url_request_status.h"
31#include "testing/gtest/include/gtest/gtest.h"
32
33using content::BrowserThread;
34
35// The following environment is configured for these tests:
36// . The TemplateURL default_t_url_ is set as the default provider.
37// . The TemplateURL keyword_t_url_ is added to the TemplateURLService. This
38//   TemplateURL has a valid suggest and search URL.
39// . The URL created by using the search term term1_ with default_t_url_ is
40//   added to history.
41// . The URL created by using the search term keyword_term_ with keyword_t_url_
42//   is added to history.
43// . test_factory_ is set as the URLFetcherFactory.
44class SearchProviderTest : public testing::Test,
45                           public AutocompleteProviderListener {
46 public:
47  SearchProviderTest()
48      : default_t_url_(NULL),
49        term1_(UTF8ToUTF16("term1")),
50        keyword_t_url_(NULL),
51        keyword_term_(UTF8ToUTF16("keyword")),
52        ui_thread_(BrowserThread::UI, &message_loop_),
53        io_thread_(BrowserThread::IO),
54        quit_when_done_(false) {
55    io_thread_.Start();
56  }
57
58  // See description above class for what this registers.
59  virtual void SetUp();
60
61  virtual void TearDown();
62
63 protected:
64  // Adds a search for |term|, using the engine |t_url| to the history, and
65  // returns the URL for that search.
66  GURL AddSearchToHistory(TemplateURL* t_url, string16 term, int visit_count);
67
68  // Looks for a match in |provider_| with |contents| equal to |contents|.
69  // Sets |match| to it if found.  Returns whether |match| was set.
70  bool FindMatchWithContents(const string16& contents,
71                             AutocompleteMatch* match);
72
73  // Looks for a match in |provider_| with destination |url|.  Sets |match| to
74  // it if found.  Returns whether |match| was set.
75  bool FindMatchWithDestination(const GURL& url, AutocompleteMatch* match);
76
77  // AutocompleteProviderListener:
78  // If we're waiting for the provider to finish, this exits the message loop.
79  virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
80
81  // Runs a nested message loop until provider_ is done. The message loop is
82  // exited by way of OnProviderUPdate.
83  void RunTillProviderDone();
84
85  // Invokes Start on provider_, then runs all pending tasks.
86  void QueryForInput(const string16& text,
87                     const string16& desired_tld,
88                     bool prevent_inline_autocomplete);
89
90  // Calls QueryForInput(), finishes any suggest query, then if |wyt_match| is
91  // non-NULL, sets it to the "what you typed" entry for |text|.
92  void QueryForInputAndSetWYTMatch(const string16& text,
93                                   AutocompleteMatch* wyt_match);
94
95  // Notifies the URLFetcher for the suggest query corresponding to the default
96  // search provider that it's done.
97  // Be sure and wrap calls to this in ASSERT_NO_FATAL_FAILURE.
98  void FinishDefaultSuggestQuery();
99
100  // See description above class for details of these fields.
101  TemplateURL* default_t_url_;
102  const string16 term1_;
103  GURL term1_url_;
104  TemplateURL* keyword_t_url_;
105  const string16 keyword_term_;
106  GURL keyword_url_;
107
108  MessageLoopForUI message_loop_;
109  content::TestBrowserThread ui_thread_;
110  content::TestBrowserThread io_thread_;
111
112  // URLFetcherFactory implementation registered.
113  net::TestURLFetcherFactory test_factory_;
114
115  // Profile we use.
116  TestingProfile profile_;
117
118  // The provider.
119  scoped_refptr<SearchProvider> provider_;
120
121  // If true, OnProviderUpdate exits out of the current message loop.
122  bool quit_when_done_;
123
124  DISALLOW_COPY_AND_ASSIGN(SearchProviderTest);
125};
126
127void SearchProviderTest::SetUp() {
128  SearchProvider::set_query_suggest_immediately(true);
129
130  // We need both the history service and template url model loaded.
131  profile_.CreateHistoryService(true, false);
132  TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
133      &profile_, &TemplateURLServiceFactory::BuildInstanceFor);
134
135  TemplateURLService* turl_model =
136      TemplateURLServiceFactory::GetForProfile(&profile_);
137
138  turl_model->Load();
139
140  // Reset the default TemplateURL.
141  TemplateURLData data;
142  data.short_name = ASCIIToUTF16("t");
143  data.SetURL("http://defaultturl/{searchTerms}");
144  data.suggestions_url = "http://defaultturl2/{searchTerms}";
145  default_t_url_ = new TemplateURL(&profile_, data);
146  turl_model->Add(default_t_url_);
147  turl_model->SetDefaultSearchProvider(default_t_url_);
148  TemplateURLID default_provider_id = default_t_url_->id();
149  ASSERT_NE(0, default_provider_id);
150
151  // Add url1, with search term term1_.
152  term1_url_ = AddSearchToHistory(default_t_url_, term1_, 1);
153
154  // Create another TemplateURL.
155  data.short_name = ASCIIToUTF16("k");
156  data.SetKeyword(ASCIIToUTF16("k"));
157  data.SetURL("http://keyword/{searchTerms}");
158  data.suggestions_url = "http://suggest_keyword/{searchTerms}";
159  keyword_t_url_ = new TemplateURL(&profile_, data);
160  turl_model->Add(keyword_t_url_);
161  ASSERT_NE(0, keyword_t_url_->id());
162
163  // Add a page and search term for keyword_t_url_.
164  keyword_url_ = AddSearchToHistory(keyword_t_url_, keyword_term_, 1);
165
166  // Keywords are updated by the InMemoryHistoryBackend only after the message
167  // has been processed on the history thread. Block until history processes all
168  // requests to ensure the InMemoryDatabase is the state we expect it.
169  profile_.BlockUntilHistoryProcessesPendingRequests();
170
171  provider_ = new SearchProvider(this, &profile_);
172}
173
174void SearchProviderTest::OnProviderUpdate(bool updated_matches) {
175  if (quit_when_done_ && provider_->done()) {
176    quit_when_done_ = false;
177    message_loop_.Quit();
178  }
179}
180
181void SearchProviderTest::RunTillProviderDone() {
182  if (provider_->done())
183    return;
184
185  quit_when_done_ = true;
186#if defined(OS_ANDROID)
187  // Android doesn't have Run(), only Start().
188  message_loop_.Start();
189#else
190  base::RunLoop run_loop;
191  run_loop.Run();
192#endif
193}
194
195void SearchProviderTest::QueryForInput(const string16& text,
196                                       const string16& desired_tld,
197                                       bool prevent_inline_autocomplete) {
198  // Start a query.
199  AutocompleteInput input(text, desired_tld, prevent_inline_autocomplete,
200                          false, true, AutocompleteInput::ALL_MATCHES);
201  provider_->Start(input, false);
202
203  // RunAllPending so that the task scheduled by SearchProvider to create the
204  // URLFetchers runs.
205  message_loop_.RunAllPending();
206}
207
208void SearchProviderTest::QueryForInputAndSetWYTMatch(
209    const string16& text,
210    AutocompleteMatch* wyt_match) {
211  QueryForInput(text, string16(), false);
212  profile_.BlockUntilHistoryProcessesPendingRequests();
213  ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery());
214  EXPECT_NE(InstantController::IsInstantEnabled(&profile_), provider_->done());
215  if (!wyt_match)
216    return;
217  ASSERT_GE(provider_->matches().size(), 1u);
218  EXPECT_TRUE(FindMatchWithDestination(GURL(
219      default_t_url_->url_ref().ReplaceSearchTerms(
220          TemplateURLRef::SearchTermsArgs(text))),
221      wyt_match));
222}
223
224void SearchProviderTest::TearDown() {
225  message_loop_.RunAllPending();
226
227  // Shutdown the provider before the profile.
228  provider_ = NULL;
229}
230
231GURL SearchProviderTest::AddSearchToHistory(TemplateURL* t_url,
232                                            string16 term,
233                                            int visit_count) {
234  HistoryService* history =
235      HistoryServiceFactory::GetForProfile(&profile_,
236                                           Profile::EXPLICIT_ACCESS);
237  GURL search(t_url->url_ref().ReplaceSearchTerms(
238      TemplateURLRef::SearchTermsArgs(term)));
239  static base::Time last_added_time;
240  last_added_time = std::max(base::Time::Now(),
241      last_added_time + base::TimeDelta::FromMicroseconds(1));
242  history->AddPageWithDetails(search, string16(), visit_count, visit_count,
243      last_added_time, false, history::SOURCE_BROWSED);
244  history->SetKeywordSearchTermsForURL(search, t_url->id(), term);
245  return search;
246}
247
248bool SearchProviderTest::FindMatchWithContents(const string16& contents,
249                                               AutocompleteMatch* match) {
250  for (ACMatches::const_iterator i = provider_->matches().begin();
251       i != provider_->matches().end(); ++i) {
252    if (i->contents == contents) {
253      *match = *i;
254      return true;
255    }
256  }
257  return false;
258}
259
260bool SearchProviderTest::FindMatchWithDestination(const GURL& url,
261                                                  AutocompleteMatch* match) {
262  for (ACMatches::const_iterator i = provider_->matches().begin();
263       i != provider_->matches().end(); ++i) {
264    if (i->destination_url == url) {
265      *match = *i;
266      return true;
267    }
268  }
269  return false;
270}
271
272void SearchProviderTest::FinishDefaultSuggestQuery() {
273  net::TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID(
274      SearchProvider::kDefaultProviderURLFetcherID);
275  ASSERT_TRUE(default_fetcher);
276
277  // Tell the SearchProvider the default suggest query is done.
278  default_fetcher->set_response_code(200);
279  default_fetcher->delegate()->OnURLFetchComplete(default_fetcher);
280}
281
282// Tests -----------------------------------------------------------------------
283
284// Make sure we query history for the default provider and a URLFetcher is
285// created for the default provider suggest results.
286TEST_F(SearchProviderTest, QueryDefaultProvider) {
287  string16 term = term1_.substr(0, term1_.length() - 1);
288  QueryForInput(term, string16(), false);
289
290  // Make sure the default providers suggest service was queried.
291  net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(
292      SearchProvider::kDefaultProviderURLFetcherID);
293  ASSERT_TRUE(fetcher);
294
295  // And the URL matches what we expected.
296  GURL expected_url(default_t_url_->suggestions_url_ref().ReplaceSearchTerms(
297      TemplateURLRef::SearchTermsArgs(term)));
298  ASSERT_TRUE(fetcher->GetOriginalURL() == expected_url);
299
300  // Tell the SearchProvider the suggest query is done.
301  fetcher->set_response_code(200);
302  fetcher->delegate()->OnURLFetchComplete(fetcher);
303  fetcher = NULL;
304
305  // Run till the history results complete.
306  RunTillProviderDone();
307
308  // The SearchProvider is done. Make sure it has a result for the history
309  // term term1.
310  AutocompleteMatch term1_match;
311  EXPECT_TRUE(FindMatchWithDestination(term1_url_, &term1_match));
312  // Term1 should not have a description, it's set later.
313  EXPECT_TRUE(term1_match.description.empty());
314
315  AutocompleteMatch wyt_match;
316  EXPECT_TRUE(FindMatchWithDestination(
317      GURL(default_t_url_->url_ref().ReplaceSearchTerms(
318          TemplateURLRef::SearchTermsArgs(term))), &wyt_match));
319  EXPECT_TRUE(wyt_match.description.empty());
320
321  // The match for term1 should be more relevant than the what you typed result.
322  EXPECT_GT(term1_match.relevance, wyt_match.relevance);
323}
324
325TEST_F(SearchProviderTest, HonorPreventInlineAutocomplete) {
326  string16 term = term1_.substr(0, term1_.length() - 1);
327  QueryForInput(term, string16(), true);
328
329  ASSERT_FALSE(provider_->matches().empty());
330  ASSERT_EQ(AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
331            provider_->matches()[0].type);
332}
333
334// Issues a query that matches the registered keyword and makes sure history
335// is queried as well as URLFetchers getting created.
336TEST_F(SearchProviderTest, QueryKeywordProvider) {
337  string16 term = keyword_term_.substr(0, keyword_term_.length() - 1);
338  QueryForInput(keyword_t_url_->keyword() + UTF8ToUTF16(" ") + term,
339                string16(), false);
340
341  // Make sure the default providers suggest service was queried.
342  net::TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID(
343      SearchProvider::kDefaultProviderURLFetcherID);
344  ASSERT_TRUE(default_fetcher);
345
346  // Tell the SearchProvider the default suggest query is done.
347  default_fetcher->set_response_code(200);
348  default_fetcher->delegate()->OnURLFetchComplete(default_fetcher);
349  default_fetcher = NULL;
350
351  // Make sure the keyword providers suggest service was queried.
352  net::TestURLFetcher* keyword_fetcher = test_factory_.GetFetcherByID(
353      SearchProvider::kKeywordProviderURLFetcherID);
354  ASSERT_TRUE(keyword_fetcher);
355
356  // And the URL matches what we expected.
357  GURL expected_url(keyword_t_url_->suggestions_url_ref().ReplaceSearchTerms(
358      TemplateURLRef::SearchTermsArgs(term)));
359  ASSERT_TRUE(keyword_fetcher->GetOriginalURL() == expected_url);
360
361  // Tell the SearchProvider the keyword suggest query is done.
362  keyword_fetcher->set_response_code(200);
363  keyword_fetcher->delegate()->OnURLFetchComplete(keyword_fetcher);
364  keyword_fetcher = NULL;
365
366  // Run till the history results complete.
367  RunTillProviderDone();
368
369  // The SearchProvider is done. Make sure it has a result for the history
370  // term keyword.
371  AutocompleteMatch match;
372  EXPECT_TRUE(FindMatchWithDestination(keyword_url_, &match));
373
374  // The match should have an associated keyword.
375  EXPECT_FALSE(match.keyword.empty());
376
377  // The fill into edit should contain the keyword.
378  EXPECT_EQ(keyword_t_url_->keyword() + char16(' ') + keyword_term_,
379            match.fill_into_edit);
380}
381
382TEST_F(SearchProviderTest, DontSendPrivateDataToSuggest) {
383  // None of the following input strings should be sent to the suggest server,
384  // because they may contain private data.
385  const char* inputs[] = {
386    "username:password",
387    "http://username:password",
388    "https://username:password",
389    "username:password@hostname",
390    "http://username:password@hostname/",
391    "file://filename",
392    "data://data",
393    "unknownscheme:anything",
394    "http://hostname/?query=q",
395    "http://hostname/path#ref",
396    "https://hostname/path",
397  };
398
399  for (size_t i = 0; i < arraysize(inputs); ++i) {
400    QueryForInput(ASCIIToUTF16(inputs[i]), string16(), false);
401    // Make sure the default providers suggest service was not queried.
402    ASSERT_TRUE(test_factory_.GetFetcherByID(
403        SearchProvider::kDefaultProviderURLFetcherID) == NULL);
404    // Run till the history results complete.
405    RunTillProviderDone();
406  }
407}
408
409// Make sure FinalizeInstantQuery works.
410TEST_F(SearchProviderTest, FinalizeInstantQuery) {
411  PrefService* service = profile_.GetPrefs();
412  service->SetBoolean(prefs::kInstantEnabled, true);
413
414  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("foo"),
415                                                      NULL));
416
417  // Tell the provider instant is done.
418  provider_->FinalizeInstantQuery(ASCIIToUTF16("foo"),
419                                  InstantSuggestion(ASCIIToUTF16("bar"),
420                                                    INSTANT_COMPLETE_NOW,
421                                                    INSTANT_SUGGESTION_SEARCH));
422
423  // The provider should now be done.
424  EXPECT_TRUE(provider_->done());
425
426  // There should be two matches, one for what you typed, the other for
427  // 'foobar'.
428  EXPECT_EQ(2u, provider_->matches().size());
429  GURL instant_url(default_t_url_->url_ref().ReplaceSearchTerms(
430      TemplateURLRef::SearchTermsArgs(ASCIIToUTF16("foobar"))));
431  AutocompleteMatch instant_match;
432  EXPECT_TRUE(FindMatchWithDestination(instant_url, &instant_match));
433
434  // And the 'foobar' match should not have a description, it'll be set later.
435  EXPECT_TRUE(instant_match.description.empty());
436
437  // Make sure the what you typed match has no description.
438  AutocompleteMatch wyt_match;
439  EXPECT_TRUE(FindMatchWithDestination(
440      GURL(default_t_url_->url_ref().ReplaceSearchTerms(
441          TemplateURLRef::SearchTermsArgs(ASCIIToUTF16("foo")))),
442          &wyt_match));
443  EXPECT_TRUE(wyt_match.description.empty());
444
445  // The instant search should be more relevant.
446  EXPECT_GT(instant_match.relevance, wyt_match.relevance);
447}
448
449// Make sure that if FinalizeInstantQuery is invoked before suggest results
450// return, the suggest text from FinalizeInstantQuery is remembered.
451TEST_F(SearchProviderTest, RememberInstantQuery) {
452  PrefService* service = profile_.GetPrefs();
453  service->SetBoolean(prefs::kInstantEnabled, true);
454
455  QueryForInput(ASCIIToUTF16("foo"), string16(), false);
456
457  // Finalize the instant query immediately.
458  provider_->FinalizeInstantQuery(ASCIIToUTF16("foo"),
459                                  InstantSuggestion(ASCIIToUTF16("bar"),
460                                                    INSTANT_COMPLETE_NOW,
461                                                    INSTANT_SUGGESTION_SEARCH));
462
463  // There should be two matches, one for what you typed, the other for
464  // 'foobar'.
465  EXPECT_EQ(2u, provider_->matches().size());
466  GURL instant_url(default_t_url_->url_ref().ReplaceSearchTerms(
467      TemplateURLRef::SearchTermsArgs(ASCIIToUTF16("foobar"))));
468  AutocompleteMatch instant_match;
469  EXPECT_TRUE(FindMatchWithDestination(instant_url, &instant_match));
470
471  // Wait until history and the suggest query complete.
472  profile_.BlockUntilHistoryProcessesPendingRequests();
473  ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery());
474
475  // Provider should be done.
476  EXPECT_TRUE(provider_->done());
477
478  // There should be two matches, one for what you typed, the other for
479  // 'foobar'.
480  EXPECT_EQ(2u, provider_->matches().size());
481  EXPECT_TRUE(FindMatchWithDestination(instant_url, &instant_match));
482
483  // And the 'foobar' match should not have a description, it'll be set later.
484  EXPECT_TRUE(instant_match.description.empty());
485}
486
487// Make sure that if trailing whitespace is added to the text supplied to
488// AutocompleteInput the default suggest text is cleared.
489TEST_F(SearchProviderTest, DifferingText) {
490  PrefService* service = profile_.GetPrefs();
491  service->SetBoolean(prefs::kInstantEnabled, true);
492
493  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("foo"),
494                                                      NULL));
495
496  // Finalize the instant query immediately.
497  provider_->FinalizeInstantQuery(ASCIIToUTF16("foo"),
498                                  InstantSuggestion(ASCIIToUTF16("bar"),
499                                                    INSTANT_COMPLETE_NOW,
500                                                    INSTANT_SUGGESTION_SEARCH));
501
502  // Query with the same input text, but trailing whitespace.
503  AutocompleteMatch instant_match;
504  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("foo "),
505                                                      &instant_match));
506
507  // There should only one match, for what you typed.
508  EXPECT_EQ(1u, provider_->matches().size());
509  EXPECT_FALSE(instant_match.destination_url.is_empty());
510}
511
512TEST_F(SearchProviderTest, DontAutocompleteURLLikeTerms) {
513  AutocompleteClassifierFactory::GetInstance()->SetTestingFactoryAndUse(
514      &profile_, &AutocompleteClassifierFactory::BuildInstanceFor);
515  GURL url = AddSearchToHistory(default_t_url_,
516                                ASCIIToUTF16("docs.google.com"), 1);
517
518  // Add the term as a url.
519  HistoryServiceFactory::GetForProfile(&profile_, Profile::EXPLICIT_ACCESS)->
520      AddPageWithDetails(GURL("http://docs.google.com"), string16(), 1, 1,
521                         base::Time::Now(), false, history::SOURCE_BROWSED);
522  profile_.BlockUntilHistoryProcessesPendingRequests();
523
524  AutocompleteMatch wyt_match;
525  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("docs"),
526                                                      &wyt_match));
527
528  // There should be two matches, one for what you typed, the other for
529  // 'docs.google.com'. The search term should have a lower priority than the
530  // what you typed match.
531  ASSERT_EQ(2u, provider_->matches().size());
532  AutocompleteMatch term_match;
533  EXPECT_TRUE(FindMatchWithDestination(url, &term_match));
534  EXPECT_GT(wyt_match.relevance, term_match.relevance);
535}
536
537// A multiword search with one visit should not autocomplete until multiple
538// words are typed.
539TEST_F(SearchProviderTest, DontAutocompleteUntilMultipleWordsTyped) {
540  GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("one search"),
541                                   1));
542  profile_.BlockUntilHistoryProcessesPendingRequests();
543
544  AutocompleteMatch wyt_match;
545  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("on"),
546                                                      &wyt_match));
547  ASSERT_EQ(2u, provider_->matches().size());
548  AutocompleteMatch term_match;
549  EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match));
550  EXPECT_GT(wyt_match.relevance, term_match.relevance);
551
552  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("one se"),
553                                                      &wyt_match));
554  ASSERT_EQ(2u, provider_->matches().size());
555  EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match));
556  EXPECT_GT(term_match.relevance, wyt_match.relevance);
557}
558
559// A multiword search with more than one visit should autocomplete immediately.
560TEST_F(SearchProviderTest, AutocompleteMultipleVisitsImmediately) {
561  GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("two searches"),
562                                   2));
563  profile_.BlockUntilHistoryProcessesPendingRequests();
564
565  AutocompleteMatch wyt_match;
566  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("tw"),
567                                                      &wyt_match));
568  ASSERT_EQ(2u, provider_->matches().size());
569  AutocompleteMatch term_match;
570  EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match));
571  EXPECT_GT(term_match.relevance, wyt_match.relevance);
572}
573
574// Autocompletion should work at a word boundary after a space.
575TEST_F(SearchProviderTest, AutocompleteAfterSpace) {
576  GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("two searches"),
577                                   2));
578  profile_.BlockUntilHistoryProcessesPendingRequests();
579
580  AutocompleteMatch wyt_match;
581  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("two "),
582                                                      &wyt_match));
583  ASSERT_EQ(2u, provider_->matches().size());
584  AutocompleteMatch term_match;
585  EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match));
586  EXPECT_GT(term_match.relevance, wyt_match.relevance);
587}
588
589// Newer multiword searches should score more highly than older ones.
590TEST_F(SearchProviderTest, ScoreNewerSearchesHigher) {
591  GURL term_url_a(AddSearchToHistory(default_t_url_,
592                                     ASCIIToUTF16("three searches aaa"), 1));
593  GURL term_url_b(AddSearchToHistory(default_t_url_,
594                                     ASCIIToUTF16("three searches bbb"), 1));
595  profile_.BlockUntilHistoryProcessesPendingRequests();
596
597  AutocompleteMatch wyt_match;
598  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("three se"),
599                                                      &wyt_match));
600  ASSERT_EQ(3u, provider_->matches().size());
601  AutocompleteMatch term_match_a;
602  EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a));
603  AutocompleteMatch term_match_b;
604  EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b));
605  EXPECT_GT(term_match_b.relevance, term_match_a.relevance);
606  EXPECT_GT(term_match_a.relevance, wyt_match.relevance);
607}
608
609// An autocompleted multiword search should not be replaced by a different
610// autocompletion while the user is still typing a valid prefix.
611TEST_F(SearchProviderTest, DontReplacePreviousAutocompletion) {
612  GURL term_url_a(AddSearchToHistory(default_t_url_,
613                                     ASCIIToUTF16("four searches aaa"), 2));
614  GURL term_url_b(AddSearchToHistory(default_t_url_,
615                                     ASCIIToUTF16("four searches bbb"), 1));
616  profile_.BlockUntilHistoryProcessesPendingRequests();
617
618  AutocompleteMatch wyt_match;
619  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("fo"),
620                                                      &wyt_match));
621  ASSERT_EQ(3u, provider_->matches().size());
622  AutocompleteMatch term_match_a;
623  EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a));
624  AutocompleteMatch term_match_b;
625  EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b));
626  EXPECT_GT(term_match_a.relevance, wyt_match.relevance);
627  EXPECT_GT(wyt_match.relevance, term_match_b.relevance);
628
629  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("four se"),
630                                                      &wyt_match));
631  ASSERT_EQ(3u, provider_->matches().size());
632  EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a));
633  EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b));
634  EXPECT_GT(term_match_a.relevance, wyt_match.relevance);
635  EXPECT_GT(wyt_match.relevance, term_match_b.relevance);
636}
637
638// Non-completable multiword searches should not crowd out single-word searches.
639TEST_F(SearchProviderTest, DontCrowdOutSingleWords) {
640  GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("five"), 1));
641  AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches bbb"), 1);
642  AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches ccc"), 1);
643  AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches ddd"), 1);
644  AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches eee"), 1);
645  profile_.BlockUntilHistoryProcessesPendingRequests();
646
647  AutocompleteMatch wyt_match;
648  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("fi"),
649                                                      &wyt_match));
650  ASSERT_EQ(AutocompleteProvider::kMaxMatches + 1, provider_->matches().size());
651  AutocompleteMatch term_match;
652  EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match));
653  EXPECT_GT(term_match.relevance, wyt_match.relevance);
654}
655
656// Inline autocomplete matches regardless of case differences from the input.
657TEST_F(SearchProviderTest, InlineMixedCaseMatches) {
658  GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("FOO"), 1));
659  profile_.BlockUntilHistoryProcessesPendingRequests();
660
661  AutocompleteMatch wyt_match;
662  ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("f"),
663                                                      &wyt_match));
664  ASSERT_EQ(2u, provider_->matches().size());
665  AutocompleteMatch term_match;
666  EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match));
667  EXPECT_GT(term_match.relevance, wyt_match.relevance);
668  EXPECT_EQ(1u, term_match.inline_autocomplete_offset);
669  EXPECT_EQ(ASCIIToUTF16("FOO"), term_match.fill_into_edit);
670}
671
672// Verifies AutocompleteControllers sets descriptions for results correctly.
673TEST_F(SearchProviderTest, UpdateKeywordDescriptions) {
674  // Add an entry that corresponds to a keyword search with 'term2'.
675  AddSearchToHistory(keyword_t_url_, ASCIIToUTF16("term2"), 1);
676  profile_.BlockUntilHistoryProcessesPendingRequests();
677
678  AutocompleteController controller(&profile_, NULL,
679      AutocompleteProvider::TYPE_SEARCH);
680  controller.Start(ASCIIToUTF16("k t"), string16(), false, false, true,
681                   AutocompleteInput::ALL_MATCHES);
682  const AutocompleteResult& result = controller.result();
683
684  // There should be two matches, one for the keyword one for what you typed.
685  ASSERT_EQ(2u, result.size());
686
687  EXPECT_FALSE(result.match_at(0).keyword.empty());
688  EXPECT_FALSE(result.match_at(1).keyword.empty());
689  EXPECT_NE(result.match_at(0).keyword, result.match_at(1).keyword);
690
691  EXPECT_FALSE(result.match_at(0).description.empty());
692  EXPECT_FALSE(result.match_at(1).description.empty());
693  EXPECT_NE(result.match_at(0).description, result.match_at(1).description);
694}
695
696// Verifies Navsuggest results don't set a TemplateURL, which instant relies on.
697// Also verifies that just the *first* navigational result is listed as a match.
698TEST_F(SearchProviderTest, NavSuggest) {
699  QueryForInput(ASCIIToUTF16("a.c"), string16(), false);
700
701  // Make sure the default providers suggest service was queried.
702  net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(
703      SearchProvider::kDefaultProviderURLFetcherID);
704  ASSERT_TRUE(fetcher);
705
706  // Tell the SearchProvider the suggest query is done.
707  fetcher->set_response_code(200);
708  fetcher->SetResponseString(
709      "[\"a.c\",[\"a.com\", \"a.com/b\"],[\"a\", \"b\"],[],"
710      "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]}]");
711  fetcher->delegate()->OnURLFetchComplete(fetcher);
712  fetcher = NULL;
713
714  // Run till the history results complete.
715  RunTillProviderDone();
716
717  // Make sure the only match is 'a.com' and it doesn't have a template_url.
718  AutocompleteMatch nav_match;
719  EXPECT_TRUE(FindMatchWithDestination(GURL("http://a.com"), &nav_match));
720  EXPECT_TRUE(nav_match.keyword.empty());
721  EXPECT_FALSE(FindMatchWithDestination(GURL("http://a.com/b"), &nav_match));
722}
723
724// Verifies that the most relevant suggest results are added properly.
725TEST_F(SearchProviderTest, SuggestRelevance) {
726  QueryForInput(ASCIIToUTF16("a"), string16(), false);
727
728  // Make sure the default provider's suggest service was queried.
729  net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(
730      SearchProvider::kDefaultProviderURLFetcherID);
731  ASSERT_TRUE(fetcher);
732
733  // Tell the SearchProvider the suggest query is done.
734  fetcher->set_response_code(200);
735  fetcher->SetResponseString("[\"a\",[\"a1\", \"a2\", \"a3\", \"a4\"]]");
736  fetcher->delegate()->OnURLFetchComplete(fetcher);
737  fetcher = NULL;
738
739  // Run till the history results complete.
740  RunTillProviderDone();
741
742  // Check the expected verbatim and (first 3) suggestions' relative relevances.
743  AutocompleteMatch verbatim, match_a1, match_a2, match_a3, match_a4;
744  EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a"), &verbatim));
745  EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a1"), &match_a1));
746  EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a2"), &match_a2));
747  EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a3"), &match_a3));
748  EXPECT_FALSE(FindMatchWithContents(ASCIIToUTF16("a4"), &match_a4));
749  EXPECT_GT(verbatim.relevance, match_a1.relevance);
750  EXPECT_GT(match_a1.relevance, match_a2.relevance);
751  EXPECT_GT(match_a2.relevance, match_a3.relevance);
752}
753
754// Verifies that suggest experiment results are added properly.
755TEST_F(SearchProviderTest, SuggestRelevanceExperiment) {
756  const std::string kNotApplicable("Not Applicable");
757  struct {
758    const std::string json;
759    const std::string matches[4];
760  } cases[] = {
761    // Ensure that suggestrelevance scores reorder matches.
762    { "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]",
763      { "a", "c", "b", kNotApplicable } },
764    { "[\"a\",[\"http://b.com\", \"http://c.com\"],[],[],"
765       "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"],"
766        "\"google:suggestrelevance\":[1, 2]}]",
767      { "a", "c.com", kNotApplicable, kNotApplicable } },
768
769    // Ensure that verbatimrelevance scores reorder or suppress what-you-typed.
770    // Negative values will have no effect; the calculated value will be used.
771    { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9999,"
772                             "\"google:suggestrelevance\":[9998]}]",
773      { "a", "a1", kNotApplicable, kNotApplicable } },
774    { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9998,"
775                             "\"google:suggestrelevance\":[9999]}]",
776      { "a1", "a", kNotApplicable, kNotApplicable } },
777    { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":0,"
778                             "\"google:suggestrelevance\":[9999]}]",
779      { "a1", kNotApplicable, kNotApplicable, kNotApplicable } },
780    { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":-1,"
781                             "\"google:suggestrelevance\":[9999]}]",
782      { "a1", "a", kNotApplicable, kNotApplicable } },
783    { "[\"a\",[\"http://a.com\"],[],[],"
784       "{\"google:suggesttype\":[\"NAVIGATION\"],"
785        "\"google:verbatimrelevance\":9999,"
786        "\"google:suggestrelevance\":[9998]}]",
787      { "a", "a.com", kNotApplicable, kNotApplicable } },
788    { "[\"a\",[\"http://a.com\"],[],[],"
789       "{\"google:suggesttype\":[\"NAVIGATION\"],"
790        "\"google:verbatimrelevance\":9998,"
791        "\"google:suggestrelevance\":[9999]}]",
792      { "a.com", "a", kNotApplicable, kNotApplicable } },
793    { "[\"a\",[\"http://a.com\"],[],[],"
794       "{\"google:suggesttype\":[\"NAVIGATION\"],"
795        "\"google:verbatimrelevance\":0,"
796        "\"google:suggestrelevance\":[9999]}]",
797      { "a.com", kNotApplicable, kNotApplicable, kNotApplicable } },
798    { "[\"a\",[\"http://a.com\"],[],[],"
799       "{\"google:suggesttype\":[\"NAVIGATION\"],"
800        "\"google:verbatimrelevance\":-1,"
801        "\"google:suggestrelevance\":[9999]}]",
802      { "a.com", "a", kNotApplicable, kNotApplicable } },
803
804    // Ensure that both types of relevance scores reorder matches together.
805    { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[9999, 9997],"
806                                     "\"google:verbatimrelevance\":9998}]",
807      { "a1", "a", "a2", kNotApplicable } },
808
809    // Ensure that only inlinable matches may be ranked as the highest result.
810    // Ignore all suggested relevance scores if this constraint is violated.
811    { "[\"a\",[\"b\"],[],[],{\"google:suggestrelevance\":[9999]}]",
812      { "a", "b", kNotApplicable, kNotApplicable } },
813    { "[\"a\",[\"b\"],[],[],{\"google:suggestrelevance\":[9999],"
814                            "\"google:verbatimrelevance\":0}]",
815      { "a", "b", kNotApplicable, kNotApplicable } },
816    { "[\"a\",[\"http://b.com\"],[],[],"
817       "{\"google:suggesttype\":[\"NAVIGATION\"],"
818        "\"google:suggestrelevance\":[9999]}]",
819      { "a", "b.com", kNotApplicable, kNotApplicable } },
820    { "[\"a\",[\"http://b.com\"],[],[],"
821       "{\"google:suggesttype\":[\"NAVIGATION\"],"
822        "\"google:suggestrelevance\":[9999],"
823        "\"google:verbatimrelevance\":0}]",
824      { "a", "b.com", kNotApplicable, kNotApplicable } },
825
826    // Ensure that the top result is ranked as highly as calculated verbatim.
827    // Ignore the suggested verbatim relevance if this constraint is violated.
828    { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":0}]",
829      { "a", "a1", kNotApplicable, kNotApplicable } },
830    { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":1}]",
831      { "a", "a1", kNotApplicable, kNotApplicable } },
832    { "[\"a\",[\"a1\"],[],[],{\"google:suggestrelevance\":[1],"
833                             "\"google:verbatimrelevance\":0}]",
834      { "a", "a1", kNotApplicable, kNotApplicable } },
835    { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[1, 2],"
836                                     "\"google:verbatimrelevance\":0}]",
837      { "a", "a2", "a1", kNotApplicable } },
838    { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[1, 3],"
839      "\"google:verbatimrelevance\":2}]",
840      { "a", "a2", "a1", kNotApplicable } },
841    { "[\"a\",[\"http://a.com\"],[],[],"
842       "{\"google:suggesttype\":[\"NAVIGATION\"],"
843        "\"google:suggestrelevance\":[1],"
844        "\"google:verbatimrelevance\":0}]",
845      { "a", "a.com", kNotApplicable, kNotApplicable } },
846    { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[],"
847       "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"],"
848        "\"google:suggestrelevance\":[1, 2],"
849        "\"google:verbatimrelevance\":0}]",
850      { "a", "a2.com", kNotApplicable, kNotApplicable } },
851
852    // Ensure that all suggestions are considered, regardless of order.
853    { "[\"a\",[\"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"],[],[],"
854       "{\"google:suggestrelevance\":[1, 2, 3, 4, 5, 6, 7]}]",
855      { "a", "h", "g", "f" } },
856    { "[\"a\",[\"http://b.com\", \"http://c.com\", \"http://d.com\","
857              "\"http://e.com\", \"http://f.com\", \"http://g.com\","
858              "\"http://h.com\"],[],[],"
859       "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\","
860                                "\"NAVIGATION\", \"NAVIGATION\","
861                                "\"NAVIGATION\", \"NAVIGATION\","
862                                "\"NAVIGATION\"],"
863        "\"google:suggestrelevance\":[1, 2, 3, 4, 5, 6, 7]}]",
864      { "a", "h.com", kNotApplicable, kNotApplicable } },
865
866    // Ensure that incorrectly sized suggestion relevance lists are ignored.
867    { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[1]}]",
868      { "a", "a1", "a2", kNotApplicable } },
869    { "[\"a\",[\"a1\"],[],[],{\"google:suggestrelevance\":[9999, 1]}]",
870      { "a", "a1", kNotApplicable, kNotApplicable } },
871    { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[],"
872       "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"],"
873        "\"google:suggestrelevance\":[1]}]",
874      { "a", "a1.com", kNotApplicable, kNotApplicable } },
875    { "[\"a\",[\"http://a1.com\"],[],[],"
876       "{\"google:suggesttype\":[\"NAVIGATION\"],"
877       "\"google:suggestrelevance\":[9999, 1]}]",
878      { "a", "a1.com", kNotApplicable, kNotApplicable } },
879
880    // Ensure that all 'verbatim' results are merged with their maximum score.
881    { "[\"a\",[\"a\", \"a1\", \"a2\"],[],[],"
882       "{\"google:suggestrelevance\":[9998, 9997, 9999]}]",
883      { "a2", "a", "a1", kNotApplicable } },
884    { "[\"a\",[\"a\", \"a1\", \"a2\"],[],[],"
885       "{\"google:suggestrelevance\":[9998, 9997, 9999],"
886        "\"google:verbatimrelevance\":0}]",
887      { "a2", "a", "a1", kNotApplicable } },
888
889    // Ensure that verbatim is always generated without other suggestions.
890    // TODO(msw): Ensure verbatimrelevance is respected (except suppression).
891    { "[\"a\",[],[],[],{\"google:verbatimrelevance\":1}]",
892      { "a", kNotApplicable, kNotApplicable, kNotApplicable } },
893    { "[\"a\",[],[],[],{\"google:verbatimrelevance\":0}]",
894      { "a", kNotApplicable, kNotApplicable, kNotApplicable } },
895  };
896
897  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
898    QueryForInput(ASCIIToUTF16("a"), string16(), false);
899    net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(
900        SearchProvider::kDefaultProviderURLFetcherID);
901    fetcher->set_response_code(200);
902    fetcher->SetResponseString(cases[i].json);
903    fetcher->delegate()->OnURLFetchComplete(fetcher);
904    RunTillProviderDone();
905
906    const ACMatches& matches = provider_->matches();
907    // The top match must inline and score as highly as calculated verbatim.
908    EXPECT_NE(string16::npos, matches[0].inline_autocomplete_offset);
909    EXPECT_GE(matches[0].relevance, 1300);
910
911    size_t j = 0;
912    // Ensure that the returned matches equal the expectations.
913    for (; j < matches.size(); ++j)
914      EXPECT_EQ(ASCIIToUTF16(cases[i].matches[j]), matches[j].contents);
915    // Ensure that no expected matches are missing.
916    for (; j < ARRAYSIZE_UNSAFE(cases[i].matches); ++j)
917      EXPECT_EQ(kNotApplicable, cases[i].matches[j]);
918  }
919}
920
921// Verifies suggest experiment behavior for URL input.
922TEST_F(SearchProviderTest, SuggestRelevanceExperimentUrlInput) {
923  const std::string kNotApplicable("Not Applicable");
924  struct {
925    const std::string input;
926    const std::string json;
927    const std::string match_contents[4];
928    const AutocompleteMatch::Type match_types[4];
929  } cases[] = {
930    // Ensure topmost NAVIGATION matches are allowed for URL input.
931    { "a.com", "[\"a.com\",[\"http://a.com/a\"],[],[],"
932                "{\"google:suggesttype\":[\"NAVIGATION\"],"
933                 "\"google:suggestrelevance\":[9999]}]",
934      { "a.com/a", "a.com", kNotApplicable, kNotApplicable },
935      { AutocompleteMatch::NAVSUGGEST, AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
936        AutocompleteMatch::NUM_TYPES, AutocompleteMatch::NUM_TYPES } },
937
938    // Ensure topmost SUGGEST matches are not allowed for URL input.
939    // SearchProvider disregards search and verbatim suggested relevances.
940    { "a.com", "[\"a.com\",[\"a.com info\"],[],[],"
941                "{\"google:suggestrelevance\":[9999]}]",
942      { "a.com", "a.com info", kNotApplicable, kNotApplicable },
943      { AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
944        AutocompleteMatch::SEARCH_SUGGEST,
945        AutocompleteMatch::NUM_TYPES, AutocompleteMatch::NUM_TYPES } },
946    { "a.com", "[\"a.com\",[\"a.com/a\"],[],[],"
947                "{\"google:suggestrelevance\":[9999]}]",
948      { "a.com", "a.com/a", kNotApplicable, kNotApplicable },
949      { AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
950        AutocompleteMatch::SEARCH_SUGGEST,
951        AutocompleteMatch::NUM_TYPES, AutocompleteMatch::NUM_TYPES } },
952
953    // Ensure the fallback mechanism allows inlinable NAVIGATION matches.
954    { "a.com", "[\"a.com\",[\"a.com/a\", \"http://a.com/b\"],[],[],"
955                "{\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"],"
956                 "\"google:suggestrelevance\":[9999, 9998]}]",
957      { "a.com/b", "a.com", "a.com/a", kNotApplicable },
958      { AutocompleteMatch::NAVSUGGEST,
959        AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
960        AutocompleteMatch::SEARCH_SUGGEST,
961        AutocompleteMatch::NUM_TYPES } },
962    { "a.com", "[\"a.com\",[\"a.com/a\", \"http://a.com/b\"],[],[],"
963                "{\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"],"
964                 "\"google:suggestrelevance\":[9998, 9997],"
965                 "\"google:verbatimrelevance\":9999}]",
966      { "a.com/b", "a.com", "a.com/a", kNotApplicable },
967      { AutocompleteMatch::NAVSUGGEST,
968        AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
969        AutocompleteMatch::SEARCH_SUGGEST,
970        AutocompleteMatch::NUM_TYPES } },
971
972    // Ensure the fallback mechanism disallows non-inlinable NAVIGATION matches.
973    { "a.com", "[\"a.com\",[\"a.com/a\", \"http://abc.com\"],[],[],"
974                "{\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"],"
975      "\"google:suggestrelevance\":[9999, 9998]}]",
976      { "a.com", "abc.com", "a.com/a", kNotApplicable },
977      { AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
978        AutocompleteMatch::NAVSUGGEST,
979        AutocompleteMatch::SEARCH_SUGGEST,
980        AutocompleteMatch::NUM_TYPES } },
981    { "a.com", "[\"a.com\",[\"a.com/a\", \"http://abc.com\"],[],[],"
982                "{\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"],"
983                 "\"google:suggestrelevance\":[9998, 9997],"
984                 "\"google:verbatimrelevance\":9999}]",
985      { "a.com", "abc.com", "a.com/a", kNotApplicable },
986      { AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
987        AutocompleteMatch::NAVSUGGEST,
988        AutocompleteMatch::SEARCH_SUGGEST,
989        AutocompleteMatch::NUM_TYPES } },
990};
991
992  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
993    QueryForInput(ASCIIToUTF16(cases[i].input), string16(), false);
994    net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(
995        SearchProvider::kDefaultProviderURLFetcherID);
996    fetcher->set_response_code(200);
997    fetcher->SetResponseString(cases[i].json);
998    fetcher->delegate()->OnURLFetchComplete(fetcher);
999    RunTillProviderDone();
1000
1001    size_t j = 0;
1002    const ACMatches& matches = provider_->matches();
1003    // Ensure that the returned matches equal the expectations.
1004    for (; j < matches.size(); ++j) {
1005      EXPECT_EQ(ASCIIToUTF16(cases[i].match_contents[j]), matches[j].contents);
1006      EXPECT_EQ(cases[i].match_types[j], matches[j].type);
1007    }
1008    // Ensure that no expected matches are missing.
1009    for (; j < ARRAYSIZE_UNSAFE(cases[i].match_contents); ++j) {
1010      EXPECT_EQ(kNotApplicable, cases[i].match_contents[j]);
1011      EXPECT_EQ(AutocompleteMatch::NUM_TYPES, cases[i].match_types[j]);
1012    }
1013  }
1014}
1015
1016// Verifies suggest experiment behavior for REQUESTED_URL input w/|desired_tld|.
1017TEST_F(SearchProviderTest, SuggestRelevanceExperimentRequestedUrlInput) {
1018  const std::string kNotApplicable("Not Applicable");
1019  struct {
1020    const std::string input;
1021    const std::string json;
1022    const std::string match_contents[4];
1023    const AutocompleteMatch::Type match_types[4];
1024  } cases[] = {
1025    // Ensure topmost NAVIGATION matches are allowed for REQUESTED_URL input.
1026    { "a", "[\"a\",[\"http://a.com/a\"],[],[],"
1027            "{\"google:suggesttype\":[\"NAVIGATION\"],"
1028             "\"google:suggestrelevance\":[9999]}]",
1029      { "a.com/a", "a", kNotApplicable, kNotApplicable },
1030      { AutocompleteMatch::NAVSUGGEST, AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
1031        AutocompleteMatch::NUM_TYPES, AutocompleteMatch::NUM_TYPES } },
1032
1033    // Disallow topmost verbatim[-like] SUGGEST matches for REQUESTED_URL input.
1034    // To prevent this, SearchProvider generates a URL_WHAT_YOU_TYPED match.
1035    { "a", "[\"a\",[\"a\"],[],[],{\"google:suggestrelevance\":[9999]}]",
1036      { "www.a.com", "a", kNotApplicable, kNotApplicable },
1037      { AutocompleteMatch::URL_WHAT_YOU_TYPED,
1038        AutocompleteMatch::SEARCH_SUGGEST,
1039        AutocompleteMatch::NUM_TYPES, AutocompleteMatch::NUM_TYPES } },
1040    { "a", "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9999}]",
1041      { "www.a.com", "a", "a1", kNotApplicable },
1042      { AutocompleteMatch::URL_WHAT_YOU_TYPED,
1043        AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
1044        AutocompleteMatch::SEARCH_SUGGEST, AutocompleteMatch::NUM_TYPES } },
1045
1046    // Allow topmost non-verbatim-like SUGGEST matches for REQUESTED_URL input.
1047    // This is needed so that (CTRL+A/C/etc.) doesn't alter inline text.
1048    { "a", "[\"a\",[\"a.com/a\"],[],[],{\"google:suggestrelevance\":[9999]}]",
1049      { "a.com/a", "a", kNotApplicable, kNotApplicable },
1050      { AutocompleteMatch::SEARCH_SUGGEST,
1051        AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
1052        AutocompleteMatch::NUM_TYPES, AutocompleteMatch::NUM_TYPES } },
1053  };
1054
1055  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
1056    QueryForInput(ASCIIToUTF16(cases[i].input), ASCIIToUTF16("com"), false);
1057    net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID(
1058        SearchProvider::kDefaultProviderURLFetcherID);
1059    fetcher->set_response_code(200);
1060    fetcher->SetResponseString(cases[i].json);
1061    fetcher->delegate()->OnURLFetchComplete(fetcher);
1062    RunTillProviderDone();
1063
1064    size_t j = 0;
1065    const ACMatches& matches = provider_->matches();
1066    // Ensure that the returned matches equal the expectations.
1067    for (; j < matches.size(); ++j) {
1068      EXPECT_EQ(ASCIIToUTF16(cases[i].match_contents[j]), matches[j].contents);
1069      EXPECT_EQ(cases[i].match_types[j], matches[j].type);
1070    }
1071    // Ensure that no expected matches are missing.
1072    for (; j < ARRAYSIZE_UNSAFE(cases[i].match_contents); ++j) {
1073      EXPECT_EQ(kNotApplicable, cases[i].match_contents[j]);
1074      EXPECT_EQ(AutocompleteMatch::NUM_TYPES, cases[i].match_types[j]);
1075    }
1076  }
1077}
1078
1079// Verifies inline autocompletion of navigational results.
1080TEST_F(SearchProviderTest, NavigationInline) {
1081  struct {
1082    const std::string input;
1083    const std::string url;
1084    // Test the expected fill_into_edit, which may drop "http://".
1085    // Some cases do not trim "http://" to match from the start of the scheme.
1086    const std::string fill_into_edit;
1087    size_t inline_offset;
1088  } cases[] = {
1089    // Do not inline matches that do not contain the input; trim http as needed.
1090    { "x",                    "http://www.abc.com",
1091                                     "www.abc.com",  string16::npos },
1092    { "https:",               "http://www.abc.com",
1093                                     "www.abc.com",  string16::npos },
1094    { "abc.com/",             "http://www.abc.com",
1095                                     "www.abc.com",  string16::npos },
1096    { "http://www.abc.com/a", "http://www.abc.com",
1097                              "http://www.abc.com",  string16::npos },
1098    { "http://www.abc.com",   "https://www.abc.com",
1099                              "https://www.abc.com", string16::npos },
1100    { "http://abc.com",       "ftp://abc.com",
1101                              "ftp://abc.com",       string16::npos },
1102    { "https://www.abc.com",  "http://www.abc.com",
1103                                     "www.abc.com",  string16::npos },
1104    { "ftp://abc.com",        "http://abc.com",
1105                                     "abc.com",      string16::npos },
1106
1107    // Do not inline matches with invalid input prefixes; trim http as needed.
1108    { "ttp",                  "http://www.abc.com",
1109                                     "www.abc.com", string16::npos },
1110    { "://w",                 "http://www.abc.com",
1111                                     "www.abc.com", string16::npos },
1112    { "ww.",                  "http://www.abc.com",
1113                                     "www.abc.com", string16::npos },
1114    { ".ab",                  "http://www.abc.com",
1115                                     "www.abc.com", string16::npos },
1116    { "bc",                   "http://www.abc.com",
1117                                     "www.abc.com", string16::npos },
1118    { ".com",                 "http://www.abc.com",
1119                                     "www.abc.com", string16::npos },
1120
1121    // Do not inline matches that omit input domain labels; trim http as needed.
1122    { "www.a",                "http://a.com",
1123                                     "a.com",       string16::npos },
1124    { "http://www.a",         "http://a.com",
1125                              "http://a.com",       string16::npos },
1126    { "www.a",                "ftp://a.com",
1127                              "ftp://a.com",        string16::npos },
1128    { "ftp://www.a",          "ftp://a.com",
1129                              "ftp://a.com",        string16::npos },
1130
1131    // Input matching but with nothing to inline will not yield an offset.
1132    { "abc.com",              "http://www.abc.com",
1133                                     "www.abc.com", string16::npos },
1134    { "http://www.abc.com",   "http://www.abc.com",
1135                              "http://www.abc.com", string16::npos },
1136
1137    // Inline matches when the input is a leading substring of the scheme.
1138    { "h",                    "http://www.abc.com",
1139                              "http://www.abc.com", 1 },
1140    { "http",                 "http://www.abc.com",
1141                              "http://www.abc.com", 4 },
1142
1143    // Inline matches when the input is a leading substring of the full URL.
1144    { "http:",                "http://www.abc.com",
1145                              "http://www.abc.com", 5 },
1146    { "http://w",             "http://www.abc.com",
1147                              "http://www.abc.com", 8 },
1148    { "http://www.",          "http://www.abc.com",
1149                              "http://www.abc.com", 11 },
1150    { "http://www.ab",        "http://www.abc.com",
1151                              "http://www.abc.com", 13 },
1152    { "http://www.abc.com/p", "http://www.abc.com/path/file.htm?q=x#foo",
1153                              "http://www.abc.com/path/file.htm?q=x#foo", 20 },
1154    { "http://abc.com/p",     "http://abc.com/path/file.htm?q=x#foo",
1155                              "http://abc.com/path/file.htm?q=x#foo",     16 },
1156
1157    // Inline matches with valid URLPrefixes; only trim "http://".
1158    { "w",                    "http://www.abc.com",
1159                                     "www.abc.com", 1 },
1160    { "www.a",                "http://www.abc.com",
1161                                     "www.abc.com", 5 },
1162    { "abc",                  "http://www.abc.com",
1163                                     "www.abc.com", 7 },
1164    { "abc.c",                "http://www.abc.com",
1165                                     "www.abc.com", 9 },
1166    { "abc.com/p",            "http://www.abc.com/path/file.htm?q=x#foo",
1167                                     "www.abc.com/path/file.htm?q=x#foo", 13 },
1168    { "abc.com/p",            "http://abc.com/path/file.htm?q=x#foo",
1169                                     "abc.com/path/file.htm?q=x#foo",     9 },
1170
1171    // Inline matches using the maximal URLPrefix components.
1172    { "h",                    "http://help.com",
1173                                     "help.com",     1 },
1174    { "http",                 "http://http.com",
1175                                     "http.com",     4 },
1176    { "h",                    "http://www.help.com",
1177                                     "www.help.com", 5 },
1178    { "http",                 "http://www.http.com",
1179                                     "www.http.com", 8 },
1180    { "w",                    "http://www.www.com",
1181                                     "www.www.com",  5 },
1182
1183    // Test similar behavior for the ftp and https schemes.
1184    { "ftp://www.ab",         "ftp://www.abc.com/path/file.htm?q=x#foo",
1185                              "ftp://www.abc.com/path/file.htm?q=x#foo",   12 },
1186    { "www.ab",               "ftp://www.abc.com/path/file.htm?q=x#foo",
1187                              "ftp://www.abc.com/path/file.htm?q=x#foo",   12 },
1188    { "ab",                   "ftp://www.abc.com/path/file.htm?q=x#foo",
1189                              "ftp://www.abc.com/path/file.htm?q=x#foo",   12 },
1190    { "ab",                   "ftp://abc.com/path/file.htm?q=x#foo",
1191                              "ftp://abc.com/path/file.htm?q=x#foo",       8 },
1192    { "https://www.ab",       "https://www.abc.com/path/file.htm?q=x#foo",
1193                              "https://www.abc.com/path/file.htm?q=x#foo", 14 },
1194    { "www.ab",               "https://www.abc.com/path/file.htm?q=x#foo",
1195                              "https://www.abc.com/path/file.htm?q=x#foo", 14 },
1196    { "ab",                   "https://www.abc.com/path/file.htm?q=x#foo",
1197                              "https://www.abc.com/path/file.htm?q=x#foo", 14 },
1198    { "ab",                   "https://abc.com/path/file.htm?q=x#foo",
1199                              "https://abc.com/path/file.htm?q=x#foo",     10 },
1200
1201    // Forced query input should inline and retain the "?" prefix.
1202    { "?http://www.ab",       "http://www.abc.com",
1203                             "?http://www.abc.com", 14 },
1204    { "?www.ab",              "http://www.abc.com",
1205                                    "?www.abc.com", 7 },
1206    { "?ab",                  "http://www.abc.com",
1207                                    "?www.abc.com", 7 },
1208  };
1209
1210  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
1211    QueryForInput(ASCIIToUTF16(cases[i].input), string16(), false);
1212    SearchProvider::NavigationResult result(GURL(cases[i].url), string16(), 0);
1213    AutocompleteMatch match(provider_->NavigationToMatch(result, false));
1214    EXPECT_EQ(cases[i].inline_offset, match.inline_autocomplete_offset);
1215    EXPECT_EQ(ASCIIToUTF16(cases[i].fill_into_edit), match.fill_into_edit);
1216  }
1217}
1218
1219// Verifies that "http://" is not trimmed for input that is a leading substring.
1220TEST_F(SearchProviderTest, NavigationInlineSchemeSubstring) {
1221  const string16 input(ASCIIToUTF16("ht"));
1222  const string16 url(ASCIIToUTF16("http://a.com"));
1223  const SearchProvider::NavigationResult result(GURL(url), string16(), 0);
1224
1225  // Check the offset and strings when inline autocompletion is allowed.
1226  QueryForInput(input, string16(), false);
1227  AutocompleteMatch match_inline(provider_->NavigationToMatch(result, false));
1228  EXPECT_EQ(2U, match_inline.inline_autocomplete_offset);
1229  EXPECT_EQ(url, match_inline.fill_into_edit);
1230  EXPECT_EQ(url, match_inline.contents);
1231
1232  // Check the same offset and strings when inline autocompletion is prevented.
1233  QueryForInput(input, string16(), true);
1234  AutocompleteMatch match_prevent(provider_->NavigationToMatch(result, false));
1235  EXPECT_EQ(string16::npos, match_prevent.inline_autocomplete_offset);
1236  EXPECT_EQ(url, match_prevent.fill_into_edit);
1237  EXPECT_EQ(url, match_prevent.contents);
1238}
1239
1240// Verifies that input "w" marks a more significant domain label than "www.".
1241TEST_F(SearchProviderTest, NavigationInlineDomainClassify) {
1242  QueryForInput(ASCIIToUTF16("w"), string16(), false);
1243  const GURL url("http://www.wow.com");
1244  const SearchProvider::NavigationResult result(url, string16(), 0);
1245  AutocompleteMatch match(provider_->NavigationToMatch(result, false));
1246  EXPECT_EQ(5U, match.inline_autocomplete_offset);
1247  EXPECT_EQ(ASCIIToUTF16("www.wow.com"), match.fill_into_edit);
1248  EXPECT_EQ(ASCIIToUTF16("www.wow.com"), match.contents);
1249
1250  // Ensure that the match for input "w" is marked on "wow" and not "www".
1251  ASSERT_EQ(3U, match.contents_class.size());
1252  EXPECT_EQ(0U, match.contents_class[0].offset);
1253  EXPECT_EQ(AutocompleteMatch::ACMatchClassification::URL,
1254            match.contents_class[0].style);
1255  EXPECT_EQ(4U, match.contents_class[1].offset);
1256  EXPECT_EQ(AutocompleteMatch::ACMatchClassification::URL |
1257            AutocompleteMatch::ACMatchClassification::MATCH,
1258            match.contents_class[1].style);
1259  EXPECT_EQ(5U, match.contents_class[2].offset);
1260  EXPECT_EQ(AutocompleteMatch::ACMatchClassification::URL,
1261            match.contents_class[2].style);
1262}
1263