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 "chrome/browser/autocomplete/shortcuts_backend.h"
6
7#include "base/files/scoped_temp_dir.h"
8#include "base/message_loop/message_loop.h"
9#include "base/path_service.h"
10#include "base/strings/stringprintf.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
13#include "chrome/browser/history/shortcuts_database.h"
14#include "chrome/browser/search_engines/template_url_service_factory.h"
15#include "chrome/test/base/testing_profile.h"
16#include "chrome/test/base/ui_test_utils.h"
17#include "components/search_engines/template_url_service.h"
18#include "content/public/test/test_browser_thread.h"
19#include "sql/statement.h"
20
21#include "testing/gtest/include/gtest/gtest.h"
22
23
24// ShortcutsBackendTest -------------------------------------------------------
25
26class ShortcutsBackendTest : public testing::Test,
27                             public ShortcutsBackend::ShortcutsBackendObserver {
28 public:
29  ShortcutsBackendTest();
30
31  history::ShortcutsDatabase::Shortcut::MatchCore MatchCoreForTesting(
32    const std::string& url,
33    const std::string& contents_class = std::string(),
34    const std::string& description_class = std::string(),
35    AutocompleteMatch::Type type = AutocompleteMatchType::URL_WHAT_YOU_TYPED);
36  void SetSearchProvider();
37
38  virtual void SetUp();
39  virtual void TearDown();
40
41  virtual void OnShortcutsLoaded() OVERRIDE;
42  virtual void OnShortcutsChanged() OVERRIDE;
43
44  const ShortcutsBackend::ShortcutMap& shortcuts_map() const {
45    return backend_->shortcuts_map();
46  }
47  bool changed_notified() const { return changed_notified_; }
48  void set_changed_notified(bool changed_notified) {
49      changed_notified_ = changed_notified;
50  }
51
52  void InitBackend();
53  bool AddShortcut(const history::ShortcutsDatabase::Shortcut& shortcut);
54  bool UpdateShortcut(const history::ShortcutsDatabase::Shortcut& shortcut);
55  bool DeleteShortcutsWithURL(const GURL& url);
56  bool DeleteShortcutsWithIDs(
57      const history::ShortcutsDatabase::ShortcutIDs& deleted_ids);
58
59 protected:
60  TestingProfile profile_;
61
62 private:
63  scoped_refptr<ShortcutsBackend> backend_;
64  base::MessageLoopForUI ui_message_loop_;
65  content::TestBrowserThread ui_thread_;
66  content::TestBrowserThread db_thread_;
67
68  bool load_notified_;
69  bool changed_notified_;
70
71  DISALLOW_COPY_AND_ASSIGN(ShortcutsBackendTest);
72};
73
74ShortcutsBackendTest::ShortcutsBackendTest()
75    : ui_thread_(content::BrowserThread::UI, &ui_message_loop_),
76      db_thread_(content::BrowserThread::DB),
77      load_notified_(false),
78      changed_notified_(false) {
79}
80
81history::ShortcutsDatabase::Shortcut::MatchCore
82    ShortcutsBackendTest::MatchCoreForTesting(
83    const std::string& url,
84    const std::string& contents_class,
85    const std::string& description_class,
86    AutocompleteMatch::Type type) {
87  AutocompleteMatch match(NULL, 0, 0, type);
88  match.destination_url = GURL(url);
89  match.contents = base::ASCIIToUTF16("test");
90  match.contents_class =
91      AutocompleteMatch::ClassificationsFromString(contents_class);
92  match.description_class =
93      AutocompleteMatch::ClassificationsFromString(description_class);
94  match.search_terms_args.reset(
95      new TemplateURLRef::SearchTermsArgs(match.contents));
96  return ShortcutsBackend::MatchToMatchCore(match, &profile_);
97}
98
99void ShortcutsBackendTest::SetSearchProvider() {
100  TemplateURLService* template_url_service =
101      TemplateURLServiceFactory::GetForProfile(&profile_);
102  TemplateURLData data;
103  data.SetURL("http://foo.com/search?bar={searchTerms}");
104  data.SetKeyword(base::UTF8ToUTF16("foo"));
105
106  TemplateURL* template_url = new TemplateURL(data);
107  // Takes ownership of |template_url|.
108  template_url_service->Add(template_url);
109  template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
110}
111
112void ShortcutsBackendTest::SetUp() {
113  db_thread_.Start();
114  ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
115      &profile_, &ShortcutsBackendFactory::BuildProfileForTesting);
116  backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
117  ASSERT_TRUE(backend_.get());
118  backend_->AddObserver(this);
119
120  TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
121      &profile_, &TemplateURLServiceFactory::BuildInstanceFor);
122  TemplateURLService* template_url_service =
123      TemplateURLServiceFactory::GetForProfile(&profile_);
124  ui_test_utils::WaitForTemplateURLServiceToLoad(template_url_service);
125}
126
127void ShortcutsBackendTest::TearDown() {
128  backend_->RemoveObserver(this);
129  db_thread_.Stop();
130}
131
132void ShortcutsBackendTest::OnShortcutsLoaded() {
133  load_notified_ = true;
134  base::MessageLoop::current()->Quit();
135}
136
137void ShortcutsBackendTest::OnShortcutsChanged() {
138  changed_notified_ = true;
139}
140
141void ShortcutsBackendTest::InitBackend() {
142  ShortcutsBackend* backend =
143      ShortcutsBackendFactory::GetForProfile(&profile_).get();
144  ASSERT_TRUE(backend);
145  ASSERT_FALSE(load_notified_);
146  ASSERT_FALSE(backend_->initialized());
147  base::MessageLoop::current()->Run();
148  EXPECT_TRUE(load_notified_);
149  EXPECT_TRUE(backend_->initialized());
150}
151
152bool ShortcutsBackendTest::AddShortcut(
153    const history::ShortcutsDatabase::Shortcut& shortcut) {
154  return backend_->AddShortcut(shortcut);
155}
156
157bool ShortcutsBackendTest::UpdateShortcut(
158    const history::ShortcutsDatabase::Shortcut& shortcut) {
159  return backend_->UpdateShortcut(shortcut);
160}
161
162bool ShortcutsBackendTest::DeleteShortcutsWithURL(const GURL& url) {
163  return backend_->DeleteShortcutsWithURL(url);
164}
165
166bool ShortcutsBackendTest::DeleteShortcutsWithIDs(
167    const history::ShortcutsDatabase::ShortcutIDs& deleted_ids) {
168  return backend_->DeleteShortcutsWithIDs(deleted_ids);
169}
170
171
172// Actual tests ---------------------------------------------------------------
173
174// Verifies that creating MatchCores strips classifications and sanitizes match
175// types.
176TEST_F(ShortcutsBackendTest, SanitizeMatchCore) {
177  struct {
178    std::string input_contents_class;
179    std::string input_description_class;
180    AutocompleteMatch::Type input_type;
181    std::string output_contents_class;
182    std::string output_description_class;
183    AutocompleteMatch::Type output_type;
184  } cases[] = {
185    { "0,1,4,0", "0,3,4,1",  AutocompleteMatchType::URL_WHAT_YOU_TYPED,
186      "0,1,4,0", "0,1",      AutocompleteMatchType::HISTORY_URL },
187    { "0,3,5,1", "0,2,5,0",  AutocompleteMatchType::NAVSUGGEST,
188      "0,1",     "0,0",      AutocompleteMatchType::HISTORY_URL },
189    { "0,1",     "0,0,11,2,15,0",
190                             AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
191      "0,1",     "0,0",      AutocompleteMatchType::SEARCH_HISTORY },
192    { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST,
193      "0,1",     "0,0",      AutocompleteMatchType::SEARCH_HISTORY },
194    { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
195      "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
196    { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_INFINITE,
197      "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
198    { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED,
199      "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
200    { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_PROFILE,
201      "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
202    { "0,1",     "0,0",      AutocompleteMatchType::SEARCH_SUGGEST_ANSWER,
203      "",        "",         AutocompleteMatchType::SEARCH_HISTORY },
204  };
205
206  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
207    history::ShortcutsDatabase::Shortcut::MatchCore match_core(
208        MatchCoreForTesting(std::string(), cases[i].input_contents_class,
209                            cases[i].input_description_class,
210                            cases[i].input_type));
211    EXPECT_EQ(cases[i].output_contents_class, match_core.contents_class)
212        << ":i:" << i << ":type:" << cases[i].input_type;
213    EXPECT_EQ(cases[i].output_description_class, match_core.description_class)
214        << ":i:" << i << ":type:" << cases[i].input_type;
215    EXPECT_EQ(cases[i].output_type, match_core.type)
216        << ":i:" << i << ":type:" << cases[i].input_type;
217  }
218}
219
220TEST_F(ShortcutsBackendTest, EntitySuggestionTest) {
221  SetSearchProvider();
222  AutocompleteMatch match;
223  match.fill_into_edit = base::UTF8ToUTF16("franklin d roosevelt");
224  match.type = AutocompleteMatchType::SEARCH_SUGGEST_ENTITY;
225  match.contents = base::UTF8ToUTF16("roosevelt");
226  match.contents_class =
227      AutocompleteMatch::ClassificationsFromString("0,0,5,2");
228  match.description = base::UTF8ToUTF16("Franklin D. Roosevelt");
229  match.description_class = AutocompleteMatch::ClassificationsFromString("0,4");
230  match.destination_url = GURL(
231      "http://www.foo.com/search?bar=franklin+d+roosevelt&gs_ssp=1234");
232  match.keyword = base::UTF8ToUTF16("foo");
233  match.search_terms_args.reset(
234      new TemplateURLRef::SearchTermsArgs(match.fill_into_edit));
235
236  history::ShortcutsDatabase::Shortcut::MatchCore match_core =
237      ShortcutsBackend::MatchToMatchCore(match, &profile_);
238
239  EXPECT_EQ("http://foo.com/search?bar=franklin+d+roosevelt",
240            match_core.destination_url.spec());
241  EXPECT_EQ(match.fill_into_edit, match_core.contents);
242  EXPECT_EQ("0,0", match_core.contents_class);
243  EXPECT_EQ(base::string16(), match_core.description);
244  EXPECT_TRUE(match_core.description_class.empty());
245}
246
247TEST_F(ShortcutsBackendTest, AddAndUpdateShortcut) {
248  InitBackend();
249  EXPECT_FALSE(changed_notified());
250
251  history::ShortcutsDatabase::Shortcut shortcut(
252      "BD85DBA2-8C29-49F9-84AE-48E1E90880DF", base::ASCIIToUTF16("goog"),
253      MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
254  EXPECT_TRUE(AddShortcut(shortcut));
255  EXPECT_TRUE(changed_notified());
256  ShortcutsBackend::ShortcutMap::const_iterator shortcut_iter(
257      shortcuts_map().find(shortcut.text));
258  ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
259  EXPECT_EQ(shortcut.id, shortcut_iter->second.id);
260  EXPECT_EQ(shortcut.match_core.contents,
261            shortcut_iter->second.match_core.contents);
262
263  set_changed_notified(false);
264  shortcut.match_core.contents = base::ASCIIToUTF16("Google Web Search");
265  EXPECT_TRUE(UpdateShortcut(shortcut));
266  EXPECT_TRUE(changed_notified());
267  shortcut_iter = shortcuts_map().find(shortcut.text);
268  ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
269  EXPECT_EQ(shortcut.id, shortcut_iter->second.id);
270  EXPECT_EQ(shortcut.match_core.contents,
271            shortcut_iter->second.match_core.contents);
272}
273
274TEST_F(ShortcutsBackendTest, DeleteShortcuts) {
275  InitBackend();
276  history::ShortcutsDatabase::Shortcut shortcut1(
277      "BD85DBA2-8C29-49F9-84AE-48E1E90880DF", base::ASCIIToUTF16("goog"),
278      MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
279  EXPECT_TRUE(AddShortcut(shortcut1));
280
281  history::ShortcutsDatabase::Shortcut shortcut2(
282      "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", base::ASCIIToUTF16("gle"),
283      MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
284  EXPECT_TRUE(AddShortcut(shortcut2));
285
286  history::ShortcutsDatabase::Shortcut shortcut3(
287      "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", base::ASCIIToUTF16("sp"),
288      MatchCoreForTesting("http://www.sport.com"), base::Time::Now(), 10);
289  EXPECT_TRUE(AddShortcut(shortcut3));
290
291  history::ShortcutsDatabase::Shortcut shortcut4(
292      "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", base::ASCIIToUTF16("mov"),
293      MatchCoreForTesting("http://www.film.com"), base::Time::Now(), 10);
294  EXPECT_TRUE(AddShortcut(shortcut4));
295
296  ASSERT_EQ(4U, shortcuts_map().size());
297  EXPECT_EQ(shortcut1.id, shortcuts_map().find(shortcut1.text)->second.id);
298  EXPECT_EQ(shortcut2.id, shortcuts_map().find(shortcut2.text)->second.id);
299  EXPECT_EQ(shortcut3.id, shortcuts_map().find(shortcut3.text)->second.id);
300  EXPECT_EQ(shortcut4.id, shortcuts_map().find(shortcut4.text)->second.id);
301
302  EXPECT_TRUE(DeleteShortcutsWithURL(shortcut1.match_core.destination_url));
303
304  ASSERT_EQ(2U, shortcuts_map().size());
305  EXPECT_EQ(0U, shortcuts_map().count(shortcut1.text));
306  EXPECT_EQ(0U, shortcuts_map().count(shortcut2.text));
307  const ShortcutsBackend::ShortcutMap::const_iterator shortcut3_iter(
308      shortcuts_map().find(shortcut3.text));
309  ASSERT_TRUE(shortcut3_iter != shortcuts_map().end());
310  EXPECT_EQ(shortcut3.id, shortcut3_iter->second.id);
311  const ShortcutsBackend::ShortcutMap::const_iterator shortcut4_iter(
312      shortcuts_map().find(shortcut4.text));
313  ASSERT_TRUE(shortcut4_iter != shortcuts_map().end());
314  EXPECT_EQ(shortcut4.id, shortcut4_iter->second.id);
315
316  history::ShortcutsDatabase::ShortcutIDs deleted_ids;
317  deleted_ids.push_back(shortcut3.id);
318  deleted_ids.push_back(shortcut4.id);
319  EXPECT_TRUE(DeleteShortcutsWithIDs(deleted_ids));
320
321  ASSERT_EQ(0U, shortcuts_map().size());
322}
323