// Copyright (c) 2011 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 "base/callback.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_vector.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/threading/thread.h" #include "base/utf_string_conversions.h" #include "chrome/browser/history/history.h" #include "chrome/browser/history/history_notifications.h" #include "chrome/browser/search_engines/search_host_to_urls_map.h" #include "chrome/browser/search_engines/search_terms_data.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/browser/search_engines/template_url_model_test_util.h" #include "chrome/browser/search_engines/template_url_prepopulate_data.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/pref_names.h" #include "chrome/test/testing_pref_service.h" #include "chrome/test/testing_profile.h" #include "content/browser/browser_thread.h" #include "content/common/notification_details.h" #include "content/common/notification_source.h" #include "testing/gtest/include/gtest/gtest.h" using base::Time; using base::TimeDelta; #if defined(OS_LINUX) // Timed out on Chromium Linux. http://crbug.com/53607 #define MAYBE_Load DISABLED_Load #else #define MAYBE_Load Load #endif // Test the GenerateSearchURL on a thread or the main thread. class TestGenerateSearchURL : public base::RefCountedThreadSafe<TestGenerateSearchURL> { public: explicit TestGenerateSearchURL(SearchTermsData* search_terms_data) : search_terms_data_(search_terms_data), passed_(false) { } // Run the test cases for GenerateSearchURL. void RunTest(); // Did the test pass? bool passed() const { return passed_; } private: friend class base::RefCountedThreadSafe<TestGenerateSearchURL>; ~TestGenerateSearchURL() {} SearchTermsData* search_terms_data_; bool passed_; DISALLOW_COPY_AND_ASSIGN(TestGenerateSearchURL); }; // Simple implementation of SearchTermsData. class TestSearchTermsData : public SearchTermsData { public: explicit TestSearchTermsData(const char* google_base_url) : google_base_url_(google_base_url) { } virtual std::string GoogleBaseURLValue() const { return google_base_url_; } virtual std::string GetApplicationLocale() const { return "yy"; } #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) // Returns the value for the Chrome Omnibox rlz. virtual string16 GetRlzParameterValue() const { return string16(); } #endif private: std::string google_base_url_; DISALLOW_COPY_AND_ASSIGN(TestSearchTermsData); }; // Create an URL that appears to have been prepopulated, but won't be in the // current data. The caller owns the returned TemplateURL*. static TemplateURL* CreatePreloadedTemplateURL() { TemplateURL* t_url = new TemplateURL(); t_url->SetURL("http://www.unittest.com/", 0, 0); t_url->set_keyword(ASCIIToUTF16("unittest")); t_url->set_short_name(ASCIIToUTF16("unittest")); t_url->set_safe_for_autoreplace(true); GURL favicon_url("http://favicon.url"); t_url->SetFaviconURL(favicon_url); t_url->set_date_created(Time::FromTimeT(100)); t_url->set_prepopulate_id(999999); return t_url; } class TemplateURLModelTest : public testing::Test { public: TemplateURLModelTest() {} virtual void SetUp() { test_util_.SetUp(); } virtual void TearDown() { test_util_.TearDown(); } TemplateURL* AddKeywordWithDate(const std::string& keyword, bool autogenerate_keyword, const std::string& url, const std::string& suggest_url, const std::string& favicon_url, const std::string& encodings, const std::string& short_name, bool safe_for_autoreplace, Time created_date) { TemplateURL* template_url = new TemplateURL(); template_url->SetURL(url, 0, 0); template_url->SetSuggestionsURL(suggest_url, 0, 0); template_url->SetFaviconURL(GURL(favicon_url)); template_url->set_keyword(UTF8ToUTF16(keyword)); template_url->set_autogenerate_keyword(autogenerate_keyword); template_url->set_short_name(UTF8ToUTF16(short_name)); std::vector<std::string> encodings_vector; base::SplitString(encodings, ';', &encodings_vector); template_url->set_input_encodings(encodings_vector); template_url->set_date_created(created_date); template_url->set_safe_for_autoreplace(safe_for_autoreplace); model()->Add(template_url); EXPECT_NE(0, template_url->id()); return template_url; } // Simulate firing by the prefs service specifying that the managed // preferences have changed. void NotifyManagedPrefsHaveChanged() { model()->Observe( NotificationType::PREF_CHANGED, Source<PrefService>(profile()->GetTestingPrefService()), Details<std::string>(NULL)); } // Verifies the two TemplateURLs are equal. void AssertEquals(const TemplateURL& expected, const TemplateURL& actual) { ASSERT_TRUE(TemplateURLRef::SameUrlRefs(expected.url(), actual.url())); ASSERT_TRUE(TemplateURLRef::SameUrlRefs(expected.suggestions_url(), actual.suggestions_url())); ASSERT_EQ(expected.keyword(), actual.keyword()); ASSERT_EQ(expected.short_name(), actual.short_name()); ASSERT_EQ(JoinString(expected.input_encodings(), ';'), JoinString(actual.input_encodings(), ';')); ASSERT_TRUE(expected.GetFaviconURL() == actual.GetFaviconURL()); ASSERT_EQ(expected.id(), actual.id()); ASSERT_EQ(expected.safe_for_autoreplace(), actual.safe_for_autoreplace()); ASSERT_EQ(expected.show_in_default_list(), actual.show_in_default_list()); ASSERT_TRUE(expected.date_created() == actual.date_created()); } // Checks that the two TemplateURLs are similar. It does not check the id // and the date_created. Neither pointer should be NULL. void ExpectSimilar(const TemplateURL* expected, const TemplateURL* actual) { ASSERT_TRUE(expected != NULL); ASSERT_TRUE(actual != NULL); EXPECT_TRUE(TemplateURLRef::SameUrlRefs(expected->url(), actual->url())); EXPECT_TRUE(TemplateURLRef::SameUrlRefs(expected->suggestions_url(), actual->suggestions_url())); EXPECT_EQ(expected->keyword(), actual->keyword()); EXPECT_EQ(expected->short_name(), actual->short_name()); EXPECT_EQ(JoinString(expected->input_encodings(), ';'), JoinString(actual->input_encodings(), ';')); EXPECT_TRUE(expected->GetFaviconURL() == actual->GetFaviconURL()); EXPECT_EQ(expected->safe_for_autoreplace(), actual->safe_for_autoreplace()); EXPECT_EQ(expected->show_in_default_list(), actual->show_in_default_list()); } // Set the managed preferences for the default search provider and trigger // notification. void SetManagedDefaultSearchPreferences(bool enabled, const char* name, const char* search_url, const char* suggest_url, const char* icon_url, const char* encodings, const char* keyword) { TestingPrefService* service = profile()->GetTestingPrefService(); service->SetManagedPref( prefs::kDefaultSearchProviderEnabled, Value::CreateBooleanValue(enabled)); service->SetManagedPref( prefs::kDefaultSearchProviderName, Value::CreateStringValue(name)); service->SetManagedPref( prefs::kDefaultSearchProviderSearchURL, Value::CreateStringValue(search_url)); service->SetManagedPref( prefs::kDefaultSearchProviderSuggestURL, Value::CreateStringValue(suggest_url)); service->SetManagedPref( prefs::kDefaultSearchProviderIconURL, Value::CreateStringValue(icon_url)); service->SetManagedPref( prefs::kDefaultSearchProviderEncodings, Value::CreateStringValue(encodings)); service->SetManagedPref( prefs::kDefaultSearchProviderKeyword, Value::CreateStringValue(keyword)); } // Remove all the managed preferences for the default search provider and // trigger notification. void RemoveManagedDefaultSearchPreferences() { TestingPrefService* service = profile()->GetTestingPrefService(); service->RemoveManagedPref( prefs::kDefaultSearchProviderSearchURL); service->RemoveManagedPref( prefs::kDefaultSearchProviderEnabled); service->RemoveManagedPref( prefs::kDefaultSearchProviderName); service->RemoveManagedPref( prefs::kDefaultSearchProviderSuggestURL); service->RemoveManagedPref( prefs::kDefaultSearchProviderIconURL); service->RemoveManagedPref( prefs::kDefaultSearchProviderEncodings); service->RemoveManagedPref( prefs::kDefaultSearchProviderKeyword); service->RemoveManagedPref( prefs::kDefaultSearchProviderID); service->RemoveManagedPref( prefs::kDefaultSearchProviderPrepopulateID); } // Creates a TemplateURL with the same prepopulated id as a real prepopulated // item. The input number determines which prepopulated item. The caller is // responsible for owning the returned TemplateURL*. TemplateURL* CreateReplaceablePreloadedTemplateURL( size_t index_offset_from_default, string16* prepopulated_display_url); // Verifies the behavior of when a preloaded url later gets changed. // Since the input is the offset from the default, when one passes in // 0, it tests the default. Passing in a number > 0 will verify what // happens when a preloaded url that is not the default gets updated. void TestLoadUpdatingPreloadedURL(size_t index_offset_from_default); // Helper methods to make calling TemplateURLModelTestUtil methods less // visually noisy in the test code. void VerifyObserverCount(int expected_changed_count) { EXPECT_EQ(expected_changed_count, test_util_.GetObserverCount()); test_util_.ResetObserverCount(); } void VerifyObserverFired() { EXPECT_LE(1, test_util_.GetObserverCount()); test_util_.ResetObserverCount(); } void BlockTillServiceProcessesRequests() { TemplateURLModelTestUtil::BlockTillServiceProcessesRequests(); } void VerifyLoad() { test_util_.VerifyLoad(); } void ChangeModelToLoadState() { test_util_.ChangeModelToLoadState(); } void ResetModel(bool verify_load) { test_util_.ResetModel(verify_load); } string16 GetAndClearSearchTerm() { return test_util_.GetAndClearSearchTerm(); } void SetGoogleBaseURL(const std::string& base_url) const { test_util_.SetGoogleBaseURL(base_url); } WebDataService* GetWebDataService() { return test_util_.GetWebDataService(); } TemplateURLModel* model() { return test_util_.model(); } TestingProfile* profile() { return test_util_.profile(); } protected: TemplateURLModelTestUtil test_util_; DISALLOW_COPY_AND_ASSIGN(TemplateURLModelTest); }; void TestGenerateSearchURL::RunTest() { struct GenerateSearchURLCase { const char* test_name; const char* url; const char* expected; } generate_url_cases[] = { { "empty TemplateURLRef", NULL, "" }, { "invalid URL", "foo{searchTerms}", "" }, { "URL with no replacements", "http://foo/", "http://foo/" }, { "basic functionality", "http://foo/{searchTerms}", "http://foo/blah.blah.blah.blah.blah" } }; // Don't use ASSERT/EXPECT since this is run on a thread in one test // and those macros aren't meant for threads at this time according to // gtest documentation. bool everything_passed = true; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(generate_url_cases); ++i) { TemplateURL t_url; if (generate_url_cases[i].url) t_url.SetURL(generate_url_cases[i].url, 0, 0); std::string result = search_terms_data_ ? TemplateURLModel::GenerateSearchURLUsingTermsData( &t_url, *search_terms_data_).spec() : TemplateURLModel::GenerateSearchURL(&t_url).spec(); if (strcmp(generate_url_cases[i].expected, result.c_str())) { LOG(ERROR) << generate_url_cases[i].test_name << " failed. Expected " << generate_url_cases[i].expected << " Actual " << result; everything_passed = false; } } passed_ = everything_passed; } TemplateURL* TemplateURLModelTest::CreateReplaceablePreloadedTemplateURL( size_t index_offset_from_default, string16* prepopulated_display_url) { TemplateURL* t_url = CreatePreloadedTemplateURL(); ScopedVector<TemplateURL> prepopulated_urls; size_t default_search_provider_index = 0; TemplateURLPrepopulateData::GetPrepopulatedEngines( profile()->GetPrefs(), &prepopulated_urls.get(), &default_search_provider_index); EXPECT_LT(index_offset_from_default, prepopulated_urls.size()); size_t prepopulated_index = (default_search_provider_index + index_offset_from_default) % prepopulated_urls.size(); t_url->set_prepopulate_id( prepopulated_urls[prepopulated_index]->prepopulate_id()); *prepopulated_display_url = prepopulated_urls[prepopulated_index]->url()->DisplayURL(); return t_url; } void TemplateURLModelTest::TestLoadUpdatingPreloadedURL( size_t index_offset_from_default) { string16 prepopulated_url; TemplateURL* t_url = CreateReplaceablePreloadedTemplateURL( index_offset_from_default, &prepopulated_url); t_url->set_safe_for_autoreplace(false); string16 original_url = t_url->url()->DisplayURL(); ASSERT_NE(prepopulated_url, original_url); // Then add it to the model and save it all. ChangeModelToLoadState(); model()->Add(t_url); const TemplateURL* keyword_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")); ASSERT_EQ(t_url, keyword_url); ASSERT_EQ(original_url, keyword_url->url()->DisplayURL()); BlockTillServiceProcessesRequests(); // Now reload the model and verify that the merge updates the url. ResetModel(true); keyword_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")); ASSERT_TRUE(keyword_url != NULL); ASSERT_EQ(prepopulated_url, keyword_url->url()->DisplayURL()); // Wait for any saves to finish. BlockTillServiceProcessesRequests(); // Reload the model to verify that change was saved correctly. ResetModel(true); keyword_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")); ASSERT_TRUE(keyword_url != NULL); ASSERT_EQ(prepopulated_url, keyword_url->url()->DisplayURL()); } TEST_F(TemplateURLModelTest, MAYBE_Load) { VerifyLoad(); } TEST_F(TemplateURLModelTest, AddUpdateRemove) { // Add a new TemplateURL. VerifyLoad(); const size_t initial_count = model()->GetTemplateURLs().size(); TemplateURL* t_url = new TemplateURL(); t_url->SetURL("http://www.google.com/foo/bar", 0, 0); t_url->set_keyword(ASCIIToUTF16("keyword")); t_url->set_short_name(ASCIIToUTF16("google")); GURL favicon_url("http://favicon.url"); t_url->SetFaviconURL(favicon_url); t_url->set_date_created(Time::FromTimeT(100)); t_url->set_safe_for_autoreplace(true); model()->Add(t_url); ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("keyword"), GURL(), NULL)); VerifyObserverCount(1); BlockTillServiceProcessesRequests(); // We need to clone as model takes ownership of TemplateURL and will // delete it. TemplateURL cloned_url(*t_url); ASSERT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); ASSERT_TRUE(model()->GetTemplateURLForKeyword(t_url->keyword()) == t_url); ASSERT_TRUE(t_url->date_created() == cloned_url.date_created()); // Reload the model to verify it was actually saved to the database. ResetModel(true); ASSERT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); const TemplateURL* loaded_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("keyword")); ASSERT_TRUE(loaded_url != NULL); AssertEquals(cloned_url, *loaded_url); ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("keyword"), GURL(), NULL)); // Mutate an element and verify it succeeded. model()->ResetTemplateURL(loaded_url, ASCIIToUTF16("a"), ASCIIToUTF16("b"), "c"); ASSERT_EQ(ASCIIToUTF16("a"), loaded_url->short_name()); ASSERT_EQ(ASCIIToUTF16("b"), loaded_url->keyword()); ASSERT_EQ("c", loaded_url->url()->url()); ASSERT_FALSE(loaded_url->safe_for_autoreplace()); ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("keyword"), GURL(), NULL)); ASSERT_FALSE(model()->CanReplaceKeyword(ASCIIToUTF16("b"), GURL(), NULL)); cloned_url = *loaded_url; BlockTillServiceProcessesRequests(); ResetModel(true); ASSERT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); loaded_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("b")); ASSERT_TRUE(loaded_url != NULL); AssertEquals(cloned_url, *loaded_url); // Remove an element and verify it succeeded. model()->Remove(loaded_url); VerifyObserverCount(1); ResetModel(true); ASSERT_EQ(initial_count, model()->GetTemplateURLs().size()); EXPECT_TRUE(model()->GetTemplateURLForKeyword(ASCIIToUTF16("b")) == NULL); } TEST_F(TemplateURLModelTest, GenerateKeyword) { ASSERT_EQ(string16(), TemplateURLModel::GenerateKeyword(GURL(), true)); // Shouldn't generate keywords for https. ASSERT_EQ(string16(), TemplateURLModel::GenerateKeyword(GURL("https://blah"), true)); ASSERT_EQ(ASCIIToUTF16("foo"), TemplateURLModel::GenerateKeyword(GURL("http://foo"), true)); // www. should be stripped. ASSERT_EQ(ASCIIToUTF16("foo"), TemplateURLModel::GenerateKeyword(GURL("http://www.foo"), true)); // Shouldn't generate keywords with paths, if autodetected. ASSERT_EQ(string16(), TemplateURLModel::GenerateKeyword(GURL("http://blah/foo"), true)); ASSERT_EQ(ASCIIToUTF16("blah"), TemplateURLModel::GenerateKeyword(GURL("http://blah/foo"), false)); // FTP shouldn't generate a keyword. ASSERT_EQ(string16(), TemplateURLModel::GenerateKeyword(GURL("ftp://blah/"), true)); // Make sure we don't get a trailing / ASSERT_EQ(ASCIIToUTF16("blah"), TemplateURLModel::GenerateKeyword(GURL("http://blah/"), true)); } TEST_F(TemplateURLModelTest, GenerateSearchURL) { scoped_refptr<TestGenerateSearchURL> test_generate_search_url( new TestGenerateSearchURL(NULL)); test_generate_search_url->RunTest(); EXPECT_TRUE(test_generate_search_url->passed()); } TEST_F(TemplateURLModelTest, GenerateSearchURLUsingTermsData) { // Run the test for GenerateSearchURLUsingTermsData on the "IO" thread and // wait for it to finish. TestSearchTermsData search_terms_data("http://google.com/"); scoped_refptr<TestGenerateSearchURL> test_generate_search_url( new TestGenerateSearchURL(&search_terms_data)); test_util_.StartIOThread(); BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)->PostTask( FROM_HERE, NewRunnableMethod(test_generate_search_url.get(), &TestGenerateSearchURL::RunTest)); TemplateURLModelTestUtil::BlockTillIOThreadProcessesRequests(); EXPECT_TRUE(test_generate_search_url->passed()); } TEST_F(TemplateURLModelTest, ClearBrowsingData_Keywords) { Time now = Time::Now(); TimeDelta one_day = TimeDelta::FromDays(1); Time month_ago = now - TimeDelta::FromDays(30); // Nothing has been added. EXPECT_EQ(0U, model()->GetTemplateURLs().size()); // Create one with a 0 time. AddKeywordWithDate("key1", false, "http://foo1", "http://suggest1", "http://icon1", "UTF-8;UTF-16", "name1", true, Time()); // Create one for now and +/- 1 day. AddKeywordWithDate("key2", false, "http://foo2", "http://suggest2", "http://icon2", "UTF-8;UTF-16", "name2", true, now - one_day); AddKeywordWithDate("key3", false, "http://foo3", "", "", "", "name3", true, now); AddKeywordWithDate("key4", false, "http://foo4", "", "", "", "name4", true, now + one_day); // Try the other three states. AddKeywordWithDate("key5", false, "http://foo5", "http://suggest5", "http://icon5", "UTF-8;UTF-16", "name5", false, now); AddKeywordWithDate("key6", false, "http://foo6", "http://suggest6", "http://icon6", "UTF-8;UTF-16", "name6", false, month_ago); // We just added a few items, validate them. EXPECT_EQ(6U, model()->GetTemplateURLs().size()); // Try removing from current timestamp. This should delete the one in the // future and one very recent one. model()->RemoveAutoGeneratedSince(now); EXPECT_EQ(4U, model()->GetTemplateURLs().size()); // Try removing from two months ago. This should only delete items that are // auto-generated. model()->RemoveAutoGeneratedSince(now - TimeDelta::FromDays(60)); EXPECT_EQ(3U, model()->GetTemplateURLs().size()); // Make sure the right values remain. EXPECT_EQ(ASCIIToUTF16("key1"), model()->GetTemplateURLs()[0]->keyword()); EXPECT_TRUE(model()->GetTemplateURLs()[0]->safe_for_autoreplace()); EXPECT_EQ(0U, model()->GetTemplateURLs()[0]->date_created().ToInternalValue()); EXPECT_EQ(ASCIIToUTF16("key5"), model()->GetTemplateURLs()[1]->keyword()); EXPECT_FALSE(model()->GetTemplateURLs()[1]->safe_for_autoreplace()); EXPECT_EQ(now.ToInternalValue(), model()->GetTemplateURLs()[1]->date_created().ToInternalValue()); EXPECT_EQ(ASCIIToUTF16("key6"), model()->GetTemplateURLs()[2]->keyword()); EXPECT_FALSE(model()->GetTemplateURLs()[2]->safe_for_autoreplace()); EXPECT_EQ(month_ago.ToInternalValue(), model()->GetTemplateURLs()[2]->date_created().ToInternalValue()); // Try removing from Time=0. This should delete one more. model()->RemoveAutoGeneratedSince(Time()); EXPECT_EQ(2U, model()->GetTemplateURLs().size()); } TEST_F(TemplateURLModelTest, Reset) { // Add a new TemplateURL. VerifyLoad(); const size_t initial_count = model()->GetTemplateURLs().size(); TemplateURL* t_url = new TemplateURL(); t_url->SetURL("http://www.google.com/foo/bar", 0, 0); t_url->set_keyword(ASCIIToUTF16("keyword")); t_url->set_short_name(ASCIIToUTF16("google")); GURL favicon_url("http://favicon.url"); t_url->SetFaviconURL(favicon_url); t_url->set_date_created(Time::FromTimeT(100)); model()->Add(t_url); VerifyObserverCount(1); BlockTillServiceProcessesRequests(); // Reset the short name, keyword, url and make sure it takes. const string16 new_short_name(ASCIIToUTF16("a")); const string16 new_keyword(ASCIIToUTF16("b")); const std::string new_url("c"); model()->ResetTemplateURL(t_url, new_short_name, new_keyword, new_url); ASSERT_EQ(new_short_name, t_url->short_name()); ASSERT_EQ(new_keyword, t_url->keyword()); ASSERT_EQ(new_url, t_url->url()->url()); // Make sure the mappings in the model were updated. ASSERT_TRUE(model()->GetTemplateURLForKeyword(new_keyword) == t_url); ASSERT_TRUE( model()->GetTemplateURLForKeyword(ASCIIToUTF16("keyword")) == NULL); TemplateURL last_url = *t_url; // Reload the model from the database and make sure the change took. ResetModel(true); t_url = NULL; EXPECT_EQ(initial_count + 1, model()->GetTemplateURLs().size()); const TemplateURL* read_url = model()->GetTemplateURLForKeyword(new_keyword); ASSERT_TRUE(read_url); AssertEquals(last_url, *read_url); } TEST_F(TemplateURLModelTest, DefaultSearchProvider) { // Add a new TemplateURL. VerifyLoad(); const size_t initial_count = model()->GetTemplateURLs().size(); TemplateURL* t_url = AddKeywordWithDate("key1", false, "http://foo1", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name1", true, Time()); test_util_.ResetObserverCount(); model()->SetDefaultSearchProvider(t_url); ASSERT_EQ(t_url, model()->GetDefaultSearchProvider()); ASSERT_TRUE(t_url->safe_for_autoreplace()); ASSERT_TRUE(t_url->show_in_default_list()); // Setting the default search provider should have caused notification. VerifyObserverCount(1); BlockTillServiceProcessesRequests(); TemplateURL cloned_url = *t_url; ResetModel(true); t_url = NULL; // Make sure when we reload we get a default search provider. EXPECT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); ASSERT_TRUE(model()->GetDefaultSearchProvider()); AssertEquals(cloned_url, *model()->GetDefaultSearchProvider()); } TEST_F(TemplateURLModelTest, TemplateURLWithNoKeyword) { VerifyLoad(); const size_t initial_count = model()->GetTemplateURLs().size(); AddKeywordWithDate("", false, "http://foo1", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name1", true, Time()); // We just added a few items, validate them. ASSERT_EQ(initial_count + 1, model()->GetTemplateURLs().size()); // Reload the model from the database and make sure we get the url back. ResetModel(true); ASSERT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); bool found_keyword = false; for (size_t i = 0; i < initial_count + 1; ++i) { if (model()->GetTemplateURLs()[i]->keyword().empty()) { found_keyword = true; break; } } ASSERT_TRUE(found_keyword); } TEST_F(TemplateURLModelTest, CantReplaceWithSameKeyword) { ChangeModelToLoadState(); ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("foo"), GURL(), NULL)); TemplateURL* t_url = AddKeywordWithDate("foo", false, "http://foo1", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name1", true, Time()); // Can still replace, newly added template url is marked safe to replace. ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("foo"), GURL("http://foo2"), NULL)); // ResetTemplateURL marks the TemplateURL as unsafe to replace, so it should // no longer be replaceable. model()->ResetTemplateURL(t_url, t_url->short_name(), t_url->keyword(), t_url->url()->url()); ASSERT_FALSE(model()->CanReplaceKeyword(ASCIIToUTF16("foo"), GURL("http://foo2"), NULL)); } TEST_F(TemplateURLModelTest, CantReplaceWithSameHosts) { ChangeModelToLoadState(); ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("foo"), GURL("http://foo.com"), NULL)); TemplateURL* t_url = AddKeywordWithDate("foo", false, "http://foo.com", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name1", true, Time()); // Can still replace, newly added template url is marked safe to replace. ASSERT_TRUE(model()->CanReplaceKeyword(ASCIIToUTF16("bar"), GURL("http://foo.com"), NULL)); // ResetTemplateURL marks the TemplateURL as unsafe to replace, so it should // no longer be replaceable. model()->ResetTemplateURL(t_url, t_url->short_name(), t_url->keyword(), t_url->url()->url()); ASSERT_FALSE(model()->CanReplaceKeyword(ASCIIToUTF16("bar"), GURL("http://foo.com"), NULL)); } TEST_F(TemplateURLModelTest, HasDefaultSearchProvider) { // We should have a default search provider even if we haven't loaded. ASSERT_TRUE(model()->GetDefaultSearchProvider()); // Now force the model to load and make sure we still have a default. VerifyLoad(); ASSERT_TRUE(model()->GetDefaultSearchProvider()); } TEST_F(TemplateURLModelTest, DefaultSearchProviderLoadedFromPrefs) { VerifyLoad(); TemplateURL* template_url = new TemplateURL(); template_url->SetURL("http://url", 0, 0); template_url->SetSuggestionsURL("http://url2", 0, 0); template_url->SetInstantURL("http://instant", 0, 0); template_url->set_short_name(ASCIIToUTF16("a")); template_url->set_safe_for_autoreplace(true); template_url->set_date_created(Time::FromTimeT(100)); model()->Add(template_url); const TemplateURLID id = template_url->id(); model()->SetDefaultSearchProvider(template_url); BlockTillServiceProcessesRequests(); TemplateURL first_default_search_provider = *template_url; template_url = NULL; // Reset the model and don't load it. The template url we set as the default // should be pulled from prefs now. ResetModel(false); // NOTE: This doesn't use AssertEquals as only a subset of the TemplateURLs // value are persisted to prefs. const TemplateURL* default_turl = model()->GetDefaultSearchProvider(); ASSERT_TRUE(default_turl); ASSERT_TRUE(default_turl->url()); ASSERT_EQ("http://url", default_turl->url()->url()); ASSERT_TRUE(default_turl->suggestions_url()); ASSERT_EQ("http://url2", default_turl->suggestions_url()->url()); ASSERT_TRUE(default_turl->instant_url()); EXPECT_EQ("http://instant", default_turl->instant_url()->url()); ASSERT_EQ(ASCIIToUTF16("a"), default_turl->short_name()); ASSERT_EQ(id, default_turl->id()); // Now do a load and make sure the default search provider really takes. VerifyLoad(); ASSERT_TRUE(model()->GetDefaultSearchProvider()); AssertEquals(first_default_search_provider, *model()->GetDefaultSearchProvider()); } TEST_F(TemplateURLModelTest, BuildQueryTerms) { struct TestData { const std::string url; const bool result; // Keys and values are a semicolon separated list of expected values in the // map. const std::string keys; const std::string values; } data[] = { // No query should return false. { "http://blah/", false, "", "" }, // Query with empty key should return false. { "http://blah/foo?=y", false, "", "" }, // Query with key occurring multiple times should return false. { "http://blah/foo?x=y&x=z", false, "", "" }, { "http://blah/foo?x=y", true, "x", "y" }, { "http://blah/foo?x=y&y=z", true, "x;y", "y;z" }, // Key occurring multiple times should get an empty string. { "http://blah/foo?x=y&x=z&y=z", true, "x;y", ";z" }, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { TemplateURLModel::QueryTerms terms; ASSERT_EQ(data[i].result, TemplateURLModel::BuildQueryTerms(GURL(data[i].url), &terms)); if (data[i].result) { std::vector<std::string> keys; std::vector<std::string> values; base::SplitString(data[i].keys, ';', &keys); base::SplitString(data[i].values, ';', &values); ASSERT_TRUE(keys.size() == values.size()); ASSERT_EQ(keys.size(), terms.size()); for (size_t j = 0; j < keys.size(); ++j) { TemplateURLModel::QueryTerms::iterator term_iterator = terms.find(keys[j]); ASSERT_TRUE(term_iterator != terms.end()); ASSERT_EQ(values[j], term_iterator->second); } } } } TEST_F(TemplateURLModelTest, UpdateKeywordSearchTermsForURL) { struct TestData { const std::string url; const string16 term; } data[] = { { "http://foo/", string16() }, { "http://foo/foo?q=xx", string16() }, { "http://x/bar?q=xx", string16() }, { "http://x/foo?y=xx", string16() }, { "http://x/foo?q=xx", ASCIIToUTF16("xx") }, { "http://x/foo?a=b&q=xx", ASCIIToUTF16("xx") }, { "http://x/foo?q=b&q=xx", string16() }, }; ChangeModelToLoadState(); AddKeywordWithDate("x", false, "http://x/foo?q={searchTerms}", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name", false, Time()); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { history::URLVisitedDetails details; details.row = history::URLRow(GURL(data[i].url)); details.transition = 0; model()->UpdateKeywordSearchTermsForURL(details); EXPECT_EQ(data[i].term, GetAndClearSearchTerm()); } } TEST_F(TemplateURLModelTest, DontUpdateKeywordSearchForNonReplaceable) { struct TestData { const std::string url; } data[] = { { "http://foo/" }, { "http://x/bar?q=xx" }, { "http://x/foo?y=xx" }, }; ChangeModelToLoadState(); AddKeywordWithDate("x", false, "http://x/foo", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name", false, Time()); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { history::URLVisitedDetails details; details.row = history::URLRow(GURL(data[i].url)); details.transition = 0; model()->UpdateKeywordSearchTermsForURL(details); ASSERT_EQ(string16(), GetAndClearSearchTerm()); } } TEST_F(TemplateURLModelTest, ChangeGoogleBaseValue) { // NOTE: Do not do a VerifyLoad() here as it will load the prepopulate data, // which also has a {google:baseURL} keyword in it, which will confuse this // test. ChangeModelToLoadState(); SetGoogleBaseURL("http://google.com/"); const TemplateURL* t_url = AddKeywordWithDate("", true, "{google:baseURL}?q={searchTerms}", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name", false, Time()); ASSERT_EQ(t_url, model()->GetTemplateURLForHost("google.com")); EXPECT_EQ("google.com", t_url->url()->GetHost()); EXPECT_EQ(ASCIIToUTF16("google.com"), t_url->keyword()); // Change the Google base url. test_util_.ResetObserverCount(); SetGoogleBaseURL("http://foo.com/"); VerifyObserverCount(1); // Make sure the host->TemplateURL map was updated appropriately. ASSERT_EQ(t_url, model()->GetTemplateURLForHost("foo.com")); EXPECT_TRUE(model()->GetTemplateURLForHost("google.com") == NULL); EXPECT_EQ("foo.com", t_url->url()->GetHost()); EXPECT_EQ(ASCIIToUTF16("foo.com"), t_url->keyword()); EXPECT_EQ("http://foo.com/?q=x", t_url->url()->ReplaceSearchTerms(*t_url, ASCIIToUTF16("x"), TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16())); } struct QueryHistoryCallbackImpl { QueryHistoryCallbackImpl() : success(false) {} void Callback(HistoryService::Handle handle, bool success, const history::URLRow* row, history::VisitVector* visits) { this->success = success; if (row) this->row = *row; if (visits) this->visits = *visits; } bool success; history::URLRow row; history::VisitVector visits; }; // Make sure TemplateURLModel generates a KEYWORD_GENERATED visit for // KEYWORD visits. TEST_F(TemplateURLModelTest, GenerateVisitOnKeyword) { VerifyLoad(); profile()->CreateHistoryService(true, false); // Create a keyword. TemplateURL* t_url = AddKeywordWithDate( "keyword", false, "http://foo.com/foo?query={searchTerms}", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "keyword", true, base::Time::Now()); // Add a visit that matches the url of the keyword. HistoryService* history = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); history->AddPage( GURL(t_url->url()->ReplaceSearchTerms(*t_url, ASCIIToUTF16("blah"), 0, string16())), NULL, 0, GURL(), PageTransition::KEYWORD, history::RedirectList(), history::SOURCE_BROWSED, false); // Wait for history to finish processing the request. profile()->BlockUntilHistoryProcessesPendingRequests(); // Query history for the generated url. CancelableRequestConsumer consumer; QueryHistoryCallbackImpl callback; history->QueryURL(GURL("http://keyword"), true, &consumer, NewCallback(&callback, &QueryHistoryCallbackImpl::Callback)); // Wait for the request to be processed. profile()->BlockUntilHistoryProcessesPendingRequests(); // And make sure the url and visit were added. EXPECT_TRUE(callback.success); EXPECT_NE(0, callback.row.id()); ASSERT_EQ(1U, callback.visits.size()); EXPECT_EQ(PageTransition::KEYWORD_GENERATED, PageTransition::StripQualifier(callback.visits[0].transition)); } // Make sure that the load routine deletes prepopulated engines that no longer // exist in the prepopulate data. TEST_F(TemplateURLModelTest, LoadDeletesUnusedProvider) { // Create a preloaded template url. Add it to a loaded model and wait for the // saves to finish. TemplateURL* t_url = CreatePreloadedTemplateURL(); ChangeModelToLoadState(); model()->Add(t_url); ASSERT_TRUE( model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")) != NULL); BlockTillServiceProcessesRequests(); // Ensure that merging clears this engine. ResetModel(true); ASSERT_TRUE( model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")) == NULL); // Wait for any saves to finish. BlockTillServiceProcessesRequests(); // Reload the model to verify that the database was updated as a result of the // merge. ResetModel(true); ASSERT_TRUE( model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")) == NULL); } // Make sure that load routine doesn't delete prepopulated engines that no // longer exist in the prepopulate data if it has been modified by the user. TEST_F(TemplateURLModelTest, LoadRetainsModifiedProvider) { // Create a preloaded template url and add it to a loaded model. TemplateURL* t_url = CreatePreloadedTemplateURL(); t_url->set_safe_for_autoreplace(false); ChangeModelToLoadState(); model()->Add(t_url); // Do the copy after t_url is added so that the id is set. TemplateURL copy_t_url = *t_url; ASSERT_EQ(t_url, model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest"))); // Wait for any saves to finish. BlockTillServiceProcessesRequests(); // Ensure that merging won't clear it if the user has edited it. ResetModel(true); const TemplateURL* url_for_unittest = model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")); ASSERT_TRUE(url_for_unittest != NULL); AssertEquals(copy_t_url, *url_for_unittest); // Wait for any saves to finish. BlockTillServiceProcessesRequests(); // Reload the model to verify that save/reload retains the item. ResetModel(true); ASSERT_TRUE( model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")) != NULL); } // Make sure that load routine doesn't delete // prepopulated engines that no longer exist in the prepopulate data if // it has been modified by the user. TEST_F(TemplateURLModelTest, LoadSavesPrepopulatedDefaultSearchProvider) { VerifyLoad(); // Verify that the default search provider is set to something. ASSERT_TRUE(model()->GetDefaultSearchProvider() != NULL); TemplateURL default_url = *model()->GetDefaultSearchProvider(); // Wait for any saves to finish. BlockTillServiceProcessesRequests(); // Reload the model and check that the default search provider // was properly saved. ResetModel(true); ASSERT_TRUE(model()->GetDefaultSearchProvider() != NULL); AssertEquals(default_url, *model()->GetDefaultSearchProvider()); } // Make sure that the load routine doesn't delete // prepopulated engines that no longer exist in the prepopulate data if // it is the default search provider. TEST_F(TemplateURLModelTest, LoadRetainsDefaultProvider) { // Set the default search provider to a preloaded template url which // is not in the current set of preloaded template urls and save // the result. TemplateURL* t_url = CreatePreloadedTemplateURL(); ChangeModelToLoadState(); model()->Add(t_url); model()->SetDefaultSearchProvider(t_url); // Do the copy after t_url is added and set as default so that its // internal state is correct. TemplateURL copy_t_url = *t_url; ASSERT_EQ(t_url, model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest"))); ASSERT_EQ(t_url, model()->GetDefaultSearchProvider()); BlockTillServiceProcessesRequests(); // Ensure that merging won't clear the prepopulated template url // which is no longer present if it's the default engine. ResetModel(true); { const TemplateURL* keyword_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")); ASSERT_TRUE(keyword_url != NULL); AssertEquals(copy_t_url, *keyword_url); ASSERT_EQ(keyword_url, model()->GetDefaultSearchProvider()); } // Wait for any saves to finish. BlockTillServiceProcessesRequests(); // Reload the model to verify that the update was saved. ResetModel(true); { const TemplateURL* keyword_url = model()->GetTemplateURLForKeyword(ASCIIToUTF16("unittest")); ASSERT_TRUE(keyword_url != NULL); AssertEquals(copy_t_url, *keyword_url); ASSERT_EQ(keyword_url, model()->GetDefaultSearchProvider()); } } // Make sure that the load routine updates the url of a preexisting // default search engine provider and that the result is saved correctly. TEST_F(TemplateURLModelTest, LoadUpdatesDefaultSearchURL) { TestLoadUpdatingPreloadedURL(0); } // Make sure that the load routine updates the url of a preexisting // non-default search engine provider and that the result is saved correctly. TEST_F(TemplateURLModelTest, LoadUpdatesSearchURL) { TestLoadUpdatingPreloadedURL(1); } // Make sure that the load does update of auto-keywords correctly. // This test basically verifies that no asserts or crashes occur // during this operation. TEST_F(TemplateURLModelTest, LoadDoesAutoKeywordUpdate) { string16 prepopulated_url; TemplateURL* t_url = CreateReplaceablePreloadedTemplateURL( 0, &prepopulated_url); t_url->set_safe_for_autoreplace(false); t_url->SetURL("{google:baseURL}?q={searchTerms}", 0, 0); t_url->set_autogenerate_keyword(true); // Then add it to the model and save it all. ChangeModelToLoadState(); model()->Add(t_url); BlockTillServiceProcessesRequests(); // Now reload the model and verify that the merge updates the url. ResetModel(true); // Wait for any saves to finish. BlockTillServiceProcessesRequests(); } // Simulates failing to load the webdb and makes sure the default search // provider is valid. TEST_F(TemplateURLModelTest, FailedInit) { VerifyLoad(); test_util_.ClearModel(); test_util_.GetWebDataService()->UnloadDatabase(); test_util_.GetWebDataService()->set_failed_init(true); ResetModel(false); model()->Load(); BlockTillServiceProcessesRequests(); ASSERT_TRUE(model()->GetDefaultSearchProvider()); } // Verifies that if the default search URL preference is managed, we report // the default search as managed. Also check that we are getting the right // values. TEST_F(TemplateURLModelTest, TestManagedDefaultSearch) { VerifyLoad(); const size_t initial_count = model()->GetTemplateURLs().size(); test_util_.ResetObserverCount(); // Set a regular default search provider. TemplateURL* regular_default = AddKeywordWithDate("key1", false, "http://foo1", "http://sugg1", "http://icon1", "UTF-8;UTF-16", "name1", true, Time()); VerifyObserverCount(1); model()->SetDefaultSearchProvider(regular_default); // Adding the URL and setting the default search provider should have caused // notifications. VerifyObserverCount(1); EXPECT_FALSE(model()->is_default_search_managed()); EXPECT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); // Set a managed preference that establishes a default search provider. const char kName[] = "test1"; const char kSearchURL[] = "http://test.com/search?t={searchTerms}"; const char kIconURL[] = "http://test.com/icon.jpg"; const char kEncodings[] = "UTF-16;UTF-32"; SetManagedDefaultSearchPreferences(true, kName, kSearchURL, "", kIconURL, kEncodings, ""); VerifyObserverFired(); EXPECT_TRUE(model()->is_default_search_managed()); EXPECT_EQ(2 + initial_count, model()->GetTemplateURLs().size()); // Verify that the default manager we are getting is the managed one. scoped_ptr<TemplateURL> expected_managed_default1(new TemplateURL()); expected_managed_default1->SetURL(kSearchURL, 0, 0); expected_managed_default1->SetFaviconURL(GURL(kIconURL)); expected_managed_default1->set_short_name(ASCIIToUTF16("test1")); std::vector<std::string> encodings_vector; base::SplitString(kEncodings, ';', &encodings_vector); expected_managed_default1->set_input_encodings(encodings_vector); expected_managed_default1->set_show_in_default_list(true); const TemplateURL* actual_managed_default = model()->GetDefaultSearchProvider(); ExpectSimilar(actual_managed_default, expected_managed_default1.get()); EXPECT_EQ(actual_managed_default->show_in_default_list(), true); // Update the managed preference and check that the model has changed. const char kNewName[] = "test2"; const char kNewSearchURL[] = "http://other.com/search?t={searchTerms}"; const char kNewSuggestURL[] = "http://other.com/suggest?t={searchTerms}"; SetManagedDefaultSearchPreferences(true, kNewName, kNewSearchURL, kNewSuggestURL, "", "", ""); VerifyObserverFired(); EXPECT_TRUE(model()->is_default_search_managed()); EXPECT_EQ(2 + initial_count, model()->GetTemplateURLs().size()); // Verify that the default manager we are now getting is the correct one. scoped_ptr<TemplateURL> expected_managed_default2(new TemplateURL()); expected_managed_default2->SetURL(kNewSearchURL, 0, 0); expected_managed_default2->SetSuggestionsURL(kNewSuggestURL, 0, 0); expected_managed_default2->set_short_name(ASCIIToUTF16("test2")); expected_managed_default2->set_show_in_default_list(true); actual_managed_default = model()->GetDefaultSearchProvider(); ExpectSimilar(actual_managed_default, expected_managed_default2.get()); EXPECT_EQ(actual_managed_default->show_in_default_list(), true); // Remove all the managed prefs and check that we are no longer managed. RemoveManagedDefaultSearchPreferences(); VerifyObserverFired(); EXPECT_FALSE(model()->is_default_search_managed()); EXPECT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); // The default should now be the first URL added const TemplateURL* actual_final_managed_default = model()->GetDefaultSearchProvider(); ExpectSimilar(actual_final_managed_default, model()->GetTemplateURLs()[0]); EXPECT_EQ(actual_final_managed_default->show_in_default_list(), true); // Disable the default search provider through policy. SetManagedDefaultSearchPreferences(false, "", "", "", "", "", ""); VerifyObserverFired(); EXPECT_TRUE(model()->is_default_search_managed()); EXPECT_TRUE(NULL == model()->GetDefaultSearchProvider()); EXPECT_EQ(1 + initial_count, model()->GetTemplateURLs().size()); // Re-enable it. SetManagedDefaultSearchPreferences(true, kName, kSearchURL, "", kIconURL, kEncodings, ""); VerifyObserverFired(); EXPECT_TRUE(model()->is_default_search_managed()); EXPECT_EQ(2 + initial_count, model()->GetTemplateURLs().size()); // Verify that the default manager we are getting is the managed one. actual_managed_default = model()->GetDefaultSearchProvider(); ExpectSimilar(actual_managed_default, expected_managed_default1.get()); EXPECT_EQ(actual_managed_default->show_in_default_list(), true); }