history_url_provider_unittest.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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/history_url_provider.h"
6
7#include <algorithm>
8
9#include "base/message_loop/message_loop.h"
10#include "base/path_service.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/time/time.h"
14#include "chrome/browser/autocomplete/autocomplete_match.h"
15#include "chrome/browser/autocomplete/autocomplete_provider.h"
16#include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
17#include "chrome/browser/autocomplete/history_quick_provider.h"
18#include "chrome/browser/history/history_service.h"
19#include "chrome/browser/history/history_service_factory.h"
20#include "chrome/browser/net/url_fixer_upper.h"
21#include "chrome/browser/search_engines/template_url.h"
22#include "chrome/browser/search_engines/template_url_service.h"
23#include "chrome/browser/search_engines/template_url_service_factory.h"
24#include "chrome/test/base/testing_browser_process.h"
25#include "chrome/test/base/testing_profile.h"
26#include "content/public/test/test_browser_thread_bundle.h"
27#include "testing/gtest/include/gtest/gtest.h"
28
29using base::Time;
30using base::TimeDelta;
31
32using content::TestBrowserThreadBundle;
33
34struct TestURLInfo {
35  const char* url;
36  const char* title;
37  int visit_count;
38  int typed_count;
39} test_db[] = {
40  {"http://www.google.com/", "Google", 3, 3},
41
42  // High-quality pages should get a host synthesized as a lower-quality match.
43  {"http://slashdot.org/favorite_page.html", "Favorite page", 200, 100},
44
45  // Less popular pages should have hosts synthesized as higher-quality
46  // matches.
47  {"http://kerneltrap.org/not_very_popular.html", "Less popular", 4, 0},
48
49  // Unpopular pages should not appear in the results at all.
50  {"http://freshmeat.net/unpopular.html", "Unpopular", 1, 0},
51
52  // If a host has a match, we should pick it up during host synthesis.
53  {"http://news.google.com/?ned=us&topic=n", "Google News - U.S.", 2, 2},
54  {"http://news.google.com/", "Google News", 1, 1},
55
56  // Matches that are normally not inline-autocompletable should be
57  // autocompleted if they are shorter substitutes for longer matches that would
58  // have been inline autocompleted.
59  {"http://synthesisatest.com/foo/", "Test A", 1, 1},
60  {"http://synthesisbtest.com/foo/", "Test B", 1, 1},
61  {"http://synthesisbtest.com/foo/bar.html", "Test B Bar", 2, 2},
62
63  // Suggested short URLs must be "good enough" and must match user input.
64  {"http://foo.com/", "Dir", 5, 5},
65  {"http://foo.com/dir/", "Dir", 2, 2},
66  {"http://foo.com/dir/another/", "Dir", 5, 1},
67  {"http://foo.com/dir/another/again/", "Dir", 10, 0},
68  {"http://foo.com/dir/another/again/myfile.html", "File", 10, 2},
69
70  // We throw in a lot of extra URLs here to make sure we're testing the
71  // history database's query, not just the autocomplete provider.
72  {"http://startest.com/y/a", "A", 2, 2},
73  {"http://startest.com/y/b", "B", 5, 2},
74  {"http://startest.com/x/c", "C", 5, 2},
75  {"http://startest.com/x/d", "D", 5, 5},
76  {"http://startest.com/y/e", "E", 4, 2},
77  {"http://startest.com/y/f", "F", 3, 2},
78  {"http://startest.com/y/g", "G", 3, 2},
79  {"http://startest.com/y/h", "H", 3, 2},
80  {"http://startest.com/y/i", "I", 3, 2},
81  {"http://startest.com/y/j", "J", 3, 2},
82  {"http://startest.com/y/k", "K", 3, 2},
83  {"http://startest.com/y/l", "L", 3, 2},
84  {"http://startest.com/y/m", "M", 3, 2},
85
86  // A file: URL is useful for testing that fixup does the right thing w.r.t.
87  // the number of trailing slashes on the user's input.
88  {"file:///C:/foo.txt", "", 2, 2},
89
90  // Results with absurdly high typed_counts so that very generic queries like
91  // "http" will give consistent results even if more data is added above.
92  {"http://bogussite.com/a", "Bogus A", 10002, 10000},
93  {"http://bogussite.com/b", "Bogus B", 10001, 10000},
94  {"http://bogussite.com/c", "Bogus C", 10000, 10000},
95
96  // Domain name with number.
97  {"http://www.17173.com/", "Domain with number", 3, 3},
98
99  // URLs to test exact-matching behavior.
100  {"http://go/", "Intranet URL", 1, 1},
101  {"http://gooey/", "Intranet URL 2", 5, 5},
102
103  // URLs for testing offset adjustment.
104  {"http://www.\xEA\xB5\x90\xEC\x9C\xA1.kr/", "Korean", 2, 2},
105  {"http://spaces.com/path%20with%20spaces/foo.html", "Spaces", 2, 2},
106  {"http://ms/c++%20style%20guide", "Style guide", 2, 2},
107
108  // URLs for testing ctrl-enter behavior.
109  {"http://binky/", "Intranet binky", 2, 2},
110  {"http://winky/", "Intranet winky", 2, 2},
111  {"http://www.winky.com/", "Internet winky", 5, 0},
112
113  // URLs used by EmptyVisits.
114  {"http://pandora.com/", "Pandora", 2, 2},
115  // This entry is explicitly added more recently than
116  // history::kLowQualityMatchAgeLimitInDays.
117  // {"http://p/", "p", 0, 0},
118
119  // For intranet based tests.
120  {"http://intra/one", "Intranet", 2, 2},
121  {"http://intra/two", "Intranet two", 1, 1},
122  {"http://intra/three", "Intranet three", 2, 2},
123  {"http://moo/bar", "Intranet moo", 1, 1},
124  {"http://typedhost/typedpath", "Intranet typed", 1, 1},
125  {"http://typedhost/untypedpath", "Intranet untyped", 1, 0},
126
127  {"http://x.com/one", "Internet", 2, 2},
128  {"http://x.com/two", "Internet two", 1, 1},
129  {"http://x.com/three", "Internet three", 2, 2},
130};
131
132class HistoryURLProviderTest : public testing::Test,
133                               public AutocompleteProviderListener {
134 public:
135  HistoryURLProviderTest()
136      : sort_matches_(false) {
137    HistoryQuickProvider::set_disabled(true);
138  }
139
140  virtual ~HistoryURLProviderTest() {
141    HistoryQuickProvider::set_disabled(false);
142  }
143
144  // AutocompleteProviderListener:
145  virtual void OnProviderUpdate(bool updated_matches) OVERRIDE;
146
147 protected:
148  static BrowserContextKeyedService* CreateTemplateURLService(
149      content::BrowserContext* profile) {
150    return new TemplateURLService(static_cast<Profile*>(profile));
151  }
152
153  // testing::Test
154  virtual void SetUp() {
155    SetUpImpl(false);
156  }
157  virtual void TearDown();
158
159  // Does the real setup.
160  void SetUpImpl(bool no_db);
161
162  // Fills test data into the history system.
163  void FillData();
164
165  // Runs an autocomplete query on |text| and checks to see that the returned
166  // results' destination URLs match those provided.  Also allows checking
167  // that the input type was identified correctly.
168  void RunTest(const string16 text,
169               const string16& desired_tld,
170               bool prevent_inline_autocomplete,
171               const std::string* expected_urls,
172               size_t num_results,
173               AutocompleteInput::Type* identified_input_type);
174
175  // A version of the above without the final |type| output parameter.
176  void RunTest(const string16 text,
177               const string16& desired_tld,
178               bool prevent_inline_autocomplete,
179               const std::string* expected_urls,
180               size_t num_results) {
181    AutocompleteInput::Type type;
182    return RunTest(text, desired_tld, prevent_inline_autocomplete,
183                   expected_urls, num_results, &type);
184  }
185
186  content::TestBrowserThreadBundle thread_bundle_;
187  ACMatches matches_;
188  scoped_ptr<TestingProfile> profile_;
189  HistoryService* history_service_;
190  scoped_refptr<HistoryURLProvider> autocomplete_;
191  // Should the matches be sorted and duplicates removed?
192  bool sort_matches_;
193};
194
195class HistoryURLProviderTestNoDB : public HistoryURLProviderTest {
196 protected:
197  virtual void SetUp() {
198    SetUpImpl(true);
199  }
200};
201
202void HistoryURLProviderTest::OnProviderUpdate(bool updated_matches) {
203  if (autocomplete_->done())
204    base::MessageLoop::current()->Quit();
205}
206
207void HistoryURLProviderTest::SetUpImpl(bool no_db) {
208  profile_.reset(new TestingProfile());
209  profile_->CreateHistoryService(true, no_db);
210  if (!no_db) {
211    profile_->BlockUntilHistoryProcessesPendingRequests();
212    profile_->BlockUntilHistoryIndexIsRefreshed();
213  }
214  history_service_ =
215      HistoryServiceFactory::GetForProfile(profile_.get(),
216                                           Profile::EXPLICIT_ACCESS);
217
218  autocomplete_ = new HistoryURLProvider(this, profile_.get(), "en-US,en,ko");
219  TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
220      profile_.get(), &HistoryURLProviderTest::CreateTemplateURLService);
221  FillData();
222}
223
224void HistoryURLProviderTest::TearDown() {
225  autocomplete_ = NULL;
226}
227
228void HistoryURLProviderTest::FillData() {
229  // All visits are a long time ago (some tests require this since we do some
230  // special logic for things visited very recently). Note that this time must
231  // be more recent than the "archived history" threshold for the data to go
232  // into the main database.
233  //
234  // TODO(brettw) It would be nice if we could test this behavior, in which
235  // case the time would be specifed in the test_db structure.
236  Time visit_time = Time::Now() - TimeDelta::FromDays(80);
237
238  for (size_t i = 0; i < arraysize(test_db); ++i) {
239    const TestURLInfo& cur = test_db[i];
240    const GURL current_url(cur.url);
241    history_service_->AddPageWithDetails(current_url, UTF8ToUTF16(cur.title),
242                                         cur.visit_count, cur.typed_count,
243                                         visit_time, false,
244                                         history::SOURCE_BROWSED);
245  }
246
247  history_service_->AddPageWithDetails(
248      GURL("http://p/"), UTF8ToUTF16("p"), 0, 0,
249      Time::Now() -
250      TimeDelta::FromDays(history::kLowQualityMatchAgeLimitInDays - 1),
251      false, history::SOURCE_BROWSED);
252}
253
254void HistoryURLProviderTest::RunTest(
255    const string16 text,
256    const string16& desired_tld,
257    bool prevent_inline_autocomplete,
258    const std::string* expected_urls,
259    size_t num_results,
260    AutocompleteInput::Type* identified_input_type) {
261  AutocompleteInput input(text, string16::npos, desired_tld, GURL(),
262                          prevent_inline_autocomplete, false, true,
263                          AutocompleteInput::ALL_MATCHES);
264  *identified_input_type = input.type();
265  autocomplete_->Start(input, false);
266  if (!autocomplete_->done())
267    base::MessageLoop::current()->Run();
268
269  matches_ = autocomplete_->matches();
270  if (sort_matches_) {
271    for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i)
272      i->ComputeStrippedDestinationURL(profile_.get());
273    std::sort(matches_.begin(), matches_.end(),
274              &AutocompleteMatch::DestinationSortFunc);
275    matches_.erase(std::unique(matches_.begin(), matches_.end(),
276                               &AutocompleteMatch::DestinationsEqual),
277                   matches_.end());
278    std::sort(matches_.begin(), matches_.end(),
279              &AutocompleteMatch::MoreRelevant);
280  }
281  ASSERT_EQ(num_results, matches_.size()) << "Input text: " << text
282                                          << "\nTLD: \"" << desired_tld << "\"";
283  for (size_t i = 0; i < num_results; ++i)
284    EXPECT_EQ(expected_urls[i], matches_[i].destination_url.spec());
285}
286
287TEST_F(HistoryURLProviderTest, PromoteShorterURLs) {
288  // Test that hosts get synthesized below popular pages.
289  const std::string expected_nonsynth[] = {
290    "http://slashdot.org/favorite_page.html",
291    "http://slashdot.org/",
292  };
293  RunTest(ASCIIToUTF16("slash"), string16(), true, expected_nonsynth,
294          arraysize(expected_nonsynth));
295
296  // Test that hosts get synthesized above less popular pages.
297  const std::string expected_synth[] = {
298    "http://kerneltrap.org/",
299    "http://kerneltrap.org/not_very_popular.html",
300  };
301  RunTest(ASCIIToUTF16("kernel"), string16(), true, expected_synth,
302          arraysize(expected_synth));
303
304  // Test that unpopular pages are ignored completely.
305  RunTest(ASCIIToUTF16("fresh"), string16(), true, NULL, 0);
306
307  // Test that if we create or promote shorter suggestions that would not
308  // normally be inline autocompletable, we make them inline autocompletable if
309  // the original suggestion (that we replaced as "top") was inline
310  // autocompletable.
311  const std::string expected_synthesisa[] = {
312    "http://synthesisatest.com/",
313    "http://synthesisatest.com/foo/",
314  };
315  RunTest(ASCIIToUTF16("synthesisa"), string16(), false, expected_synthesisa,
316          arraysize(expected_synthesisa));
317  EXPECT_LT(matches_.front().relevance, 1200);
318  const std::string expected_synthesisb[] = {
319    "http://synthesisbtest.com/foo/",
320    "http://synthesisbtest.com/foo/bar.html",
321  };
322  RunTest(ASCIIToUTF16("synthesisb"), string16(), false, expected_synthesisb,
323          arraysize(expected_synthesisb));
324  EXPECT_GE(matches_.front().relevance, 1410);
325
326  // Test that if we have a synthesized host that matches a suggestion, they
327  // get combined into one.
328  const std::string expected_combine[] = {
329    "http://news.google.com/",
330    "http://news.google.com/?ned=us&topic=n",
331  };
332  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("news"), string16(), true,
333      expected_combine, arraysize(expected_combine)));
334  // The title should also have gotten set properly on the host for the
335  // synthesized one, since it was also in the results.
336  EXPECT_EQ(ASCIIToUTF16("Google News"), matches_.front().description);
337
338  // Test that short URL matching works correctly as the user types more
339  // (several tests):
340  // The entry for foo.com is the best of all five foo.com* entries.
341  const std::string short_1[] = {
342    "http://foo.com/",
343    "http://foo.com/dir/another/again/myfile.html",
344    "http://foo.com/dir/",
345  };
346  RunTest(ASCIIToUTF16("foo"), string16(), true, short_1, arraysize(short_1));
347
348  // When the user types the whole host, make sure we don't get two results for
349  // it.
350  const std::string short_2[] = {
351    "http://foo.com/",
352    "http://foo.com/dir/another/again/myfile.html",
353    "http://foo.com/dir/",
354    "http://foo.com/dir/another/",
355  };
356  RunTest(ASCIIToUTF16("foo.com"), string16(), true, short_2,
357          arraysize(short_2));
358  RunTest(ASCIIToUTF16("foo.com/"), string16(), true, short_2,
359          arraysize(short_2));
360
361  // The filename is the second best of the foo.com* entries, but there is a
362  // shorter URL that's "good enough".  The host doesn't match the user input
363  // and so should not appear.
364  const std::string short_3[] = {
365    "http://foo.com/d",
366    "http://foo.com/dir/another/",
367    "http://foo.com/dir/another/again/myfile.html",
368    "http://foo.com/dir/",
369  };
370  RunTest(ASCIIToUTF16("foo.com/d"), string16(), true, short_3,
371          arraysize(short_3));
372
373  // We shouldn't promote shorter URLs than the best if they're not good
374  // enough.
375  const std::string short_4[] = {
376    "http://foo.com/dir/another/a",
377    "http://foo.com/dir/another/again/myfile.html",
378    "http://foo.com/dir/another/again/",
379  };
380  RunTest(ASCIIToUTF16("foo.com/dir/another/a"), string16(), true, short_4,
381          arraysize(short_4));
382
383  // Exact matches should always be best no matter how much more another match
384  // has been typed.
385  const std::string short_5a[] = {
386    "http://gooey/",
387    "http://www.google.com/",
388    "http://go/",
389  };
390  const std::string short_5b[] = {
391    "http://go/",
392    "http://gooey/",
393    "http://www.google.com/",
394  };
395  RunTest(ASCIIToUTF16("g"), string16(), false, short_5a, arraysize(short_5a));
396  RunTest(ASCIIToUTF16("go"), string16(), false, short_5b, arraysize(short_5b));
397}
398
399TEST_F(HistoryURLProviderTest, CullRedirects) {
400  // URLs we will be using, plus the visit counts they will initially get
401  // (the redirect set below will also increment the visit counts). We want
402  // the results to be in A,B,C order. Note also that our visit counts are
403  // all high enough so that domain synthesizing won't get triggered.
404  struct TestCase {
405    const char* url;
406    int count;
407  } test_cases[] = {
408    {"http://redirects/A", 30},
409    {"http://redirects/B", 20},
410    {"http://redirects/C", 10}
411  };
412  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
413    history_service_->AddPageWithDetails(GURL(test_cases[i].url),
414        UTF8ToUTF16("Title"), test_cases[i].count, test_cases[i].count,
415        Time::Now(), false, history::SOURCE_BROWSED);
416  }
417
418  // Create a B->C->A redirect chain, but set the visit counts such that they
419  // will appear in A,B,C order in the results. The autocomplete query will
420  // search for the most recent visit when looking for redirects, so this will
421  // be found even though the previous visits had no redirects.
422  history::RedirectList redirects_to_a;
423  redirects_to_a.push_back(GURL(test_cases[1].url));
424  redirects_to_a.push_back(GURL(test_cases[2].url));
425  redirects_to_a.push_back(GURL(test_cases[0].url));
426  history_service_->AddPage(GURL(test_cases[0].url), base::Time::Now(),
427      NULL, 0, GURL(), redirects_to_a, content::PAGE_TRANSITION_TYPED,
428      history::SOURCE_BROWSED, true);
429
430  // Because all the results are part of a redirect chain with other results,
431  // all but the first one (A) should be culled. We should get the default
432  // "what you typed" result, plus this one.
433  const string16 typing(ASCIIToUTF16("http://redirects/"));
434  const std::string expected_results[] = {
435    UTF16ToUTF8(typing),
436    test_cases[0].url,
437  };
438  RunTest(typing, string16(), true, expected_results,
439          arraysize(expected_results));
440}
441
442TEST_F(HistoryURLProviderTest, WhatYouTyped) {
443  // Make sure we suggest a What You Typed match at the right times.
444  RunTest(ASCIIToUTF16("wytmatch"), string16(), false, NULL, 0);
445  RunTest(ASCIIToUTF16("wytmatch foo bar"), string16(), false, NULL, 0);
446  RunTest(ASCIIToUTF16("wytmatch+foo+bar"), string16(), false, NULL, 0);
447  RunTest(ASCIIToUTF16("wytmatch+foo+bar.com"), string16(), false, NULL, 0);
448
449  const std::string results_1[] = {"http://www.wytmatch.com/"};
450  RunTest(ASCIIToUTF16("wytmatch"), ASCIIToUTF16("com"), false, results_1,
451          arraysize(results_1));
452
453  const std::string results_2[] = {"http://wytmatch%20foo%20bar/"};
454  RunTest(ASCIIToUTF16("http://wytmatch foo bar"), string16(), false, results_2,
455          arraysize(results_2));
456
457  const std::string results_3[] = {"https://wytmatch%20foo%20bar/"};
458  RunTest(ASCIIToUTF16("https://wytmatch foo bar"), string16(), false,
459          results_3, arraysize(results_3));
460}
461
462TEST_F(HistoryURLProviderTest, Fixup) {
463  // Test for various past crashes we've had.
464  RunTest(ASCIIToUTF16("\\"), string16(), false, NULL, 0);
465  RunTest(ASCIIToUTF16("#"), string16(), false, NULL, 0);
466  RunTest(ASCIIToUTF16("%20"), string16(), false, NULL, 0);
467  const std::string fixup_crash[] = {"http://%EF%BD%A5@s/"};
468  RunTest(WideToUTF16(L"\uff65@s"), string16(), false, fixup_crash,
469          arraysize(fixup_crash));
470  RunTest(WideToUTF16(L"\u2015\u2015@ \uff7c"), string16(), false, NULL, 0);
471
472  // Fixing up "file:" should result in an inline autocomplete offset of just
473  // after "file:", not just after "file://".
474  const string16 input_1(ASCIIToUTF16("file:"));
475  const std::string fixup_1[] = {"file:///C:/foo.txt"};
476  ASSERT_NO_FATAL_FAILURE(RunTest(input_1, string16(), false, fixup_1,
477                                  arraysize(fixup_1)));
478  EXPECT_EQ(ASCIIToUTF16("///C:/foo.txt"),
479            matches_.front().inline_autocompletion);
480
481  // Fixing up "http:/" should result in an inline autocomplete offset of just
482  // after "http:/", not just after "http:".
483  const string16 input_2(ASCIIToUTF16("http:/"));
484  const std::string fixup_2[] = {
485    "http://bogussite.com/a",
486    "http://bogussite.com/b",
487    "http://bogussite.com/c",
488  };
489  ASSERT_NO_FATAL_FAILURE(RunTest(input_2, string16(), false, fixup_2,
490                                  arraysize(fixup_2)));
491  EXPECT_EQ(ASCIIToUTF16("/bogussite.com/a"),
492            matches_.front().inline_autocompletion);
493
494  // Adding a TLD to a small number like "56" should result in "www.56.com"
495  // rather than "0.0.0.56.com".
496  const std::string fixup_3[] = {"http://www.56.com/"};
497  RunTest(ASCIIToUTF16("56"), ASCIIToUTF16("com"), true, fixup_3,
498          arraysize(fixup_3));
499
500  // An input looks like a IP address like "127.0.0.1" should result in
501  // "http://127.0.0.1/".
502  const std::string fixup_4[] = {"http://127.0.0.1/"};
503  RunTest(ASCIIToUTF16("127.0.0.1"), string16(), false, fixup_4,
504          arraysize(fixup_4));
505
506  // An number "17173" should result in "http://www.17173.com/" in db.
507  const std::string fixup_5[] = {"http://www.17173.com/"};
508  RunTest(ASCIIToUTF16("17173"), string16(), false, fixup_5,
509          arraysize(fixup_5));
510}
511
512// Make sure the results for the input 'p' don't change between the first and
513// second passes.
514TEST_F(HistoryURLProviderTest, EmptyVisits) {
515  // Wait for history to create the in memory DB.
516  profile_->BlockUntilHistoryProcessesPendingRequests();
517
518  AutocompleteInput input(ASCIIToUTF16("p"), string16::npos, string16(), GURL(),
519                          false, false, true, AutocompleteInput::ALL_MATCHES);
520  autocomplete_->Start(input, false);
521  // HistoryURLProvider shouldn't be done (waiting on async results).
522  EXPECT_FALSE(autocomplete_->done());
523
524  // We should get back an entry for pandora.
525  matches_ = autocomplete_->matches();
526  ASSERT_GT(matches_.size(), 0u);
527  EXPECT_EQ(GURL("http://pandora.com/"), matches_[0].destination_url);
528  int pandora_relevance = matches_[0].relevance;
529
530  // Run the message loop. When |autocomplete_| finishes the loop is quit.
531  base::MessageLoop::current()->Run();
532  EXPECT_TRUE(autocomplete_->done());
533  matches_ = autocomplete_->matches();
534  ASSERT_GT(matches_.size(), 0u);
535  EXPECT_EQ(GURL("http://pandora.com/"), matches_[0].destination_url);
536  EXPECT_EQ(pandora_relevance, matches_[0].relevance);
537}
538
539TEST_F(HistoryURLProviderTestNoDB, NavigateWithoutDB) {
540  // Ensure that we will still produce matches for navigation when there is no
541  // database.
542  std::string navigation_1[] = {"http://test.com/"};
543  RunTest(ASCIIToUTF16("test.com"), string16(), false, navigation_1,
544          arraysize(navigation_1));
545
546  std::string navigation_2[] = {"http://slash/"};
547  RunTest(ASCIIToUTF16("slash"), string16(), false, navigation_2,
548          arraysize(navigation_2));
549
550  RunTest(ASCIIToUTF16("this is a query"), string16(), false, NULL, 0);
551}
552
553TEST_F(HistoryURLProviderTest, DontAutocompleteOnTrailingWhitespace) {
554  AutocompleteInput input(ASCIIToUTF16("slash "), string16::npos, string16(),
555                          GURL(), false, false, true,
556                          AutocompleteInput::ALL_MATCHES);
557  autocomplete_->Start(input, false);
558  if (!autocomplete_->done())
559    base::MessageLoop::current()->Run();
560
561  // None of the matches should attempt to autocomplete.
562  matches_ = autocomplete_->matches();
563  for (size_t i = 0; i < matches_.size(); ++i)
564    EXPECT_TRUE(matches_[i].inline_autocompletion.empty());
565}
566
567TEST_F(HistoryURLProviderTest, TreatEmailsAsSearches) {
568  // Visiting foo.com should not make this string be treated as a navigation.
569  // That means the result should be scored around 1200 ("what you typed")
570  // and not 1400+.
571  const std::string expected[] = {"http://user@foo.com/"};
572  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("user@foo.com"), string16(),
573                                  false, expected, arraysize(expected)));
574  EXPECT_LE(1200, matches_[0].relevance);
575  EXPECT_LT(matches_[0].relevance, 1210);
576}
577
578TEST_F(HistoryURLProviderTest, IntranetURLsWithPaths) {
579  struct TestCase {
580    const char* input;
581    int relevance;
582  } test_cases[] = {
583    { "fooey", 0 },
584    { "fooey/", 1200 },     // 1200 for URL would still navigate by default.
585    { "fooey/a", 1200 },    // 1200 for UNKNOWN would not.
586    { "fooey/a b", 1200 },  // Also UNKNOWN.
587    { "gooey", 1410 },
588    { "gooey/", 1410 },
589    { "gooey/a", 1400 },
590    { "gooey/a b", 1400 },
591  };
592  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
593    SCOPED_TRACE(test_cases[i].input);
594    if (test_cases[i].relevance == 0) {
595      RunTest(ASCIIToUTF16(test_cases[i].input), string16(), false, NULL, 0);
596    } else {
597      const std::string output[] = {
598        URLFixerUpper::FixupURL(test_cases[i].input, std::string()).spec()
599      };
600      ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16(test_cases[i].input),
601                              string16(), false, output, arraysize(output)));
602      // Actual relevance should be at least what test_cases expects and
603      // and no more than 10 more.
604      EXPECT_LE(test_cases[i].relevance, matches_[0].relevance);
605      EXPECT_LT(matches_[0].relevance, test_cases[i].relevance + 10);
606    }
607  }
608}
609
610TEST_F(HistoryURLProviderTest, IntranetURLsWithRefs) {
611  struct TestCase {
612    const char* input;
613    int relevance;
614    AutocompleteInput::Type type;
615  } test_cases[] = {
616    { "gooey", 1410, AutocompleteInput::UNKNOWN },
617    { "gooey/", 1410, AutocompleteInput::URL },
618    { "gooey#", 1200, AutocompleteInput::UNKNOWN },
619    { "gooey/#", 1200, AutocompleteInput::URL },
620    { "gooey#foo", 1200, AutocompleteInput::UNKNOWN },
621    { "gooey/#foo", 1200, AutocompleteInput::URL },
622    { "gooey# foo", 1200, AutocompleteInput::UNKNOWN },
623    { "gooey/# foo", 1200, AutocompleteInput::URL },
624  };
625  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
626    SCOPED_TRACE(test_cases[i].input);
627    const std::string output[] = {
628      URLFixerUpper::FixupURL(test_cases[i].input, std::string()).spec()
629    };
630    AutocompleteInput::Type type;
631    ASSERT_NO_FATAL_FAILURE(
632        RunTest(ASCIIToUTF16(test_cases[i].input),
633                string16(), false, output, arraysize(output), &type));
634    // Actual relevance should be at least what test_cases expects and
635    // and no more than 10 more.
636    EXPECT_LE(test_cases[i].relevance, matches_[0].relevance);
637    EXPECT_LT(matches_[0].relevance, test_cases[i].relevance + 10);
638    // Input type should be what we expect.  This is important because
639    // this provider counts on SearchProvider to give queries a relevance
640    // score >1200 for UNKNOWN inputs and <1200 for URL inputs.  (That's
641    // already tested in search_provider_unittest.cc.)  For this test
642    // here to test that the user sees the correct behavior, it needs
643    // to check that the input type was identified correctly.
644    EXPECT_EQ(test_cases[i].type, type);
645  }
646}
647
648// Makes sure autocompletion happens for intranet sites that have been
649// previoulsy visited.
650TEST_F(HistoryURLProviderTest, IntranetURLCompletion) {
651  sort_matches_ = true;
652
653  const std::string expected1[] = {
654    "http://intra/three",
655    "http://intra/two",
656  };
657  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("intra/t"), string16(), false,
658                                  expected1, arraysize(expected1)));
659  EXPECT_LE(1410, matches_[0].relevance);
660  EXPECT_LT(matches_[0].relevance, 1420);
661  EXPECT_EQ(matches_[0].relevance - 1, matches_[1].relevance);
662
663  const std::string expected2[] = {
664    "http://moo/b",
665    "http://moo/bar",
666  };
667  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("moo/b"), string16(), false,
668                                  expected2, arraysize(expected2)));
669  // The url what you typed match should be around 1400, otherwise the
670  // search what you typed match is going to be first.
671  EXPECT_LE(1400, matches_[0].relevance);
672  EXPECT_LT(matches_[0].relevance, 1410);
673
674  const std::string expected3[] = {
675    "http://intra/one",
676    "http://intra/three",
677    "http://intra/two",
678  };
679  RunTest(ASCIIToUTF16("intra"), string16(), false, expected3,
680          arraysize(expected3));
681
682  const std::string expected4[] = {
683    "http://intra/one",
684    "http://intra/three",
685    "http://intra/two",
686  };
687  RunTest(ASCIIToUTF16("intra/"), string16(), false, expected4,
688          arraysize(expected4));
689
690  const std::string expected5[] = {
691    "http://intra/one",
692  };
693  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("intra/o"), string16(), false,
694                                  expected5, arraysize(expected5)));
695  EXPECT_LE(1410, matches_[0].relevance);
696  EXPECT_LT(matches_[0].relevance, 1420);
697
698  const std::string expected6[] = {
699    "http://intra/x",
700  };
701  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("intra/x"), string16(), false,
702                                  expected6, arraysize(expected6)));
703  EXPECT_LE(1400, matches_[0].relevance);
704  EXPECT_LT(matches_[0].relevance, 1410);
705
706  const std::string expected7[] = {
707    "http://typedhost/untypedpath",
708  };
709  ASSERT_NO_FATAL_FAILURE(RunTest(ASCIIToUTF16("typedhost/untypedpath"),
710      string16(), false, expected7, arraysize(expected7)));
711  EXPECT_LE(1400, matches_[0].relevance);
712  EXPECT_LT(matches_[0].relevance, 1410);
713}
714
715TEST_F(HistoryURLProviderTest, CrashDueToFixup) {
716  // This test passes if we don't crash.  The results don't matter.
717  const char* const test_cases[] = {
718    "//c",
719    "\\@st"
720  };
721  for (size_t i = 0; i < arraysize(test_cases); ++i) {
722    AutocompleteInput input(ASCIIToUTF16(test_cases[i]), string16::npos,
723                            string16(), GURL(), false, false, true,
724                            AutocompleteInput::ALL_MATCHES);
725    autocomplete_->Start(input, false);
726    if (!autocomplete_->done())
727      base::MessageLoop::current()->Run();
728  }
729}
730
731TEST_F(HistoryURLProviderTest, CullSearchResults) {
732  // Set up a default search engine.
733  TemplateURLData data;
734  data.SetKeyword(ASCIIToUTF16("TestEngine"));
735  data.SetURL("http://testsearch.com/?q={searchTerms}");
736  TemplateURLService* template_url_service =
737      TemplateURLServiceFactory::GetForProfile(profile_.get());
738  TemplateURL* template_url = new TemplateURL(profile_.get(), data);
739  template_url_service->Add(template_url);
740  template_url_service->SetDefaultSearchProvider(template_url);
741  template_url_service->Load();
742
743  // URLs we will be using, plus the visit counts they will initially get
744  // (the redirect set below will also increment the visit counts). We want
745  // the results to be in A,B,C order. Note also that our visit counts are
746  // all high enough so that domain synthesizing won't get triggered.
747  struct TestCase {
748    const char* url;
749    int count;
750  } test_cases[] = {
751    {"https://testsearch.com/", 30},
752    {"https://testsearch.com/?q=foobar", 20},
753    {"http://foobar.com/", 10}
754  };
755  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
756    history_service_->AddPageWithDetails(GURL(test_cases[i].url),
757        UTF8ToUTF16("Title"), test_cases[i].count, test_cases[i].count,
758        Time::Now(), false, history::SOURCE_BROWSED);
759  }
760
761  // We should not see search URLs when typing a previously used query.
762  const std::string expected_when_searching_query[] = {
763    test_cases[2].url
764  };
765  RunTest(ASCIIToUTF16("foobar"), string16(), true,
766      expected_when_searching_query, arraysize(expected_when_searching_query));
767
768  // We should not see search URLs when typing the search engine name.
769  const std::string expected_when_searching_site[] = {
770    test_cases[0].url
771  };
772  RunTest(ASCIIToUTF16("testsearch"), string16(), true,
773      expected_when_searching_site, arraysize(expected_when_searching_site));
774}
775
776TEST_F(HistoryURLProviderTest, SuggestExactInput) {
777  const size_t npos = std::string::npos;
778  struct TestCase {
779    // Inputs:
780    const char* input;
781    bool trim_http;
782    // Expected Outputs:
783    const char* contents;
784    // Offsets of the ACMatchClassifications, terminated by npos.
785    size_t offsets[3];
786    // The index of the ACMatchClassification that should have the MATCH bit
787    // set, npos if no ACMatchClassification should have the MATCH bit set.
788    size_t match_classification_index;
789  } test_cases[] = {
790    { "http://www.somesite.com", false,
791      "http://www.somesite.com", {0, npos, npos}, 0 },
792    { "www.somesite.com", true,
793      "www.somesite.com", {0, npos, npos}, 0 },
794    { "www.somesite.com", false,
795      "http://www.somesite.com", {0, 7, npos}, 1 },
796    { "somesite.com", true,
797      "somesite.com", {0, npos, npos}, 0 },
798    { "somesite.com", false,
799      "http://somesite.com", {0, 7, npos}, 1 },
800    { "w", true,
801      "w", {0, npos, npos}, 0 },
802    { "w", false,
803      "http://w", {0, 7, npos}, 1 },
804    { "w.com", true,
805      "w.com", {0, npos, npos}, 0 },
806    { "w.com", false,
807      "http://w.com", {0, 7, npos}, 1 },
808    { "www.w.com", true,
809      "www.w.com", {0, npos, npos}, 0 },
810    { "www.w.com", false,
811      "http://www.w.com", {0, 7, npos}, 1 },
812    { "view-source:www.w.com/", true,
813      "view-source:www.w.com", {0, npos, npos}, npos },
814    { "view-source:www.w.com/", false,
815      "view-source:http://www.w.com", {0, npos, npos}, npos },
816    { "view-source:http://www.w.com/", false,
817      "view-source:http://www.w.com", {0, npos, npos}, 0 },
818    { "   view-source:", true,
819      "view-source:", {0, npos, npos}, 0 },
820    { "http:////////w.com", false,
821      "http://w.com", {0, npos, npos}, npos },
822    { "    http:////////www.w.com", false,
823      "http://www.w.com", {0, npos, npos}, npos },
824    { "http:a///www.w.com", false,
825      "http://a///www.w.com", {0, npos, npos}, npos },
826    { "mailto://a@b.com", true,
827      "mailto://a@b.com", {0, npos, npos}, 0 },
828    { "mailto://a@b.com", false,
829      "mailto://a@b.com", {0, npos, npos}, 0 },
830  };
831  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
832    SCOPED_TRACE(testing::Message() << "Index " << i << " input: "
833                                    << test_cases[i].input << ", trim_http: "
834                                    << test_cases[i].trim_http);
835
836    AutocompleteInput input(ASCIIToUTF16(test_cases[i].input), string16::npos,
837                            string16(), GURL("about:blank"),
838                            false, false, true, AutocompleteInput::ALL_MATCHES);
839    AutocompleteMatch match = HistoryURLProvider::SuggestExactInput(
840        autocomplete_.get(), input, test_cases[i].trim_http);
841    EXPECT_EQ(ASCIIToUTF16(test_cases[i].contents), match.contents);
842    for (size_t match_index = 0; match_index < match.contents_class.size();
843         ++match_index) {
844      EXPECT_EQ(test_cases[i].offsets[match_index],
845                match.contents_class[match_index].offset);
846      EXPECT_EQ(ACMatchClassification::URL |
847                (match_index == test_cases[i].match_classification_index ?
848                 ACMatchClassification::MATCH : 0),
849                match.contents_class[match_index].style);
850    }
851    EXPECT_EQ(npos, test_cases[i].offsets[match.contents_class.size()]);
852  }
853}
854