blob: 556589f21b66accf3273e49aee00bc9e1e6aabc4 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/search_engines/template_url_prepopulate_data.h"
#include <stddef.h>
#include <memory>
#include <numeric>
#include <optional>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/to_vector.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/country_codes/country_codes.h"
#include "components/google/core/common/google_switches.h"
#include "components/regional_capabilities/regional_capabilities_country_id.h"
#include "components/regional_capabilities/regional_capabilities_switches.h"
#include "components/regional_capabilities/regional_capabilities_test_utils.h"
#include "components/regional_capabilities/regional_capabilities_utils.h"
#include "components/search_engines/search_engine_type.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_engines_test_environment.h"
#include "components/search_engines/search_engines_test_util.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_data_util.h"
#include "components/search_engines/template_url_prepopulate_data_resolver.h"
#include "components/search_engines/testing_search_terms_data.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/search_engines_data/resources/definitions/prepopulated_engines.h"
#include "third_party/search_engines_data/resources/definitions/regional_settings.h"
using ::base::ASCIIToUTF16;
using ::country_codes::CountryId;
using ::TemplateURLPrepopulateData::BuiltinKeywordsMetadata;
using ::TemplateURLPrepopulateData::kCurrentDataVersion;
namespace TemplateURLPrepopulateData {
bool operator==(const BuiltinKeywordsMetadata& lhs,
const BuiltinKeywordsMetadata& rhs) {
return lhs.data_version == rhs.data_version &&
lhs.country_id == rhs.country_id;
}
std::ostream& operator<<(std::ostream& os,
const BuiltinKeywordsMetadata& value) {
return os << "{country_id=" << value.country_id.GetForTesting().Serialize()
<< ", data_version=" << value.data_version << "}";
}
} // namespace TemplateURLPrepopulateData
namespace {
SearchEngineType GetEngineType(const std::string& url) {
TemplateURLData data;
data.SetURL(url);
return TemplateURL(data).GetEngineType(SearchTermsData());
}
std::string GetHostFromTemplateURLData(const TemplateURLData& data) {
return TemplateURL(data).url_ref().GetHost(SearchTermsData());
}
const CountryId kAllCountryIds[] = {
CountryId("AD"), CountryId("AE"), CountryId("AF"), CountryId("AG"),
CountryId("AI"), CountryId("AL"), CountryId("AM"), CountryId("AN"),
CountryId("AO"), CountryId("AQ"), CountryId("AR"), CountryId("AS"),
CountryId("AT"), CountryId("AU"), CountryId("AW"), CountryId("AX"),
CountryId("AZ"), CountryId("BA"), CountryId("BB"), CountryId("BD"),
CountryId("BE"), CountryId("BF"), CountryId("BG"), CountryId("BH"),
CountryId("BI"), CountryId("BJ"), CountryId("BL"), CountryId("BM"),
CountryId("BN"), CountryId("BO"), CountryId("BR"), CountryId("BS"),
CountryId("BT"), CountryId("BV"), CountryId("BW"), CountryId("BY"),
CountryId("BZ"), CountryId("CA"), CountryId("CC"), CountryId("CD"),
CountryId("CF"), CountryId("CG"), CountryId("CH"), CountryId("CI"),
CountryId("CK"), CountryId("CL"), CountryId("CM"), CountryId("CN"),
CountryId("CO"), CountryId("CQ"), CountryId("CR"), CountryId("CU"),
CountryId("CV"), CountryId("CX"), CountryId("CY"), CountryId("CZ"),
CountryId("DE"), CountryId("DJ"), CountryId("DK"), CountryId("DM"),
CountryId("DO"), CountryId("DZ"), CountryId("EA"), CountryId("EC"),
CountryId("EE"), CountryId("EG"), CountryId("EH"), CountryId("ER"),
CountryId("ES"), CountryId("ET"), CountryId("FI"), CountryId("FJ"),
CountryId("FK"), CountryId("FM"), CountryId("FO"), CountryId("FR"),
CountryId("GA"), CountryId("GB"), CountryId("GD"), CountryId("GE"),
CountryId("GF"), CountryId("GG"), CountryId("GH"), CountryId("GI"),
CountryId("GL"), CountryId("GM"), CountryId("GN"), CountryId("GP"),
CountryId("GQ"), CountryId("GR"), CountryId("GS"), CountryId("GT"),
CountryId("GU"), CountryId("GW"), CountryId("GY"), CountryId("HK"),
CountryId("HM"), CountryId("HN"), CountryId("HR"), CountryId("HT"),
CountryId("HU"), CountryId("IC"), CountryId("ID"), CountryId("IE"),
CountryId("IL"), CountryId("IM"), CountryId("IN"), CountryId("IO"),
CountryId("IQ"), CountryId("IR"), CountryId("IS"), CountryId("IT"),
CountryId("JE"), CountryId("JM"), CountryId("JO"), CountryId("JP"),
CountryId("KE"), CountryId("KG"), CountryId("KH"), CountryId("KI"),
CountryId("KM"), CountryId("KN"), CountryId("KP"), CountryId("KR"),
CountryId("KW"), CountryId("KY"), CountryId("KZ"), CountryId("LA"),
CountryId("LB"), CountryId("LC"), CountryId("LI"), CountryId("LK"),
CountryId("LR"), CountryId("LS"), CountryId("LT"), CountryId("LU"),
CountryId("LV"), CountryId("LY"), CountryId("MA"), CountryId("MC"),
CountryId("MD"), CountryId("ME"), CountryId("MF"), CountryId("MG"),
CountryId("MH"), CountryId("MK"), CountryId("ML"), CountryId("MM"),
CountryId("MN"), CountryId("MO"), CountryId("MP"), CountryId("MQ"),
CountryId("MR"), CountryId("MS"), CountryId("MT"), CountryId("MU"),
CountryId("MV"), CountryId("MW"), CountryId("MX"), CountryId("MY"),
CountryId("MZ"), CountryId("NA"), CountryId("NC"), CountryId("NE"),
CountryId("NF"), CountryId("NG"), CountryId("NI"), CountryId("NL"),
CountryId("NO"), CountryId("NP"), CountryId("NR"), CountryId("NU"),
CountryId("NZ"), CountryId("OM"), CountryId("PA"), CountryId("PE"),
CountryId("PF"), CountryId("PG"), CountryId("PH"), CountryId("PK"),
CountryId("PL"), CountryId("PM"), CountryId("PN"), CountryId("PR"),
CountryId("PS"), CountryId("PT"), CountryId("PW"), CountryId("PY"),
CountryId("QA"), CountryId("RE"), CountryId("RO"), CountryId("RS"),
CountryId("RU"), CountryId("RW"), CountryId("SA"), CountryId("SB"),
CountryId("SC"), CountryId("SD"), CountryId("SE"), CountryId("SG"),
CountryId("SH"), CountryId("SI"), CountryId("SJ"), CountryId("SK"),
CountryId("SL"), CountryId("SM"), CountryId("SN"), CountryId("SO"),
CountryId("SR"), CountryId("ST"), CountryId("SV"), CountryId("SY"),
CountryId("SZ"), CountryId("TC"), CountryId("TD"), CountryId("TF"),
CountryId("TG"), CountryId("TH"), CountryId("TJ"), CountryId("TK"),
CountryId("TL"), CountryId("TM"), CountryId("TN"), CountryId("TO"),
CountryId("TR"), CountryId("TT"), CountryId("TV"), CountryId("TW"),
CountryId("TZ"), CountryId("UA"), CountryId("UG"), CountryId("UM"),
CountryId("US"), CountryId("UY"), CountryId("UZ"), CountryId("VA"),
CountryId("VC"), CountryId("VE"), CountryId("VG"), CountryId("VI"),
CountryId("VN"), CountryId("VU"), CountryId("WF"), CountryId("WS"),
CountryId("YE"), CountryId("YT"), CountryId("ZA"), CountryId("ZM"),
CountryId("ZW"), CountryId("ZZ")};
void CheckUrlIsEmptyOrSecure(const std::string url) {
ASSERT_TRUE(url.empty() || url.starts_with("{google:") ||
url.starts_with(url::kHttpsScheme));
}
void CheckTemplateUrlRefIsCryptographic(const TemplateURLRef& url_ref) {
TestingSearchTermsData search_terms_data("https://www.google.com/");
if (!url_ref.IsValid(search_terms_data)) {
ADD_FAILURE() << url_ref.GetURL();
return;
}
// Double parentheses around the string16 constructor to prevent the compiler
// from parsing it as a function declaration.
TemplateURLRef::SearchTermsArgs search_term_args((std::u16string()));
GURL url(url_ref.ReplaceSearchTerms(search_term_args, search_terms_data));
EXPECT_TRUE(url.is_empty() || url.SchemeIsCryptographic()) << url;
}
} // namespace
class TemplateURLPrepopulateDataTest : public testing::Test {
public:
TemplateURLPrepopulateData::Resolver& prepopulate_data_resolver() {
return search_engines_test_environment_.prepopulate_data_resolver();
}
sync_preferences::TestingPrefServiceSyncable* pref_service() {
return &search_engines_test_environment_.pref_service();
}
void SetupForChoiceScreenDisplay() {
// Pick any EEA country
const CountryId kFranceCountryId("FR");
OverrideCountryId(kFranceCountryId);
}
void OverrideCountryId(CountryId country_id) {
OverrideCountryCommandLine(country_id.CountryCode());
}
void OverrideCountryCommandLine(std::string_view country_string) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSearchEngineChoiceCountry)) {
base::CommandLine::ForCurrentProcess()->RemoveSwitch(
switches::kSearchEngineChoiceCountry);
}
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kSearchEngineChoiceCountry, country_string);
}
protected:
search_engines::SearchEnginesTestEnvironment search_engines_test_environment_;
};
// Verifies the set of prepopulate data doesn't contain entries with duplicate
// ids.
TEST_F(TemplateURLPrepopulateDataTest, UniqueIDs) {
for (CountryId country_id : kAllCountryIds) {
OverrideCountryId(country_id);
std::vector<std::unique_ptr<TemplateURLData>> urls =
prepopulate_data_resolver().GetPrepopulatedEngines();
std::set<int> unique_ids;
for (const std::unique_ptr<TemplateURLData>& url : urls) {
ASSERT_TRUE(unique_ids.find(url->prepopulate_id) == unique_ids.end());
unique_ids.insert(url->prepopulate_id);
}
}
}
// Verifies that the prepopulated search engines configured by country are
// consistent with the set of countries in EeaChoiceCountry. For example, the
// per region limits `kMaxEeaPrepopulatedEngines` and
// `kMaxRowPrepopulatedEngines` should apply as expected.
TEST_F(TemplateURLPrepopulateDataTest, NumberOfEntriesPerCountryConsistency) {
const size_t kMinEea = 8;
const size_t kMinRow = 3;
for (CountryId country_id : kAllCountryIds) {
OverrideCountryId(country_id);
const size_t kNumberOfSearchEngines =
prepopulate_data_resolver().GetPrepopulatedEngines().size();
if (regional_capabilities::IsEeaCountry(country_id)) {
EXPECT_GE(kNumberOfSearchEngines, kMinEea)
<< " for country " << country_id.CountryCode();
EXPECT_LE(kNumberOfSearchEngines,
TemplateURLPrepopulateData::kMaxEeaPrepopulatedEngines)
<< " for country " << country_id.CountryCode();
} else {
EXPECT_GE(kNumberOfSearchEngines, kMinRow)
<< " for country " << country_id.CountryCode();
EXPECT_LE(kNumberOfSearchEngines,
TemplateURLPrepopulateData::kMaxRowPrepopulatedEngines)
<< " for country " << country_id.CountryCode();
}
}
}
TEST_F(TemplateURLPrepopulateDataTest, EntriesPerCountryConsistency) {
for (CountryId country_id : kAllCountryIds) {
if (!regional_capabilities::IsEeaCountry(country_id)) {
// "unhandled" countries can cause some issues when inheriting a config
// from an EEA country. Covering them via
// TemplateURLPrepopulateDataTest.NumberOfEntriesPerCountryConsistency is
// enough, so they we exclude non-EEA countries in the rest of this test
// for simplicity.
continue;
}
OverrideCountryId(country_id);
// Obtained by calling the normal API to fetch engines for the current
// country.
std::vector<std::string> actual_urls =
base::ToVector(prepopulate_data_resolver().GetPrepopulatedEngines(),
[](const auto& t_url) { return t_url->url(); });
// Pulled straight from the country -> engine mapping.
auto expected_urls = base::ToVector(
TemplateURLPrepopulateData::kRegionalSettings.find(country_id)
->second->search_engines,
&TemplateURLPrepopulateData::PrepopulatedEngine::search_url);
EXPECT_THAT(actual_urls, testing::UnorderedElementsAreArray(expected_urls));
}
}
// Verifies that the order of the randomly shuffled search engines stays
// constant per-profile.
TEST_F(TemplateURLPrepopulateDataTest,
SearchEnginesOrderDoesNotChangePerProfile) {
SetupForChoiceScreenDisplay();
// Fetch the list of search engines twice and make sure the order stays the
// same.
std::vector<std::unique_ptr<TemplateURLData>> t_urls_1 =
prepopulate_data_resolver().GetPrepopulatedEngines();
std::vector<std::unique_ptr<TemplateURLData>> t_urls_2 =
prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_EQ(t_urls_1.size(), t_urls_2.size());
for (size_t i = 0; i < t_urls_1.size(); i++) {
// Each prepopulated engine has a unique prepopulate_id, so we simply
// compare those.
ASSERT_EQ(t_urls_1[i]->prepopulate_id, t_urls_2[i]->prepopulate_id);
}
}
// Verifies that the the search engines are re-shuffled on Chrome update.
TEST_F(TemplateURLPrepopulateDataTest,
SearchEnginesOrderChangesOnChromeUpdate) {
SetupForChoiceScreenDisplay();
std::vector<std::unique_ptr<TemplateURLData>> t_urls =
prepopulate_data_resolver().GetPrepopulatedEngines();
// Change the saved chrome milestone to something else.
pref_service()->SetInteger(
prefs::kDefaultSearchProviderChoiceScreenShuffleMilestone, 3);
std::vector<std::unique_ptr<TemplateURLData>> t_urls_after_update =
prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_EQ(t_urls.size(), t_urls_after_update.size());
bool is_order_same = true;
for (size_t i = 0; i < t_urls.size(); i++) {
// Each prepopulated engine has a unique prepopulate_id, so we simply
// compare those.
is_order_same &=
t_urls[i]->prepopulate_id == t_urls_after_update[i]->prepopulate_id;
if (!is_order_same) {
break;
}
}
ASSERT_FALSE(is_order_same);
}
// Verifies that default search providers from the preferences file
// override the built-in ones.
TEST_F(TemplateURLPrepopulateDataTest, ProvidersFromPrefs) {
pref_service()->SetUserPref(prefs::kSearchProviderOverridesVersion,
std::make_unique<base::Value>(1));
base::Value::List overrides;
// Set only the minimal required settings for a search provider configuration.
base::Value::Dict entry =
base::Value::Dict()
.Set("name", "foo")
.Set("keyword", "fook")
.Set("search_url", "http://foo.com/s?q={searchTerms}")
.Set("favicon_url", "http://foi.com/favicon.ico")
.Set("encoding", "UTF-8")
.Set("id", 1001);
overrides.Append(entry.Clone());
pref_service()->SetUserPref(prefs::kSearchProviderOverrides,
std::move(overrides));
int version = TemplateURLPrepopulateData::GetDataVersion(pref_service());
EXPECT_EQ(1, version);
std::vector<std::unique_ptr<TemplateURLData>> t_urls =
prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_EQ(1u, t_urls.size());
EXPECT_EQ(u"foo", t_urls[0]->short_name());
EXPECT_EQ(u"fook", t_urls[0]->keyword());
EXPECT_EQ("foo.com", GetHostFromTemplateURLData(*t_urls[0]));
EXPECT_EQ("foi.com", t_urls[0]->favicon_url.host());
EXPECT_EQ(1u, t_urls[0]->input_encodings.size());
EXPECT_EQ(1001, t_urls[0]->prepopulate_id);
EXPECT_TRUE(t_urls[0]->suggestions_url.empty());
EXPECT_EQ(0u, t_urls[0]->alternate_urls.size());
EXPECT_TRUE(t_urls[0]->safe_for_autoreplace);
EXPECT_TRUE(t_urls[0]->date_created.is_null());
EXPECT_TRUE(t_urls[0]->last_modified.is_null());
// Test the optional settings too.
entry.Set("suggest_url", "http://foo.com/suggest?q={searchTerms}");
entry.Set("alternate_urls", base::Value::List().Append(
"http://foo.com/alternate?q={searchTerms}"));
overrides = base::Value::List().Append(entry.Clone());
pref_service()->SetUserPref(prefs::kSearchProviderOverrides,
std::move(overrides));
t_urls = prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_EQ(1u, t_urls.size());
EXPECT_EQ(u"foo", t_urls[0]->short_name());
EXPECT_EQ(u"fook", t_urls[0]->keyword());
EXPECT_EQ("foo.com", GetHostFromTemplateURLData(*t_urls[0]));
EXPECT_EQ("foi.com", t_urls[0]->favicon_url.host());
EXPECT_EQ(1u, t_urls[0]->input_encodings.size());
EXPECT_EQ(1001, t_urls[0]->prepopulate_id);
EXPECT_EQ("http://foo.com/suggest?q={searchTerms}",
t_urls[0]->suggestions_url);
ASSERT_EQ(1u, t_urls[0]->alternate_urls.size());
EXPECT_EQ("http://foo.com/alternate?q={searchTerms}",
t_urls[0]->alternate_urls[0]);
// Test that subsequent providers are loaded even if an intermediate
// provider has an incomplete configuration.
overrides = base::Value::List().Append(entry.Clone());
entry.Set("id", 1002);
entry.Set("name", "bar");
entry.Set("keyword", "bark");
entry.Set("encoding", std::string());
overrides.Append(entry.Clone());
entry.Set("id", 1003);
entry.Set("name", "baz");
entry.Set("keyword", "bazk");
entry.Set("encoding", "UTF-8");
overrides.Append(entry.Clone());
pref_service()->SetUserPref(prefs::kSearchProviderOverrides,
std::move(overrides));
t_urls = prepopulate_data_resolver().GetPrepopulatedEngines();
EXPECT_EQ(2u, t_urls.size());
}
TEST_F(TemplateURLPrepopulateDataTest, ClearProvidersFromPrefs) {
OverrideCountryId(CountryId());
pref_service()->SetUserPref(prefs::kSearchProviderOverridesVersion,
std::make_unique<base::Value>(1));
// Set only the minimal required settings for a search provider configuration.
base::Value::Dict entry =
base::Value::Dict()
.Set("name", "foo")
.Set("keyword", "fook")
.Set("search_url", "http://foo.com/s?q={searchTerms}")
.Set("favicon_url", "http://foi.com/favicon.ico")
.Set("encoding", "UTF-8")
.Set("id", 1001);
base::Value::List overrides = base::Value::List().Append(std::move(entry));
pref_service()->SetUserPref(prefs::kSearchProviderOverrides,
std::move(overrides));
int version = TemplateURLPrepopulateData::GetDataVersion(pref_service());
EXPECT_EQ(1, version);
// This call removes the above search engine.
TemplateURLPrepopulateData::ClearPrepopulatedEnginesInPrefs(pref_service());
version = TemplateURLPrepopulateData::GetDataVersion(pref_service());
EXPECT_EQ(TemplateURLPrepopulateData::kCurrentDataVersion, version);
std::vector<std::unique_ptr<TemplateURLData>> t_urls =
prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_FALSE(t_urls.empty());
for (size_t i = 0; i < t_urls.size(); ++i) {
EXPECT_NE(u"foo", t_urls[i]->short_name());
EXPECT_NE(u"fook", t_urls[i]->keyword());
EXPECT_NE("foi.com", t_urls[i]->favicon_url.host());
EXPECT_NE("foo.com", GetHostFromTemplateURLData(*t_urls[i]));
EXPECT_NE(1001, t_urls[i]->prepopulate_id);
}
// Ensures the fallback URL is Google and has the optional fields filled.
std::unique_ptr<TemplateURLData> fallback_t_url =
prepopulate_data_resolver().GetFallbackSearch();
EXPECT_EQ(TemplateURLPrepopulateData::google.name,
fallback_t_url->short_name());
EXPECT_FALSE(fallback_t_url->suggestions_url.empty());
EXPECT_FALSE(fallback_t_url->image_url.empty());
EXPECT_FALSE(fallback_t_url->contextual_search_url.empty());
EXPECT_FALSE(fallback_t_url->image_url_post_params.empty());
EXPECT_EQ(TemplateURLPrepopulateData::google.type,
TemplateURL(*fallback_t_url).GetEngineType(SearchTermsData()));
}
// Verifies that built-in search providers are processed correctly.
TEST_F(TemplateURLPrepopulateDataTest, ProvidersFromPrepopulated) {
// Use United States.
OverrideCountryId(CountryId("US"));
std::vector<std::unique_ptr<TemplateURLData>> t_urls =
prepopulate_data_resolver().GetPrepopulatedEngines();
// Ensure all the URLs have the required fields populated.
ASSERT_FALSE(t_urls.empty());
for (size_t i = 0; i < t_urls.size(); ++i) {
ASSERT_FALSE(t_urls[i]->short_name().empty());
ASSERT_FALSE(t_urls[i]->keyword().empty());
ASSERT_FALSE(t_urls[i]->favicon_url.host().empty());
ASSERT_FALSE(GetHostFromTemplateURLData(*t_urls[i]).empty());
ASSERT_FALSE(t_urls[i]->input_encodings.empty());
EXPECT_GT(t_urls[i]->prepopulate_id, 0);
EXPECT_TRUE(t_urls[0]->safe_for_autoreplace);
EXPECT_TRUE(t_urls[0]->date_created.is_null());
EXPECT_TRUE(t_urls[0]->last_modified.is_null());
}
// Ensures the fallback URL is Google and has the optional fields filled.
std::unique_ptr<TemplateURLData> fallback_t_url =
prepopulate_data_resolver().GetFallbackSearch();
EXPECT_EQ(TemplateURLPrepopulateData::google.name,
fallback_t_url->short_name());
EXPECT_FALSE(fallback_t_url->suggestions_url.empty());
EXPECT_FALSE(fallback_t_url->image_url.empty());
EXPECT_FALSE(fallback_t_url->contextual_search_url.empty());
EXPECT_FALSE(fallback_t_url->image_url_post_params.empty());
// Expect at least 2 alternate_urls.
// This caught a bug with static initialization of arrays, so leave this in.
EXPECT_GT(fallback_t_url->alternate_urls.size(), 1u);
for (size_t i = 0; i < fallback_t_url->alternate_urls.size(); ++i) {
EXPECT_FALSE(fallback_t_url->alternate_urls[i].empty());
}
EXPECT_EQ(TemplateURLPrepopulateData::google.type,
TemplateURL(*fallback_t_url).GetEngineType(SearchTermsData()));
}
// Verifies that all built-in search providers available across all countries
// use https urls.
TEST_F(TemplateURLPrepopulateDataTest, PrepopulatedAreHttps) {
for (CountryId country_id : kAllCountryIds) {
OverrideCountryId(country_id);
std::vector<std::unique_ptr<TemplateURLData>> t_urls =
prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_FALSE(t_urls.empty());
for (const auto& t_url : t_urls) {
CheckUrlIsEmptyOrSecure(t_url->url());
CheckUrlIsEmptyOrSecure(t_url->image_url);
CheckUrlIsEmptyOrSecure(t_url->image_translate_url);
CheckUrlIsEmptyOrSecure(t_url->new_tab_url);
CheckUrlIsEmptyOrSecure(t_url->contextual_search_url);
CheckUrlIsEmptyOrSecure(t_url->suggestions_url);
CheckUrlIsEmptyOrSecure(t_url->favicon_url.scheme());
CheckUrlIsEmptyOrSecure(t_url->logo_url.scheme());
}
}
}
TEST_F(TemplateURLPrepopulateDataTest, GetEngineTypeBasic) {
EXPECT_EQ(SEARCH_ENGINE_OTHER, GetEngineType("http://example.com/"));
EXPECT_EQ(SEARCH_ENGINE_ASK, GetEngineType("http://www.ask.com/"));
EXPECT_EQ(SEARCH_ENGINE_OTHER, GetEngineType("http://search.atlas.cz/"));
EXPECT_EQ(TemplateURLPrepopulateData::google.type,
GetEngineType("http://www.google.com/"));
}
TEST_F(TemplateURLPrepopulateDataTest, GetEngineTypeAdvanced) {
// Google URLs in different forms.
const char* kGoogleURLs[] = {
// Original with google:baseURL:
"{google:baseURL}search?q={searchTerms}&{google:RLZ}"
"{google:originalQueryForSuggestion}{google:searchFieldtrialParameter}"
"sourceid=chrome&ie={inputEncoding}",
// Custom with google.com and reordered query params:
"http://google.com/search?{google:RLZ}{google:originalQueryForSuggestion}"
"{google:searchFieldtrialParameter}"
"sourceid=chrome&ie={inputEncoding}&q={searchTerms}",
// Custom with a country TLD and almost no query params:
"http://www.google.ru/search?q={searchTerms}"
};
for (const char* google_url : kGoogleURLs) {
EXPECT_EQ(TemplateURLPrepopulateData::google.type,
GetEngineType(google_url));
}
// Non-Google URLs.
const char* kYahooURLs[] = {
("http://search.yahoo.com/search?"
"ei={inputEncoding}&fr=crmas&p={searchTerms}"),
"http://search.yahoo.com/search?p={searchTerms}",
// Aggressively match types by checking just TLD+1.
"http://someothersite.yahoo.com/",
};
for (const char* yahoo_url : kYahooURLs) {
EXPECT_EQ(SEARCH_ENGINE_YAHOO, GetEngineType(yahoo_url));
}
// Search URL for which no prepopulated search provider exists.
EXPECT_EQ(SEARCH_ENGINE_OTHER,
GetEngineType("http://example.net/search?q={searchTerms}"));
EXPECT_EQ(SEARCH_ENGINE_OTHER, GetEngineType("invalid:search:url"));
// URL that doesn't look Google-related, but matches a Google base URL
// specified on the command line.
const std::string foo_url("http://www.foo.com/search?q={searchTerms}");
EXPECT_EQ(SEARCH_ENGINE_OTHER, GetEngineType(foo_url));
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kGoogleBaseURL, "http://www.foo.com/");
EXPECT_EQ(TemplateURLPrepopulateData::google.type, GetEngineType(foo_url));
}
TEST_F(TemplateURLPrepopulateDataTest, GetEngineTypeForAllPrepopulatedEngines) {
using PrepopulatedEngine = TemplateURLPrepopulateData::PrepopulatedEngine;
const auto all_engines =
TemplateURLPrepopulateData::GetAllPrepopulatedEngines();
for (const PrepopulatedEngine* engine : all_engines) {
std::unique_ptr<TemplateURLData> data =
TemplateURLDataFromPrepopulatedEngine(*engine);
EXPECT_EQ(engine->type,
TemplateURL(*data).GetEngineType(SearchTermsData()));
}
}
TEST_F(TemplateURLPrepopulateDataTest, CheckSearchURLDetection) {
using PrepopulatedEngine = TemplateURLPrepopulateData::PrepopulatedEngine;
const auto all_engines =
TemplateURLPrepopulateData::GetAllPrepopulatedEngines();
for (const PrepopulatedEngine* engine : all_engines) {
std::unique_ptr<TemplateURLData> data =
TemplateURLDataFromPrepopulatedEngine(*engine);
TemplateURL t_url(*data);
SearchTermsData search_data;
// Test that search term is successfully extracted from generated search
// url.
GURL search_url = t_url.GenerateSearchURL(search_data);
EXPECT_TRUE(t_url.IsSearchURL(search_url, search_data))
<< "Search url is incorrectly detected for " << search_url;
}
}
TEST_F(TemplateURLPrepopulateDataTest, HttpsUrls) {
// Search engines that don't use HTTPS URLs.
// Since Chrome and the Internet are trying to transition from HTTP to HTTPS,
// please get approval from a PM before entering new HTTP exceptions here.
std::set<int> exceptions{
4, 6, 16, 17, 21, 27, 35, 36, 43, 44, 45, 50, 54, 55, 56, 60, 61,
62, 63, 64, 65, 66, 68, 70, 74, 75, 76, 77, 78, 79, 80, 81, 85, 90,
};
using PrepopulatedEngine = TemplateURLPrepopulateData::PrepopulatedEngine;
const auto all_engines =
TemplateURLPrepopulateData::GetAllPrepopulatedEngines();
for (const PrepopulatedEngine* engine : all_engines) {
std::unique_ptr<TemplateURLData> data =
TemplateURLDataFromPrepopulatedEngine(*engine);
if (base::Contains(exceptions, data->prepopulate_id))
continue;
GURL logo_url = data->logo_url;
EXPECT_TRUE(logo_url.is_empty() || logo_url.SchemeIsCryptographic())
<< logo_url;
GURL doodle_url = data->doodle_url;
EXPECT_TRUE(doodle_url.is_empty() || doodle_url.SchemeIsCryptographic())
<< doodle_url;
EXPECT_TRUE(logo_url.is_empty() || doodle_url.is_empty())
<< "Only one of logo_url or doodle_url should be set.";
GURL favicon_url = data->favicon_url;
EXPECT_TRUE(favicon_url.is_empty() || favicon_url.SchemeIsCryptographic())
<< favicon_url;
TemplateURL template_url(*data);
// Intentionally don't check alternate URLs, because those are only used
// for matching.
CheckTemplateUrlRefIsCryptographic(template_url.url_ref());
CheckTemplateUrlRefIsCryptographic(template_url.suggestions_url_ref());
CheckTemplateUrlRefIsCryptographic(template_url.image_url_ref());
CheckTemplateUrlRefIsCryptographic(template_url.new_tab_url_ref());
CheckTemplateUrlRefIsCryptographic(
template_url.contextual_search_url_ref());
}
}
TEST_F(TemplateURLPrepopulateDataTest, FindGoogleAsFallback) {
std::unique_ptr<TemplateURLData> fallback_url;
// Google is first in US, so confirm index 0.
CountryId us_country_id("US");
OverrideCountryId(us_country_id);
EXPECT_EQ(TemplateURLPrepopulateData::kRegionalSettings.find(us_country_id)
->second->search_engines[0]
->id,
TemplateURLPrepopulateData::google.id);
fallback_url = prepopulate_data_resolver().GetFallbackSearch();
EXPECT_EQ(fallback_url->prepopulate_id,
TemplateURLPrepopulateData::google.id);
// Google is not first in CN; confirm it is found at index > 0.
// If Google ever does reach top in China, this test will need to be adjusted:
// check template_url_prepopulate_data.cc reference orders (engines_CN, etc.)
// to find a suitable country and index.
CountryId cn_country_id("CN");
OverrideCountryId(cn_country_id);
fallback_url = prepopulate_data_resolver().GetFallbackSearch();
EXPECT_NE(TemplateURLPrepopulateData::kRegionalSettings.find(cn_country_id)
->second->search_engines[0]
->id,
TemplateURLPrepopulateData::google.id);
EXPECT_TRUE(fallback_url);
EXPECT_EQ(fallback_url->prepopulate_id,
TemplateURLPrepopulateData::google.id);
}
// Regression test for https://crbug.com/1500526.
TEST_F(TemplateURLPrepopulateDataTest, GetPrepopulatedEngineFromFullList) {
// Ensure that we use the default set of search engines, which is google,
// bing, yahoo.
OverrideCountryId(CountryId());
ASSERT_EQ(prepopulate_data_resolver().GetPrepopulatedEngines().size(), 3u);
// `GetPrepopulatedEngine()` only looks in the profile country's prepopulated
// list.
EXPECT_FALSE(prepopulate_data_resolver().GetPrepopulatedEngine(
TemplateURLPrepopulateData::ecosia.id));
// Here we look in the full list.
auto found_engine = prepopulate_data_resolver().GetEngineFromFullList(
TemplateURLPrepopulateData::ecosia.id);
EXPECT_TRUE(found_engine);
auto expected_engine =
TemplateURLDataFromPrepopulatedEngine(TemplateURLPrepopulateData::ecosia);
ExpectSimilar(expected_engine.get(), found_engine.get());
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(TemplateURLPrepopulateDataTest, GetLocalPrepopulatedEngines) {
constexpr char sample_country[] = "US";
OverrideCountryId(CountryId(sample_country));
// For a given country, the output from `GetLocalPrepopulatedEngines`
// should match the template URLs obtained from `GetPrepopulatedEngines`.
auto expected_urls = prepopulate_data_resolver().GetPrepopulatedEngines();
auto actual_urls = TemplateURLPrepopulateData::GetLocalPrepopulatedEngines(
sample_country, *pref_service());
ASSERT_EQ(actual_urls.size(), expected_urls.size());
for (unsigned int i = 0; i < actual_urls.size(); ++i) {
EXPECT_EQ(actual_urls[i]->prepopulate_id, expected_urls[i]->prepopulate_id);
EXPECT_EQ(actual_urls[i]->keyword(), expected_urls[i]->keyword());
EXPECT_EQ(actual_urls[i]->url(), expected_urls[i]->url());
}
EXPECT_THAT(TemplateURLPrepopulateData::GetLocalPrepopulatedEngines(
"NOT A COUNTRY", *pref_service()),
testing::IsEmpty());
}
#endif // BUILDFLAG(IS_ANDROID)
struct UpdateRequirementsTestParams {
std::string test_case_name;
std::string db_country;
int db_version;
std::string profile_country;
std::optional<int> pref_override_version;
std::optional<BuiltinKeywordsMetadata> expected_output;
};
std::ostream& operator<<(std::ostream& os,
const UpdateRequirementsTestParams& value) {
os << "{db_country=" << value.db_country
<< ", db_version=" << value.db_version
<< ", profile_country=" << value.profile_country;
if (value.pref_override_version.has_value()) {
os << ", pref_override_version=" << value.pref_override_version.value();
}
os << ", expected_output=";
if (value.expected_output.has_value()) {
os << value.expected_output.value();
} else {
os << "nullopt";
}
return os << "}";
}
class TemplateURLPrepopulateDataUpdateRequirementsTest
: public TemplateURLPrepopulateDataTest,
public testing::WithParamInterface<UpdateRequirementsTestParams> {
public:
void SetUp() override {
TemplateURLPrepopulateDataTest::SetUp();
OverrideCountryCommandLine(GetParam().profile_country);
if (GetParam().pref_override_version.has_value()) {
pref_service()->SetInteger(prefs::kSearchProviderOverridesVersion,
GetParam().pref_override_version.value());
}
}
static auto Cases() {
return ::testing::ValuesIn({
UpdateRequirementsTestParams{
.test_case_name = "UpToDateMetadata",
.db_country = "DE",
.db_version = kCurrentDataVersion,
.profile_country = "DE",
.expected_output = std::nullopt, // Update not needed.
},
{
.test_case_name = "DifferentCountry",
.db_country = "DE",
.db_version = kCurrentDataVersion,
.profile_country = "FR",
.expected_output =
BuildMetadata(CountryId("FR"), kCurrentDataVersion),
},
{
.test_case_name = "DbCountryMissing",
.db_country = "",
.db_version = kCurrentDataVersion,
.profile_country = "FR",
.expected_output = std::nullopt, // Update suppressed.
},
{
.test_case_name = "CountryOverride",
.db_country = "DE",
.db_version = kCurrentDataVersion,
.profile_country = switches::kEeaListCountryOverride,
.expected_output = BuildMetadata(CountryId(), kCurrentDataVersion),
},
{
.test_case_name = "DbMoreRecent",
.db_country = "DE",
.db_version = kCurrentDataVersion + 1,
.profile_country = "DE",
.expected_output = std::nullopt, // Update suppressed.
},
{
.test_case_name = "DbOlder",
.db_country = "DE",
.db_version = kCurrentDataVersion - 1,
.profile_country = "DE",
.expected_output =
BuildMetadata(CountryId("DE"), kCurrentDataVersion),
},
{
.test_case_name = "PrefOverride",
.db_country = "DE",
.db_version = kCurrentDataVersion,
.profile_country = "DE",
.pref_override_version = kCurrentDataVersion + 42,
.expected_output =
BuildMetadata(CountryId("DE"), kCurrentDataVersion + 42),
},
});
}
static std::string ParamToTestSuffix(
const ::testing::TestParamInfo<UpdateRequirementsTestParams>& info) {
return info.param.test_case_name;
}
static BuiltinKeywordsMetadata BuildMetadata(CountryId country_id,
int version) {
return {
.country_id = regional_capabilities::CountryIdHolder(country_id),
.data_version = version,
};
}
};
TEST_P(TemplateURLPrepopulateDataUpdateRequirementsTest,
ComputeDatabaseUpdateRequirements) {
WDKeywordsResult::Metadata database_metadata;
database_metadata.builtin_keyword_data_version = GetParam().db_version;
database_metadata.builtin_keyword_country =
GetParam().db_country.empty()
? std::nullopt
: std::optional(regional_capabilities::CountryIdHolder(
country_codes::CountryId(GetParam().db_country)));
std::optional<BuiltinKeywordsMetadata> out =
prepopulate_data_resolver().ComputeDatabaseUpdateRequirements(
database_metadata);
EXPECT_EQ(GetParam().expected_output, out);
}
INSTANTIATE_TEST_SUITE_P(
,
TemplateURLPrepopulateDataUpdateRequirementsTest,
TemplateURLPrepopulateDataUpdateRequirementsTest::Cases(),
TemplateURLPrepopulateDataUpdateRequirementsTest::ParamToTestSuffix);
// -- Choice screen randomization checks --------------------------------------
class TemplateURLPrepopulateDataListTest
: public TemplateURLPrepopulateDataTest,
public testing::WithParamInterface<CountryId> {
public:
// The data type for prepopulate IDs
// (`TemplateURLPrepopulateData::PrepopulatedEngine::id`), declared explicitly
// for readability.
using prepopulate_id_t = int;
static std::string ParamToTestSuffix(
const ::testing::TestParamInfo<CountryId>& info) {
return std::string(info.param.CountryCode());
}
TemplateURLPrepopulateDataListTest()
: country_id_(GetParam()), country_code_(country_id_.CountryCode()) {}
void SetUp() override {
if (kSkippedCountries.contains(country_id_)) {
GTEST_SKIP() << "Skipping, the Default set is used for country code "
<< country_code_;
}
TemplateURLPrepopulateDataTest::SetUp();
OverrideCountryId(country_id_);
for (const auto& engine :
TemplateURLPrepopulateData::kRegionalSettings.find(country_id_)
->second->search_engines) {
id_to_engine_[engine->id] = engine;
}
}
protected:
void RunEeaEngineListIsRandomTest(int number_of_iterations,
double max_allowed_deviation) {
// `per_engine_and_index_observations` maps from search engine prepopulate
// ID to the number of times we saw this engine at a given position in the
// list of items on the choice screen (the index in the vector is the
// observed index in the choice screen list).
base::flat_map<prepopulate_id_t, std::vector<int>>
per_engine_and_index_observations;
for (const auto& entry : id_to_engine_) {
// Fill the map with 0s.
per_engine_and_index_observations[entry.first] =
std::vector<int>(kEeaChoiceScreenItemCount, 0);
}
// Gather observations, loading the search engines `number_of_iterations`
// times and recording the returned positions.
for (int current_run = 0; current_run < number_of_iterations;
++current_run) {
// Simulate being a fresh new profile, where the shuffle seed is not set.
pref_service()->ClearPref(
prefs::kDefaultSearchProviderChoiceScreenRandomShuffleSeed);
std::vector<std::unique_ptr<TemplateURLData>> actual_list =
prepopulate_data_resolver().GetPrepopulatedEngines();
ASSERT_EQ(actual_list.size(), kEeaChoiceScreenItemCount);
for (size_t index = 0; index < kEeaChoiceScreenItemCount; ++index) {
per_engine_and_index_observations[actual_list[index]->prepopulate_id]
[index]++;
}
}
// variance in the appearance rate of the various engines for a given index.
std::vector<double> per_index_max_deviation(kEeaChoiceScreenItemCount, 0);
base::flat_map<prepopulate_id_t, double> per_engine_max_deviation;
base::flat_map<prepopulate_id_t, std::vector<double>>
per_engine_and_index_probabilities;
for (auto& entry : per_engine_and_index_observations) {
prepopulate_id_t engine_id = entry.first;
auto& per_index_observations = entry.second;
int total_engine_appearances = 0;
std::vector<double> probabilities(kEeaChoiceScreenItemCount, 0);
for (size_t index = 0; index < kEeaChoiceScreenItemCount; ++index) {
total_engine_appearances += per_index_observations[index];
double appearance_probability_at_index =
static_cast<double>(per_index_observations[index]) /
number_of_iterations;
double deviation =
appearance_probability_at_index - kExpectedItemProbability;
EXPECT_LE(deviation, max_allowed_deviation);
per_engine_max_deviation[engine_id] =
std::max(std::abs(deviation), per_engine_max_deviation[engine_id]);
per_index_max_deviation[index] =
std::max(std::abs(deviation), per_index_max_deviation[index]);
probabilities[index] = appearance_probability_at_index;
}
ASSERT_EQ(total_engine_appearances, number_of_iterations);
per_engine_and_index_probabilities[engine_id] = probabilities;
}
// Log the values if needed.
if (testing::Test::HasFailure() || VLOG_IS_ON(1)) {
std::string table_string = AssembleRowString(
/*header_text=*/"",
/*cell_values=*/{"0", "1", "2", "3", "4", "5", "6", "7", "max_dev"});
for (auto& id_and_engine : id_to_engine_) {
table_string += base::StringPrintf(
"\n%s| %.4f ",
AssembleRowString(
/*header_text=*/base::UTF16ToUTF8(
std::u16string(id_and_engine.second->name)),
/*cell_values=*/per_engine_and_index_probabilities.at(
id_and_engine.first))
.c_str(),
per_engine_max_deviation.at(id_and_engine.first));
}
table_string += "\n" + AssembleRowString(/*header_text=*/"max deviation",
per_index_max_deviation);
if (testing::Test::HasFailure()) {
LOG(ERROR) << "Failure in the search engine distributions:\n"
<< table_string;
} else {
VLOG(1) << "Search engine distributions:\n" << table_string;
}
}
}
private:
// TODO(b/341047036): Investigate how to not have to skip this here.
static inline const std::set<CountryId> kSkippedCountries = {
CountryId("BL"), // St. Barthélemy
CountryId("EA"), // Ceuta & Melilla
CountryId("IC"), // Canary Islands
CountryId("MF"), // St. Martin
};
static constexpr size_t kEeaChoiceScreenItemCount = 8u;
static constexpr double kExpectedItemProbability =
1. / kEeaChoiceScreenItemCount;
static constexpr char row_header_format[] = "%s %20s ";
static constexpr char string_cell_format[] = "| %5s ";
static constexpr char probability_cell_format[] = "| %.3f ";
std::string AssembleRowString(std::string_view header_text,
std::vector<double> cell_values) {
std::string row_string = base::StringPrintf(
row_header_format, country_code_.c_str(), header_text.data());
for (double& val : cell_values) {
row_string += base::StringPrintf(probability_cell_format, val);
}
return row_string;
}
std::string AssembleRowString(std::string_view header_text,
std::vector<std::string_view> cell_values) {
std::string row_string = base::StringPrintf(
row_header_format, country_code_.c_str(), header_text.data());
for (std::string_view& val : cell_values) {
row_string += base::StringPrintf(string_cell_format, val.data());
}
return row_string;
}
const CountryId country_id_;
const std::string country_code_;
base::flat_map<int,
raw_ptr<const TemplateURLPrepopulateData::PrepopulatedEngine>>
id_to_engine_;
};
INSTANTIATE_TEST_SUITE_P(
,
TemplateURLPrepopulateDataListTest,
::testing::ValuesIn(regional_capabilities::kEeaChoiceCountriesIds.begin(),
regional_capabilities::kEeaChoiceCountriesIds.end()),
TemplateURLPrepopulateDataListTest::ParamToTestSuffix);
// Quick version of the test intended to flag glaring issues as part of the
// automated test suites.
TEST_P(TemplateURLPrepopulateDataListTest,
QuickEeaEngineListIsRandomPerCountry) {
RunEeaEngineListIsRandomTest(2000, 0.05);
}
// This test is permanently disabled as it is slow. It must *not* be removed as
// it is necessary for compliance (see b/341066703). It is used manually to
// prove that the search engines are shuffled in a randomly distributed order
// when they are loaded from disk.
// To run this test, use the following command line:
//
// $OUT_DIR/components_unittests --gtest_also_run_disabled_tests \
// --gtest_filter="*ManualEeaEngineListIsRandomPerCountry/*" \
// --vmodule="*unittest*=1*"
//
// Explanations:
// - Since the test is marked as disabled, we need to explicitly instruct
// filters to not discard it.
// - By default the test logs the stats only if it picks up an error. If we
// want to always get the logs for all the countries, we need to enable
// verbose logging for this file. The stats tables are logged to STDERR, so
// append `2> output.txt` if it needs to be gathered in a file.
TEST_P(TemplateURLPrepopulateDataListTest,
DISABLED_ManualEeaEngineListIsRandomPerCountry) {
RunEeaEngineListIsRandomTest(20000, 0.01);
}