1// Copyright (c) 2011 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 "base/memory/scoped_ptr.h"
6#include "base/message_loop.h"
7#include "base/string_number_conversions.h"
8#include "base/string_util.h"
9#include "base/utf_string_conversions.h"
10#include "chrome/browser/autocomplete/autocomplete.h"
11#include "chrome/browser/autocomplete/autocomplete_match.h"
12#include "chrome/browser/autocomplete/keyword_provider.h"
13#include "chrome/browser/autocomplete/search_provider.h"
14#include "chrome/browser/search_engines/template_url.h"
15#include "chrome/browser/search_engines/template_url_model.h"
16#include "chrome/test/testing_browser_process.h"
17#include "chrome/test/testing_browser_process_test.h"
18#include "chrome/test/testing_profile.h"
19#include "content/common/notification_observer.h"
20#include "content/common/notification_registrar.h"
21#include "content/common/notification_service.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24static std::ostream& operator<<(std::ostream& os,
25                                const AutocompleteResult::const_iterator& it) {
26  return os << static_cast<const AutocompleteMatch*>(&(*it));
27}
28
29namespace {
30
31const size_t num_results_per_provider = 3;
32
33// Autocomplete provider that provides known results. Note that this is
34// refcounted so that it can also be a task on the message loop.
35class TestProvider : public AutocompleteProvider {
36 public:
37  TestProvider(int relevance, const string16& prefix)
38      : AutocompleteProvider(NULL, NULL, ""),
39        relevance_(relevance),
40        prefix_(prefix) {
41  }
42
43  virtual void Start(const AutocompleteInput& input,
44                     bool minimal_changes);
45
46  void set_listener(ACProviderListener* listener) {
47    listener_ = listener;
48  }
49
50 private:
51  ~TestProvider() {}
52
53  void Run();
54
55  void AddResults(int start_at, int num);
56
57  int relevance_;
58  const string16 prefix_;
59};
60
61void TestProvider::Start(const AutocompleteInput& input,
62                         bool minimal_changes) {
63  if (minimal_changes)
64    return;
65
66  matches_.clear();
67
68  // Generate one result synchronously, the rest later.
69  AddResults(0, 1);
70
71  if (input.matches_requested() == AutocompleteInput::ALL_MATCHES) {
72    done_ = false;
73    MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
74        this, &TestProvider::Run));
75  }
76}
77
78void TestProvider::Run() {
79  DCHECK_GT(num_results_per_provider, 0U);
80  AddResults(1, num_results_per_provider);
81  done_ = true;
82  DCHECK(listener_);
83  listener_->OnProviderUpdate(true);
84}
85
86void TestProvider::AddResults(int start_at, int num) {
87  for (int i = start_at; i < num; i++) {
88    AutocompleteMatch match(this, relevance_ - i, false,
89                            AutocompleteMatch::URL_WHAT_YOU_TYPED);
90
91    match.fill_into_edit = prefix_ + UTF8ToUTF16(base::IntToString(i));
92    match.destination_url = GURL(UTF16ToUTF8(match.fill_into_edit));
93
94    match.contents = match.fill_into_edit;
95    match.contents_class.push_back(
96        ACMatchClassification(0, ACMatchClassification::NONE));
97    match.description = match.fill_into_edit;
98    match.description_class.push_back(
99        ACMatchClassification(0, ACMatchClassification::NONE));
100
101    matches_.push_back(match);
102  }
103}
104
105class AutocompleteProviderTest : public testing::Test,
106                                 public NotificationObserver {
107 protected:
108  void ResetControllerWithTestProviders(bool same_destinations);
109
110  // Runs a query on the input "a", and makes sure both providers' input is
111  // properly collected.
112  void RunTest();
113
114  void ResetControllerWithTestProvidersWithKeywordAndSearchProviders();
115  void RunExactKeymatchTest(bool allow_exact_keyword_match);
116
117  // These providers are owned by the controller once it's created.
118  ACProviders providers_;
119
120  AutocompleteResult result_;
121
122 private:
123  // NotificationObserver
124  virtual void Observe(NotificationType type,
125                       const NotificationSource& source,
126                       const NotificationDetails& details);
127
128  ScopedTestingBrowserProcess browser_process_;
129
130  MessageLoopForUI message_loop_;
131  scoped_ptr<AutocompleteController> controller_;
132  NotificationRegistrar registrar_;
133  TestingProfile profile_;
134};
135
136void AutocompleteProviderTest::ResetControllerWithTestProviders(
137    bool same_destinations) {
138  // Forget about any existing providers.  The controller owns them and will
139  // Release() them below, when we delete it during the call to reset().
140  providers_.clear();
141
142  // Construct two new providers, with either the same or different prefixes.
143  TestProvider* providerA = new TestProvider(num_results_per_provider,
144                                             ASCIIToUTF16("http://a"));
145  providerA->AddRef();
146  providers_.push_back(providerA);
147
148  TestProvider* providerB = new TestProvider(num_results_per_provider * 2,
149      same_destinations ? ASCIIToUTF16("http://a") : ASCIIToUTF16("http://b"));
150  providerB->AddRef();
151  providers_.push_back(providerB);
152
153  // Reset the controller to contain our new providers.
154  AutocompleteController* controller = new AutocompleteController(providers_);
155  controller_.reset(controller);
156  providerA->set_listener(controller);
157  providerB->set_listener(controller);
158
159  // The providers don't complete synchronously, so listen for "result updated"
160  // notifications.
161  registrar_.Add(this, NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_READY,
162                 NotificationService::AllSources());
163}
164
165void AutocompleteProviderTest::
166    ResetControllerWithTestProvidersWithKeywordAndSearchProviders() {
167  profile_.CreateTemplateURLModel();
168
169  // Reset the default TemplateURL.
170  TemplateURL* default_t_url = new TemplateURL();
171  default_t_url->SetURL("http://defaultturl/{searchTerms}", 0, 0);
172  TemplateURLModel* turl_model = profile_.GetTemplateURLModel();
173  turl_model->Add(default_t_url);
174  turl_model->SetDefaultSearchProvider(default_t_url);
175  TemplateURLID default_provider_id = default_t_url->id();
176  ASSERT_NE(0, default_provider_id);
177
178  // Create another TemplateURL for KeywordProvider.
179  TemplateURL* keyword_t_url = new TemplateURL();
180  keyword_t_url->set_short_name(ASCIIToUTF16("k"));
181  keyword_t_url->set_keyword(ASCIIToUTF16("k"));
182  keyword_t_url->SetURL("http://keyword/{searchTerms}", 0, 0);
183  profile_.GetTemplateURLModel()->Add(keyword_t_url);
184  ASSERT_NE(0, keyword_t_url->id());
185
186  // Forget about any existing providers.  The controller owns them and will
187  // Release() them below, when we delete it during the call to reset().
188  providers_.clear();
189
190  // Create both a keyword and search provider, and add them in that order.
191  // (Order is important; see comments in RunExactKeymatchTest().)
192  AutocompleteProvider* keyword_provider = new KeywordProvider(NULL,
193                                                                &profile_);
194  keyword_provider->AddRef();
195  providers_.push_back(keyword_provider);
196  AutocompleteProvider* search_provider = new SearchProvider(NULL, &profile_);
197  search_provider->AddRef();
198  providers_.push_back(search_provider);
199
200  AutocompleteController* controller = new AutocompleteController(providers_);
201  controller_.reset(controller);
202}
203
204void AutocompleteProviderTest::RunTest() {
205  result_.Reset();
206  controller_->Start(ASCIIToUTF16("a"), string16(), true, false, true,
207                     AutocompleteInput::ALL_MATCHES);
208
209  // The message loop will terminate when all autocomplete input has been
210  // collected.
211  MessageLoop::current()->Run();
212}
213
214void AutocompleteProviderTest::RunExactKeymatchTest(
215    bool allow_exact_keyword_match) {
216  // Send the controller input which exactly matches the keyword provider we
217  // created in ResetControllerWithKeywordAndSearchProviders().  The default
218  // match should thus be a keyword match iff |allow_exact_keyword_match| is
219  // true.
220  controller_->Start(ASCIIToUTF16("k test"), string16(), true, false,
221                     allow_exact_keyword_match,
222                     AutocompleteInput::SYNCHRONOUS_MATCHES);
223  EXPECT_TRUE(controller_->done());
224  // ResetControllerWithKeywordAndSearchProviders() adds the keyword provider
225  // first, then the search provider.  So if the default match is a keyword
226  // match, it will come from provider 0, otherwise from provider 1.
227  EXPECT_EQ(providers_[allow_exact_keyword_match ? 0 : 1],
228      controller_->result().default_match()->provider);
229}
230
231void AutocompleteProviderTest::Observe(NotificationType type,
232                                       const NotificationSource& source,
233                                       const NotificationDetails& details) {
234  if (controller_->done()) {
235    result_.CopyFrom(controller_->result());
236    MessageLoop::current()->Quit();
237  }
238}
239
240// Tests that the default selection is set properly when updating results.
241TEST_F(AutocompleteProviderTest, Query) {
242  ResetControllerWithTestProviders(false);
243  RunTest();
244
245  // Make sure the default match gets set to the highest relevance match.  The
246  // highest relevance matches should come from the second provider.
247  EXPECT_EQ(num_results_per_provider * 2, result_.size());  // two providers
248  ASSERT_NE(result_.end(), result_.default_match());
249  EXPECT_EQ(providers_[1], result_.default_match()->provider);
250}
251
252TEST_F(AutocompleteProviderTest, RemoveDuplicates) {
253  ResetControllerWithTestProviders(true);
254  RunTest();
255
256  // Make sure all the first provider's results were eliminated by the second
257  // provider's.
258  EXPECT_EQ(num_results_per_provider, result_.size());
259  for (AutocompleteResult::const_iterator i(result_.begin());
260       i != result_.end(); ++i)
261    EXPECT_EQ(providers_[1], i->provider);
262}
263
264TEST_F(AutocompleteProviderTest, AllowExactKeywordMatch) {
265  ResetControllerWithTestProvidersWithKeywordAndSearchProviders();
266  RunExactKeymatchTest(true);
267  RunExactKeymatchTest(false);
268}
269
270typedef TestingBrowserProcessTest AutocompleteTest;
271
272TEST_F(AutocompleteTest, InputType) {
273  struct test_data {
274    const string16 input;
275    const AutocompleteInput::Type type;
276  } input_cases[] = {
277    { ASCIIToUTF16(""), AutocompleteInput::INVALID },
278    { ASCIIToUTF16("?"), AutocompleteInput::FORCED_QUERY },
279    { ASCIIToUTF16("?foo"), AutocompleteInput::FORCED_QUERY },
280    { ASCIIToUTF16("?foo bar"), AutocompleteInput::FORCED_QUERY },
281    { ASCIIToUTF16("?http://foo.com/bar"), AutocompleteInput::FORCED_QUERY },
282    { ASCIIToUTF16("foo"), AutocompleteInput::UNKNOWN },
283    { ASCIIToUTF16("foo.c"), AutocompleteInput::UNKNOWN },
284    { ASCIIToUTF16("foo.com"), AutocompleteInput::URL },
285    { ASCIIToUTF16("-.com"), AutocompleteInput::UNKNOWN },
286    { ASCIIToUTF16("foo/bar"), AutocompleteInput::URL },
287    { ASCIIToUTF16("foo;bar"), AutocompleteInput::QUERY },
288    { ASCIIToUTF16("foo/bar baz"), AutocompleteInput::UNKNOWN },
289    { ASCIIToUTF16("foo bar.com"), AutocompleteInput::QUERY },
290    { ASCIIToUTF16("foo bar"), AutocompleteInput::QUERY },
291    { ASCIIToUTF16("foo+bar"), AutocompleteInput::QUERY },
292    { ASCIIToUTF16("foo+bar.com"), AutocompleteInput::UNKNOWN },
293    { ASCIIToUTF16("\"foo:bar\""), AutocompleteInput::QUERY },
294    { ASCIIToUTF16("link:foo.com"), AutocompleteInput::UNKNOWN },
295    { ASCIIToUTF16("foo:81"), AutocompleteInput::URL },
296    { ASCIIToUTF16("www.foo.com:81"), AutocompleteInput::URL },
297    { ASCIIToUTF16("localhost:8080"), AutocompleteInput::URL },
298    { ASCIIToUTF16("foo.com:123456"), AutocompleteInput::QUERY },
299    { ASCIIToUTF16("foo.com:abc"), AutocompleteInput::QUERY },
300    { ASCIIToUTF16("1.2.3.4:abc"), AutocompleteInput::QUERY },
301    { ASCIIToUTF16("user@foo.com"), AutocompleteInput::UNKNOWN },
302    { ASCIIToUTF16("user:pass@"), AutocompleteInput::UNKNOWN },
303    { ASCIIToUTF16("user:pass@!foo.com"), AutocompleteInput::UNKNOWN },
304    { ASCIIToUTF16("user:pass@foo"), AutocompleteInput::URL },
305    { ASCIIToUTF16("user:pass@foo.c"), AutocompleteInput::URL },
306    { ASCIIToUTF16("user:pass@foo.com"), AutocompleteInput::URL },
307    { ASCIIToUTF16("user:pass@foo.com:81"), AutocompleteInput::URL },
308    { ASCIIToUTF16("user:pass@foo:81"), AutocompleteInput::URL },
309    { ASCIIToUTF16("1.2"), AutocompleteInput::UNKNOWN },
310    { ASCIIToUTF16("1.2/45"), AutocompleteInput::UNKNOWN },
311    { ASCIIToUTF16("1.2:45"), AutocompleteInput::UNKNOWN },
312    { ASCIIToUTF16("user@1.2:45"), AutocompleteInput::UNKNOWN },
313    { ASCIIToUTF16("user:pass@1.2:45"), AutocompleteInput::URL },
314    { ASCIIToUTF16("ps/2 games"), AutocompleteInput::UNKNOWN },
315    { ASCIIToUTF16("en.wikipedia.org/wiki/James Bond"),
316        AutocompleteInput::URL },
317    // In Chrome itself, mailto: will get handled by ShellExecute, but in
318    // unittest mode, we don't have the data loaded in the external protocol
319    // handler to know this.
320    // { ASCIIToUTF16("mailto:abuse@foo.com"), AutocompleteInput::URL },
321    { ASCIIToUTF16("view-source:http://www.foo.com/"), AutocompleteInput::URL },
322    { ASCIIToUTF16("javascript:alert(\"Hey there!\");"),
323        AutocompleteInput::URL },
324#if defined(OS_WIN)
325    { ASCIIToUTF16("C:\\Program Files"), AutocompleteInput::URL },
326    { ASCIIToUTF16("\\\\Server\\Folder\\File"), AutocompleteInput::URL },
327#endif  // defined(OS_WIN)
328    { ASCIIToUTF16("http:foo"), AutocompleteInput::URL },
329    { ASCIIToUTF16("http://foo"), AutocompleteInput::URL },
330    { ASCIIToUTF16("http://foo.c"), AutocompleteInput::URL },
331    { ASCIIToUTF16("http://foo.com"), AutocompleteInput::URL },
332    { ASCIIToUTF16("http://foo_bar.com"), AutocompleteInput::URL },
333    { ASCIIToUTF16("http://foo/bar baz"), AutocompleteInput::URL },
334    { ASCIIToUTF16("http://-.com"), AutocompleteInput::UNKNOWN },
335    { ASCIIToUTF16("http://_foo_.com"), AutocompleteInput::UNKNOWN },
336    { ASCIIToUTF16("http://foo.com:abc"), AutocompleteInput::QUERY },
337    { ASCIIToUTF16("http://foo.com:123456"), AutocompleteInput::QUERY },
338    { ASCIIToUTF16("http://1.2.3.4:abc"), AutocompleteInput::QUERY },
339    { ASCIIToUTF16("http:user@foo.com"), AutocompleteInput::URL },
340    { ASCIIToUTF16("http://user@foo.com"), AutocompleteInput::URL },
341    { ASCIIToUTF16("http:user:pass@foo.com"), AutocompleteInput::URL },
342    { ASCIIToUTF16("http://user:pass@foo.com"), AutocompleteInput::URL },
343    { ASCIIToUTF16("http://1.2"), AutocompleteInput::URL },
344    { ASCIIToUTF16("http://1.2/45"), AutocompleteInput::URL },
345    { ASCIIToUTF16("http:ps/2 games"), AutocompleteInput::URL },
346    { ASCIIToUTF16("http://ps/2 games"), AutocompleteInput::URL },
347    { ASCIIToUTF16("https://foo.com"), AutocompleteInput::URL },
348    { ASCIIToUTF16("127.0.0.1"), AutocompleteInput::URL },
349    { ASCIIToUTF16("127.0.1"), AutocompleteInput::UNKNOWN },
350    { ASCIIToUTF16("127.0.1/"), AutocompleteInput::UNKNOWN },
351    { ASCIIToUTF16("browser.tabs.closeButtons"), AutocompleteInput::UNKNOWN },
352    { WideToUTF16(L"\u6d4b\u8bd5"), AutocompleteInput::UNKNOWN },
353    { ASCIIToUTF16("[2001:]"), AutocompleteInput::QUERY },  // Not a valid IP
354    { ASCIIToUTF16("[2001:dB8::1]"), AutocompleteInput::URL },
355    { ASCIIToUTF16("192.168.0.256"),
356        AutocompleteInput::QUERY },  // Invalid IPv4 literal.
357    { ASCIIToUTF16("[foo.com]"),
358        AutocompleteInput::QUERY },  // Invalid IPv6 literal.
359  };
360
361  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
362    SCOPED_TRACE(input_cases[i].input);
363    AutocompleteInput input(input_cases[i].input, string16(), true, false,
364                            true, AutocompleteInput::ALL_MATCHES);
365    EXPECT_EQ(input_cases[i].type, input.type());
366  }
367}
368
369TEST_F(AutocompleteTest, InputTypeWithDesiredTLD) {
370  struct test_data {
371    const string16 input;
372    const AutocompleteInput::Type type;
373  } input_cases[] = {
374    { ASCIIToUTF16("401k"), AutocompleteInput::REQUESTED_URL },
375    { ASCIIToUTF16("999999999999999"), AutocompleteInput::REQUESTED_URL },
376  };
377
378  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
379    AutocompleteInput input(input_cases[i].input, ASCIIToUTF16("com"), true,
380                            false, true, AutocompleteInput::ALL_MATCHES);
381    EXPECT_EQ(input_cases[i].type, input.type()) << "Input: " <<
382        input_cases[i].input;
383  }
384}
385
386// This tests for a regression where certain input in the omnibox caused us to
387// crash. As long as the test completes without crashing, we're fine.
388TEST_F(AutocompleteTest, InputCrash) {
389  AutocompleteInput input(WideToUTF16(L"\uff65@s"), string16(), true, false,
390                          true, AutocompleteInput::ALL_MATCHES);
391}
392
393// Test comparing matches relevance.
394TEST(AutocompleteMatch, MoreRelevant) {
395  struct RelevantCases {
396    int r1;
397    int r2;
398    bool expected_result;
399  } cases[] = {
400    {  10,   0, true  },
401    {  10,  -5, true  },
402    {  -5,  10, false },
403    {   0,  10, false },
404    { -10,  -5, false  },
405    {  -5, -10, true },
406  };
407
408  AutocompleteMatch m1(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED);
409  AutocompleteMatch m2(NULL, 0, false, AutocompleteMatch::URL_WHAT_YOU_TYPED);
410
411  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
412    m1.relevance = cases[i].r1;
413    m2.relevance = cases[i].r2;
414    EXPECT_EQ(cases[i].expected_result,
415              AutocompleteMatch::MoreRelevant(m1, m2));
416  }
417}
418
419TEST(AutocompleteInput, ParseForEmphasizeComponent) {
420  using url_parse::Component;
421  Component kInvalidComponent(0, -1);
422  struct test_data {
423    const string16 input;
424    const Component scheme;
425    const Component host;
426  } input_cases[] = {
427    { ASCIIToUTF16(""), kInvalidComponent, kInvalidComponent },
428    { ASCIIToUTF16("?"), kInvalidComponent, kInvalidComponent },
429    { ASCIIToUTF16("?http://foo.com/bar"), kInvalidComponent,
430        kInvalidComponent },
431    { ASCIIToUTF16("foo/bar baz"), kInvalidComponent, Component(0, 3) },
432    { ASCIIToUTF16("http://foo/bar baz"), Component(0, 4), Component(7, 3) },
433    { ASCIIToUTF16("link:foo.com"), Component(0, 4), kInvalidComponent },
434    { ASCIIToUTF16("www.foo.com:81"), kInvalidComponent, Component(0, 11) },
435    { WideToUTF16(L"\u6d4b\u8bd5"), kInvalidComponent, Component(0, 2) },
436    { ASCIIToUTF16("view-source:http://www.foo.com/"), Component(12, 4),
437        Component(19, 11) },
438    { ASCIIToUTF16("view-source:https://example.com/"),
439      Component(12, 5), Component(20, 11) },
440    { ASCIIToUTF16("view-source:www.foo.com"), kInvalidComponent,
441        Component(12, 11) },
442    { ASCIIToUTF16("view-source:"), Component(0, 11), kInvalidComponent },
443    { ASCIIToUTF16("view-source:garbage"), kInvalidComponent,
444        Component(12, 7) },
445    { ASCIIToUTF16("view-source:http://http://foo"), Component(12, 4),
446        Component(19, 4) },
447    { ASCIIToUTF16("view-source:view-source:http://example.com/"),
448        Component(12, 11), kInvalidComponent }
449  };
450
451  ScopedTestingBrowserProcess browser_process;
452
453  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_cases); ++i) {
454    Component scheme, host;
455    AutocompleteInput::ParseForEmphasizeComponents(input_cases[i].input,
456                                                   string16(),
457                                                   &scheme,
458                                                   &host);
459    AutocompleteInput input(input_cases[i].input, string16(), true, false,
460                            true, AutocompleteInput::ALL_MATCHES);
461    EXPECT_EQ(input_cases[i].scheme.begin, scheme.begin) << "Input: " <<
462        input_cases[i].input;
463    EXPECT_EQ(input_cases[i].scheme.len, scheme.len) << "Input: " <<
464        input_cases[i].input;
465    EXPECT_EQ(input_cases[i].host.begin, host.begin) << "Input: " <<
466        input_cases[i].input;
467    EXPECT_EQ(input_cases[i].host.len, host.len) << "Input: " <<
468        input_cases[i].input;
469  }
470}
471
472}  // namespace
473