// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/omnibox/search_provider.h" #include #include "base/command_line.h" #include "base/metrics/field_trial.h" #include "base/prefs/pref_service.h" #include "base/run_loop.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "build/build_config.h" #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" #include "chrome/browser/autocomplete/autocomplete_controller.h" #include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h" #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h" #include "chrome/browser/autocomplete/history_url_provider.h" #include "chrome/browser/history/history_service.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_browser_process.h" #include "chrome/test/base/testing_profile.h" #include "components/google/core/browser/google_switches.h" #include "components/metrics/proto/omnibox_event.pb.h" #include "components/omnibox/autocomplete_input.h" #include "components/omnibox/autocomplete_match.h" #include "components/omnibox/autocomplete_provider.h" #include "components/omnibox/autocomplete_provider_listener.h" #include "components/omnibox/omnibox_field_trial.h" #include "components/omnibox/omnibox_switches.h" #include "components/search_engines/search_engine_type.h" #include "components/search_engines/search_engines_switches.h" #include "components/search_engines/search_terms_data.h" #include "components/search_engines/template_url.h" #include "components/search_engines/template_url_service.h" #include "components/signin/core/browser/signin_manager.h" #include "components/sync_driver/pref_names.h" #include "components/variations/entropy_provider.h" #include "components/variations/variations_associated_data.h" #include "content/public/test/test_browser_thread_bundle.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_request_status.h" #include "testing/gtest/include/gtest/gtest.h" using base::ASCIIToUTF16; namespace { // Returns the first match in |matches| with |allowed_to_be_default_match| // set to true. ACMatches::const_iterator FindDefaultMatch(const ACMatches& matches) { ACMatches::const_iterator it = matches.begin(); while ((it != matches.end()) && !it->allowed_to_be_default_match) ++it; return it; } class SuggestionDeletionHandler; class SearchProviderForTest : public SearchProvider { public: SearchProviderForTest(AutocompleteProviderListener* listener, TemplateURLService* template_url_service, Profile* profile); bool is_success() { return is_success_; }; protected: ~SearchProviderForTest() override; private: void RecordDeletionResult(bool success) override; bool is_success_; DISALLOW_COPY_AND_ASSIGN(SearchProviderForTest); }; SearchProviderForTest::SearchProviderForTest( AutocompleteProviderListener* listener, TemplateURLService* template_url_service, Profile* profile) : SearchProvider(listener, template_url_service, scoped_ptr( new ChromeAutocompleteProviderClient(profile))), is_success_(false) { } SearchProviderForTest::~SearchProviderForTest() { } void SearchProviderForTest::RecordDeletionResult(bool success) { is_success_ = success; } } // namespace // SearchProviderTest --------------------------------------------------------- // The following environment is configured for these tests: // . The TemplateURL default_t_url_ is set as the default provider. // . The TemplateURL keyword_t_url_ is added to the TemplateURLService. This // TemplateURL has a valid suggest and search URL. // . The URL created by using the search term term1_ with default_t_url_ is // added to history. // . The URL created by using the search term keyword_term_ with keyword_t_url_ // is added to history. // . test_factory_ is set as the URLFetcherFactory. class SearchProviderTest : public testing::Test, public AutocompleteProviderListener { public: struct ResultInfo { ResultInfo() : result_type(AutocompleteMatchType::NUM_TYPES), allowed_to_be_default_match(false) { } ResultInfo(GURL gurl, AutocompleteMatch::Type result_type, bool allowed_to_be_default_match, base::string16 fill_into_edit) : gurl(gurl), result_type(result_type), allowed_to_be_default_match(allowed_to_be_default_match), fill_into_edit(fill_into_edit) { } const GURL gurl; const AutocompleteMatch::Type result_type; const bool allowed_to_be_default_match; const base::string16 fill_into_edit; }; struct TestData { const base::string16 input; const size_t num_results; const ResultInfo output[3]; }; struct ExpectedMatch { std::string contents; bool allowed_to_be_default_match; }; SearchProviderTest() : default_t_url_(NULL), term1_(ASCIIToUTF16("term1")), keyword_t_url_(NULL), keyword_term_(ASCIIToUTF16("keyword")), run_loop_(NULL) { ResetFieldTrialList(); } // See description above class for what this registers. virtual void SetUp() override; virtual void TearDown() override; void RunTest(TestData* cases, int num_cases, bool prefer_keyword); protected: // Needed for AutocompleteFieldTrial::ActivateStaticTrials(); scoped_ptr field_trial_list_; // Default values used for testing. static const std::string kNotApplicable; static const ExpectedMatch kEmptyExpectedMatch; // Adds a search for |term|, using the engine |t_url| to the history, and // returns the URL for that search. GURL AddSearchToHistory(TemplateURL* t_url, base::string16 term, int visit_count); // Looks for a match in |provider_| with |contents| equal to |contents|. // Sets |match| to it if found. Returns whether |match| was set. bool FindMatchWithContents(const base::string16& contents, AutocompleteMatch* match); // Looks for a match in |provider_| with destination |url|. Sets |match| to // it if found. Returns whether |match| was set. bool FindMatchWithDestination(const GURL& url, AutocompleteMatch* match); // AutocompleteProviderListener: // If we're waiting for the provider to finish, this exits the message loop. void OnProviderUpdate(bool updated_matches) override; // Runs a nested message loop until provider_ is done. The message loop is // exited by way of OnProviderUpdate. void RunTillProviderDone(); // Invokes Start on provider_, then runs all pending tasks. void QueryForInput(const base::string16& text, bool prevent_inline_autocomplete, bool prefer_keyword); // Calls QueryForInput(), finishes any suggest query, then if |wyt_match| is // non-NULL, sets it to the "what you typed" entry for |text|. void QueryForInputAndSetWYTMatch(const base::string16& text, AutocompleteMatch* wyt_match); // Calls QueryForInput(), sets the JSON responses for the default and keyword // fetchers, and waits until the responses have been returned and the matches // returned. Use empty responses for each fetcher that shouldn't be set up / // configured. void QueryForInputAndWaitForFetcherResponses( const base::string16& text, const bool prefer_keyword, const std::string& default_fetcher_response, const std::string& keyword_fetcher_response); // Notifies the URLFetcher for the suggest query corresponding to the default // search provider that it's done. // Be sure and wrap calls to this in ASSERT_NO_FATAL_FAILURE. void FinishDefaultSuggestQuery(); // Runs SearchProvider on |input|, for which the suggest server replies // with |json|, and expects that the resulting matches' contents equals // that in |matches|. An empty entry in |matches| means no match should // be returned in that position. Reports any errors with a message that // includes |error_description|. void ForcedQueryTestHelper(const std::string& input, const std::string& json, const std::string matches[3], const std::string& error_description); // Verifies that |matches| and |expected_matches| agree on the first // |num_expected_matches|, displaying an error message that includes // |description| for any disagreement. void CheckMatches(const std::string& description, const size_t num_expected_matches, const ExpectedMatch expected_matches[], const ACMatches& matches); void ResetFieldTrialList(); // Create a field trial, with ZeroSuggest activation based on |enabled|. base::FieldTrial* CreateZeroSuggestFieldTrial(bool enabled); void ClearAllResults(); // See description above class for details of these fields. TemplateURL* default_t_url_; const base::string16 term1_; GURL term1_url_; TemplateURL* keyword_t_url_; const base::string16 keyword_term_; GURL keyword_url_; content::TestBrowserThreadBundle thread_bundle_; // URLFetcherFactory implementation registered. net::TestURLFetcherFactory test_factory_; // Profile we use. TestingProfile profile_; // The provider. scoped_refptr provider_; // If non-NULL, OnProviderUpdate quits the current |run_loop_|. base::RunLoop* run_loop_; DISALLOW_COPY_AND_ASSIGN(SearchProviderTest); }; // static const std::string SearchProviderTest::kNotApplicable = "Not Applicable"; const SearchProviderTest::ExpectedMatch SearchProviderTest::kEmptyExpectedMatch = { kNotApplicable, false }; void SearchProviderTest::SetUp() { // Make sure that fetchers are automatically ungregistered upon destruction. test_factory_.set_remove_fetcher_on_delete(true); // We need both the history service and template url model loaded. ASSERT_TRUE(profile_.CreateHistoryService(true, false)); TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse( &profile_, &TemplateURLServiceFactory::BuildInstanceFor); TemplateURLService* turl_model = TemplateURLServiceFactory::GetForProfile(&profile_); turl_model->Load(); // Reset the default TemplateURL. TemplateURLData data; data.short_name = ASCIIToUTF16("t"); data.SetURL("http://defaultturl/{searchTerms}"); data.suggestions_url = "http://defaultturl2/{searchTerms}"; data.instant_url = "http://does/not/exist?strk=1"; data.search_terms_replacement_key = "strk"; default_t_url_ = new TemplateURL(data); turl_model->Add(default_t_url_); turl_model->SetUserSelectedDefaultSearchProvider(default_t_url_); TemplateURLID default_provider_id = default_t_url_->id(); ASSERT_NE(0, default_provider_id); // Add url1, with search term term1_. term1_url_ = AddSearchToHistory(default_t_url_, term1_, 1); // Create another TemplateURL. data.short_name = ASCIIToUTF16("k"); data.SetKeyword(ASCIIToUTF16("k")); data.SetURL("http://keyword/{searchTerms}"); data.suggestions_url = "http://suggest_keyword/{searchTerms}"; keyword_t_url_ = new TemplateURL(data); turl_model->Add(keyword_t_url_); ASSERT_NE(0, keyword_t_url_->id()); // Add a page and search term for keyword_t_url_. keyword_url_ = AddSearchToHistory(keyword_t_url_, keyword_term_, 1); // Keywords are updated by the InMemoryHistoryBackend only after the message // has been processed on the history thread. Block until history processes all // requests to ensure the InMemoryDatabase is the state we expect it. profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteClassifierFactory::GetInstance()->SetTestingFactoryAndUse( &profile_, &AutocompleteClassifierFactory::BuildInstanceFor); provider_ = new SearchProviderForTest(this, turl_model, &profile_); OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs = 0; } void SearchProviderTest::TearDown() { base::RunLoop().RunUntilIdle(); // Shutdown the provider before the profile. provider_ = NULL; } void SearchProviderTest::RunTest(TestData* cases, int num_cases, bool prefer_keyword) { ACMatches matches; for (int i = 0; i < num_cases; ++i) { AutocompleteInput input(cases[i].input, base::string16::npos, std::string(), GURL(), metrics::OmniboxEventProto::INVALID_SPEC, false, prefer_keyword, true, true, ChromeAutocompleteSchemeClassifier(&profile_)); provider_->Start(input, false); matches = provider_->matches(); SCOPED_TRACE( ASCIIToUTF16("Input was: ") + cases[i].input + ASCIIToUTF16("; prefer_keyword was: ") + (prefer_keyword ? ASCIIToUTF16("true") : ASCIIToUTF16("false"))); EXPECT_EQ(cases[i].num_results, matches.size()); if (matches.size() == cases[i].num_results) { for (size_t j = 0; j < cases[i].num_results; ++j) { EXPECT_EQ(cases[i].output[j].gurl, matches[j].destination_url); EXPECT_EQ(cases[i].output[j].result_type, matches[j].type); EXPECT_EQ(cases[i].output[j].fill_into_edit, matches[j].fill_into_edit); EXPECT_EQ(cases[i].output[j].allowed_to_be_default_match, matches[j].allowed_to_be_default_match); } } } } void SearchProviderTest::OnProviderUpdate(bool updated_matches) { if (run_loop_ && provider_->done()) { run_loop_->Quit(); run_loop_ = NULL; } } void SearchProviderTest::RunTillProviderDone() { if (provider_->done()) return; base::RunLoop run_loop; run_loop_ = &run_loop; run_loop.Run(); } void SearchProviderTest::QueryForInput(const base::string16& text, bool prevent_inline_autocomplete, bool prefer_keyword) { // Start a query. AutocompleteInput input(text, base::string16::npos, std::string(), GURL(), metrics::OmniboxEventProto::INVALID_SPEC, prevent_inline_autocomplete, prefer_keyword, true, true, ChromeAutocompleteSchemeClassifier(&profile_)); provider_->Start(input, false); // RunUntilIdle so that the task scheduled by SearchProvider to create the // URLFetchers runs. base::RunLoop().RunUntilIdle(); } void SearchProviderTest::QueryForInputAndSetWYTMatch( const base::string16& text, AutocompleteMatch* wyt_match) { QueryForInput(text, false, false); profile_.BlockUntilHistoryProcessesPendingRequests(); ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); if (!wyt_match) return; ASSERT_GE(provider_->matches().size(), 1u); EXPECT_TRUE(FindMatchWithDestination( GURL(default_t_url_->url_ref().ReplaceSearchTerms( TemplateURLRef::SearchTermsArgs(base::CollapseWhitespace( text, false)), TemplateURLServiceFactory::GetForProfile( &profile_)->search_terms_data())), wyt_match)); } void SearchProviderTest::QueryForInputAndWaitForFetcherResponses( const base::string16& text, const bool prefer_keyword, const std::string& default_fetcher_response, const std::string& keyword_fetcher_response) { QueryForInput(text, false, prefer_keyword); if (!default_fetcher_response.empty()) { net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(fetcher); fetcher->set_response_code(200); fetcher->SetResponseString(default_fetcher_response); fetcher->delegate()->OnURLFetchComplete(fetcher); } if (!keyword_fetcher_response.empty()) { net::TestURLFetcher* keyword_fetcher = test_factory_.GetFetcherByID( SearchProvider::kKeywordProviderURLFetcherID); ASSERT_TRUE(keyword_fetcher); keyword_fetcher->set_response_code(200); keyword_fetcher->SetResponseString(keyword_fetcher_response); keyword_fetcher->delegate()->OnURLFetchComplete(keyword_fetcher); } RunTillProviderDone(); } GURL SearchProviderTest::AddSearchToHistory(TemplateURL* t_url, base::string16 term, int visit_count) { HistoryService* history = HistoryServiceFactory::GetForProfile(&profile_, Profile::EXPLICIT_ACCESS); GURL search(t_url->url_ref().ReplaceSearchTerms( TemplateURLRef::SearchTermsArgs(term), TemplateURLServiceFactory::GetForProfile( &profile_)->search_terms_data())); static base::Time last_added_time; last_added_time = std::max(base::Time::Now(), last_added_time + base::TimeDelta::FromMicroseconds(1)); history->AddPageWithDetails(search, base::string16(), visit_count, visit_count, last_added_time, false, history::SOURCE_BROWSED); history->SetKeywordSearchTermsForURL(search, t_url->id(), term); return search; } bool SearchProviderTest::FindMatchWithContents(const base::string16& contents, AutocompleteMatch* match) { for (ACMatches::const_iterator i = provider_->matches().begin(); i != provider_->matches().end(); ++i) { if (i->contents == contents) { *match = *i; return true; } } return false; } bool SearchProviderTest::FindMatchWithDestination(const GURL& url, AutocompleteMatch* match) { for (ACMatches::const_iterator i = provider_->matches().begin(); i != provider_->matches().end(); ++i) { if (i->destination_url == url) { *match = *i; return true; } } return false; } void SearchProviderTest::FinishDefaultSuggestQuery() { net::TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(default_fetcher); // Tell the SearchProvider the default suggest query is done. default_fetcher->set_response_code(200); default_fetcher->delegate()->OnURLFetchComplete(default_fetcher); } void SearchProviderTest::ForcedQueryTestHelper( const std::string& input, const std::string& json, const std::string expected_matches[3], const std::string& error_description) { // Send the query twice in order to have a synchronous pass after the first // response is received. This is necessary because SearchProvider doesn't // allow an asynchronous response to change the default match. for (size_t i = 0; i < 2; ++i) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(input), false, json, std::string()); } const ACMatches& matches = provider_->matches(); ASSERT_LE(matches.size(), 3u); size_t i = 0; // Ensure that the returned matches equal the expectations. for (; i < matches.size(); ++i) { EXPECT_EQ(ASCIIToUTF16(expected_matches[i]), matches[i].contents) << error_description; } // Ensure that no expected matches are missing. for (; i < 3u; ++i) { EXPECT_EQ(std::string(), expected_matches[i]) << "Case #" << i << ": " << error_description; } } void SearchProviderTest::CheckMatches(const std::string& description, const size_t num_expected_matches, const ExpectedMatch expected_matches[], const ACMatches& matches) { ASSERT_FALSE(matches.empty()); ASSERT_LE(matches.size(), num_expected_matches); size_t i = 0; SCOPED_TRACE(description); // Ensure that the returned matches equal the expectations. for (; i < matches.size(); ++i) { SCOPED_TRACE(" Case # " + base::IntToString(i)); EXPECT_EQ(ASCIIToUTF16(expected_matches[i].contents), matches[i].contents); EXPECT_EQ(expected_matches[i].allowed_to_be_default_match, matches[i].allowed_to_be_default_match); } // Ensure that no expected matches are missing. for (; i < num_expected_matches; ++i) { SCOPED_TRACE(" Case # " + base::IntToString(i)); EXPECT_EQ(kNotApplicable, expected_matches[i].contents); } } void SearchProviderTest::ResetFieldTrialList() { // Destroy the existing FieldTrialList before creating a new one to avoid // a DCHECK. field_trial_list_.reset(); field_trial_list_.reset(new base::FieldTrialList( new metrics::SHA1EntropyProvider("foo"))); variations::testing::ClearAllVariationParams(); base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial( "AutocompleteDynamicTrial_0", "DefaultGroup"); trial->group(); } base::FieldTrial* SearchProviderTest::CreateZeroSuggestFieldTrial( bool enabled) { std::map params; params[std::string(OmniboxFieldTrial::kZeroSuggestRule)] = enabled ? "true" : "false"; variations::AssociateVariationParams( OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params); return base::FieldTrialList::CreateFieldTrial( OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A"); } void SearchProviderTest::ClearAllResults() { provider_->ClearAllResults(); } // Actual Tests --------------------------------------------------------------- // Make sure we query history for the default provider and a URLFetcher is // created for the default provider suggest results. TEST_F(SearchProviderTest, QueryDefaultProvider) { base::string16 term = term1_.substr(0, term1_.length() - 1); QueryForInput(term, false, false); // Make sure the default providers suggest service was queried. net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(fetcher); // And the URL matches what we expected. GURL expected_url(default_t_url_->suggestions_url_ref().ReplaceSearchTerms( TemplateURLRef::SearchTermsArgs(term), TemplateURLServiceFactory::GetForProfile( &profile_)->search_terms_data())); ASSERT_TRUE(fetcher->GetOriginalURL() == expected_url); // Tell the SearchProvider the suggest query is done. fetcher->set_response_code(200); fetcher->delegate()->OnURLFetchComplete(fetcher); fetcher = NULL; // Run till the history results complete. RunTillProviderDone(); // The SearchProvider is done. Make sure it has a result for the history // term term1. AutocompleteMatch term1_match; EXPECT_TRUE(FindMatchWithDestination(term1_url_, &term1_match)); // Term1 should not have a description, it's set later. EXPECT_TRUE(term1_match.description.empty()); AutocompleteMatch wyt_match; EXPECT_TRUE(FindMatchWithDestination( GURL(default_t_url_->url_ref().ReplaceSearchTerms( TemplateURLRef::SearchTermsArgs(term), TemplateURLServiceFactory::GetForProfile( &profile_)->search_terms_data())), &wyt_match)); EXPECT_TRUE(wyt_match.description.empty()); // The match for term1 should be more relevant than the what you typed match. EXPECT_GT(term1_match.relevance, wyt_match.relevance); // This longer match should be inlineable. EXPECT_TRUE(term1_match.allowed_to_be_default_match); // The what you typed match should be too, of course. EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } TEST_F(SearchProviderTest, HonorPreventInlineAutocomplete) { base::string16 term = term1_.substr(0, term1_.length() - 1); QueryForInput(term, true, false); ASSERT_FALSE(provider_->matches().empty()); ASSERT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, provider_->matches()[0].type); EXPECT_TRUE(provider_->matches()[0].allowed_to_be_default_match); } // Issues a query that matches the registered keyword and makes sure history // is queried as well as URLFetchers getting created. TEST_F(SearchProviderTest, QueryKeywordProvider) { base::string16 term = keyword_term_.substr(0, keyword_term_.length() - 1); QueryForInput(keyword_t_url_->keyword() + ASCIIToUTF16(" ") + term, false, false); // Make sure the default providers suggest service was queried. net::TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(default_fetcher); // Tell the SearchProvider the default suggest query is done. default_fetcher->set_response_code(200); default_fetcher->delegate()->OnURLFetchComplete(default_fetcher); default_fetcher = NULL; // Make sure the keyword providers suggest service was queried. net::TestURLFetcher* keyword_fetcher = test_factory_.GetFetcherByID( SearchProvider::kKeywordProviderURLFetcherID); ASSERT_TRUE(keyword_fetcher); // And the URL matches what we expected. GURL expected_url(keyword_t_url_->suggestions_url_ref().ReplaceSearchTerms( TemplateURLRef::SearchTermsArgs(term), TemplateURLServiceFactory::GetForProfile( &profile_)->search_terms_data())); ASSERT_TRUE(keyword_fetcher->GetOriginalURL() == expected_url); // Tell the SearchProvider the keyword suggest query is done. keyword_fetcher->set_response_code(200); keyword_fetcher->delegate()->OnURLFetchComplete(keyword_fetcher); keyword_fetcher = NULL; // Run till the history results complete. RunTillProviderDone(); // The SearchProvider is done. Make sure it has a result for the history // term keyword. AutocompleteMatch match; EXPECT_TRUE(FindMatchWithDestination(keyword_url_, &match)); // The match should have an associated keyword. EXPECT_FALSE(match.keyword.empty()); // The fill into edit should contain the keyword. EXPECT_EQ(keyword_t_url_->keyword() + base::char16(' ') + keyword_term_, match.fill_into_edit); } TEST_F(SearchProviderTest, DontSendPrivateDataToSuggest) { // None of the following input strings should be sent to the suggest server, // because they may contain private data. const char* inputs[] = { "username:password", "http://username:password", "https://username:password", "username:password@hostname", "http://username:password@hostname/", "file://filename", "data://data", "unknownscheme:anything", "http://hostname/?query=q", "http://hostname/path#ref", "http://hostname/path #ref", "https://hostname/path", }; for (size_t i = 0; i < arraysize(inputs); ++i) { QueryForInput(ASCIIToUTF16(inputs[i]), false, false); // Make sure the default provider's suggest service was not queried. ASSERT_TRUE(test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID) == NULL); // Run till the history results complete. RunTillProviderDone(); } } TEST_F(SearchProviderTest, SendNonPrivateDataToSuggest) { // All of the following input strings should be sent to the suggest server, // because they should not get caught by the private data checks. const char* inputs[] = { "query", "query with spaces", "http://hostname", "http://hostname/path", "http://hostname #ref", "www.hostname.com #ref", "https://hostname", "#hashtag", "foo https://hostname/path" }; profile_.BlockUntilHistoryProcessesPendingRequests(); for (size_t i = 0; i < arraysize(inputs); ++i) { QueryForInput(ASCIIToUTF16(inputs[i]), false, false); // Make sure the default provider's suggest service was queried. ASSERT_TRUE(test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID) != NULL); } } TEST_F(SearchProviderTest, DontAutocompleteURLLikeTerms) { GURL url = AddSearchToHistory(default_t_url_, ASCIIToUTF16("docs.google.com"), 1); // Add the term as a url. HistoryServiceFactory::GetForProfile(&profile_, Profile::EXPLICIT_ACCESS)-> AddPageWithDetails(GURL("http://docs.google.com"), base::string16(), 1, 1, base::Time::Now(), false, history::SOURCE_BROWSED); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("docs"), &wyt_match)); // There should be two matches, one for what you typed, the other for // 'docs.google.com'. The search term should have a lower priority than the // what you typed match. ASSERT_EQ(2u, provider_->matches().size()); AutocompleteMatch term_match; EXPECT_TRUE(FindMatchWithDestination(url, &term_match)); EXPECT_GT(wyt_match.relevance, term_match.relevance); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); EXPECT_TRUE(term_match.allowed_to_be_default_match); } TEST_F(SearchProviderTest, DontGiveNavsuggestionsInForcedQueryMode) { const std::string kEmptyMatch; struct { const std::string json; const std::string matches_in_default_mode[3]; const std::string matches_in_forced_query_mode[3]; } cases[] = { // Without suggested relevance scores. { "[\"a\",[\"http://a1.com\", \"a2\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"QUERY\"]}]", { "a", "a1.com", "a2" }, { "a", "a2", kEmptyMatch } }, // With suggested relevance scores in a situation where navsuggest would // go second. { "[\"a\",[\"http://a1.com\", \"a2\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"QUERY\"]," "\"google:suggestrelevance\":[1250, 1200]}]", { "a", "a1.com", "a2" }, { "a", "a2", kEmptyMatch } }, // With suggested relevance scores in a situation where navsuggest // would go first. { "[\"a\",[\"http://a1.com\", \"a2\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"QUERY\"]," "\"google:suggestrelevance\":[1350, 1250]}]", { "a1.com", "a", "a2" }, { "a", "a2", kEmptyMatch } }, // With suggested relevance scores in a situation where navsuggest // would go first only because verbatim has been demoted. { "[\"a\",[\"http://a1.com\", \"a2\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"QUERY\"]," "\"google:suggestrelevance\":[1450, 1400]," "\"google:verbatimrelevance\":1350}]", { "a1.com", "a2", "a" }, { "a2", "a", kEmptyMatch } }, }; for (size_t i = 0; i < arraysize(cases); ++i) { ForcedQueryTestHelper("a", cases[i].json, cases[i].matches_in_default_mode, "regular input with json=" + cases[i].json); ForcedQueryTestHelper("?a", cases[i].json, cases[i].matches_in_forced_query_mode, "forced query input with json=" + cases[i].json); } } // A multiword search with one visit should not autocomplete until multiple // words are typed. TEST_F(SearchProviderTest, DontAutocompleteUntilMultipleWordsTyped) { GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("one search"), 1)); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("on"), &wyt_match)); ASSERT_EQ(2u, provider_->matches().size()); AutocompleteMatch term_match; EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match)); EXPECT_GT(wyt_match.relevance, term_match.relevance); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); EXPECT_TRUE(term_match.allowed_to_be_default_match); ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("one se"), &wyt_match)); ASSERT_EQ(2u, provider_->matches().size()); EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match)); EXPECT_GT(term_match.relevance, wyt_match.relevance); EXPECT_TRUE(term_match.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } // A multiword search with more than one visit should autocomplete immediately. TEST_F(SearchProviderTest, AutocompleteMultipleVisitsImmediately) { GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("two searches"), 2)); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("tw"), &wyt_match)); ASSERT_EQ(2u, provider_->matches().size()); AutocompleteMatch term_match; EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match)); EXPECT_GT(term_match.relevance, wyt_match.relevance); EXPECT_TRUE(term_match.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } // Autocompletion should work at a word boundary after a space, and should // offer a suggestion for the trimmed search query. TEST_F(SearchProviderTest, AutocompleteAfterSpace) { AddSearchToHistory(default_t_url_, ASCIIToUTF16("two searches "), 2); GURL suggested_url(default_t_url_->url_ref().ReplaceSearchTerms( TemplateURLRef::SearchTermsArgs(ASCIIToUTF16("two searches")), TemplateURLServiceFactory::GetForProfile( &profile_)->search_terms_data())); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("two "), &wyt_match)); ASSERT_EQ(2u, provider_->matches().size()); AutocompleteMatch term_match; EXPECT_TRUE(FindMatchWithDestination(suggested_url, &term_match)); EXPECT_GT(term_match.relevance, wyt_match.relevance); EXPECT_TRUE(term_match.allowed_to_be_default_match); EXPECT_EQ(ASCIIToUTF16("searches"), term_match.inline_autocompletion); EXPECT_EQ(ASCIIToUTF16("two searches"), term_match.fill_into_edit); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } // Newer multiword searches should score more highly than older ones. TEST_F(SearchProviderTest, ScoreNewerSearchesHigher) { GURL term_url_a(AddSearchToHistory(default_t_url_, ASCIIToUTF16("three searches aaa"), 1)); GURL term_url_b(AddSearchToHistory(default_t_url_, ASCIIToUTF16("three searches bbb"), 1)); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("three se"), &wyt_match)); ASSERT_EQ(3u, provider_->matches().size()); AutocompleteMatch term_match_a; EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a)); AutocompleteMatch term_match_b; EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b)); EXPECT_GT(term_match_b.relevance, term_match_a.relevance); EXPECT_GT(term_match_a.relevance, wyt_match.relevance); EXPECT_TRUE(term_match_b.allowed_to_be_default_match); EXPECT_TRUE(term_match_a.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } // An autocompleted multiword search should not be replaced by a different // autocompletion while the user is still typing a valid prefix unless the // user has typed the prefix as a query before. TEST_F(SearchProviderTest, DontReplacePreviousAutocompletion) { GURL term_url_a(AddSearchToHistory(default_t_url_, ASCIIToUTF16("four searches aaa"), 3)); GURL term_url_b(AddSearchToHistory(default_t_url_, ASCIIToUTF16("four searches bbb"), 1)); GURL term_url_c(AddSearchToHistory(default_t_url_, ASCIIToUTF16("four searches"), 1)); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("fo"), &wyt_match)); ASSERT_EQ(4u, provider_->matches().size()); AutocompleteMatch term_match_a; EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a)); AutocompleteMatch term_match_b; EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b)); AutocompleteMatch term_match_c; EXPECT_TRUE(FindMatchWithDestination(term_url_c, &term_match_c)); EXPECT_GT(term_match_a.relevance, wyt_match.relevance); // We don't care about the relative order of b and c. EXPECT_GT(wyt_match.relevance, term_match_b.relevance); EXPECT_GT(wyt_match.relevance, term_match_c.relevance); EXPECT_TRUE(term_match_a.allowed_to_be_default_match); EXPECT_TRUE(term_match_b.allowed_to_be_default_match); EXPECT_TRUE(term_match_c.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("four se"), &wyt_match)); ASSERT_EQ(4u, provider_->matches().size()); EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a)); EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b)); EXPECT_TRUE(FindMatchWithDestination(term_url_c, &term_match_c)); EXPECT_GT(term_match_a.relevance, wyt_match.relevance); EXPECT_GT(wyt_match.relevance, term_match_b.relevance); EXPECT_GT(wyt_match.relevance, term_match_c.relevance); EXPECT_TRUE(term_match_a.allowed_to_be_default_match); EXPECT_TRUE(term_match_b.allowed_to_be_default_match); EXPECT_TRUE(term_match_c.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); // For the exact previously-issued query, the what-you-typed match should win. ASSERT_NO_FATAL_FAILURE( QueryForInputAndSetWYTMatch(ASCIIToUTF16("four searches"), &wyt_match)); ASSERT_EQ(3u, provider_->matches().size()); EXPECT_TRUE(FindMatchWithDestination(term_url_a, &term_match_a)); EXPECT_TRUE(FindMatchWithDestination(term_url_b, &term_match_b)); EXPECT_GT(wyt_match.relevance, term_match_a.relevance); EXPECT_GT(wyt_match.relevance, term_match_b.relevance); EXPECT_TRUE(term_match_a.allowed_to_be_default_match); EXPECT_TRUE(term_match_b.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } // Non-completable multiword searches should not crowd out single-word searches. TEST_F(SearchProviderTest, DontCrowdOutSingleWords) { GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("five"), 1)); AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches bbb"), 1); AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches ccc"), 1); AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches ddd"), 1); AddSearchToHistory(default_t_url_, ASCIIToUTF16("five searches eee"), 1); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("fi"), &wyt_match)); ASSERT_EQ(AutocompleteProvider::kMaxMatches + 1, provider_->matches().size()); AutocompleteMatch term_match; EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match)); EXPECT_GT(term_match.relevance, wyt_match.relevance); EXPECT_TRUE(term_match.allowed_to_be_default_match); EXPECT_TRUE(wyt_match.allowed_to_be_default_match); } // Inline autocomplete matches regardless of case differences from the input. TEST_F(SearchProviderTest, InlineMixedCaseMatches) { GURL term_url(AddSearchToHistory(default_t_url_, ASCIIToUTF16("FOO"), 1)); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch wyt_match; ASSERT_NO_FATAL_FAILURE(QueryForInputAndSetWYTMatch(ASCIIToUTF16("f"), &wyt_match)); ASSERT_EQ(2u, provider_->matches().size()); AutocompleteMatch term_match; EXPECT_TRUE(FindMatchWithDestination(term_url, &term_match)); EXPECT_GT(term_match.relevance, wyt_match.relevance); EXPECT_EQ(ASCIIToUTF16("FOO"), term_match.fill_into_edit); EXPECT_EQ(ASCIIToUTF16("OO"), term_match.inline_autocompletion); EXPECT_TRUE(term_match.allowed_to_be_default_match); } // Verifies AutocompleteControllers return results (including keyword // results) in the right order and set descriptions for them correctly. TEST_F(SearchProviderTest, KeywordOrderingAndDescriptions) { // Add an entry that corresponds to a keyword search with 'term2'. AddSearchToHistory(keyword_t_url_, ASCIIToUTF16("term2"), 1); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteController controller(&profile_, TemplateURLServiceFactory::GetForProfile(&profile_), NULL, AutocompleteProvider::TYPE_SEARCH); controller.Start(AutocompleteInput( ASCIIToUTF16("k t"), base::string16::npos, std::string(), GURL(), metrics::OmniboxEventProto::INVALID_SPEC, false, false, true, true, ChromeAutocompleteSchemeClassifier(&profile_))); const AutocompleteResult& result = controller.result(); // There should be three matches, one for the keyword history, one for // keyword provider's what-you-typed, and one for the default provider's // what you typed, in that order. ASSERT_EQ(3u, result.size()); EXPECT_EQ(AutocompleteMatchType::SEARCH_HISTORY, result.match_at(0).type); EXPECT_EQ(AutocompleteMatchType::SEARCH_OTHER_ENGINE, result.match_at(1).type); EXPECT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, result.match_at(2).type); EXPECT_GT(result.match_at(0).relevance, result.match_at(1).relevance); EXPECT_GT(result.match_at(1).relevance, result.match_at(2).relevance); EXPECT_TRUE(result.match_at(0).allowed_to_be_default_match); EXPECT_TRUE(result.match_at(1).allowed_to_be_default_match); EXPECT_FALSE(result.match_at(2).allowed_to_be_default_match); // The two keyword results should come with the keyword we expect. EXPECT_EQ(ASCIIToUTF16("k"), result.match_at(0).keyword); EXPECT_EQ(ASCIIToUTF16("k"), result.match_at(1).keyword); // The default provider has a different keyword. (We don't explicitly // set it during this test, so all we do is assert that it's different.) EXPECT_NE(result.match_at(0).keyword, result.match_at(2).keyword); // The top result will always have a description. The third result, // coming from a different provider than the first two, should also. // Whether the second result has one doesn't matter much. (If it was // missing, people would infer that it's the same search provider as // the one above it.) EXPECT_FALSE(result.match_at(0).description.empty()); EXPECT_FALSE(result.match_at(2).description.empty()); EXPECT_NE(result.match_at(0).description, result.match_at(2).description); } TEST_F(SearchProviderTest, KeywordVerbatim) { TestData cases[] = { // Test a simple keyword input. { ASCIIToUTF16("k foo"), 2, { ResultInfo(GURL("http://keyword/foo"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo")), ResultInfo(GURL("http://defaultturl/k%20foo"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("k foo") ) } }, // Make sure extra whitespace after the keyword doesn't change the // keyword verbatim query. Also verify that interior consecutive // whitespace gets trimmed. { ASCIIToUTF16("k foo"), 2, { ResultInfo(GURL("http://keyword/foo"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo")), ResultInfo(GURL("http://defaultturl/k%20foo"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("k foo")) } }, // Leading whitespace should be stripped before SearchProvider gets the // input; hence there are no tests here about how it handles those inputs. // Verify that interior consecutive whitespace gets trimmed in either case. { ASCIIToUTF16("k foo bar"), 2, { ResultInfo(GURL("http://keyword/foo%20bar"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo bar")), ResultInfo(GURL("http://defaultturl/k%20foo%20bar"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("k foo bar")) } }, // Verify that trailing whitespace gets trimmed. { ASCIIToUTF16("k foo bar "), 2, { ResultInfo(GURL("http://keyword/foo%20bar"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo bar")), ResultInfo(GURL("http://defaultturl/k%20foo%20bar"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("k foo bar")) } }, // Keywords can be prefixed by certain things that should get ignored // when constructing the keyword match. { ASCIIToUTF16("www.k foo"), 2, { ResultInfo(GURL("http://keyword/foo"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo")), ResultInfo(GURL("http://defaultturl/www.k%20foo"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("www.k foo")) } }, { ASCIIToUTF16("http://k foo"), 2, { ResultInfo(GURL("http://keyword/foo"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo")), ResultInfo(GURL("http://defaultturl/http%3A//k%20foo"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("http://k foo")) } }, { ASCIIToUTF16("http://www.k foo"), 2, { ResultInfo(GURL("http://keyword/foo"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k foo")), ResultInfo(GURL("http://defaultturl/http%3A//www.k%20foo"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("http://www.k foo")) } }, // A keyword with no remaining input shouldn't get a keyword // verbatim match. { ASCIIToUTF16("k"), 1, { ResultInfo(GURL("http://defaultturl/k"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true, ASCIIToUTF16("k")) } }, // Ditto. Trailing whitespace shouldn't make a difference. { ASCIIToUTF16("k "), 1, { ResultInfo(GURL("http://defaultturl/k"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true, ASCIIToUTF16("k")) } } // The fact that verbatim queries to keyword are handled by KeywordProvider // not SearchProvider is tested in // chrome/browser/extensions/api/omnibox/omnibox_apitest.cc. }; // Test not in keyword mode. RunTest(cases, arraysize(cases), false); // Test in keyword mode. (Both modes should give the same result.) RunTest(cases, arraysize(cases), true); } // Ensures command-line flags are reflected in the URLs the search provider // generates. TEST_F(SearchProviderTest, CommandLineOverrides) { TemplateURLService* turl_model = TemplateURLServiceFactory::GetForProfile(&profile_); TemplateURLData data; data.short_name = ASCIIToUTF16("default"); data.SetKeyword(data.short_name); data.SetURL("{google:baseURL}{searchTerms}"); default_t_url_ = new TemplateURL(data); turl_model->Add(default_t_url_); turl_model->SetUserSelectedDefaultSearchProvider(default_t_url_); CommandLine::ForCurrentProcess()->AppendSwitchASCII(switches::kGoogleBaseURL, "http://www.bar.com/"); CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kExtraSearchQueryParams, "a=b"); TestData cases[] = { { ASCIIToUTF16("k a"), 2, { ResultInfo(GURL("http://keyword/a"), AutocompleteMatchType::SEARCH_OTHER_ENGINE, true, ASCIIToUTF16("k a")), ResultInfo(GURL("http://www.bar.com/k%20a?a=b"), AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false, ASCIIToUTF16("k a")) } }, }; RunTest(cases, arraysize(cases), false); } // Verifies Navsuggest results don't set a TemplateURL, which Instant relies on. // Also verifies that just the *first* navigational result is listed as a match // if suggested relevance scores were not sent. TEST_F(SearchProviderTest, NavSuggestNoSuggestedRelevanceScores) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("a.c"), false, "[\"a.c\",[\"a.com\", \"a.com/b\"],[\"a\", \"b\"],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]}]", std::string()); // Make sure the only match is 'a.com' and it doesn't have a template_url. AutocompleteMatch nav_match; EXPECT_TRUE(FindMatchWithDestination(GURL("http://a.com"), &nav_match)); EXPECT_TRUE(nav_match.keyword.empty()); EXPECT_FALSE(nav_match.allowed_to_be_default_match); EXPECT_FALSE(FindMatchWithDestination(GURL("http://a.com/b"), &nav_match)); } // Verifies that the most relevant suggest results are added properly. TEST_F(SearchProviderTest, SuggestRelevance) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("a"), false, "[\"a\",[\"a1\", \"a2\", \"a3\", \"a4\"]]", std::string()); // Check the expected verbatim and (first 3) suggestions' relative relevances. AutocompleteMatch verbatim, match_a1, match_a2, match_a3, match_a4; EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a"), &verbatim)); EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a1"), &match_a1)); EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a2"), &match_a2)); EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a3"), &match_a3)); EXPECT_FALSE(FindMatchWithContents(ASCIIToUTF16("a4"), &match_a4)); EXPECT_GT(verbatim.relevance, match_a1.relevance); EXPECT_GT(match_a1.relevance, match_a2.relevance); EXPECT_GT(match_a2.relevance, match_a3.relevance); EXPECT_TRUE(verbatim.allowed_to_be_default_match); EXPECT_FALSE(match_a1.allowed_to_be_default_match); EXPECT_FALSE(match_a2.allowed_to_be_default_match); EXPECT_FALSE(match_a3.allowed_to_be_default_match); } // Verifies that the default provider abandons suggested relevance scores // when in keyword mode. This should happen regardless of whether the // keyword provider returns suggested relevance scores. TEST_F(SearchProviderTest, DefaultProviderNoSuggestRelevanceInKeywordMode) { struct { const std::string default_provider_json; const std::string keyword_provider_json; const std::string matches[5]; } cases[] = { // First, try an input where the keyword provider does not deliver // suggested relevance scores. { "[\"k a\",[\"k adefault-query\", \"adefault.com\"],[],[]," "{\"google:verbatimrelevance\":9700," "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9900, 9800]}]", "[\"a\",[\"akeyword-query\"],[],[],{\"google:suggesttype\":[\"QUERY\"]}]", { "a", "akeyword-query", "k a", "adefault.com", "k adefault-query" } }, // Now try with keyword provider suggested relevance scores. { "[\"k a\",[\"k adefault-query\", \"adefault.com\"],[],[]," "{\"google:verbatimrelevance\":9700," "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9900, 9800]}]", "[\"a\",[\"akeyword-query\"],[],[],{\"google:suggesttype\":[\"QUERY\"]," "\"google:verbatimrelevance\":9500," "\"google:suggestrelevance\":[9600]}]", { "akeyword-query", "a", "k a", "adefault.com", "k adefault-query" } } }; for (size_t i = 0; i < arraysize(cases); ++i) { // Send the query twice in order to have a synchronous pass after the first // response is received. This is necessary because SearchProvider doesn't // allow an asynchronous response to change the default match. for (size_t j = 0; j < 2; ++j) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("k a"), true, cases[i].default_provider_json, cases[i].keyword_provider_json); } SCOPED_TRACE( "for input with default_provider_json=" + cases[i].default_provider_json + " and keyword_provider_json=" + cases[i].keyword_provider_json); const ACMatches& matches = provider_->matches(); ASSERT_LE(matches.size(), arraysize(cases[i].matches)); size_t j = 0; // Ensure that the returned matches equal the expectations. for (; j < matches.size(); ++j) EXPECT_EQ(ASCIIToUTF16(cases[i].matches[j]), matches[j].contents); // Ensure that no expected matches are missing. for (; j < arraysize(cases[i].matches); ++j) EXPECT_EQ(std::string(), cases[i].matches[j]); } } // Verifies that suggest results with relevance scores are added // properly when using the default fetcher. When adding a new test // case to this test, please consider adding it to the tests in // KeywordFetcherSuggestRelevance below. TEST_F(SearchProviderTest, DefaultFetcherSuggestRelevance) { struct { const std::string json; const ExpectedMatch matches[6]; const std::string inline_autocompletion; } cases[] = { // Ensure that suggestrelevance scores reorder matches. { "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]", { { "a", true }, { "c", false }, { "b", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"http://b.com\", \"http://c.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[1, 2]}]", { { "a", true }, { "c.com", false }, { "b.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, // Without suggested relevance scores, we should only allow one // navsuggest result to be be displayed. { "[\"a\",[\"http://b.com\", \"http://c.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]}]", { { "a", true }, { "b.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, // Ensure that verbatimrelevance scores reorder or suppress verbatim. // Negative values will have no effect; the calculated value will be used. { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9999," "\"google:suggestrelevance\":[9998]}]", { { "a", true}, { "a1", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9998," "\"google:suggestrelevance\":[9999]}]", { { "a1", true }, { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9999]}]", { { "a1", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":-1," "\"google:suggestrelevance\":[9999]}]", { { "a1", true }, { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, { "[\"a\",[\"http://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:verbatimrelevance\":9999," "\"google:suggestrelevance\":[9998]}]", { { "a", true }, { "a.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"http://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:verbatimrelevance\":9998," "\"google:suggestrelevance\":[9999]}]", { { "a.com", true }, { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, ".com" }, { "[\"a\",[\"http://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9999]}]", { { "a.com", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, ".com" }, { "[\"a\",[\"http://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:verbatimrelevance\":-1," "\"google:suggestrelevance\":[9999]}]", { { "a.com", true }, { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, ".com" }, // Ensure that both types of relevance scores reorder matches together. { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[9999, 9997]," "\"google:verbatimrelevance\":9998}]", { { "a1", true }, { "a", true }, { "a2", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, // Check that an inlineable result appears first regardless of its score. // Also, if the result set lacks a single inlineable result, abandon the // request to suppress verbatim (verbatim_relevance=0), which will then // cause verbatim to appear (first). { "[\"a\",[\"b\"],[],[],{\"google:suggestrelevance\":[9999]}]", { { "a", true }, { "b", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"b\"],[],[],{\"google:suggestrelevance\":[9999]," "\"google:verbatimrelevance\":0}]", { { "a", true }, { "b", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"http://b.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "a", true }, { "b.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"http://b.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]," "\"google:verbatimrelevance\":0}]", { { "a", true }, { "b.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, // Allow low-scoring matches. { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":0}]", { { "a1", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":10}]", { { "a1", true }, { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:suggestrelevance\":[10]," "\"google:verbatimrelevance\":0}]", { { "a1", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "1" }, { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[10, 20]," "\"google:verbatimrelevance\":0}]", { { "a2", true }, { "a1", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "2" }, { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[10, 30]," "\"google:verbatimrelevance\":20}]", { { "a2", true }, { "a", true }, { "a1", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "2" }, { "[\"a\",[\"http://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[10]," "\"google:verbatimrelevance\":0}]", { { "a.com", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, ".com" }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[10, 20]," "\"google:verbatimrelevance\":0}]", { { "a2.com", true }, { "a1.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "2.com" }, // Ensure that all suggestions are considered, regardless of order. { "[\"a\",[\"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"],[],[]," "{\"google:suggestrelevance\":[10, 20, 30, 40, 50, 60, 70]}]", { { "a", true }, { "h", false }, { "g", false }, { "f", false }, { "e", false }, { "d", false } }, std::string() }, { "[\"a\",[\"http://b.com\", \"http://c.com\", \"http://d.com\"," "\"http://e.com\", \"http://f.com\", \"http://g.com\"," "\"http://h.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"," "\"NAVIGATION\", \"NAVIGATION\"," "\"NAVIGATION\", \"NAVIGATION\"," "\"NAVIGATION\"]," "\"google:suggestrelevance\":[10, 20, 30, 40, 50, 60, 70]}]", { { "a", true }, { "h.com", false }, { "g.com", false }, { "f.com", false }, { "e.com", false }, { "d.com", false } }, std::string() }, // Ensure that incorrectly sized suggestion relevance lists are ignored. { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[10]}]", { { "a", true }, { "a1", false }, { "a2", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"a1\"],[],[],{\"google:suggestrelevance\":[9999, 10]}]", { { "a", true }, { "a1", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[10]}]", { { "a", true }, { "a1.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[\"http://a1.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999, 10]}]", { { "a", true }, { "a1.com", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, // Ensure that all 'verbatim' results are merged with their maximum score. { "[\"a\",[\"a\", \"a1\", \"a2\"],[],[]," "{\"google:suggestrelevance\":[9998, 9997, 9999]}]", { { "a2", true }, { "a", true }, { "a1", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "2" }, { "[\"a\",[\"a\", \"a1\", \"a2\"],[],[]," "{\"google:suggestrelevance\":[9998, 9997, 9999]," "\"google:verbatimrelevance\":0}]", { { "a2", true }, { "a", true }, { "a1", false }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, "2" }, // Ensure that verbatim is always generated without other suggestions. // TODO(msw): Ensure verbatimrelevance is respected (except suppression). { "[\"a\",[],[],[],{\"google:verbatimrelevance\":1}]", { { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, { "[\"a\",[],[],[],{\"google:verbatimrelevance\":0}]", { { "a", true }, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch, kEmptyExpectedMatch }, std::string() }, }; for (size_t i = 0; i < arraysize(cases); ++i) { // Send the query twice in order to have a synchronous pass after the first // response is received. This is necessary because SearchProvider doesn't // allow an asynchronous response to change the default match. for (size_t j = 0; j < 2; ++j) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("a"), false, cases[i].json, std::string()); } const std::string description = "for input with json=" + cases[i].json; CheckMatches(description, arraysize(cases[i].matches), cases[i].matches, provider_->matches()); } } // Verifies that suggest results with relevance scores are added // properly when using the keyword fetcher. This is similar to the // test DefaultFetcherSuggestRelevance above but this uses inputs that // trigger keyword suggestions (i.e., "k a" rather than "a") and has // different expectations (because now the results are a mix of // keyword suggestions and default provider suggestions). When a new // test is added to this TEST_F, please consider if it would be // appropriate to add to DefaultFetcherSuggestRelevance as well. TEST_F(SearchProviderTest, KeywordFetcherSuggestRelevance) { struct KeywordFetcherMatch { std::string contents; bool from_keyword; bool allowed_to_be_default_match; }; const KeywordFetcherMatch kEmptyMatch = { kNotApplicable, false, false }; struct { const std::string json; const KeywordFetcherMatch matches[6]; const std::string inline_autocompletion; } cases[] = { // Ensure that suggest relevance scores reorder matches and that // the keyword verbatim (lacking a suggested verbatim score) beats // the default provider verbatim. { "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]", { { "a", true, true }, { "k a", false, false }, { "c", true, false }, { "b", true, false }, kEmptyMatch, kEmptyMatch }, std::string() }, // Again, check that relevance scores reorder matches, just this // time with navigation matches. This also checks that with // suggested relevance scores we allow multiple navsuggest results. // Note that navsuggest results that come from a keyword provider // are marked as not a keyword result. (They don't go to a // keyword search engine.) { "[\"a\",[\"http://b.com\", \"http://c.com\", \"d\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:suggestrelevance\":[1301, 1302, 1303]}]", { { "a", true, true }, { "d", true, false }, { "c.com", false, false }, { "b.com", false, false }, { "k a", false, false }, kEmptyMatch }, std::string() }, // Without suggested relevance scores, we should only allow one // navsuggest result to be be displayed. { "[\"a\",[\"http://b.com\", \"http://c.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]}]", { { "a", true, true }, { "b.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // Ensure that verbatimrelevance scores reorder or suppress verbatim. // Negative values will have no effect; the calculated value will be used. { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9999," "\"google:suggestrelevance\":[9998]}]", { { "a", true, true }, { "a1", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":9998," "\"google:suggestrelevance\":[9999]}]", { { "a1", true, true }, { "a", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9999]}]", { { "a1", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":-1," "\"google:suggestrelevance\":[9999]}]", { { "a1", true, true }, { "a", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "1" }, { "[\"a\",[\"http://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:verbatimrelevance\":9999," "\"google:suggestrelevance\":[9998]}]", { { "a", true, true }, { "a.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // Ensure that both types of relevance scores reorder matches together. { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[9999, 9997]," "\"google:verbatimrelevance\":9998}]", { { "a1", true, true }, { "a", true, true }, { "a2", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, "1" }, // Check that an inlineable match appears first regardless of its score. { "[\"a\",[\"b\"],[],[],{\"google:suggestrelevance\":[9999]}]", { { "a", true, true }, { "b", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"http://b.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "a", true, true }, { "b.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // If there is no inlineable match, restore the keyword verbatim score. // The keyword verbatim match will then appear first. { "[\"a\",[\"b\"],[],[],{\"google:suggestrelevance\":[9999]," "\"google:verbatimrelevance\":0}]", { { "a", true, true }, { "b", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"http://b.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]," "\"google:verbatimrelevance\":0}]", { { "a", true, true }, { "b.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // The top result does not have to score as highly as calculated // verbatim. i.e., there are no minimum score restrictions in // this provider. { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":0}]", { { "a1", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:verbatimrelevance\":10}]", { { "a1", true, true }, { "k a", false, false }, { "a", true, true }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "1" }, { "[\"a\",[\"a1\"],[],[],{\"google:suggestrelevance\":[10]," "\"google:verbatimrelevance\":0}]", { { "a1", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "1" }, { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[10, 20]," "\"google:verbatimrelevance\":0}]", { { "a2", true, true }, { "k a", false, false }, { "a1", true, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, "2" }, { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[10, 30]," "\"google:verbatimrelevance\":20}]", { { "a2", true, true }, { "k a", false, false }, { "a", true, true }, { "a1", true, false }, kEmptyMatch, kEmptyMatch }, "2" }, // Ensure that all suggestions are considered, regardless of order. { "[\"a\",[\"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"],[],[]," "{\"google:suggestrelevance\":[10, 20, 30, 40, 50, 60, 70]}]", { { "a", true, true }, { "k a", false, false }, { "h", true, false }, { "g", true, false }, { "f", true, false }, { "e", true, false } }, std::string() }, { "[\"a\",[\"http://b.com\", \"http://c.com\", \"http://d.com\"," "\"http://e.com\", \"http://f.com\", \"http://g.com\"," "\"http://h.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"," "\"NAVIGATION\", \"NAVIGATION\"," "\"NAVIGATION\", \"NAVIGATION\"," "\"NAVIGATION\"]," "\"google:suggestrelevance\":[10, 20, 30, 40, 50, 60, 70]}]", { { "a", true, true }, { "k a", false, false }, { "h.com", false, false }, { "g.com", false, false }, { "f.com", false, false }, { "e.com", false, false } }, std::string() }, // Ensure that incorrectly sized suggestion relevance lists are ignored. // Note that keyword suggestions by default (not in suggested relevance // mode) score more highly than the default verbatim. { "[\"a\",[\"a1\", \"a2\"],[],[],{\"google:suggestrelevance\":[1]}]", { { "a", true, true }, { "a1", true, false }, { "a2", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"a1\"],[],[],{\"google:suggestrelevance\":[9999, 1]}]", { { "a", true, true }, { "a1", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // In this case, ignoring the suggested relevance scores means we keep // only one navsuggest result. { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[1]}]", { { "a", true, true }, { "a1.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"http://a1.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999, 1]}]", { { "a", true, true }, { "a1.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // Ensure that all 'verbatim' results are merged with their maximum score. { "[\"a\",[\"a\", \"a1\", \"a2\"],[],[]," "{\"google:suggestrelevance\":[9998, 9997, 9999]}]", { { "a2", true, true }, { "a", true, true }, { "a1", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, "2" }, { "[\"a\",[\"a\", \"a1\", \"a2\"],[],[]," "{\"google:suggestrelevance\":[9998, 9997, 9999]," "\"google:verbatimrelevance\":0}]", { { "a2", true, true }, { "a", true, true }, { "a1", true, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, "2" }, // Ensure that verbatim is always generated without other suggestions. // TODO(mpearson): Ensure the value of verbatimrelevance is respected // (except when suggested relevances are ignored). { "[\"a\",[],[],[],{\"google:verbatimrelevance\":1}]", { { "a", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[],[],[],{\"google:verbatimrelevance\":0}]", { { "a", true, true }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // In reorder mode, navsuggestions will not need to be demoted (because // they are marked as not allowed to be default match and will be // reordered as necessary). { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9998, 9999]}]", { { "a", true, true }, { "a2.com", false, false }, { "a1.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9999, 9998]}]", { { "a", true, true }, { "a1.com", false, false }, { "a2.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"https://a/\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "a", true, true }, { "https://a", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, std::string() }, // Check when navsuggest scores more than verbatim and there is query // suggestion but it scores lower. { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9998, 9999, 1300]}]", { { "a", true, true }, { "a2.com", false, false }, { "a1.com", false, false }, { "a3", true, false }, { "k a", false, false }, kEmptyMatch }, std::string() }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9999, 9998, 1300]}]", { { "a", true, true }, { "a1.com", false, false }, { "a2.com", false, false }, { "a3", true, false }, { "k a", false, false }, kEmptyMatch }, std::string() }, // Check when navsuggest scores more than a query suggestion. There is // a verbatim but it scores lower. { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9998, 9999, 9997]}]", { { "a3", true, true }, { "a2.com", false, false }, { "a1.com", false, false }, { "a", true, true }, { "k a", false, false }, kEmptyMatch }, "3" }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9999, 9998, 9997]}]", { { "a3", true, true }, { "a1.com", false, false }, { "a2.com", false, false }, { "a", true, true }, { "k a", false, false }, kEmptyMatch }, "3" }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9998, 9999, 9997]}]", { { "a3", true, true }, { "a2.com", false, false }, { "a1.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, "3" }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9999, 9998, 9997]}]", { { "a3", true, true }, { "a1.com", false, false }, { "a2.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, "3" }, // Check when there is neither verbatim nor a query suggestion that, // because we can't demote navsuggestions below a query suggestion, // we restore the keyword verbatim score. { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9998, 9999]}]", { { "a", true, true }, { "a2.com", false, false }, { "a1.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, std::string() }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:verbatimrelevance\":0," "\"google:suggestrelevance\":[9999, 9998]}]", { { "a", true, true }, { "a1.com", false, false }, { "a2.com", false, false }, { "k a", false, false }, kEmptyMatch, kEmptyMatch }, std::string() }, // More checks that everything works when it's not necessary to demote. { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9997, 9998, 9999]}]", { { "a3", true, true }, { "a2.com", false, false }, { "a1.com", false, false }, { "a", true, true }, { "k a", false, false }, kEmptyMatch }, "3" }, { "[\"a\",[\"http://a1.com\", \"http://a2.com\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\", \"QUERY\"]," "\"google:verbatimrelevance\":9990," "\"google:suggestrelevance\":[9998, 9997, 9999]}]", { { "a3", true, true }, { "a1.com", false, false }, { "a2.com", false, false }, { "a", true, true }, { "k a", false, false }, kEmptyMatch }, "3" }, }; for (size_t i = 0; i < arraysize(cases); ++i) { // Send the query twice in order to have a synchronous pass after the first // response is received. This is necessary because SearchProvider doesn't // allow an asynchronous response to change the default match. for (size_t j = 0; j < 2; ++j) { QueryForInput(ASCIIToUTF16("k a"), false, true); // Set up a default fetcher with no results. net::TestURLFetcher* default_fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(default_fetcher); default_fetcher->set_response_code(200); default_fetcher->delegate()->OnURLFetchComplete(default_fetcher); default_fetcher = NULL; // Set up a keyword fetcher with provided results. net::TestURLFetcher* keyword_fetcher = test_factory_.GetFetcherByID( SearchProvider::kKeywordProviderURLFetcherID); ASSERT_TRUE(keyword_fetcher); keyword_fetcher->set_response_code(200); keyword_fetcher->SetResponseString(cases[i].json); keyword_fetcher->delegate()->OnURLFetchComplete(keyword_fetcher); keyword_fetcher = NULL; RunTillProviderDone(); } SCOPED_TRACE("for input with json=" + cases[i].json); const ACMatches& matches = provider_->matches(); ASSERT_FALSE(matches.empty()); // Find the first match that's allowed to be the default match and check // its inline_autocompletion. ACMatches::const_iterator it = FindDefaultMatch(matches); ASSERT_NE(matches.end(), it); EXPECT_EQ(ASCIIToUTF16(cases[i].inline_autocompletion), it->inline_autocompletion); ASSERT_LE(matches.size(), arraysize(cases[i].matches)); size_t j = 0; // Ensure that the returned matches equal the expectations. for (; j < matches.size(); ++j) { EXPECT_EQ(ASCIIToUTF16(cases[i].matches[j].contents), matches[j].contents); EXPECT_EQ(cases[i].matches[j].from_keyword, matches[j].keyword == ASCIIToUTF16("k")); EXPECT_EQ(cases[i].matches[j].allowed_to_be_default_match, matches[j].allowed_to_be_default_match); } // Ensure that no expected matches are missing. for (; j < arraysize(cases[i].matches); ++j) { SCOPED_TRACE(" Case # " + base::IntToString(i)); EXPECT_EQ(kNotApplicable, cases[i].matches[j].contents); } } } TEST_F(SearchProviderTest, DontInlineAutocompleteAsynchronously) { // This test sends two separate queries, each receiving different JSON // replies, and checks that at each stage of processing (receiving first // asynchronous response, handling new keystroke synchronously / sending the // second request, and receiving the second asynchronous response) we have the // expected matches. In particular, receiving the second response shouldn't // cause an unexpected inline autcompletion. struct { const std::string first_json; const ExpectedMatch first_async_matches[4]; const ExpectedMatch sync_matches[4]; const std::string second_json; const ExpectedMatch second_async_matches[4]; } cases[] = { // A simple test that verifies we don't inline autocomplete after the // first asynchronous response, but we do at the next keystroke if the // response's results were good enough. Furthermore, we should continue // inline autocompleting after the second asynchronous response if the new // top suggestion is the same as the old inline autocompleted suggestion. { "[\"a\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9001]}]", { { "a", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch }, { { "ab1", true }, { "ab2", true }, { "ab", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9001]}]", { { "ab1", true }, { "ab2", false }, { "ab", true }, kEmptyExpectedMatch } }, // Ditto, just for a navigation suggestion. { "[\"a\",[\"ab1.com\", \"ab2.com\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9002, 9001]}]", { { "a", true }, { "ab1.com", false }, { "ab2.com", false }, kEmptyExpectedMatch }, { { "ab1.com", true }, { "ab2.com", true }, { "ab", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1.com\", \"ab2.com\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9002, 9001]}]", { { "ab1.com", true }, { "ab2.com", false }, { "ab", true }, kEmptyExpectedMatch } }, // A more realistic test of the same situation. { "[\"a\",[\"abcdef\", \"abcdef.com\", \"abc\"],[],[]," "{\"google:verbatimrelevance\":900," "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\", \"QUERY\"]," "\"google:suggestrelevance\":[1250, 1200, 1000]}]", { { "a", true }, { "abcdef", false }, { "abcdef.com", false }, { "abc", false } }, { { "abcdef", true }, { "abcdef.com", true }, { "abc", true }, { "ab", true } }, "[\"ab\",[\"abcdef\", \"abcdef.com\", \"abc\"],[],[]," "{\"google:verbatimrelevance\":900," "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\", \"QUERY\"]," "\"google:suggestrelevance\":[1250, 1200, 1000]}]", { { "abcdef", true }, { "abcdef.com", false }, { "abc", false }, { "ab", true } } }, // Without an original inline autcompletion, a new inline autcompletion // should be rejected. { "[\"a\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[8000, 7000]}]", { { "a", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch }, { { "ab", true }, { "ab1", true }, { "ab2", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9001]}]", { { "ab", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch } }, // For the same test except with the queries scored in the opposite order // on the second JSON response, the queries should be ordered by the second // response's scores, not the first. { "[\"a\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[8000, 7000]}]", { { "a", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch }, { { "ab", true }, { "ab1", true }, { "ab2", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9001, 9002]}]", { { "ab", true }, { "ab2", false }, { "ab1", false }, kEmptyExpectedMatch } }, // Now, the same verifications but with the new inline autocompletion as a // navsuggestion. The new autocompletion should still be rejected. { "[\"a\",[\"ab1.com\", \"ab2.com\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[8000, 7000]}]", { { "a", true }, { "ab1.com", false }, { "ab2.com", false }, kEmptyExpectedMatch }, { { "ab", true }, { "ab1.com", true }, { "ab2.com", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1.com\", \"ab2.com\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9002, 9001]}]", { { "ab", true }, { "ab1.com", false }, { "ab2.com", false }, kEmptyExpectedMatch } }, { "[\"a\",[\"ab1.com\", \"ab2.com\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[8000, 7000]}]", { { "a", true }, { "ab1.com", false }, { "ab2.com", false }, kEmptyExpectedMatch }, { { "ab", true }, { "ab1.com", true }, { "ab2.com", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1.com\", \"ab2.com\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggesttype\":[\"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9001, 9002]}]", { { "ab", true }, { "ab2.com", false }, { "ab1.com", false }, kEmptyExpectedMatch } }, // It's okay to abandon an inline autocompletion asynchronously. { "[\"a\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9001]}]", { { "a", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch }, { { "ab1", true }, { "ab2", true }, { "ab", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[8000, 7000]}]", { { "ab", true }, { "ab1", true }, { "ab2", false }, kEmptyExpectedMatch } }, // Note: it's possible that the suggest server returns a suggestion with // an inline autocompletion (that as usual we delay in allowing it to // be displayed as an inline autocompletion until the next keystroke), // then, in response to the next keystroke, the server returns a different // suggestion as an inline autocompletion. This is not likely to happen. // Regardless, if it does, one could imagine three different behaviors: // - keep the original inline autocompletion until the next keystroke // (i.e., don't abandon an inline autocompletion asynchronously), then // use the new suggestion // - abandon all inline autocompletions upon the server response, then use // the new suggestion on the next keystroke // - ignore the new inline autocompletion provided by the server, yet // possibly keep the original if it scores well in the most recent // response, then use the new suggestion on the next keystroke // All of these behaviors are reasonable. The main thing we want to // ensure is that the second asynchronous response shouldn't cause *a new* // inline autocompletion to be displayed. We test that here. // The current implementation does the third bullet, but all of these // behaviors seem reasonable. { "[\"a\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9001]}]", { { "a", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch }, { { "ab1", true }, { "ab2", true }, { "ab", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1\", \"ab3\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9900]}]", { { "ab1", true }, { "ab3", false }, { "ab", true }, kEmptyExpectedMatch } }, { "[\"a\",[\"ab1\", \"ab2\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[9002, 9001]}]", { { "a", true }, { "ab1", false }, { "ab2", false }, kEmptyExpectedMatch }, { { "ab1", true }, { "ab2", true }, { "ab", true }, kEmptyExpectedMatch }, "[\"ab\",[\"ab1\", \"ab3\"],[],[]," "{\"google:verbatimrelevance\":9000," "\"google:suggestrelevance\":[8000, 9500]}]", { { "ab", true }, { "ab3", false }, { "ab1", true }, kEmptyExpectedMatch } }, }; for (size_t i = 0; i < arraysize(cases); ++i) { // First, send the query "a" and receive the JSON response |first_json|. ClearAllResults(); QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("a"), false, cases[i].first_json, std::string()); // Verify that the matches after the asynchronous results are as expected. std::string description = "first asynchronous response for input with " "first_json=" + cases[i].first_json; CheckMatches(description, arraysize(cases[i].first_async_matches), cases[i].first_async_matches, provider_->matches()); // Then, send the query "ab" and check the synchronous matches. description = "synchronous response after the first keystroke after input " "with first_json=" + cases[i].first_json; QueryForInput(ASCIIToUTF16("ab"), false, false); CheckMatches(description, arraysize(cases[i].sync_matches), cases[i].sync_matches, provider_->matches()); // Finally, get the provided JSON response, |second_json|, and verify the // matches after the second asynchronous response are as expected. description = "second asynchronous response after input with first_json=" + cases[i].first_json + " and second_json=" + cases[i].second_json; net::TestURLFetcher* second_fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(second_fetcher); second_fetcher->set_response_code(200); second_fetcher->SetResponseString(cases[i].second_json); second_fetcher->delegate()->OnURLFetchComplete(second_fetcher); RunTillProviderDone(); CheckMatches(description, arraysize(cases[i].second_async_matches), cases[i].second_async_matches, provider_->matches()); } } TEST_F(SearchProviderTest, LocalAndRemoteRelevances) { // We hardcode the string "term1" below, so ensure that the search term that // got added to history already is that string. ASSERT_EQ(ASCIIToUTF16("term1"), term1_); base::string16 term = term1_.substr(0, term1_.length() - 1); AddSearchToHistory(default_t_url_, term + ASCIIToUTF16("2"), 2); profile_.BlockUntilHistoryProcessesPendingRequests(); struct { const base::string16 input; const std::string json; const std::string matches[6]; } cases[] = { // The history results outscore the default verbatim score. term2 has more // visits so it outscores term1. The suggestions are still returned since // they're server-scored. { term, "[\"term\",[\"a1\", \"a2\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"QUERY\", \"QUERY\"]," "\"google:suggestrelevance\":[1, 2, 3]}]", { "term2", "term1", "term", "a3", "a2", "a1" } }, // Because we already have three suggestions by the time we see the history // results, they don't get returned. { term, "[\"term\",[\"a1\", \"a2\", \"a3\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"QUERY\", \"QUERY\"]," "\"google:verbatimrelevance\":1450," "\"google:suggestrelevance\":[1440, 1430, 1420]}]", { "term", "a1", "a2", "a3", kNotApplicable, kNotApplicable } }, // If we only have two suggestions, we have room for a history result. { term, "[\"term\",[\"a1\", \"a2\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"QUERY\"]," "\"google:verbatimrelevance\":1450," "\"google:suggestrelevance\":[1430, 1410]}]", { "term", "a1", "a2", "term2", kNotApplicable, kNotApplicable } }, // If we have more than three suggestions, they should all be returned as // long as we have enough total space for them. { term, "[\"term\",[\"a1\", \"a2\", \"a3\", \"a4\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"QUERY\", \"QUERY\", \"QUERY\"]," "\"google:verbatimrelevance\":1450," "\"google:suggestrelevance\":[1440, 1430, 1420, 1410]}]", { "term", "a1", "a2", "a3", "a4", kNotApplicable } }, { term, "[\"term\",[\"a1\", \"a2\", \"a3\", \"a4\", \"a5\", \"a6\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"QUERY\", \"QUERY\", \"QUERY\"," "\"QUERY\", \"QUERY\"]," "\"google:verbatimrelevance\":1450," "\"google:suggestrelevance\":[1440, 1430, 1420, 1410, 1400, 1390]}]", { "term", "a1", "a2", "a3", "a4", "a5" } }, { term, "[\"term\",[\"a1\", \"a2\", \"a3\", \"a4\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"QUERY\", \"QUERY\", \"QUERY\"]," "\"google:verbatimrelevance\":1450," "\"google:suggestrelevance\":[1430, 1410, 1390, 1370]}]", { "term", "a1", "a2", "term2", "a3", "a4" } } }; for (size_t i = 0; i < arraysize(cases); ++i) { QueryForInputAndWaitForFetcherResponses( cases[i].input, false, cases[i].json, std::string()); const std::string description = "for input with json=" + cases[i].json; const ACMatches& matches = provider_->matches(); // Ensure no extra matches are present. ASSERT_LE(matches.size(), arraysize(cases[i].matches)); size_t j = 0; // Ensure that the returned matches equal the expectations. for (; j < matches.size(); ++j) EXPECT_EQ(ASCIIToUTF16(cases[i].matches[j]), matches[j].contents) << description; // Ensure that no expected matches are missing. for (; j < arraysize(cases[i].matches); ++j) EXPECT_EQ(kNotApplicable, cases[i].matches[j]) << "Case # " << i << " " << description; } } // Verifies suggest relevance behavior for URL input. TEST_F(SearchProviderTest, DefaultProviderSuggestRelevanceScoringUrlInput) { struct DefaultFetcherUrlInputMatch { const std::string match_contents; AutocompleteMatch::Type match_type; bool allowed_to_be_default_match; }; const DefaultFetcherUrlInputMatch kEmptyMatch = { kNotApplicable, AutocompleteMatchType::NUM_TYPES, false }; struct { const std::string input; const std::string json; const DefaultFetcherUrlInputMatch output[4]; } cases[] = { // Ensure NAVIGATION matches are allowed to be listed first for URL input. // Non-inlineable matches should not be allowed to be the default match. // Note that the top-scoring inlineable match is moved to the top // regardless of its score. { "a.com", "[\"a.com\",[\"http://b.com/\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "b.com", AutocompleteMatchType::NAVSUGGEST, false }, kEmptyMatch, kEmptyMatch } }, { "a.com", "[\"a.com\",[\"https://b.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "https://b.com", AutocompleteMatchType::NAVSUGGEST, false }, kEmptyMatch, kEmptyMatch } }, { "a.com", "[\"a.com\",[\"http://a.com/a\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "a.com/a", AutocompleteMatchType::NAVSUGGEST, true }, { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, kEmptyMatch, kEmptyMatch } }, { "a.com", "[\"a.com\",[\"https://a.com\"],[],[]," "{\"google:suggesttype\":[\"NAVIGATION\"]," "\"google:suggestrelevance\":[9999]}]", { { "https://a.com", AutocompleteMatchType::NAVSUGGEST, true }, { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, kEmptyMatch, kEmptyMatch } }, // Ensure topmost inlineable SUGGEST matches are NOT allowed for URL // input. SearchProvider disregards search and verbatim suggested // relevances. { "a.com", "[\"a.com\",[\"a.com info\"],[],[]," "{\"google:suggestrelevance\":[9999]}]", { { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "a.com info", AutocompleteMatchType::SEARCH_SUGGEST, false }, kEmptyMatch, kEmptyMatch } }, { "a.com", "[\"a.com\",[\"a.com info\"],[],[]," "{\"google:suggestrelevance\":[9999]}]", { { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "a.com info", AutocompleteMatchType::SEARCH_SUGGEST, false }, kEmptyMatch, kEmptyMatch } }, // Ensure the fallback mechanism allows inlineable NAVIGATION matches. { "a.com", "[\"a.com\",[\"a.com info\", \"http://a.com/b\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9999, 9998]}]", { { "a.com/b", AutocompleteMatchType::NAVSUGGEST, true }, { "a.com info", AutocompleteMatchType::SEARCH_SUGGEST, false }, { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, kEmptyMatch } }, { "a.com", "[\"a.com\",[\"a.com info\", \"http://a.com/b\"],[],[]," "{\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[9998, 9997]," "\"google:verbatimrelevance\":9999}]", { { "a.com/b", AutocompleteMatchType::NAVSUGGEST, true }, { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "a.com info", AutocompleteMatchType::SEARCH_SUGGEST, false }, kEmptyMatch } }, // Ensure non-inlineable SUGGEST matches are allowed for URL input // assuming the best inlineable match is not a query (i.e., is a // NAVSUGGEST). The best inlineable match will be at the top of the // list regardless of its score. { "a.com", "[\"a.com\",[\"info\"],[],[]," "{\"google:suggestrelevance\":[9999]}]", { { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "info", AutocompleteMatchType::SEARCH_SUGGEST, false }, kEmptyMatch, kEmptyMatch } }, { "a.com", "[\"a.com\",[\"info\"],[],[]," "{\"google:suggestrelevance\":[9999]}]", { { "a.com", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, true }, { "info", AutocompleteMatchType::SEARCH_SUGGEST, false }, kEmptyMatch, kEmptyMatch } }, }; for (size_t i = 0; i < arraysize(cases); ++i) { // Send the query twice in order to have a synchronous pass after the first // response is received. This is necessary because SearchProvider doesn't // allow an asynchronous response to change the default match. for (size_t j = 0; j < 2; ++j) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(cases[i].input), false, cases[i].json, std::string()); } SCOPED_TRACE("input=" + cases[i].input + " json=" + cases[i].json); size_t j = 0; const ACMatches& matches = provider_->matches(); ASSERT_LE(matches.size(), arraysize(cases[i].output)); // Ensure that the returned matches equal the expectations. for (; j < matches.size(); ++j) { EXPECT_EQ(ASCIIToUTF16(cases[i].output[j].match_contents), matches[j].contents); EXPECT_EQ(cases[i].output[j].match_type, matches[j].type); EXPECT_EQ(cases[i].output[j].allowed_to_be_default_match, matches[j].allowed_to_be_default_match); } // Ensure that no expected matches are missing. for (; j < arraysize(cases[i].output); ++j) { EXPECT_EQ(kNotApplicable, cases[i].output[j].match_contents); EXPECT_EQ(AutocompleteMatchType::NUM_TYPES, cases[i].output[j].match_type); EXPECT_FALSE(cases[i].output[j].allowed_to_be_default_match); } } } // A basic test that verifies the field trial triggered parsing logic. TEST_F(SearchProviderTest, FieldTrialTriggeredParsing) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("foo"), false, "[\"foo\",[\"foo bar\"],[\"\"],[]," "{\"google:suggesttype\":[\"QUERY\"]," "\"google:fieldtrialtriggered\":true}]", std::string()); { // Check for the match and field trial triggered bits. AutocompleteMatch match; EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("foo bar"), &match)); ProvidersInfo providers_info; provider_->AddProviderInfo(&providers_info); ASSERT_EQ(1U, providers_info.size()); EXPECT_EQ(1, providers_info[0].field_trial_triggered_size()); EXPECT_EQ(1, providers_info[0].field_trial_triggered_in_session_size()); } { // Reset the session and check that bits are reset. provider_->ResetSession(); ProvidersInfo providers_info; provider_->AddProviderInfo(&providers_info); ASSERT_EQ(1U, providers_info.size()); EXPECT_EQ(1, providers_info[0].field_trial_triggered_size()); EXPECT_EQ(0, providers_info[0].field_trial_triggered_in_session_size()); } } // Verifies inline autocompletion of navigational results. TEST_F(SearchProviderTest, NavigationInline) { struct { const std::string input; const std::string url; // Test the expected fill_into_edit, which may drop "http://". // Some cases do not trim "http://" to match from the start of the scheme. const std::string fill_into_edit; const std::string inline_autocompletion; const bool allowed_to_be_default_match_in_regular_mode; const bool allowed_to_be_default_match_in_prevent_inline_mode; } cases[] = { // Do not inline matches that do not contain the input; trim http as needed. { "x", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { "https:", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { "http://www.abc.com/a", "http://www.abc.com", "http://www.abc.com", std::string(), false, false }, // Do not inline matches with invalid input prefixes; trim http as needed. { "ttp", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { "://w", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { "ww.", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { ".ab", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { "bc", "http://www.abc.com", "www.abc.com", std::string(), false, false }, { ".com", "http://www.abc.com", "www.abc.com", std::string(), false, false }, // Do not inline matches that omit input domain labels; trim http as needed. { "www.a", "http://a.com", "a.com", std::string(), false, false }, { "http://www.a", "http://a.com", "http://a.com", std::string(), false, false }, { "www.a", "ftp://a.com", "ftp://a.com", std::string(), false, false }, { "ftp://www.a", "ftp://a.com", "ftp://a.com", std::string(), false, false }, // Input matching but with nothing to inline will not yield an offset, but // will be allowed to be default. { "abc.com", "http://www.abc.com", "www.abc.com", std::string(), true, true }, { "abc.com/", "http://www.abc.com", "www.abc.com", std::string(), true, true }, { "http://www.abc.com", "http://www.abc.com", "http://www.abc.com", std::string(), true, true }, { "http://www.abc.com/", "http://www.abc.com", "http://www.abc.com", std::string(), true, true }, // Inputs with trailing whitespace should inline when possible. { "abc.com ", "http://www.abc.com", "www.abc.com", std::string(), true, true }, { "abc.com/ ", "http://www.abc.com", "www.abc.com", std::string(), true, true }, { "abc.com ", "http://www.abc.com/bar", "www.abc.com/bar", "/bar", false, false }, // A suggestion that's equivalent to what the input gets fixed up to // should be inlined. { "abc.com:", "http://abc.com/", "abc.com", std::string(), true, true }, { "abc.com:", "http://www.abc.com", "www.abc.com", std::string(), true, true }, // Inline matches when the input is a leading substring of the scheme. { "h", "http://www.abc.com", "http://www.abc.com", "ttp://www.abc.com", true, false }, { "http", "http://www.abc.com", "http://www.abc.com", "://www.abc.com", true, false }, // Inline matches when the input is a leading substring of the full URL. { "http:", "http://www.abc.com", "http://www.abc.com", "//www.abc.com", true, false }, { "http://w", "http://www.abc.com", "http://www.abc.com", "ww.abc.com", true, false }, { "http://www.", "http://www.abc.com", "http://www.abc.com", "abc.com", true, false }, { "http://www.ab", "http://www.abc.com", "http://www.abc.com", "c.com", true, false }, { "http://www.abc.com/p", "http://www.abc.com/path/file.htm?q=x#foo", "http://www.abc.com/path/file.htm?q=x#foo", "ath/file.htm?q=x#foo", true, false }, { "http://abc.com/p", "http://abc.com/path/file.htm?q=x#foo", "http://abc.com/path/file.htm?q=x#foo", "ath/file.htm?q=x#foo", true, false}, // Inline matches with valid URLPrefixes; only trim "http://". { "w", "http://www.abc.com", "www.abc.com", "ww.abc.com", true, false }, { "www.a", "http://www.abc.com", "www.abc.com", "bc.com", true, false }, { "abc", "http://www.abc.com", "www.abc.com", ".com", true, false }, { "abc.c", "http://www.abc.com", "www.abc.com", "om", true, false }, { "abc.com/p", "http://www.abc.com/path/file.htm?q=x#foo", "www.abc.com/path/file.htm?q=x#foo", "ath/file.htm?q=x#foo", true, false }, { "abc.com/p", "http://abc.com/path/file.htm?q=x#foo", "abc.com/path/file.htm?q=x#foo", "ath/file.htm?q=x#foo", true, false }, // Inline matches using the maximal URLPrefix components. { "h", "http://help.com", "help.com", "elp.com", true, false }, { "http", "http://http.com", "http.com", ".com", true, false }, { "h", "http://www.help.com", "www.help.com", "elp.com", true, false }, { "http", "http://www.http.com", "www.http.com", ".com", true, false }, { "w", "http://www.www.com", "www.www.com", "ww.com", true, false }, // Test similar behavior for the ftp and https schemes. { "ftp://www.ab", "ftp://www.abc.com/path/file.htm?q=x#foo", "ftp://www.abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "www.ab", "ftp://www.abc.com/path/file.htm?q=x#foo", "ftp://www.abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "ab", "ftp://www.abc.com/path/file.htm?q=x#foo", "ftp://www.abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "ab", "ftp://abc.com/path/file.htm?q=x#foo", "ftp://abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "https://www.ab", "https://www.abc.com/path/file.htm?q=x#foo", "https://www.abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "www.ab", "https://www.abc.com/path/file.htm?q=x#foo", "https://www.abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "ab", "https://www.abc.com/path/file.htm?q=x#foo", "https://www.abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, { "ab", "https://abc.com/path/file.htm?q=x#foo", "https://abc.com/path/file.htm?q=x#foo", "c.com/path/file.htm?q=x#foo", true, false }, // Forced query input should inline and retain the "?" prefix. { "?http://www.ab", "http://www.abc.com", "?http://www.abc.com", "c.com", true, false }, { "?www.ab", "http://www.abc.com", "?www.abc.com", "c.com", true, false }, { "?ab", "http://www.abc.com", "?www.abc.com", "c.com", true, false }, { "?abc.com", "http://www.abc.com", "?www.abc.com", std::string(), true, true }, }; for (size_t i = 0; i < arraysize(cases); ++i) { // First test regular mode. QueryForInput(ASCIIToUTF16(cases[i].input), false, false); SearchSuggestionParser::NavigationResult result( ChromeAutocompleteSchemeClassifier(&profile_), GURL(cases[i].url), AutocompleteMatchType::NAVSUGGEST, base::string16(), std::string(), false, 0, false, ASCIIToUTF16(cases[i].input), std::string()); result.set_received_after_last_keystroke(false); AutocompleteMatch match(provider_->NavigationToMatch(result)); EXPECT_EQ(ASCIIToUTF16(cases[i].inline_autocompletion), match.inline_autocompletion); EXPECT_EQ(ASCIIToUTF16(cases[i].fill_into_edit), match.fill_into_edit); EXPECT_EQ(cases[i].allowed_to_be_default_match_in_regular_mode, match.allowed_to_be_default_match); // Then test prevent-inline-autocomplete mode. QueryForInput(ASCIIToUTF16(cases[i].input), true, false); SearchSuggestionParser::NavigationResult result_prevent_inline( ChromeAutocompleteSchemeClassifier(&profile_), GURL(cases[i].url), AutocompleteMatchType::NAVSUGGEST, base::string16(), std::string(), false, 0, false, ASCIIToUTF16(cases[i].input), std::string()); result_prevent_inline.set_received_after_last_keystroke(false); AutocompleteMatch match_prevent_inline( provider_->NavigationToMatch(result_prevent_inline)); EXPECT_EQ(ASCIIToUTF16(cases[i].inline_autocompletion), match_prevent_inline.inline_autocompletion); EXPECT_EQ(ASCIIToUTF16(cases[i].fill_into_edit), match_prevent_inline.fill_into_edit); EXPECT_EQ(cases[i].allowed_to_be_default_match_in_prevent_inline_mode, match_prevent_inline.allowed_to_be_default_match); } } // Verifies that "http://" is not trimmed for input that is a leading substring. TEST_F(SearchProviderTest, NavigationInlineSchemeSubstring) { const base::string16 input(ASCIIToUTF16("ht")); const base::string16 url(ASCIIToUTF16("http://a.com")); SearchSuggestionParser::NavigationResult result( ChromeAutocompleteSchemeClassifier(&profile_), GURL(url), AutocompleteMatchType::NAVSUGGEST, base::string16(), std::string(), false, 0, false, input, std::string()); result.set_received_after_last_keystroke(false); // Check the offset and strings when inline autocompletion is allowed. QueryForInput(input, false, false); AutocompleteMatch match_inline(provider_->NavigationToMatch(result)); EXPECT_EQ(url, match_inline.fill_into_edit); EXPECT_EQ(url.substr(2), match_inline.inline_autocompletion); EXPECT_TRUE(match_inline.allowed_to_be_default_match); EXPECT_EQ(url, match_inline.contents); // Check the same strings when inline autocompletion is prevented. QueryForInput(input, true, false); AutocompleteMatch match_prevent(provider_->NavigationToMatch(result)); EXPECT_EQ(url, match_prevent.fill_into_edit); EXPECT_FALSE(match_prevent.allowed_to_be_default_match); EXPECT_EQ(url, match_prevent.contents); } // Verifies that input "w" marks a more significant domain label than "www.". TEST_F(SearchProviderTest, NavigationInlineDomainClassify) { QueryForInput(ASCIIToUTF16("w"), false, false); SearchSuggestionParser::NavigationResult result( ChromeAutocompleteSchemeClassifier(&profile_), GURL("http://www.wow.com"), AutocompleteMatchType::NAVSUGGEST, base::string16(), std::string(), false, 0, false, ASCIIToUTF16("w"), std::string()); result.set_received_after_last_keystroke(false); AutocompleteMatch match(provider_->NavigationToMatch(result)); EXPECT_EQ(ASCIIToUTF16("ow.com"), match.inline_autocompletion); EXPECT_TRUE(match.allowed_to_be_default_match); EXPECT_EQ(ASCIIToUTF16("www.wow.com"), match.fill_into_edit); EXPECT_EQ(ASCIIToUTF16("www.wow.com"), match.contents); // Ensure that the match for input "w" is marked on "wow" and not "www". ASSERT_EQ(3U, match.contents_class.size()); EXPECT_EQ(0U, match.contents_class[0].offset); EXPECT_EQ(AutocompleteMatch::ACMatchClassification::URL, match.contents_class[0].style); EXPECT_EQ(4U, match.contents_class[1].offset); EXPECT_EQ(AutocompleteMatch::ACMatchClassification::URL | AutocompleteMatch::ACMatchClassification::MATCH, match.contents_class[1].style); EXPECT_EQ(5U, match.contents_class[2].offset); EXPECT_EQ(AutocompleteMatch::ACMatchClassification::URL, match.contents_class[2].style); } #if !defined(OS_WIN) // Verify entity suggestion parsing. TEST_F(SearchProviderTest, ParseEntitySuggestion) { struct Match { std::string contents; std::string description; std::string query_params; std::string fill_into_edit; AutocompleteMatchType::Type type; }; const Match kEmptyMatch = { kNotApplicable, kNotApplicable, kNotApplicable, kNotApplicable, AutocompleteMatchType::NUM_TYPES}; struct { const std::string input_text; const std::string response_json; const Match matches[5]; } cases[] = { // A query and an entity suggestion with different search terms. { "x", "[\"x\",[\"xy\", \"yy\"],[\"\",\"\"],[]," " {\"google:suggestdetail\":[{}," " {\"a\":\"A\",\"t\":\"xy\",\"q\":\"p=v\"}]," "\"google:suggesttype\":[\"QUERY\",\"ENTITY\"]}]", { { "x", "", "", "x", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "xy", "", "", "xy", AutocompleteMatchType::SEARCH_SUGGEST }, { "xy", "A", "p=v", "yy", AutocompleteMatchType::SEARCH_SUGGEST_ENTITY }, kEmptyMatch, kEmptyMatch }, }, // A query and an entity suggestion with same search terms. { "x", "[\"x\",[\"xy\", \"xy\"],[\"\",\"\"],[]," " {\"google:suggestdetail\":[{}," " {\"a\":\"A\",\"t\":\"xy\",\"q\":\"p=v\"}]," "\"google:suggesttype\":[\"QUERY\",\"ENTITY\"]}]", { { "x", "", "", "x", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "xy", "", "", "xy", AutocompleteMatchType::SEARCH_SUGGEST }, { "xy", "A", "p=v", "xy", AutocompleteMatchType::SEARCH_SUGGEST_ENTITY }, kEmptyMatch, kEmptyMatch }, }, }; for (size_t i = 0; i < arraysize(cases); ++i) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(cases[i].input_text), false, cases[i].response_json, std::string()); const ACMatches& matches = provider_->matches(); ASSERT_FALSE(matches.empty()); SCOPED_TRACE("for input with json = " + cases[i].response_json); ASSERT_LE(matches.size(), arraysize(cases[i].matches)); size_t j = 0; // Ensure that the returned matches equal the expectations. for (; j < matches.size(); ++j) { const Match& match = cases[i].matches[j]; SCOPED_TRACE(" and match index: " + base::IntToString(j)); EXPECT_EQ(match.contents, base::UTF16ToUTF8(matches[j].contents)); EXPECT_EQ(match.description, base::UTF16ToUTF8(matches[j].description)); EXPECT_EQ(match.query_params, matches[j].search_terms_args->suggest_query_params); EXPECT_EQ(match.fill_into_edit, base::UTF16ToUTF8(matches[j].fill_into_edit)); EXPECT_EQ(match.type, matches[j].type); } // Ensure that no expected matches are missing. for (; j < arraysize(cases[i].matches); ++j) { SCOPED_TRACE(" and match index: " + base::IntToString(j)); EXPECT_EQ(cases[i].matches[j].contents, kNotApplicable); EXPECT_EQ(cases[i].matches[j].description, kNotApplicable); EXPECT_EQ(cases[i].matches[j].query_params, kNotApplicable); EXPECT_EQ(cases[i].matches[j].fill_into_edit, kNotApplicable); EXPECT_EQ(cases[i].matches[j].type, AutocompleteMatchType::NUM_TYPES); } } } #endif // !defined(OS_WIN) // A basic test that verifies the prefetch metadata parsing logic. TEST_F(SearchProviderTest, PrefetchMetadataParsing) { struct Match { std::string contents; bool allowed_to_be_prefetched; AutocompleteMatchType::Type type; bool from_keyword; }; const Match kEmptyMatch = { kNotApplicable, false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false }; struct { const std::string input_text; bool prefer_keyword_provider_results; const std::string default_provider_response_json; const std::string keyword_provider_response_json; const Match matches[5]; } cases[] = { // Default provider response does not have prefetch details. Ensure that the // suggestions are not marked as prefetch query. { "a", false, "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]", std::string(), { { "a", false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false }, { "c", false, AutocompleteMatchType::SEARCH_SUGGEST, false }, { "b", false, AutocompleteMatchType::SEARCH_SUGGEST, false }, kEmptyMatch, kEmptyMatch }, }, // Ensure that default provider suggest response prefetch details are // parsed and recorded in AutocompleteMatch. { "ab", false, "[\"ab\",[\"abc\", \"http://b.com\", \"http://c.com\"],[],[]," "{\"google:clientdata\":{\"phi\": 0}," "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[999, 12, 1]}]", std::string(), { { "ab", false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false }, { "abc", true, AutocompleteMatchType::SEARCH_SUGGEST, false }, { "b.com", false, AutocompleteMatchType::NAVSUGGEST, false }, { "c.com", false, AutocompleteMatchType::NAVSUGGEST, false }, kEmptyMatch }, }, // Default provider suggest response has prefetch details. // SEARCH_WHAT_YOU_TYPE suggestion outranks SEARCH_SUGGEST suggestion for // the same query string. Ensure that the prefetch details from // SEARCH_SUGGEST match are set onto SEARCH_WHAT_YOU_TYPE match. { "ab", false, "[\"ab\",[\"ab\", \"http://ab.com\"],[],[]," "{\"google:clientdata\":{\"phi\": 0}," "\"google:suggesttype\":[\"QUERY\", \"NAVIGATION\"]," "\"google:suggestrelevance\":[99, 98]}]", std::string(), { {"ab", true, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false }, {"ab.com", false, AutocompleteMatchType::NAVSUGGEST, false }, kEmptyMatch, kEmptyMatch, kEmptyMatch }, }, // Default provider response has prefetch details. We prefer keyword // provider results. Ensure that prefetch bit for a suggestion from the // default search provider does not get copied onto a higher-scoring match // for the same query string from the keyword provider. { "k a", true, "[\"k a\",[\"a\", \"ab\"],[],[], {\"google:clientdata\":{\"phi\": 0}," "\"google:suggesttype\":[\"QUERY\", \"QUERY\"]," "\"google:suggestrelevance\":[9, 12]}]", "[\"a\",[\"b\", \"c\"],[],[],{\"google:suggestrelevance\":[1, 2]}]", { { "a", false, AutocompleteMatchType::SEARCH_OTHER_ENGINE, true}, { "k a", false, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, false }, { "ab", false, AutocompleteMatchType::SEARCH_SUGGEST, false }, { "c", false, AutocompleteMatchType::SEARCH_SUGGEST, true }, { "b", false, AutocompleteMatchType::SEARCH_SUGGEST, true } }, } }; for (size_t i = 0; i < arraysize(cases); ++i) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(cases[i].input_text), cases[i].prefer_keyword_provider_results, cases[i].default_provider_response_json, cases[i].prefer_keyword_provider_results ? cases[i].keyword_provider_response_json : std::string()); const std::string description = "for input with json =" + cases[i].default_provider_response_json; const ACMatches& matches = provider_->matches(); // The top match must inline and score as highly as calculated verbatim. ASSERT_FALSE(matches.empty()); EXPECT_GE(matches[0].relevance, 1300); ASSERT_LE(matches.size(), arraysize(cases[i].matches)); // Ensure that the returned matches equal the expectations. for (size_t j = 0; j < matches.size(); ++j) { SCOPED_TRACE(description); EXPECT_EQ(cases[i].matches[j].contents, base::UTF16ToUTF8(matches[j].contents)); EXPECT_EQ(cases[i].matches[j].allowed_to_be_prefetched, SearchProvider::ShouldPrefetch(matches[j])); EXPECT_EQ(cases[i].matches[j].type, matches[j].type); EXPECT_EQ(cases[i].matches[j].from_keyword, matches[j].keyword == ASCIIToUTF16("k")); } } } TEST_F(SearchProviderTest, XSSIGuardedJSONParsing_InvalidResponse) { ClearAllResults(); std::string input_str("abc"); QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(input_str), false, "this is a bad non-json response", std::string()); const ACMatches& matches = provider_->matches(); // Should have exactly one "search what you typed" match ASSERT_TRUE(matches.size() == 1); EXPECT_EQ(input_str, base::UTF16ToUTF8(matches[0].contents)); EXPECT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, matches[0].type); } // A basic test that verifies that the XSSI guarded JSON response is parsed // correctly. TEST_F(SearchProviderTest, XSSIGuardedJSONParsing_ValidResponses) { struct Match { std::string contents; AutocompleteMatchType::Type type; }; const Match kEmptyMatch = { kNotApplicable, AutocompleteMatchType::NUM_TYPES }; struct { const std::string input_text; const std::string default_provider_response_json; const Match matches[4]; } cases[] = { // No XSSI guard. { "a", "[\"a\",[\"b\", \"c\"],[],[]," "{\"google:suggesttype\":[\"QUERY\",\"QUERY\"]," "\"google:suggestrelevance\":[1, 2]}]", { { "a", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "c", AutocompleteMatchType::SEARCH_SUGGEST }, { "b", AutocompleteMatchType::SEARCH_SUGGEST }, kEmptyMatch, }, }, // Standard XSSI guard - )]}'\n. { "a", ")]}'\n[\"a\",[\"b\", \"c\"],[],[]," "{\"google:suggesttype\":[\"QUERY\",\"QUERY\"]," "\"google:suggestrelevance\":[1, 2]}]", { { "a", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "c", AutocompleteMatchType::SEARCH_SUGGEST }, { "b", AutocompleteMatchType::SEARCH_SUGGEST }, kEmptyMatch, }, }, // Modified XSSI guard - contains "[". { "a", ")]}'\n[)\"[\"a\",[\"b\", \"c\"],[],[]," "{\"google:suggesttype\":[\"QUERY\",\"QUERY\"]," "\"google:suggestrelevance\":[1, 2]}]", { { "a", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "c", AutocompleteMatchType::SEARCH_SUGGEST }, { "b", AutocompleteMatchType::SEARCH_SUGGEST }, kEmptyMatch, }, }, }; for (size_t i = 0; i < arraysize(cases); ++i) { ClearAllResults(); QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(cases[i].input_text), false, cases[i].default_provider_response_json, std::string()); const ACMatches& matches = provider_->matches(); // The top match must inline and score as highly as calculated verbatim. ASSERT_FALSE(matches.empty()); EXPECT_GE(matches[0].relevance, 1300); SCOPED_TRACE("for case: " + base::IntToString(i)); ASSERT_LE(matches.size(), arraysize(cases[i].matches)); size_t j = 0; // Ensure that the returned matches equal the expectations. for (; j < matches.size(); ++j) { SCOPED_TRACE("and match: " + base::IntToString(j)); EXPECT_EQ(cases[i].matches[j].contents, base::UTF16ToUTF8(matches[j].contents)); EXPECT_EQ(cases[i].matches[j].type, matches[j].type); } for (; j < arraysize(cases[i].matches); ++j) { SCOPED_TRACE("and match: " + base::IntToString(j)); EXPECT_EQ(cases[i].matches[j].contents, kNotApplicable); EXPECT_EQ(cases[i].matches[j].type, AutocompleteMatchType::NUM_TYPES); } } } // Test that deletion url gets set on an AutocompleteMatch when available for a // personalized query or a personalized URL. TEST_F(SearchProviderTest, ParseDeletionUrl) { struct Match { std::string contents; std::string deletion_url; AutocompleteMatchType::Type type; }; const Match kEmptyMatch = { kNotApplicable, std::string(), AutocompleteMatchType::NUM_TYPES }; const char* url[] = { "http://defaultturl/complete/deleteitems" "?delq=ab&client=chrome&deltok=xsrf124", "http://defaultturl/complete/deleteitems" "?delq=www.amazon.com&client=chrome&deltok=xsrf123", }; struct { const std::string input_text; const std::string response_json; const Match matches[5]; } cases[] = { // A deletion URL on a personalized query should be reflected in the // resulting AutocompleteMatch. { "a", "[\"a\",[\"ab\", \"ac\",\"www.amazon.com\"],[],[]," "{\"google:suggesttype\":[\"PERSONALIZED_QUERY\",\"QUERY\"," "\"PERSONALIZED_NAVIGATION\"]," "\"google:suggestrelevance\":[3, 2, 1]," "\"google:suggestdetail\":[{\"du\":" "\"/complete/deleteitems?delq=ab&client=chrome" "&deltok=xsrf124\"}, {}, {\"du\":" "\"/complete/deleteitems?delq=www.amazon.com&" "client=chrome&deltok=xsrf123\"}]}]", { { "a", "", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "ab", url[0], AutocompleteMatchType::SEARCH_SUGGEST }, { "ac", "", AutocompleteMatchType::SEARCH_SUGGEST }, { "www.amazon.com", url[1], AutocompleteMatchType::NAVSUGGEST_PERSONALIZED }, kEmptyMatch, }, }, // Personalized queries or a personalized URL without deletion URLs // shouldn't cause errors. { "a", "[\"a\",[\"ab\", \"ac\"],[],[]," "{\"google:suggesttype\":[\"PERSONALIZED_QUERY\",\"QUERY\"," "\"PERSONALIZED_NAVIGATION\"]," "\"google:suggestrelevance\":[1, 2]," "\"google:suggestdetail\":[{}, {}]}]", { { "a", "", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "ac", "", AutocompleteMatchType::SEARCH_SUGGEST }, { "ab", "", AutocompleteMatchType::SEARCH_SUGGEST }, { "www.amazon.com", "", AutocompleteMatchType::NAVSUGGEST_PERSONALIZED }, kEmptyMatch, }, }, // Personalized queries or a personalized URL without // google:suggestdetail shouldn't cause errors. { "a", "[\"a\",[\"ab\", \"ac\"],[],[]," "{\"google:suggesttype\":[\"PERSONALIZED_QUERY\",\"QUERY\"," "\"PERSONALIZED_NAVIGATION\"]," "\"google:suggestrelevance\":[1, 2]}]", { { "a", "", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED }, { "ac", "", AutocompleteMatchType::SEARCH_SUGGEST }, { "ab", "", AutocompleteMatchType::SEARCH_SUGGEST }, { "www.amazon.com", "", AutocompleteMatchType::NAVSUGGEST_PERSONALIZED }, kEmptyMatch, }, }, }; for (size_t i = 0; i < arraysize(cases); ++i) { QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16(cases[i].input_text), false, cases[i].response_json, std::string()); const ACMatches& matches = provider_->matches(); ASSERT_FALSE(matches.empty()); SCOPED_TRACE("for input with json = " + cases[i].response_json); for (size_t j = 0; j < matches.size(); ++j) { const Match& match = cases[i].matches[j]; SCOPED_TRACE(" and match index: " + base::IntToString(j)); EXPECT_EQ(match.contents, base::UTF16ToUTF8(matches[j].contents)); EXPECT_EQ(match.deletion_url, matches[j].GetAdditionalInfo( "deletion_url")); } } } TEST_F(SearchProviderTest, ReflectsBookmarkBarState) { profile_.GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, false); base::string16 term = term1_.substr(0, term1_.length() - 1); QueryForInput(term, true, false); ASSERT_FALSE(provider_->matches().empty()); EXPECT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, provider_->matches()[0].type); ASSERT_TRUE(provider_->matches()[0].search_terms_args != NULL); EXPECT_FALSE(provider_->matches()[0].search_terms_args->bookmark_bar_pinned); profile_.GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, true); term = term1_.substr(0, term1_.length() - 1); QueryForInput(term, true, false); ASSERT_FALSE(provider_->matches().empty()); EXPECT_EQ(AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, provider_->matches()[0].type); ASSERT_TRUE(provider_->matches()[0].search_terms_args != NULL); EXPECT_TRUE(provider_->matches()[0].search_terms_args->bookmark_bar_pinned); } TEST_F(SearchProviderTest, CanSendURL) { TemplateURLData template_url_data; template_url_data.short_name = ASCIIToUTF16("t"); template_url_data.SetURL("http://www.google.com/{searchTerms}"); template_url_data.suggestions_url = "http://www.google.com/{searchTerms}"; template_url_data.instant_url = "http://does/not/exist?strk=1"; template_url_data.search_terms_replacement_key = "strk"; template_url_data.id = SEARCH_ENGINE_GOOGLE; TemplateURL google_template_url(template_url_data); // Create field trial. CreateZeroSuggestFieldTrial(true); ChromeAutocompleteProviderClient client(&profile_); // Not signed in. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); SigninManagerBase* signin = SigninManagerFactory::GetForProfile(&profile_); signin->SetAuthenticatedUsername("test"); // All conditions should be met. EXPECT_TRUE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); // Not in field trial. ResetFieldTrialList(); CreateZeroSuggestFieldTrial(false); EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); ResetFieldTrialList(); CreateZeroSuggestFieldTrial(true); // Invalid page URL. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("badpageurl"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); // Invalid page classification. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS, SearchTermsData(), &client)); // Invalid page classification. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS, SearchTermsData(), &client)); // HTTPS page URL on same domain as provider. EXPECT_TRUE(SearchProvider::CanSendURL( GURL("https://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); // Non-HTTP[S] page URL on same domain as provider. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("ftp://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); // Non-HTTP page URL on different domain. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("https://www.notgoogle.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); // Non-HTTPS provider. EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("http://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); // Suggest disabled. profile_.GetPrefs()->SetBoolean(prefs::kSearchSuggestEnabled, false); EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); profile_.GetPrefs()->SetBoolean(prefs::kSearchSuggestEnabled, true); // Incognito. ChromeAutocompleteProviderClient client_incognito( profile_.GetOffTheRecordProfile()); EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client_incognito)); // Tab sync not enabled. profile_.GetPrefs()->SetBoolean(sync_driver::prefs::kSyncKeepEverythingSynced, false); profile_.GetPrefs()->SetBoolean(sync_driver::prefs::kSyncTabs, false); EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); profile_.GetPrefs()->SetBoolean(sync_driver::prefs::kSyncTabs, true); // Tab sync is encrypted. ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->GetForProfile(&profile_); syncer::ModelTypeSet encrypted_types = service->GetEncryptedDataTypes(); encrypted_types.Put(syncer::SESSIONS); service->OnEncryptedTypesChanged(encrypted_types, false); EXPECT_FALSE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); encrypted_types.Remove(syncer::SESSIONS); service->OnEncryptedTypesChanged(encrypted_types, false); // Check that there were no side effects from previous tests. EXPECT_TRUE(SearchProvider::CanSendURL( GURL("http://www.google.com/search"), GURL("https://www.google.com/complete/search"), &google_template_url, metrics::OmniboxEventProto::OTHER, SearchTermsData(), &client)); } TEST_F(SearchProviderTest, TestDeleteMatch) { AutocompleteMatch match( provider_.get(), 0, true, AutocompleteMatchType::SEARCH_SUGGEST); match.RecordAdditionalInfo( SearchProvider::kDeletionUrlKey, "https://www.google.com/complete/deleteitem?q=foo"); // Test a successful deletion request. provider_->matches_.push_back(match); provider_->DeleteMatch(match); EXPECT_FALSE(provider_->deletion_handlers_.empty()); EXPECT_TRUE(provider_->matches_.empty()); // Set up a default fetcher with provided results. net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID( SearchProvider::kDeletionURLFetcherID); ASSERT_TRUE(fetcher); fetcher->set_response_code(200); fetcher->delegate()->OnURLFetchComplete(fetcher); EXPECT_TRUE(provider_->deletion_handlers_.empty()); EXPECT_TRUE(provider_->is_success()); // Test a failing deletion request. provider_->matches_.push_back(match); provider_->DeleteMatch(match); EXPECT_FALSE(provider_->deletion_handlers_.empty()); // Set up a default fetcher with provided results. fetcher = test_factory_.GetFetcherByID( SearchProvider::kDeletionURLFetcherID); ASSERT_TRUE(fetcher); fetcher->set_response_code(500); fetcher->delegate()->OnURLFetchComplete(fetcher); EXPECT_TRUE(provider_->deletion_handlers_.empty()); EXPECT_FALSE(provider_->is_success()); } TEST_F(SearchProviderTest, TestDeleteHistoryQueryMatch) { GURL term_url( AddSearchToHistory(default_t_url_, ASCIIToUTF16("flash games"), 1)); profile_.BlockUntilHistoryProcessesPendingRequests(); AutocompleteMatch games; QueryForInput(ASCIIToUTF16("fla"), false, false); profile_.BlockUntilHistoryProcessesPendingRequests(); ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); ASSERT_TRUE(FindMatchWithContents(ASCIIToUTF16("flash games"), &games)); size_t matches_before = provider_->matches().size(); provider_->DeleteMatch(games); EXPECT_EQ(matches_before - 1, provider_->matches().size()); // Process history deletions. profile_.BlockUntilHistoryProcessesPendingRequests(); // Check that the match is gone. QueryForInput(ASCIIToUTF16("fla"), false, false); profile_.BlockUntilHistoryProcessesPendingRequests(); ASSERT_NO_FATAL_FAILURE(FinishDefaultSuggestQuery()); EXPECT_FALSE(FindMatchWithContents(ASCIIToUTF16("flash games"), &games)); } // Verifies that duplicates are preserved in AddMatchToMap(). TEST_F(SearchProviderTest, CheckDuplicateMatchesSaved) { AddSearchToHistory(default_t_url_, ASCIIToUTF16("a"), 1); AddSearchToHistory(default_t_url_, ASCIIToUTF16("alpha"), 1); AddSearchToHistory(default_t_url_, ASCIIToUTF16("avid"), 1); profile_.BlockUntilHistoryProcessesPendingRequests(); QueryForInputAndWaitForFetcherResponses( ASCIIToUTF16("a"), false, "[\"a\",[\"a\", \"alpha\", \"avid\", \"apricot\"],[],[]," "{\"google:suggestrelevance\":[1450, 1200, 1150, 1100]," "\"google:verbatimrelevance\":1350}]", std::string()); AutocompleteMatch verbatim, match_alpha, match_apricot, match_avid; EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("a"), &verbatim)); EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("alpha"), &match_alpha)); EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("apricot"), &match_apricot)); EXPECT_TRUE(FindMatchWithContents(ASCIIToUTF16("avid"), &match_avid)); // Verbatim match duplicates are added such that each one has a higher // relevance than the previous one. EXPECT_EQ(2U, verbatim.duplicate_matches.size()); // Other match duplicates are added in descending relevance order. EXPECT_EQ(1U, match_alpha.duplicate_matches.size()); EXPECT_EQ(1U, match_avid.duplicate_matches.size()); EXPECT_EQ(0U, match_apricot.duplicate_matches.size()); } TEST_F(SearchProviderTest, SuggestQueryUsesToken) { CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableAnswersInSuggest); TemplateURLService* turl_model = TemplateURLServiceFactory::GetForProfile(&profile_); TemplateURLData data; data.short_name = ASCIIToUTF16("default"); data.SetKeyword(data.short_name); data.SetURL("http://example/{searchTerms}{google:sessionToken}"); data.suggestions_url = "http://suggest/?q={searchTerms}&{google:sessionToken}"; default_t_url_ = new TemplateURL(data); turl_model->Add(default_t_url_); turl_model->SetUserSelectedDefaultSearchProvider(default_t_url_); base::string16 term = term1_.substr(0, term1_.length() - 1); QueryForInput(term, false, false); // Make sure the default provider's suggest service was queried. net::TestURLFetcher* fetcher = test_factory_.GetFetcherByID( SearchProvider::kDefaultProviderURLFetcherID); ASSERT_TRUE(fetcher); // And the URL matches what we expected. TemplateURLRef::SearchTermsArgs search_terms_args(term); search_terms_args.session_token = provider_->current_token_; GURL expected_url(default_t_url_->suggestions_url_ref().ReplaceSearchTerms( search_terms_args, turl_model->search_terms_data())); EXPECT_EQ(fetcher->GetOriginalURL().spec(), expected_url.spec()); // Complete running the fetcher to clean up. fetcher->set_response_code(200); fetcher->delegate()->OnURLFetchComplete(fetcher); RunTillProviderDone(); } TEST_F(SearchProviderTest, SessionToken) { // Subsequent calls always get the same token. std::string token = provider_->GetSessionToken(); std::string token2 = provider_->GetSessionToken(); EXPECT_EQ(token, token2); EXPECT_FALSE(token.empty()); // Calls do not regenerate a token. provider_->current_token_ = "PRE-EXISTING TOKEN"; token = provider_->GetSessionToken(); EXPECT_EQ(token, "PRE-EXISTING TOKEN"); // ... unless the token has expired. provider_->current_token_.clear(); const base::TimeDelta kSmallDelta = base::TimeDelta::FromMilliseconds(1); provider_->token_expiration_time_ = base::TimeTicks::Now() - kSmallDelta; token = provider_->GetSessionToken(); EXPECT_FALSE(token.empty()); EXPECT_EQ(token, provider_->current_token_); // The expiration time is always updated. provider_->GetSessionToken(); base::TimeTicks expiration_time_1 = provider_->token_expiration_time_; base::PlatformThread::Sleep(kSmallDelta); provider_->GetSessionToken(); base::TimeTicks expiration_time_2 = provider_->token_expiration_time_; EXPECT_GT(expiration_time_2, expiration_time_1); EXPECT_GE(expiration_time_2, expiration_time_1 + kSmallDelta); } TEST_F(SearchProviderTest, AnswersCache) { AutocompleteResult result; ACMatches matches; AutocompleteMatch match1; match1.answer_contents = base::ASCIIToUTF16("m1"); match1.answer_type = base::ASCIIToUTF16("2334"); match1.fill_into_edit = base::ASCIIToUTF16("weather los angeles"); AutocompleteMatch non_answer_match1; non_answer_match1.fill_into_edit = base::ASCIIToUTF16("weather laguna beach"); // Test that an answer in the first slot populates the cache. matches.push_back(match1); matches.push_back(non_answer_match1); result.AppendMatches(matches); provider_->RegisterDisplayedAnswers(result); ASSERT_FALSE(provider_->answers_cache_.empty()); // Without scored results, no answers will be retrieved. AnswersQueryData answer = provider_->FindAnswersPrefetchData(); EXPECT_TRUE(answer.full_query_text.empty()); EXPECT_TRUE(answer.query_type.empty()); // Inject a scored result, which will trigger answer retrieval. base::string16 query = base::ASCIIToUTF16("weather los angeles"); SearchSuggestionParser::SuggestResult suggest_result( query, AutocompleteMatchType::SEARCH_HISTORY, query, base::string16(), base::string16(), base::string16(), base::string16(), std::string(), std::string(), false, 1200, false, false, query); QueryForInput(ASCIIToUTF16("weather l"), false, false); provider_->transformed_default_history_results_.push_back(suggest_result); answer = provider_->FindAnswersPrefetchData(); EXPECT_EQ(base::ASCIIToUTF16("weather los angeles"), answer.full_query_text); EXPECT_EQ(base::ASCIIToUTF16("2334"), answer.query_type); } TEST_F(SearchProviderTest, RemoveExtraAnswers) { ACMatches matches; AutocompleteMatch match1, match2, match3, match4, match5; match1.answer_contents = base::ASCIIToUTF16("the answer"); match1.answer_type = base::ASCIIToUTF16("42"); match3.answer_contents = base::ASCIIToUTF16("not to play"); match3.answer_type = base::ASCIIToUTF16("1983"); match5.answer_contents = base::ASCIIToUTF16("a man"); match5.answer_type = base::ASCIIToUTF16("423"); matches.push_back(match1); matches.push_back(match2); matches.push_back(match3); matches.push_back(match4); matches.push_back(match5); SearchProvider::RemoveExtraAnswers(&matches); EXPECT_EQ(base::ASCIIToUTF16("the answer"), matches[0].answer_contents); EXPECT_EQ(base::ASCIIToUTF16("42"), matches[0].answer_type); EXPECT_TRUE(matches[1].answer_contents.empty()); EXPECT_TRUE(matches[1].answer_type.empty()); EXPECT_TRUE(matches[2].answer_contents.empty()); EXPECT_TRUE(matches[2].answer_type.empty()); EXPECT_TRUE(matches[3].answer_contents.empty()); EXPECT_TRUE(matches[3].answer_type.empty()); EXPECT_TRUE(matches[4].answer_contents.empty()); EXPECT_TRUE(matches[4].answer_type.empty()); }