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/contact_provider_chromeos.h"
6
7#include <cmath>
8#include <map>
9#include <string>
10#include <vector>
11
12#include "base/memory/scoped_ptr.h"
13#include "base/message_loop/message_loop.h"
14#include "base/strings/string16.h"
15#include "base/strings/string_number_conversions.h"
16#include "base/strings/utf_string_conversions.h"
17#include "chrome/browser/autocomplete/autocomplete_input.h"
18#include "chrome/browser/autocomplete/autocomplete_match.h"
19#include "chrome/browser/autocomplete/autocomplete_provider.h"
20#include "chrome/browser/chromeos/contacts/contact.pb.h"
21#include "chrome/browser/chromeos/contacts/contact_manager_stub.h"
22#include "chrome/browser/chromeos/contacts/contact_test_util.h"
23#include "chrome/test/base/testing_browser_process.h"
24#include "chrome/test/base/testing_profile.h"
25#include "chrome/test/base/testing_profile_manager.h"
26#include "content/public/browser/browser_thread.h"
27#include "content/public/test/test_browser_thread.h"
28#include "testing/gtest/include/gtest/gtest.h"
29
30using content::BrowserThread;
31
32namespace {
33
34// Initializes |contact| with the passed-in data.
35void InitContact(const std::string& contact_id,
36                 const std::string& full_name,
37                 const std::string& given_name,
38                 const std::string& family_name,
39                 contacts::Contact* contact) {
40  contact->set_contact_id(contact_id);
41  contact->set_full_name(full_name);
42  contact->set_given_name(given_name);
43  contact->set_family_name(family_name);
44}
45
46}  // namespace
47
48class ContactProviderTest : public testing::Test {
49 public:
50  ContactProviderTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
51  virtual ~ContactProviderTest() {}
52
53 protected:
54  // testing::Test implementation.
55  virtual void SetUp() OVERRIDE {
56    profile_manager_.reset(
57        new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
58    ASSERT_TRUE(profile_manager_->SetUp());
59    profile_ = profile_manager_->CreateTestingProfile("test_profile");
60    contact_manager_.reset(new contacts::ContactManagerStub(profile_));
61    contact_provider_ =
62        new ContactProvider(NULL, profile_, contact_manager_->GetWeakPtr());
63  }
64
65  // Starts a (synchronous) query for |utf8_text| in |contact_provider_|.
66  void StartQuery(const std::string& utf8_text) {
67    contact_provider_->Start(
68        AutocompleteInput(UTF8ToUTF16(utf8_text),
69                          base::string16::npos,
70                          base::string16(),
71                          GURL(),
72                          AutocompleteInput::INVALID_SPEC,
73                          false,
74                          false,
75                          false,
76                          AutocompleteInput::ALL_MATCHES),
77        false);  // minimal_changes
78  }
79
80  // Returns the contact ID in |match|'s additional info, or an empty string if
81  // no ID is present.
82  std::string GetContactIdFromMatch(const AutocompleteMatch& match) {
83    AutocompleteMatch::AdditionalInfo::const_iterator it =
84        match.additional_info.find(ContactProvider::kMatchContactIdKey);
85    return it != match.additional_info.end() ? it->second : std::string();
86  }
87
88  // Returns pointers to all of the Contact objects referenced in
89  // |contact_provider_|'s current results.
90  contacts::ContactPointers GetMatchedContacts() {
91    contacts::ContactPointers contacts;
92    const ACMatches& matches = contact_provider_->matches();
93    for (size_t i = 0; i < matches.size(); ++i) {
94      const contacts::Contact* contact = contact_manager_->GetContactById(
95          profile_, GetContactIdFromMatch(matches[i]));
96      DCHECK(contact) << "Unable to find contact for match " << i;
97      contacts.push_back(contact);
98    }
99    return contacts;
100  }
101
102  // Returns a semicolon-separated string containing string representations (as
103  // provided by AutocompleteMatch::ClassificationsToString()) of the
104  // |contents_class| fields of all current matches.  Results are sorted by
105  // contact ID.
106  std::string GetMatchClassifications() {
107    typedef std::map<std::string, std::string> StringMap;
108    StringMap contact_id_classifications;
109    const ACMatches& matches = contact_provider_->matches();
110    for (size_t i = 0; i < matches.size(); ++i) {
111      std::string id = GetContactIdFromMatch(matches[i]);
112      DCHECK(!id.empty()) << "Match " << i << " lacks contact ID";
113      contact_id_classifications[id] = AutocompleteMatch::
114          ClassificationsToString(matches[i].contents_class);
115    }
116
117    std::string result;
118    for (StringMap::const_iterator it = contact_id_classifications.begin();
119         it != contact_id_classifications.end(); ++it) {
120      if (!result.empty())
121        result += ";";
122      result += it->second;
123    }
124    return result;
125  }
126
127  base::MessageLoopForUI message_loop_;
128  content::TestBrowserThread ui_thread_;
129
130  scoped_ptr<TestingProfileManager> profile_manager_;
131  TestingProfile* profile_;
132
133  scoped_ptr<contacts::ContactManagerStub> contact_manager_;
134  scoped_refptr<ContactProvider> contact_provider_;
135};
136
137TEST_F(ContactProviderTest, BasicMatching) {
138  const std::string kContactId1 = "contact_1";
139  scoped_ptr<contacts::Contact> contact1(new contacts::Contact);
140  InitContact(kContactId1, "Bob Smith", "Bob", "Smith", contact1.get());
141
142  const std::string kContactId2 = "contact_2";
143  scoped_ptr<contacts::Contact> contact2(new contacts::Contact);
144  InitContact(kContactId2, "Dr. Jane Smith", "Jane", "Smith", contact2.get());
145
146  contacts::ContactPointers contacts;
147  contacts.push_back(contact1.get());
148  contacts.push_back(contact2.get());
149  contact_manager_->SetContacts(contacts);
150  contact_manager_->NotifyObserversAboutUpdatedContacts();
151
152  StartQuery("b");
153  EXPECT_EQ(
154      contacts::test::VarContactsToString(1, contact1.get()),
155      contacts::test::ContactsToString(GetMatchedContacts()));
156  EXPECT_EQ("0,2,1,0", GetMatchClassifications());
157
158  StartQuery("bob");
159  EXPECT_EQ(
160      contacts::test::VarContactsToString(1, contact1.get()),
161      contacts::test::ContactsToString(GetMatchedContacts()));
162  EXPECT_EQ("0,2,3,0", GetMatchClassifications());
163
164  StartQuery("bob smith");
165  EXPECT_EQ(
166      contacts::test::VarContactsToString(1, contact1.get()),
167      contacts::test::ContactsToString(GetMatchedContacts()));
168  EXPECT_EQ("0,2", GetMatchClassifications());
169
170  StartQuery("sm");
171  EXPECT_EQ(
172      contacts::test::VarContactsToString(2, contact1.get(), contact2.get()),
173      contacts::test::ContactsToString(GetMatchedContacts()));
174  EXPECT_EQ("0,0,4,2,6,0;" "0,0,9,2,11,0", GetMatchClassifications());
175
176  StartQuery("smith");
177  EXPECT_EQ(
178      contacts::test::VarContactsToString(2, contact1.get(), contact2.get()),
179      contacts::test::ContactsToString(GetMatchedContacts()));
180  EXPECT_EQ("0,0,4,2;" "0,0,9,2", GetMatchClassifications());
181
182  StartQuery("smIth BOb");
183  EXPECT_EQ(
184      contacts::test::VarContactsToString(1, contact1.get()),
185      contacts::test::ContactsToString(GetMatchedContacts()));
186  EXPECT_EQ("0,2,3,0,4,2", GetMatchClassifications());
187
188  StartQuery("bobo");
189  EXPECT_EQ("", contacts::test::ContactsToString(GetMatchedContacts()));
190  EXPECT_EQ("", GetMatchClassifications());
191
192  StartQuery("mith");
193  EXPECT_EQ("", contacts::test::ContactsToString(GetMatchedContacts()));
194  EXPECT_EQ("", GetMatchClassifications());
195
196  StartQuery("dr");
197  EXPECT_EQ(
198      contacts::test::VarContactsToString(1, contact2.get()),
199      contacts::test::ContactsToString(GetMatchedContacts()));
200  EXPECT_EQ("0,2,2,0", GetMatchClassifications());
201
202  StartQuery("dr. j");
203  EXPECT_EQ(
204      contacts::test::VarContactsToString(1, contact2.get()),
205      contacts::test::ContactsToString(GetMatchedContacts()));
206  EXPECT_EQ("0,2,5,0", GetMatchClassifications());
207
208  StartQuery("jane");
209  EXPECT_EQ(
210      contacts::test::VarContactsToString(1, contact2.get()),
211      contacts::test::ContactsToString(GetMatchedContacts()));
212  EXPECT_EQ("0,0,4,2,8,0", GetMatchClassifications());
213}
214
215TEST_F(ContactProviderTest, Collation) {
216  scoped_ptr<contacts::Contact> contact(new contacts::Contact);
217  InitContact("1", "Bj\xC3\xB6rn Adelsv\xC3\xA4rd",
218              "Bj\xC3\xB6rn", "Adelsv\xC3\xA4rd",
219              contact.get());
220
221  contacts::ContactPointers contacts;
222  contacts.push_back(contact.get());
223  contact_manager_->SetContacts(contacts);
224  contact_manager_->NotifyObserversAboutUpdatedContacts();
225
226  StartQuery("bjorn");
227  EXPECT_EQ(
228      contacts::test::VarContactsToString(1, contact.get()),
229      contacts::test::ContactsToString(GetMatchedContacts()));
230  EXPECT_EQ("0,2,5,0", GetMatchClassifications());
231
232  StartQuery("adelsvard");
233  EXPECT_EQ(
234      contacts::test::VarContactsToString(1, contact.get()),
235      contacts::test::ContactsToString(GetMatchedContacts()));
236  EXPECT_EQ("0,0,6,2", GetMatchClassifications());
237}
238
239TEST_F(ContactProviderTest, Relevance) {
240  // Create more contacts than the maximum number of results that an
241  // AutocompleteProvider should return.  Give them all the same family name and
242  // ascending affinities from 0.0 to 1.0.
243  const size_t kNumContacts = AutocompleteProvider::kMaxMatches + 1;
244  const std::string kFamilyName = "Jones";
245
246  ScopedVector<contacts::Contact> contacts;
247  contacts::ContactPointers contact_pointers;
248  for (size_t i = 0; i < kNumContacts; ++i) {
249    contacts::Contact* contact = new contacts::Contact;
250    std::string id_string = base::IntToString(static_cast<int>(i));
251    InitContact(id_string, id_string, kFamilyName,
252                id_string + " " + kFamilyName, contact);
253    contact->set_affinity(static_cast<float>(i) / kNumContacts);
254    contacts.push_back(contact);
255    contact_pointers.push_back(contact);
256  }
257
258  contact_manager_->SetContacts(contact_pointers);
259  contact_manager_->NotifyObserversAboutUpdatedContacts();
260
261  // Do a search for the family name and check that the total number of results
262  // is limited as expected and that the results are ordered by descending
263  // affinity.
264  StartQuery(kFamilyName);
265  const ACMatches& matches = contact_provider_->matches();
266  ASSERT_EQ(AutocompleteProvider::kMaxMatches, matches.size());
267
268  int previous_relevance = 0;
269  for (size_t i = 0; i < matches.size(); ++i) {
270    const contacts::Contact& exp_contact =
271        *(contacts[kNumContacts - 1 - i]);
272    std::string match_id = GetContactIdFromMatch(matches[i]);
273    EXPECT_EQ(exp_contact.contact_id(), match_id)
274        << "Expected contact ID " << exp_contact.contact_id()
275        << " for match " << i << " but got " << match_id << " instead";
276    if (i > 0) {
277      EXPECT_LE(matches[i].relevance, previous_relevance)
278          << "Match " << i << " has greater relevance than previous match";
279    }
280    EXPECT_FALSE(matches[i].allowed_to_be_default_match);
281    previous_relevance = matches[i].relevance;
282  }
283}
284