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/file_util.h"
6#include "base/path_service.h"
7#include "base/string_util.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/browser/autocomplete/autocomplete.h"
10#include "chrome/browser/autocomplete/autocomplete_match.h"
11#include "chrome/browser/autocomplete/history_contents_provider.h"
12#include "chrome/browser/bookmarks/bookmark_model.h"
13#include "chrome/browser/history/history.h"
14#include "chrome/test/testing_browser_process.h"
15#include "chrome/test/testing_browser_process_test.h"
16#include "chrome/test/testing_profile.h"
17#include "content/browser/browser_thread.h"
18#include "testing/gtest/include/gtest/gtest.h"
19
20using base::Time;
21using base::TimeDelta;
22
23namespace {
24
25struct TestEntry {
26  const char* url;
27  const char* title;
28  const char* body;
29} test_entries[] = {
30  {"http://www.google.com/1", "PAGEONE 1",   "FOO some body text"},
31  {"http://www.google.com/2", "PAGEONE 2",   "FOO some more blah blah"},
32  {"http://www.google.com/3", "PAGETHREE 3", "BAR some hello world for you"},
33};
34
35class HistoryContentsProviderTest : public TestingBrowserProcessTest,
36                                    public ACProviderListener {
37 public:
38  HistoryContentsProviderTest()
39      : ui_thread_(BrowserThread::UI, &message_loop_),
40        file_thread_(BrowserThread::FILE, &message_loop_) {}
41
42  void RunQuery(const AutocompleteInput& input,
43                bool minimal_changes) {
44    provider_->Start(input, minimal_changes);
45
46    // When we're waiting for asynchronous messages, we have to spin the message
47    // loop. This will be exited in the OnProviderUpdate function when complete.
48    if (input.matches_requested() == AutocompleteInput::ALL_MATCHES)
49      MessageLoop::current()->Run();
50  }
51
52  const ACMatches& matches() const { return provider_->matches(); }
53
54  TestingProfile* profile() const { return profile_.get(); }
55
56  HistoryContentsProvider* provider() const { return provider_.get(); }
57
58 private:
59  // testing::Test
60  virtual void SetUp() {
61    profile_.reset(new TestingProfile());
62    profile_->CreateHistoryService(false, false);
63
64    HistoryService* history_service =
65        profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
66
67    // Populate history.
68    for (size_t i = 0; i < arraysize(test_entries); i++) {
69      // We need the ID scope and page ID so that the visit tracker can find it.
70      // We just use the index for the page ID below.
71      const void* id_scope = reinterpret_cast<void*>(1);
72      GURL url(test_entries[i].url);
73
74      // Add everything in order of time. We don't want to have a time that
75      // is "right now" or it will nondeterministically appear in the results.
76      Time t = Time::Now() - TimeDelta::FromDays(arraysize(test_entries) + i);
77
78      history_service->AddPage(url, t, id_scope, i, GURL(),
79                               PageTransition::LINK, history::RedirectList(),
80                               history::SOURCE_BROWSED, false);
81      history_service->SetPageTitle(url, UTF8ToUTF16(test_entries[i].title));
82      history_service->SetPageContents(url, UTF8ToUTF16(test_entries[i].body));
83    }
84
85    provider_ = new HistoryContentsProvider(this, profile_.get());
86  }
87
88  virtual void TearDown() {
89    provider_ = NULL;
90    profile_.reset(NULL);
91  }
92
93  // ACProviderListener
94  virtual void OnProviderUpdate(bool updated_matches) {
95    // We must quit the message loop (if running) to return control to the test.
96    // Note, calling Quit() directly will checkfail if the loop isn't running,
97    // so we post a task, which is safe for either case.
98    MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
99  }
100
101  MessageLoopForUI message_loop_;
102  BrowserThread ui_thread_;
103  BrowserThread file_thread_;
104
105  scoped_ptr<TestingProfile> profile_;
106  scoped_refptr<HistoryContentsProvider> provider_;
107};
108
109TEST_F(HistoryContentsProviderTest, Body) {
110  AutocompleteInput input(ASCIIToUTF16("FOO"), string16(), true, false, true,
111                          AutocompleteInput::ALL_MATCHES);
112  RunQuery(input, false);
113
114  // The results should be the first two pages, in decreasing order.
115  const ACMatches& m = matches();
116  ASSERT_EQ(2U, m.size());
117  EXPECT_EQ(test_entries[0].url, m[0].destination_url.spec());
118  EXPECT_STREQ(test_entries[0].title, UTF16ToUTF8(m[0].description).c_str());
119  EXPECT_EQ(test_entries[1].url, m[1].destination_url.spec());
120  EXPECT_STREQ(test_entries[1].title, UTF16ToUTF8(m[1].description).c_str());
121}
122
123TEST_F(HistoryContentsProviderTest, Title) {
124  AutocompleteInput input(ASCIIToUTF16("PAGEONE"), string16(), true, false,
125                          true, AutocompleteInput::ALL_MATCHES);
126  RunQuery(input, false);
127
128  // The results should be the first two pages.
129  const ACMatches& m = matches();
130  ASSERT_EQ(2U, m.size());
131  EXPECT_EQ(test_entries[0].url, m[0].destination_url.spec());
132  EXPECT_STREQ(test_entries[0].title, UTF16ToUTF8(m[0].description).c_str());
133  EXPECT_EQ(test_entries[1].url, m[1].destination_url.spec());
134  EXPECT_STREQ(test_entries[1].title, UTF16ToUTF8(m[1].description).c_str());
135}
136
137// The "minimal changes" flag should mean that we don't re-query the DB.
138TEST_F(HistoryContentsProviderTest, MinimalChanges) {
139  // A minimal changes request when there have been no real queries should
140  // give us no results.
141  AutocompleteInput sync_input(ASCIIToUTF16("PAGEONE"), string16(), true, false,
142                               true, AutocompleteInput::SYNCHRONOUS_MATCHES);
143  RunQuery(sync_input, true);
144  const ACMatches& m1 = matches();
145  EXPECT_EQ(0U, m1.size());
146
147  // Now do a "regular" query to get the results.
148  AutocompleteInput async_input(ASCIIToUTF16("PAGEONE"), string16(), true,
149                                false, true, AutocompleteInput::ALL_MATCHES);
150  RunQuery(async_input, false);
151  const ACMatches& m2 = matches();
152  EXPECT_EQ(2U, m2.size());
153
154  // Now do a minimal one where we want synchronous results, and the results
155  // should still be there.
156  RunQuery(sync_input, true);
157  const ACMatches& m3 = matches();
158  EXPECT_EQ(2U, m3.size());
159}
160
161// Tests that the BookmarkModel is queried correctly.
162TEST_F(HistoryContentsProviderTest, Bookmarks) {
163  profile()->CreateBookmarkModel(false);
164  profile()->BlockUntilBookmarkModelLoaded();
165
166  // Add a bookmark.
167  GURL bookmark_url("http://www.google.com/4");
168  profile()->GetBookmarkModel()->SetURLStarred(bookmark_url,
169                                               ASCIIToUTF16("bar"), true);
170
171  // Ask for synchronous. This should only get the bookmark.
172  AutocompleteInput sync_input(ASCIIToUTF16("bar"), string16(), true, false,
173                               true, AutocompleteInput::SYNCHRONOUS_MATCHES);
174  RunQuery(sync_input, false);
175  const ACMatches& m1 = matches();
176  ASSERT_EQ(1U, m1.size());
177  EXPECT_EQ(bookmark_url, m1[0].destination_url);
178  EXPECT_EQ(ASCIIToUTF16("bar"), m1[0].description);
179  EXPECT_TRUE(m1[0].starred);
180
181  // Ask for async. We should get the bookmark immediately.
182  AutocompleteInput async_input(ASCIIToUTF16("bar"), string16(), true, false,
183                                true, AutocompleteInput::ALL_MATCHES);
184  provider()->Start(async_input, false);
185  const ACMatches& m2 = matches();
186  ASSERT_EQ(1U, m2.size());
187  EXPECT_EQ(bookmark_url, m2[0].destination_url);
188
189  // Run the message loop (needed for async history results).
190  MessageLoop::current()->Run();
191
192  // We should have two urls now, bookmark_url and http://www.google.com/3.
193  const ACMatches& m3 = matches();
194  ASSERT_EQ(2U, m3.size());
195  if (bookmark_url == m3[0].destination_url) {
196    EXPECT_EQ("http://www.google.com/3", m3[1].destination_url.spec());
197  } else {
198    EXPECT_EQ(bookmark_url, m3[1].destination_url);
199    EXPECT_EQ("http://www.google.com/3", m3[0].destination_url.spec());
200  }
201}
202
203// Tests that history is deleted properly.
204TEST_F(HistoryContentsProviderTest, DeleteMatch) {
205  AutocompleteInput input(ASCIIToUTF16("bar"), string16(), true, false, true,
206                          AutocompleteInput::ALL_MATCHES);
207  RunQuery(input, false);
208
209  // Query; the result should be the third page.
210  const ACMatches& m = matches();
211  ASSERT_EQ(1U, m.size());
212  EXPECT_EQ(test_entries[2].url, m[0].destination_url.spec());
213
214  // Now delete the match and ensure it was removed.
215  provider()->DeleteMatch(m[0]);
216  EXPECT_EQ(0U, matches().size());
217}
218
219// Tests deleting starred results from history, not affecting bookmarks/matches.
220TEST_F(HistoryContentsProviderTest, DeleteStarredMatch) {
221  profile()->CreateBookmarkModel(false);
222  profile()->BlockUntilBookmarkModelLoaded();
223
224  // Bookmark a history item.
225  GURL bookmark_url(test_entries[2].url);
226  profile()->GetBookmarkModel()->SetURLStarred(bookmark_url,
227                                               ASCIIToUTF16("bar"), true);
228
229  // Get the match to delete its history
230  AutocompleteInput input(ASCIIToUTF16("bar"), string16(), true, false, true,
231                          AutocompleteInput::ALL_MATCHES);
232  RunQuery(input, false);
233  const ACMatches& m = matches();
234  ASSERT_EQ(1U, m.size());
235
236  // Now delete the match and ensure it was *not* removed.
237  provider()->DeleteMatch(m[0]);
238  EXPECT_EQ(1U, matches().size());
239
240  // Run a query that would only match history (but the history is deleted)
241  AutocompleteInput you_input(ASCIIToUTF16("you"), string16(), true, false,
242                              true, AutocompleteInput::ALL_MATCHES);
243  RunQuery(you_input, false);
244  EXPECT_EQ(0U, matches().size());
245
246  // Run a query that matches the bookmark
247  RunQuery(input, false);
248  EXPECT_EQ(1U, matches().size());
249}
250
251}  // namespace
252