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