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:
SearchProviderTest()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
SetUp()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
OnProviderUpdate(bool updated_matches)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
RunTillProviderDone()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
QueryForInput(const string16 & text,bool prevent_inline_autocomplete,bool minimal_changes)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
TearDown()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
FindMatchWithDestination(const GURL & url)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
FinishDefaultSuggestQuery()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.
TEST_F(SearchProviderTest,QueryDefaultProvider)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
TEST_F(SearchProviderTest,HonorPreventInlineAutocomplete)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.
TEST_F(SearchProviderTest,QueryKeywordProvider)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
TEST_F(SearchProviderTest,DontSendPrivateDataToSuggest)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.
TEST_F(SearchProviderTest,FinalizeInstantQuery)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.
TEST_F(SearchProviderTest,RememberInstantQuery)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.
TEST_F(SearchProviderTest,DifferingText)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
TEST_F(SearchProviderTest,DontAutocompleteURLLikeTerms)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