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_provider.h"
6
7#include <math.h>
8
9#include <algorithm>
10#include <functional>
11#include <set>
12#include <string>
13#include <vector>
14
15#include "base/memory/ref_counted.h"
16#include "base/message_loop/message_loop.h"
17#include "base/prefs/pref_service.h"
18#include "base/strings/stringprintf.h"
19#include "base/strings/utf_string_conversions.h"
20#include "chrome/browser/autocomplete/autocomplete_input.h"
21#include "chrome/browser/autocomplete/autocomplete_match.h"
22#include "chrome/browser/autocomplete/autocomplete_provider.h"
23#include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
24#include "chrome/browser/autocomplete/autocomplete_result.h"
25#include "chrome/browser/chrome_notification_types.h"
26#include "chrome/browser/history/history_service.h"
27#include "chrome/browser/history/in_memory_url_index.h"
28#include "chrome/browser/history/shortcuts_backend.h"
29#include "chrome/browser/history/shortcuts_backend_factory.h"
30#include "chrome/browser/history/url_database.h"
31#include "chrome/common/pref_names.h"
32#include "chrome/test/base/testing_profile.h"
33#include "content/public/browser/notification_service.h"
34#include "content/public/test/test_browser_thread.h"
35#include "extensions/common/extension.h"
36#include "extensions/common/extension_builder.h"
37#include "extensions/common/value_builder.h"
38#include "testing/gtest/include/gtest/gtest.h"
39
40
41// TestShortcutInfo -----------------------------------------------------------
42
43namespace {
44
45struct TestShortcutInfo {
46  std::string guid;
47  std::string text;
48  std::string fill_into_edit;
49  std::string destination_url;
50  std::string contents;
51  std::string contents_class;
52  std::string description;
53  std::string description_class;
54  content::PageTransition transition;
55  AutocompleteMatch::Type type;
56  std::string keyword;
57  int days_from_now;
58  int number_of_hits;
59} shortcut_test_db[] = {
60  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", "goog", "www.google.com",
61    "http://www.google.com/", "Google", "0,1,4,0", "Google", "0,3,4,1",
62    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
63    100 },
64  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", "slash", "slashdot.org",
65    "http://slashdot.org/", "slashdot.org", "0,3,5,1",
66    "Slashdot - News for nerds, stuff that matters", "0,2,5,0",
67    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 0,
68    100 },
69  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", "news", "slashdot.org",
70    "http://slashdot.org/", "slashdot.org", "0,1",
71    "Slashdot - News for nerds, stuff that matters", "0,0,11,2,15,0",
72    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 0,
73    5 },
74  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", "news", "sports.yahoo.com",
75    "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
76    "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
77    "0,0,23,2,27,0", content::PAGE_TRANSITION_TYPED,
78    AutocompleteMatchType::HISTORY_TITLE, "", 2, 5 },
79  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E4", "news weather",
80    "www.cnn.com/index.html", "http://www.cnn.com/index.html",
81    "www.cnn.com/index.html", "0,1",
82    "CNN.com - Breaking News, U.S., World, Weather, Entertainment & Video",
83    "0,0,19,2,23,0,38,2,45,0", content::PAGE_TRANSITION_TYPED,
84    AutocompleteMatchType::HISTORY_TITLE, "", 1, 10 },
85  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E5", "nhl scores", "sports.yahoo.com",
86    "http://sports.yahoo.com/", "sports.yahoo.com", "0,1",
87    "Yahoo! Sports - Sports News, Scores, Rumors, Fantasy Games, and more",
88    "0,0,29,2,35,0", content::PAGE_TRANSITION_TYPED,
89    AutocompleteMatchType::HISTORY_BODY, "", 1, 10 },
90  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E6", "nhl scores",
91    "www.nhl.com/scores/index.html", "http://www.nhl.com/scores/index.html",
92    "www.nhl.com/scores/index.html", "0,1,4,3,7,1",
93    "January 13, 2010 - NHL.com - Scores", "0,0,19,2,22,0,29,2,35,0",
94    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 5,
95    1 },
96  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E7", "just", "www.testsite.com/a.html",
97    "http://www.testsite.com/a.html", "www.testsite.com/a.html", "0,1",
98    "Test - site - just a test", "0,0,14,2,18,0",
99    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
100    1 },
101  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E8", "just", "www.testsite.com/b.html",
102    "http://www.testsite.com/b.html", "www.testsite.com/b.html", "0,1",
103    "Test - site - just a test", "0,0,14,2,18,0",
104    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 5,
105    2 },
106  { "BD85DBA2-8C29-49F9-84AE-48E1E90880E9", "just", "www.testsite.com/c.html",
107    "http://www.testsite.com/c.html", "www.testsite.com/c.html", "0,1",
108    "Test - site - just a test", "0,0,14,2,18,0",
109    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "", 8,
110    1 },
111  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EA", "just a", "www.testsite.com/d.html",
112    "http://www.testsite.com/d.html", "www.testsite.com/d.html", "0,1",
113    "Test - site - just a test", "0,0,14,2,18,0",
114    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_TITLE, "",
115    12, 1 },
116  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EB", "just a t",
117    "www.testsite.com/e.html", "http://www.testsite.com/e.html",
118    "www.testsite.com/e.html", "0,1", "Test - site - just a test",
119    "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
120    AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
121  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EC", "just a te",
122    "www.testsite.com/f.html", "http://www.testsite.com/f.html",
123    "www.testsite.com/f.html", "0,1", "Test - site - just a test",
124    "0,0,14,2,18,0", content::PAGE_TRANSITION_TYPED,
125    AutocompleteMatchType::HISTORY_TITLE, "", 12, 1 },
126  { "BD85DBA2-8C29-49F9-84AE-48E1E90880ED", "ago", "www.daysagotest.com/a.html",
127    "http://www.daysagotest.com/a.html", "www.daysagotest.com/a.html",
128    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
129    AutocompleteMatchType::HISTORY_URL, "", 1, 1 },
130  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EE", "ago", "www.daysagotest.com/b.html",
131    "http://www.daysagotest.com/b.html", "www.daysagotest.com/b.html",
132    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
133    AutocompleteMatchType::HISTORY_URL, "", 2, 1 },
134  { "BD85DBA2-8C29-49F9-84AE-48E1E90880EF", "ago", "www.daysagotest.com/c.html",
135    "http://www.daysagotest.com/c.html", "www.daysagotest.com/c.html",
136    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
137    AutocompleteMatchType::HISTORY_URL, "", 3, 1 },
138  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F0", "ago", "www.daysagotest.com/d.html",
139    "http://www.daysagotest.com/d.html", "www.daysagotest.com/d.html",
140    "0,1,8,3,11,1", "Test - site", "0,0", content::PAGE_TRANSITION_TYPED,
141    AutocompleteMatchType::HISTORY_URL, "", 4, 1 },
142  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F1", "echo echo", "echo echo",
143    "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo",
144    "Run Echo command: echo", "0,0", "Echo", "0,4",
145    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::EXTENSION_APP,
146    "echo", 1, 1 },
147  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F2", "abcdef.com", "http://abcdef.com",
148    "http://abcdef.com/", "Abcdef", "0,1,4,0", "Abcdef", "0,3,4,1",
149    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "", 1,
150    100 },
151  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F3", "query", "query",
152    "https://www.google.com/search?q=query", "query", "0,0",
153    "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
154    AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
155  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F4", "word", "www.word",
156    "https://www.google.com/search?q=www.word", "www.word", "0,0",
157    "Google Search", "0,4", content::PAGE_TRANSITION_GENERATED,
158    AutocompleteMatchType::SEARCH_HISTORY, "", 1, 100 },
159  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F5", "about:o", "chrome://omnibox",
160    "chrome://omnibox/", "about:omnibox", "0,3,10,1", "", "",
161    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::NAVSUGGEST, "",
162    1, 100 },
163  { "BD85DBA2-8C29-49F9-84AE-48E1E90880F6", "www/real sp",
164    "http://www/real space/long-url-with-space.html",
165    "http://www/real%20space/long-url-with-space.html",
166    "www/real space/long-url-with-space.html", "0,3,11,1",
167    "Page With Space; Input with Space", "0,0",
168    content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL, "",
169    1, 100 },
170};
171
172}  // namespace
173
174
175// ClassifyTest ---------------------------------------------------------------
176
177// Helper class to make running tests of ClassifyAllMatchesInString() more
178// convenient.
179class ClassifyTest {
180 public:
181  ClassifyTest(const base::string16& text, ACMatchClassifications matches);
182  ~ClassifyTest();
183
184  ACMatchClassifications RunTest(const base::string16& find_text);
185
186 private:
187  const base::string16 text_;
188  const ACMatchClassifications matches_;
189};
190
191ClassifyTest::ClassifyTest(const base::string16& text,
192                           ACMatchClassifications matches)
193    : text_(text),
194      matches_(matches) {
195}
196
197ClassifyTest::~ClassifyTest() {
198}
199
200ACMatchClassifications ClassifyTest::RunTest(const base::string16& find_text) {
201  return ShortcutsProvider::ClassifyAllMatchesInString(find_text,
202      ShortcutsProvider::CreateWordMapForString(find_text), text_, matches_);
203}
204
205namespace history {
206
207
208// ShortcutsProviderTest ------------------------------------------------------
209
210class ShortcutsProviderTest : public testing::Test,
211                              public AutocompleteProviderListener {
212 public:
213  ShortcutsProviderTest();
214
215  // AutocompleteProviderListener:
216  virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
217
218 protected:
219  typedef std::pair<std::string, bool> ExpectedURLAndAllowedToBeDefault;
220  typedef std::vector<ExpectedURLAndAllowedToBeDefault> ExpectedURLs;
221
222  class SetShouldContain
223      : public std::unary_function<const ExpectedURLAndAllowedToBeDefault&,
224                                   std::set<std::string> > {
225   public:
226    explicit SetShouldContain(const ACMatches& matched_urls);
227
228    void operator()(const ExpectedURLAndAllowedToBeDefault& expected);
229    std::set<ExpectedURLAndAllowedToBeDefault> Leftovers() const {
230        return matches_;
231    }
232
233   private:
234    std::set<ExpectedURLAndAllowedToBeDefault> matches_;
235  };
236
237  virtual void SetUp();
238  virtual void TearDown();
239
240  // Fills test data into the provider.
241  void FillData(TestShortcutInfo* db, size_t db_size);
242
243  // Runs an autocomplete query on |text| with the provided
244  // |prevent_inline_autocomplete| setting and checks to see that the returned
245  // results' destination URLs match those provided. |expected_urls| does not
246  // need to be in sorted order, but |expected_top_result| should be the top
247  // match, and it should have inline autocompletion
248  // |top_result_inline_autocompletion|.
249  void RunTest(const base::string16 text,
250               bool prevent_inline_autocomplete,
251               const ExpectedURLs& expected_urls,
252               std::string expected_top_result,
253               base::string16 top_result_inline_autocompletion);
254
255  // Passthrough to the private function in provider_.
256  int CalculateScore(const std::string& terms,
257                     const ShortcutsBackend::Shortcut& shortcut,
258                     int max_relevance);
259
260  base::MessageLoopForUI message_loop_;
261  content::TestBrowserThread ui_thread_;
262  content::TestBrowserThread file_thread_;
263
264  TestingProfile profile_;
265
266  ACMatches ac_matches_;  // The resulting matches after running RunTest.
267
268  scoped_refptr<ShortcutsBackend> backend_;
269  scoped_refptr<ShortcutsProvider> provider_;
270};
271
272ShortcutsProviderTest::ShortcutsProviderTest()
273    : ui_thread_(content::BrowserThread::UI, &message_loop_),
274      file_thread_(content::BrowserThread::FILE, &message_loop_) {
275}
276
277void ShortcutsProviderTest::OnProviderUpdate(bool updated_matches) {}
278
279void ShortcutsProviderTest::SetUp() {
280  ShortcutsBackendFactory::GetInstance()->SetTestingFactoryAndUse(
281      &profile_, &ShortcutsBackendFactory::BuildProfileNoDatabaseForTesting);
282  backend_ = ShortcutsBackendFactory::GetForProfile(&profile_);
283  ASSERT_TRUE(backend_.get());
284  ASSERT_TRUE(profile_.CreateHistoryService(true, false));
285  provider_ = new ShortcutsProvider(this, &profile_);
286  FillData(shortcut_test_db, arraysize(shortcut_test_db));
287}
288
289void ShortcutsProviderTest::TearDown() {
290  // Run all pending tasks or else some threads hold on to the message loop
291  // and prevent it from being deleted.
292  message_loop_.RunUntilIdle();
293  provider_ = NULL;
294}
295
296void ShortcutsProviderTest::FillData(TestShortcutInfo* db, size_t db_size) {
297  DCHECK(provider_.get());
298  size_t expected_size = backend_->shortcuts_map().size() + db_size;
299  for (size_t i = 0; i < db_size; ++i) {
300    const TestShortcutInfo& cur = db[i];
301    ShortcutsBackend::Shortcut shortcut(
302        cur.guid, ASCIIToUTF16(cur.text),
303        ShortcutsBackend::Shortcut::MatchCore(
304            ASCIIToUTF16(cur.fill_into_edit), GURL(cur.destination_url),
305            ASCIIToUTF16(cur.contents),
306            AutocompleteMatch::ClassificationsFromString(cur.contents_class),
307            ASCIIToUTF16(cur.description),
308            AutocompleteMatch::ClassificationsFromString(cur.description_class),
309            cur.transition, cur.type, ASCIIToUTF16(cur.keyword)),
310        base::Time::Now() - base::TimeDelta::FromDays(cur.days_from_now),
311        cur.number_of_hits);
312    backend_->AddShortcut(shortcut);
313  }
314  EXPECT_EQ(expected_size, backend_->shortcuts_map().size());
315}
316
317ShortcutsProviderTest::SetShouldContain::SetShouldContain(
318    const ACMatches& matched_urls) {
319  for (ACMatches::const_iterator iter = matched_urls.begin();
320       iter != matched_urls.end(); ++iter)
321    matches_.insert(ExpectedURLAndAllowedToBeDefault(
322        iter->destination_url.spec(), iter->allowed_to_be_default_match));
323}
324
325void ShortcutsProviderTest::SetShouldContain::operator()(
326    const ExpectedURLAndAllowedToBeDefault& expected) {
327  EXPECT_EQ(1U, matches_.erase(expected));
328}
329
330void ShortcutsProviderTest::RunTest(
331    const base::string16 text,
332    bool prevent_inline_autocomplete,
333    const ExpectedURLs& expected_urls,
334    std::string expected_top_result,
335    base::string16 top_result_inline_autocompletion) {
336  base::MessageLoop::current()->RunUntilIdle();
337  AutocompleteInput input(text, base::string16::npos, base::string16(), GURL(),
338                          AutocompleteInput::INVALID_SPEC,
339                          prevent_inline_autocomplete, false, true,
340                          AutocompleteInput::ALL_MATCHES);
341  provider_->Start(input, false);
342  EXPECT_TRUE(provider_->done());
343
344  ac_matches_ = provider_->matches();
345
346  // We should have gotten back at most AutocompleteProvider::kMaxMatches.
347  EXPECT_LE(ac_matches_.size(), AutocompleteProvider::kMaxMatches);
348
349  // If the number of expected and actual matches aren't equal then we need
350  // test no further, but let's do anyway so that we know which URLs failed.
351  EXPECT_EQ(expected_urls.size(), ac_matches_.size());
352
353  // Verify that all expected URLs were found and that all found URLs
354  // were expected.
355  std::set<ExpectedURLAndAllowedToBeDefault> Leftovers =
356      for_each(expected_urls.begin(), expected_urls.end(),
357               SetShouldContain(ac_matches_)).Leftovers();
358  EXPECT_EQ(0U, Leftovers.size());
359
360  // See if we got the expected top scorer.
361  if (!ac_matches_.empty()) {
362    std::partial_sort(ac_matches_.begin(), ac_matches_.begin() + 1,
363                      ac_matches_.end(), AutocompleteMatch::MoreRelevant);
364    EXPECT_EQ(expected_top_result, ac_matches_[0].destination_url.spec());
365    EXPECT_EQ(top_result_inline_autocompletion,
366              ac_matches_[0].inline_autocompletion);
367  }
368}
369
370int ShortcutsProviderTest::CalculateScore(
371    const std::string& terms,
372    const ShortcutsBackend::Shortcut& shortcut,
373    int max_relevance) {
374  return provider_->CalculateScore(ASCIIToUTF16(terms), shortcut,
375                                   max_relevance);
376}
377
378
379// Actual tests ---------------------------------------------------------------
380
381TEST_F(ShortcutsProviderTest, SimpleSingleMatch) {
382  base::string16 text(ASCIIToUTF16("go"));
383  std::string expected_url("http://www.google.com/");
384  ExpectedURLs expected_urls;
385  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
386  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
387
388  // Same test with prevent inline autocomplete.
389  expected_urls.clear();
390  expected_urls.push_back(
391      ExpectedURLAndAllowedToBeDefault(expected_url, false));
392  // The match will have an |inline_autocompletion| set, but the value will not
393  // be used because |allowed_to_be_default_match| will be false.
394  RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ogle.com"));
395
396  // A pair of analogous tests where the shortcut ends at the end of
397  // |fill_into_edit|.  This exercises the inline autocompletion and default
398  // match code.
399  text = ASCIIToUTF16("abcdef.com");
400  expected_url = "http://abcdef.com/";
401  expected_urls.clear();
402  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
403  RunTest(text, false, expected_urls, expected_url, base::string16());
404  // With prevent inline autocomplete, the suggestion should be the same
405  // (because there is no completion).
406  RunTest(text, true, expected_urls, expected_url, base::string16());
407
408  // Another test, simply for a query match type, not a navigation URL match
409  // type.
410  text = ASCIIToUTF16("que");
411  expected_url = "https://www.google.com/search?q=query";
412  expected_urls.clear();
413  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
414  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("ry"));
415
416  // Same test with prevent inline autocomplete.
417  expected_urls.clear();
418  expected_urls.push_back(
419      ExpectedURLAndAllowedToBeDefault(expected_url, false));
420  // The match will have an |inline_autocompletion| set, but the value will not
421  // be used because |allowed_to_be_default_match| will be false.
422  RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("ry"));
423
424  // A pair of analogous tests where the shortcut ends at the end of
425  // |fill_into_edit|.  This exercises the inline autocompletion and default
426  // match code.
427  text = ASCIIToUTF16("query");
428  expected_urls.clear();
429  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
430  RunTest(text, false, expected_urls, expected_url, base::string16());
431  // With prevent inline autocomplete, the suggestion should be the same
432  // (because there is no completion).
433  RunTest(text, true, expected_urls, expected_url, base::string16());
434
435  // Now the shortcut ends at the end of |fill_into_edit| but has a
436  // non-droppable prefix.  ("www.", for instance, is not droppable for
437  // queries.)
438  text = ASCIIToUTF16("word");
439  expected_url = "https://www.google.com/search?q=www.word";
440  expected_urls.clear();
441  expected_urls.push_back(
442      ExpectedURLAndAllowedToBeDefault(expected_url, false));
443  RunTest(text, false, expected_urls, expected_url, base::string16());
444}
445
446// These tests are like those in SimpleSingleMatch but more complex,
447// involving URLs that need to be fixed up to match properly.
448TEST_F(ShortcutsProviderTest, TrickySingleMatch) {
449  // Test that about: URLs are fixed up/transformed to chrome:// URLs.
450  base::string16 text(ASCIIToUTF16("about:o"));
451  std::string expected_url("chrome://omnibox/");
452  ExpectedURLs expected_urls;
453  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
454  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
455
456  // Same test with prevent inline autocomplete.
457  expected_urls.clear();
458  expected_urls.push_back(
459      ExpectedURLAndAllowedToBeDefault(expected_url, false));
460  // The match will have an |inline_autocompletion| set, but the value will not
461  // be used because |allowed_to_be_default_match| will be false.
462  RunTest(text, true, expected_urls, expected_url, ASCIIToUTF16("mnibox"));
463
464  // Test that an input with a space can match URLs with a (escaped) space.
465  // This would fail if we didn't try to lookup the un-fixed-up string.
466  text = ASCIIToUTF16("www/real sp");
467  expected_url = "http://www/real%20space/long-url-with-space.html";
468  expected_urls.clear();
469  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(expected_url, true));
470  RunTest(text, false, expected_urls, expected_url,
471          ASCIIToUTF16("ace/long-url-with-space.html"));
472
473  // Same test with prevent inline autocomplete.
474  expected_urls.clear();
475  expected_urls.push_back(
476      ExpectedURLAndAllowedToBeDefault(expected_url, false));
477  // The match will have an |inline_autocompletion| set, but the value will not
478  // be used because |allowed_to_be_default_match| will be false.
479  RunTest(text, true, expected_urls, expected_url,
480          ASCIIToUTF16("ace/long-url-with-space.html"));
481}
482
483TEST_F(ShortcutsProviderTest, MultiMatch) {
484  base::string16 text(ASCIIToUTF16("NEWS"));
485  ExpectedURLs expected_urls;
486  // Scores high because of completion length.
487  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
488      "http://slashdot.org/", false));
489  // Scores high because of visit count.
490  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
491      "http://sports.yahoo.com/", false));
492  // Scores high because of visit count but less match span,
493  // which is more important.
494  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
495      "http://www.cnn.com/index.html", false));
496  RunTest(text, false, expected_urls, "http://slashdot.org/", base::string16());
497}
498
499TEST_F(ShortcutsProviderTest, TypedCountMatches) {
500  base::string16 text(ASCIIToUTF16("just"));
501  ExpectedURLs expected_urls;
502  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
503      "http://www.testsite.com/b.html", false));
504  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
505      "http://www.testsite.com/a.html", false));
506  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
507      "http://www.testsite.com/c.html", false));
508  RunTest(text, false, expected_urls, "http://www.testsite.com/b.html",
509          base::string16());
510}
511
512TEST_F(ShortcutsProviderTest, FragmentLengthMatches) {
513  base::string16 text(ASCIIToUTF16("just a"));
514  ExpectedURLs expected_urls;
515  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
516      "http://www.testsite.com/d.html", false));
517  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
518      "http://www.testsite.com/e.html", false));
519  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
520      "http://www.testsite.com/f.html", false));
521  RunTest(text, false, expected_urls, "http://www.testsite.com/d.html",
522          base::string16());
523}
524
525TEST_F(ShortcutsProviderTest, DaysAgoMatches) {
526  base::string16 text(ASCIIToUTF16("ago"));
527  ExpectedURLs expected_urls;
528  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
529      "http://www.daysagotest.com/a.html", false));
530  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
531      "http://www.daysagotest.com/b.html", false));
532  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
533      "http://www.daysagotest.com/c.html", false));
534  RunTest(text, false, expected_urls, "http://www.daysagotest.com/a.html",
535          base::string16());
536}
537
538TEST_F(ShortcutsProviderTest, ClassifyAllMatchesInString) {
539  ACMatchClassifications matches =
540      AutocompleteMatch::ClassificationsFromString("0,0");
541  ClassifyTest classify_test(ASCIIToUTF16("A man, a plan, a canal Panama"),
542                             matches);
543
544  ACMatchClassifications spans_a = classify_test.RunTest(ASCIIToUTF16("man"));
545  // ACMatch spans should be: '--MMM------------------------'
546  EXPECT_EQ("0,0,2,2,5,0", AutocompleteMatch::ClassificationsToString(spans_a));
547
548  ACMatchClassifications spans_b = classify_test.RunTest(ASCIIToUTF16("man p"));
549  // ACMatch spans should be: '--MMM----M-------------M-----'
550  EXPECT_EQ("0,0,2,2,5,0,9,2,10,0,23,2,24,0",
551            AutocompleteMatch::ClassificationsToString(spans_b));
552
553  ACMatchClassifications spans_c =
554      classify_test.RunTest(ASCIIToUTF16("man plan panama"));
555  // ACMatch spans should be:'--MMM----MMMM----------MMMMMM'
556  EXPECT_EQ("0,0,2,2,5,0,9,2,13,0,23,2",
557            AutocompleteMatch::ClassificationsToString(spans_c));
558
559  ClassifyTest classify_test2(ASCIIToUTF16("Yahoo! Sports - Sports News, "
560      "Scores, Rumors, Fantasy Games, and more"), matches);
561
562  ACMatchClassifications spans_d = classify_test2.RunTest(ASCIIToUTF16("ne"));
563  // ACMatch spans should match first two letters of the "news".
564  EXPECT_EQ("0,0,23,2,25,0",
565            AutocompleteMatch::ClassificationsToString(spans_d));
566
567  ACMatchClassifications spans_e =
568      classify_test2.RunTest(ASCIIToUTF16("news r"));
569  EXPECT_EQ("0,0,10,2,11,0,19,2,20,0,23,2,27,0,32,2,33,0,37,2,38,0,41,2,42,0,"
570            "66,2,67,0", AutocompleteMatch::ClassificationsToString(spans_e));
571
572  matches = AutocompleteMatch::ClassificationsFromString("0,1");
573  ClassifyTest classify_test3(ASCIIToUTF16("livescore.goal.com"), matches);
574
575  ACMatchClassifications spans_f = classify_test3.RunTest(ASCIIToUTF16("go"));
576  // ACMatch spans should match first two letters of the "goal".
577  EXPECT_EQ("0,1,10,3,12,1",
578            AutocompleteMatch::ClassificationsToString(spans_f));
579
580  matches = AutocompleteMatch::ClassificationsFromString("0,0,13,1");
581  ClassifyTest classify_test4(ASCIIToUTF16("Email login: mail.somecorp.com"),
582                              matches);
583
584  ACMatchClassifications spans_g = classify_test4.RunTest(ASCIIToUTF16("ail"));
585  EXPECT_EQ("0,0,2,2,5,0,13,1,14,3,17,1",
586            AutocompleteMatch::ClassificationsToString(spans_g));
587
588  ACMatchClassifications spans_h =
589      classify_test4.RunTest(ASCIIToUTF16("lo log"));
590  EXPECT_EQ("0,0,6,2,9,0,13,1",
591            AutocompleteMatch::ClassificationsToString(spans_h));
592
593  ACMatchClassifications spans_i =
594      classify_test4.RunTest(ASCIIToUTF16("ail em"));
595  // 'Email' and 'ail' should be matched.
596  EXPECT_EQ("0,2,5,0,13,1,14,3,17,1",
597            AutocompleteMatch::ClassificationsToString(spans_i));
598
599  // Some web sites do not have a description.  If the string being searched is
600  // empty, the classifications must also be empty: http://crbug.com/148647
601  // Extra parens in the next line hack around C++03's "most vexing parse".
602  class ClassifyTest classify_test5((base::string16()),
603                                    ACMatchClassifications());
604  ACMatchClassifications spans_j = classify_test5.RunTest(ASCIIToUTF16("man"));
605  ASSERT_EQ(0U, spans_j.size());
606
607  // Matches which end at beginning of classification merge properly.
608  matches = AutocompleteMatch::ClassificationsFromString("0,4,9,0");
609  ClassifyTest classify_test6(ASCIIToUTF16("html password example"), matches);
610
611  // Extra space in the next string avoids having the string be a prefix of the
612  // text above, which would allow for two different valid classification sets,
613  // one of which uses two spans (the first of which would mark all of "html
614  // pass" as a match) and one which uses four (which marks the individual words
615  // as matches but not the space between them).  This way only the latter is
616  // valid.
617  ACMatchClassifications spans_k =
618      classify_test6.RunTest(ASCIIToUTF16("html  pass"));
619  EXPECT_EQ("0,6,4,4,5,6,9,0",
620            AutocompleteMatch::ClassificationsToString(spans_k));
621
622  // Multiple matches with both beginning and end at beginning of
623  // classifications merge properly.
624  matches = AutocompleteMatch::ClassificationsFromString("0,1,11,0");
625  ClassifyTest classify_test7(ASCIIToUTF16("http://a.co is great"), matches);
626
627  ACMatchClassifications spans_l =
628      classify_test7.RunTest(ASCIIToUTF16("ht co"));
629  EXPECT_EQ("0,3,2,1,9,3,11,0",
630            AutocompleteMatch::ClassificationsToString(spans_l));
631}
632
633TEST_F(ShortcutsProviderTest, CalculateScore) {
634  ShortcutsBackend::Shortcut shortcut(
635      std::string(), ASCIIToUTF16("test"),
636      ShortcutsBackend::Shortcut::MatchCore(
637          ASCIIToUTF16("www.test.com"), GURL("http://www.test.com"),
638          ASCIIToUTF16("www.test.com"),
639          AutocompleteMatch::ClassificationsFromString("0,1,4,3,8,1"),
640          ASCIIToUTF16("A test"),
641          AutocompleteMatch::ClassificationsFromString("0,0,2,2"),
642          content::PAGE_TRANSITION_TYPED, AutocompleteMatchType::HISTORY_URL,
643          base::string16()),
644      base::Time::Now(), 1);
645
646  // Maximal score.
647  const int max_relevance = AutocompleteResult::kLowestDefaultScore - 1;
648  const int kMaxScore = CalculateScore("test", shortcut, max_relevance);
649
650  // Score decreases as percent of the match is decreased.
651  int score_three_quarters = CalculateScore("tes", shortcut, max_relevance);
652  EXPECT_LT(score_three_quarters, kMaxScore);
653  int score_one_half = CalculateScore("te", shortcut, max_relevance);
654  EXPECT_LT(score_one_half, score_three_quarters);
655  int score_one_quarter = CalculateScore("t", shortcut, max_relevance);
656  EXPECT_LT(score_one_quarter, score_one_half);
657
658  // Should decay with time - one week.
659  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(7);
660  int score_week_old = CalculateScore("test", shortcut, max_relevance);
661  EXPECT_LT(score_week_old, kMaxScore);
662
663  // Should decay more in two weeks.
664  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
665  int score_two_weeks_old = CalculateScore("test", shortcut, max_relevance);
666  EXPECT_LT(score_two_weeks_old, score_week_old);
667
668  // But not if it was activly clicked on. 2 hits slow decaying power.
669  shortcut.number_of_hits = 2;
670  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
671  int score_popular_two_weeks_old =
672      CalculateScore("test", shortcut, max_relevance);
673  EXPECT_LT(score_two_weeks_old, score_popular_two_weeks_old);
674  // But still decayed.
675  EXPECT_LT(score_popular_two_weeks_old, kMaxScore);
676
677  // 3 hits slow decaying power even more.
678  shortcut.number_of_hits = 3;
679  shortcut.last_access_time = base::Time::Now() - base::TimeDelta::FromDays(14);
680  int score_more_popular_two_weeks_old =
681      CalculateScore("test", shortcut, max_relevance);
682  EXPECT_LT(score_two_weeks_old, score_more_popular_two_weeks_old);
683  EXPECT_LT(score_popular_two_weeks_old, score_more_popular_two_weeks_old);
684  // But still decayed.
685  EXPECT_LT(score_more_popular_two_weeks_old, kMaxScore);
686}
687
688TEST_F(ShortcutsProviderTest, DeleteMatch) {
689  TestShortcutInfo shortcuts_to_test_delete[] = {
690    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F1", "delete", "www.deletetest.com/1",
691      "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
692      "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
693      AutocompleteMatchType::HISTORY_URL, "", 1, 1},
694    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F2", "erase", "www.deletetest.com/1",
695      "http://www.deletetest.com/1", "http://www.deletetest.com/1", "0,2",
696      "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
697      AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
698    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F3", "keep", "www.deletetest.com/1/2",
699      "http://www.deletetest.com/1/2", "http://www.deletetest.com/1/2", "0,2",
700      "Keep this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
701      AutocompleteMatchType::HISTORY_TITLE, "", 1, 1},
702    { "BD85DBA2-8C29-49F9-84AE-48E1E90881F4", "delete", "www.deletetest.com/2",
703      "http://www.deletetest.com/2", "http://www.deletetest.com/2", "0,2",
704      "Erase this shortcut!", "0,0", content::PAGE_TRANSITION_TYPED,
705      AutocompleteMatchType::HISTORY_URL, "", 1, 1},
706  };
707
708  size_t original_shortcuts_count = backend_->shortcuts_map().size();
709
710  FillData(shortcuts_to_test_delete, arraysize(shortcuts_to_test_delete));
711
712  EXPECT_EQ(original_shortcuts_count + 4, backend_->shortcuts_map().size());
713  EXPECT_FALSE(backend_->shortcuts_map().end() ==
714               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
715  EXPECT_FALSE(backend_->shortcuts_map().end() ==
716               backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
717
718  AutocompleteMatch match(
719      provider_.get(), 1200, true, AutocompleteMatchType::HISTORY_TITLE);
720
721  match.destination_url = GURL(shortcuts_to_test_delete[0].destination_url);
722  match.contents = ASCIIToUTF16(shortcuts_to_test_delete[0].contents);
723  match.description = ASCIIToUTF16(shortcuts_to_test_delete[0].description);
724
725  provider_->DeleteMatch(match);
726
727  // shortcuts_to_test_delete[0] and shortcuts_to_test_delete[1] should be
728  // deleted, but not shortcuts_to_test_delete[2] or
729  // shortcuts_to_test_delete[3], which have different URLs.
730  EXPECT_EQ(original_shortcuts_count + 2, backend_->shortcuts_map().size());
731  EXPECT_FALSE(backend_->shortcuts_map().end() ==
732               backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
733  EXPECT_TRUE(backend_->shortcuts_map().end() ==
734              backend_->shortcuts_map().find(ASCIIToUTF16("erase")));
735
736  match.destination_url = GURL(shortcuts_to_test_delete[3].destination_url);
737  match.contents = ASCIIToUTF16(shortcuts_to_test_delete[3].contents);
738  match.description = ASCIIToUTF16(shortcuts_to_test_delete[3].description);
739
740  provider_->DeleteMatch(match);
741  EXPECT_EQ(original_shortcuts_count + 1, backend_->shortcuts_map().size());
742  EXPECT_TRUE(backend_->shortcuts_map().end() ==
743              backend_->shortcuts_map().find(ASCIIToUTF16("delete")));
744}
745
746TEST_F(ShortcutsProviderTest, Extension) {
747  // Try an input string that matches an extension URL.
748  base::string16 text(ASCIIToUTF16("echo"));
749  std::string expected_url(
750      "chrome-extension://cedabbhfglmiikkmdgcpjdkocfcmbkee/?q=echo");
751  ExpectedURLs expected_urls;
752  expected_urls.push_back(ExpectedURLAndAllowedToBeDefault(
753      expected_url, true));
754  RunTest(text, false, expected_urls, expected_url, ASCIIToUTF16(" echo"));
755
756  // Claim the extension has been unloaded.
757  scoped_refptr<const extensions::Extension> extension =
758      extensions::ExtensionBuilder()
759          .SetManifest(extensions::DictionaryBuilder()
760              .Set("name", "Echo")
761              .Set("version", "1.0"))
762          .SetID("cedabbhfglmiikkmdgcpjdkocfcmbkee")
763          .Build();
764  extensions::UnloadedExtensionInfo details(
765      extension.get(), extensions::UnloadedExtensionInfo::REASON_UNINSTALL);
766  content::NotificationService::current()->Notify(
767      chrome::NOTIFICATION_EXTENSION_UNLOADED,
768      content::Source<Profile>(&profile_),
769      content::Details<extensions::UnloadedExtensionInfo>(&details));
770
771  // Now the URL should have disappeared.
772  RunTest(text, false, ExpectedURLs(), std::string(), base::string16());
773}
774
775}  // namespace history
776