1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/omnibox/browser/shortcuts_backend.h"
6 
7 #include <stddef.h>
8 
9 #include <memory>
10 
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/run_loop.h"
13 #include "base/stl_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/test/task_environment.h"
17 #include "components/history/core/browser/history_service.h"
18 #include "components/history/core/test/history_service_test_util.h"
19 #include "components/omnibox/browser/shortcuts_constants.h"
20 #include "components/omnibox/browser/shortcuts_database.h"
21 #include "components/search_engines/search_terms_data.h"
22 #include "components/search_engines/template_url_service.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 
25 // ShortcutsBackendTest -------------------------------------------------------
26 
27 class ShortcutsBackendTest : public testing::Test,
28                              public ShortcutsBackend::ShortcutsBackendObserver {
29  public:
30   ShortcutsBackendTest();
31   ShortcutsBackendTest(const ShortcutsBackendTest&) = delete;
32   ShortcutsBackendTest& operator=(const ShortcutsBackendTest&) = delete;
33 
34   ShortcutsDatabase::Shortcut::MatchCore MatchCoreForTesting(
35       const std::string& url,
36       const std::string& contents_class = std::string(),
37       const std::string& description_class = std::string(),
38       AutocompleteMatch::Type type = AutocompleteMatchType::URL_WHAT_YOU_TYPED);
39   void SetSearchProvider();
40 
41   void SetUp() override;
42   void TearDown() override;
43 
44   void OnShortcutsLoaded() override;
45   void OnShortcutsChanged() override;
46 
shortcuts_map() const47   const ShortcutsBackend::ShortcutMap& shortcuts_map() const {
48     return backend_->shortcuts_map();
49   }
changed_notified() const50   bool changed_notified() const { return changed_notified_; }
set_changed_notified(bool changed_notified)51   void set_changed_notified(bool changed_notified) {
52     changed_notified_ = changed_notified;
53   }
54 
55   void InitBackend();
56   bool AddShortcut(const ShortcutsDatabase::Shortcut& shortcut);
57   bool UpdateShortcut(const ShortcutsDatabase::Shortcut& shortcut);
58   bool DeleteShortcutsWithURL(const GURL& url);
59   bool DeleteShortcutsWithIDs(
60       const ShortcutsDatabase::ShortcutIDs& deleted_ids);
61 
62   TemplateURLService* GetTemplateURLService();
63 
64  private:
65   base::ScopedTempDir profile_dir_;
66   base::test::TaskEnvironment task_environment_;
67   std::unique_ptr<TemplateURLService> template_url_service_;
68   std::unique_ptr<history::HistoryService> history_service_;
69 
70   scoped_refptr<ShortcutsBackend> backend_;
71 
72   bool load_notified_;
73   bool changed_notified_;
74 };
75 
ShortcutsBackendTest()76 ShortcutsBackendTest::ShortcutsBackendTest()
77     : load_notified_(false), changed_notified_(false) {}
78 
79 ShortcutsDatabase::Shortcut::MatchCore
MatchCoreForTesting(const std::string & url,const std::string & contents_class,const std::string & description_class,AutocompleteMatch::Type type)80 ShortcutsBackendTest::MatchCoreForTesting(const std::string& url,
81                                           const std::string& contents_class,
82                                           const std::string& description_class,
83                                           AutocompleteMatch::Type type) {
84   AutocompleteMatch match(nullptr, 0, 0, type);
85   match.destination_url = GURL(url);
86   match.contents = base::ASCIIToUTF16("test");
87   match.contents_class =
88       AutocompleteMatch::ClassificationsFromString(contents_class);
89   match.description_class =
90       AutocompleteMatch::ClassificationsFromString(description_class);
91   match.search_terms_args.reset(
92       new TemplateURLRef::SearchTermsArgs(match.contents));
93   SearchTermsData search_terms_data;
94   return ShortcutsBackend::MatchToMatchCore(match, template_url_service_.get(),
95                                             &search_terms_data);
96 }
97 
SetSearchProvider()98 void ShortcutsBackendTest::SetSearchProvider() {
99   TemplateURLData data;
100   data.SetURL("http://foo.com/search?bar={searchTerms}");
101   data.SetShortName(base::UTF8ToUTF16("foo"));
102   data.SetKeyword(base::UTF8ToUTF16("foo"));
103 
104   TemplateURL* template_url =
105       template_url_service_->Add(std::make_unique<TemplateURL>(data));
106   template_url_service_->SetUserSelectedDefaultSearchProvider(template_url);
107 }
108 
SetUp()109 void ShortcutsBackendTest::SetUp() {
110   ASSERT_TRUE(profile_dir_.CreateUniqueTempDir());
111   template_url_service_.reset(new TemplateURLService(nullptr, 0));
112   history_service_ =
113       history::CreateHistoryService(profile_dir_.GetPath(), true);
114   ASSERT_TRUE(history_service_);
115 
116   base::FilePath shortcuts_database_path =
117       profile_dir_.GetPath().Append(kShortcutsDatabaseName);
118   backend_ = new ShortcutsBackend(
119       template_url_service_.get(), std::make_unique<SearchTermsData>(),
120       history_service_.get(), shortcuts_database_path, false);
121   ASSERT_TRUE(backend_.get());
122   backend_->AddObserver(this);
123 }
124 
TearDown()125 void ShortcutsBackendTest::TearDown() {
126   backend_->RemoveObserver(this);
127   backend_->ShutdownOnUIThread();
128   backend_.reset();
129 
130   // Explicitly shut down the history service and wait for its backend to be
131   // destroyed to prevent resource leaks.
132   base::RunLoop run_loop;
133   history_service_->SetOnBackendDestroyTask(run_loop.QuitClosure());
134   history_service_->Shutdown();
135   run_loop.Run();
136 
137   task_environment_.RunUntilIdle();
138   EXPECT_TRUE(profile_dir_.Delete());
139 }
140 
OnShortcutsLoaded()141 void ShortcutsBackendTest::OnShortcutsLoaded() {
142   load_notified_ = true;
143 }
144 
OnShortcutsChanged()145 void ShortcutsBackendTest::OnShortcutsChanged() {
146   changed_notified_ = true;
147 }
148 
InitBackend()149 void ShortcutsBackendTest::InitBackend() {
150   ASSERT_TRUE(backend_);
151   ASSERT_FALSE(load_notified_);
152   ASSERT_FALSE(backend_->initialized());
153   backend_->Init();
154   task_environment_.RunUntilIdle();
155   EXPECT_TRUE(load_notified_);
156   EXPECT_TRUE(backend_->initialized());
157 }
158 
AddShortcut(const ShortcutsDatabase::Shortcut & shortcut)159 bool ShortcutsBackendTest::AddShortcut(
160     const ShortcutsDatabase::Shortcut& shortcut) {
161   return backend_->AddShortcut(shortcut);
162 }
163 
UpdateShortcut(const ShortcutsDatabase::Shortcut & shortcut)164 bool ShortcutsBackendTest::UpdateShortcut(
165     const ShortcutsDatabase::Shortcut& shortcut) {
166   return backend_->UpdateShortcut(shortcut);
167 }
168 
DeleteShortcutsWithURL(const GURL & url)169 bool ShortcutsBackendTest::DeleteShortcutsWithURL(const GURL& url) {
170   return backend_->DeleteShortcutsWithURL(url);
171 }
172 
DeleteShortcutsWithIDs(const ShortcutsDatabase::ShortcutIDs & deleted_ids)173 bool ShortcutsBackendTest::DeleteShortcutsWithIDs(
174     const ShortcutsDatabase::ShortcutIDs& deleted_ids) {
175   return backend_->DeleteShortcutsWithIDs(deleted_ids);
176 }
177 
GetTemplateURLService()178 TemplateURLService* ShortcutsBackendTest::GetTemplateURLService() {
179   return template_url_service_.get();
180 }
181 
182 // Actual tests ---------------------------------------------------------------
183 
184 // Verifies that creating MatchCores strips classifications and sanitizes match
185 // types.
TEST_F(ShortcutsBackendTest,SanitizeMatchCore)186 TEST_F(ShortcutsBackendTest, SanitizeMatchCore) {
187   struct {
188     std::string input_contents_class;
189     std::string input_description_class;
190     AutocompleteMatch::Type input_type;
191     std::string output_contents_class;
192     std::string output_description_class;
193     AutocompleteMatch::Type output_type;
194   } cases[] = {
195     { "0,1,4,0", "0,3,4,1",  AutocompleteMatchType::URL_WHAT_YOU_TYPED,
196       "0,1,4,0", "0,1",      AutocompleteMatchType::HISTORY_URL },
197     { "0,3,5,1", "0,2,5,0",  AutocompleteMatchType::NAVSUGGEST,
198       "0,1",     "0,0",      AutocompleteMatchType::HISTORY_URL },
199     { "0,1",     "0,0,11,2,15,0",
200                              AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
201       "0,1",     "0,0",      AutocompleteMatchType::SEARCH_HISTORY },
202     { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST,
203       "0,1",     "0,0",      AutocompleteMatchType::SEARCH_HISTORY },
204     { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
205       "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
206     { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_TAIL,
207       "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
208     { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED,
209       "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
210     { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_PROFILE,
211       "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
212   };
213 
214   for (size_t i = 0; i < base::size(cases); ++i) {
215     ShortcutsDatabase::Shortcut::MatchCore match_core(MatchCoreForTesting(
216         std::string(), cases[i].input_contents_class,
217         cases[i].input_description_class, cases[i].input_type));
218     EXPECT_EQ(cases[i].output_contents_class, match_core.contents_class)
219         << ":i:" << i << ":type:" << cases[i].input_type;
220     EXPECT_EQ(cases[i].output_description_class, match_core.description_class)
221         << ":i:" << i << ":type:" << cases[i].input_type;
222     EXPECT_EQ(cases[i].output_type, match_core.type)
223         << ":i:" << i << ":type:" << cases[i].input_type;
224   }
225 }
226 
TEST_F(ShortcutsBackendTest,EntitySuggestionTest)227 TEST_F(ShortcutsBackendTest, EntitySuggestionTest) {
228   SetSearchProvider();
229   AutocompleteMatch match;
230   match.fill_into_edit = base::UTF8ToUTF16("franklin d roosevelt");
231   match.type = AutocompleteMatchType::SEARCH_SUGGEST_ENTITY;
232   match.contents = base::UTF8ToUTF16("roosevelt");
233   match.contents_class =
234       AutocompleteMatch::ClassificationsFromString("0,0,5,2");
235   match.description = base::UTF8ToUTF16("Franklin D. Roosevelt");
236   match.description_class = AutocompleteMatch::ClassificationsFromString("0,4");
237   match.destination_url =
238       GURL("http://www.foo.com/search?bar=franklin+d+roosevelt&gs_ssp=1234");
239   match.keyword = base::UTF8ToUTF16("foo");
240   match.search_terms_args.reset(
241       new TemplateURLRef::SearchTermsArgs(match.fill_into_edit));
242 
243   SearchTermsData search_terms_data;
244   ShortcutsDatabase::Shortcut::MatchCore match_core =
245       ShortcutsBackend::MatchToMatchCore(match, GetTemplateURLService(),
246                                          &search_terms_data);
247   EXPECT_EQ("http://foo.com/search?bar=franklin+d+roosevelt",
248             match_core.destination_url.spec());
249   EXPECT_EQ(match.fill_into_edit, match_core.contents);
250   EXPECT_EQ("0,0", match_core.contents_class);
251   EXPECT_EQ(base::string16(), match_core.description);
252   EXPECT_TRUE(match_core.description_class.empty());
253 }
254 
TEST_F(ShortcutsBackendTest,MatchCoreDescriptionTest)255 TEST_F(ShortcutsBackendTest, MatchCoreDescriptionTest) {
256   // When match.description_for_shortcuts is empty, match_core should use
257   // match.description.
258   {
259     AutocompleteMatch match;
260     match.description = base::UTF8ToUTF16("the cat");
261     match.description_class =
262         AutocompleteMatch::ClassificationsFromString("0,1");
263 
264     SearchTermsData search_terms_data;
265     ShortcutsDatabase::Shortcut::MatchCore match_core =
266         ShortcutsBackend::MatchToMatchCore(match, GetTemplateURLService(),
267                                            &search_terms_data);
268     EXPECT_EQ(match_core.description, match.description);
269     EXPECT_EQ(
270         match_core.description_class,
271         AutocompleteMatch::ClassificationsToString(match.description_class));
272   }
273 
274   // When match.description_for_shortcuts is set, match_core should use it
275   // instead of match.description.
276   {
277     AutocompleteMatch match;
278     match.description = base::UTF8ToUTF16("the cat");
279     match.description_class =
280         AutocompleteMatch::ClassificationsFromString("0,1");
281     match.description_for_shortcuts = base::UTF8ToUTF16("the elephant");
282     match.description_class_for_shortcuts =
283         AutocompleteMatch::ClassificationsFromString("0,4");
284 
285     SearchTermsData search_terms_data;
286     ShortcutsDatabase::Shortcut::MatchCore match_core =
287         ShortcutsBackend::MatchToMatchCore(match, GetTemplateURLService(),
288                                            &search_terms_data);
289     EXPECT_EQ(match_core.description, match.description_for_shortcuts);
290     EXPECT_EQ(match_core.description_class,
291               AutocompleteMatch::ClassificationsToString(
292                   match.description_class_for_shortcuts));
293   }
294 }
295 
TEST_F(ShortcutsBackendTest,AddAndUpdateShortcut)296 TEST_F(ShortcutsBackendTest, AddAndUpdateShortcut) {
297   InitBackend();
298   EXPECT_FALSE(changed_notified());
299 
300   ShortcutsDatabase::Shortcut shortcut(
301       "BD85DBA2-8C29-49F9-84AE-48E1E90880DF", base::ASCIIToUTF16("goog"),
302       MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
303   EXPECT_TRUE(AddShortcut(shortcut));
304   EXPECT_TRUE(changed_notified());
305   auto shortcut_iter(shortcuts_map().find(shortcut.text));
306   ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
307   EXPECT_EQ(shortcut.id, shortcut_iter->second.id);
308   EXPECT_EQ(shortcut.match_core.contents,
309             shortcut_iter->second.match_core.contents);
310 
311   set_changed_notified(false);
312   shortcut.match_core.contents = base::ASCIIToUTF16("Google Web Search");
313   EXPECT_TRUE(UpdateShortcut(shortcut));
314   EXPECT_TRUE(changed_notified());
315   shortcut_iter = shortcuts_map().find(shortcut.text);
316   ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
317   EXPECT_EQ(shortcut.id, shortcut_iter->second.id);
318   EXPECT_EQ(shortcut.match_core.contents,
319             shortcut_iter->second.match_core.contents);
320 }
321 
TEST_F(ShortcutsBackendTest,DeleteShortcuts)322 TEST_F(ShortcutsBackendTest, DeleteShortcuts) {
323   InitBackend();
324   ShortcutsDatabase::Shortcut shortcut1(
325       "BD85DBA2-8C29-49F9-84AE-48E1E90880DF", base::ASCIIToUTF16("goog"),
326       MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
327   EXPECT_TRUE(AddShortcut(shortcut1));
328 
329   ShortcutsDatabase::Shortcut shortcut2(
330       "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", base::ASCIIToUTF16("gle"),
331       MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
332   EXPECT_TRUE(AddShortcut(shortcut2));
333 
334   ShortcutsDatabase::Shortcut shortcut3(
335       "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", base::ASCIIToUTF16("sp"),
336       MatchCoreForTesting("http://www.sport.com"), base::Time::Now(), 10);
337   EXPECT_TRUE(AddShortcut(shortcut3));
338 
339   ShortcutsDatabase::Shortcut shortcut4(
340       "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", base::ASCIIToUTF16("mov"),
341       MatchCoreForTesting("http://www.film.com"), base::Time::Now(), 10);
342   EXPECT_TRUE(AddShortcut(shortcut4));
343 
344   ASSERT_EQ(4U, shortcuts_map().size());
345   EXPECT_EQ(shortcut1.id, shortcuts_map().find(shortcut1.text)->second.id);
346   EXPECT_EQ(shortcut2.id, shortcuts_map().find(shortcut2.text)->second.id);
347   EXPECT_EQ(shortcut3.id, shortcuts_map().find(shortcut3.text)->second.id);
348   EXPECT_EQ(shortcut4.id, shortcuts_map().find(shortcut4.text)->second.id);
349 
350   EXPECT_TRUE(DeleteShortcutsWithURL(shortcut1.match_core.destination_url));
351 
352   ASSERT_EQ(2U, shortcuts_map().size());
353   EXPECT_EQ(0U, shortcuts_map().count(shortcut1.text));
354   EXPECT_EQ(0U, shortcuts_map().count(shortcut2.text));
355   const ShortcutsBackend::ShortcutMap::const_iterator shortcut3_iter(
356       shortcuts_map().find(shortcut3.text));
357   ASSERT_TRUE(shortcut3_iter != shortcuts_map().end());
358   EXPECT_EQ(shortcut3.id, shortcut3_iter->second.id);
359   const ShortcutsBackend::ShortcutMap::const_iterator shortcut4_iter(
360       shortcuts_map().find(shortcut4.text));
361   ASSERT_TRUE(shortcut4_iter != shortcuts_map().end());
362   EXPECT_EQ(shortcut4.id, shortcut4_iter->second.id);
363 
364   ShortcutsDatabase::ShortcutIDs deleted_ids;
365   deleted_ids.push_back(shortcut3.id);
366   deleted_ids.push_back(shortcut4.id);
367   EXPECT_TRUE(DeleteShortcutsWithIDs(deleted_ids));
368 
369   ASSERT_EQ(0U, shortcuts_map().size());
370 }
371