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/chromeos/contacts/contact_database.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/file_util.h"
11#include "base/files/file_enumerator.h"
12#include "base/files/file_path.h"
13#include "base/files/scoped_temp_dir.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/memory/scoped_vector.h"
16#include "base/message_loop/message_loop.h"
17#include "chrome/browser/chromeos/contacts/contact.pb.h"
18#include "chrome/browser/chromeos/contacts/contact_test_util.h"
19#include "content/public/browser/browser_thread.h"
20#include "content/public/test/test_browser_thread.h"
21#include "testing/gtest/include/gtest/gtest.h"
22#include "ui/gfx/size.h"
23
24using content::BrowserThread;
25
26namespace contacts {
27namespace test {
28
29// Name of the directory created within a temporary directory to store the
30// contacts database.
31const base::FilePath::CharType kDatabaseDirectoryName[] =
32    FILE_PATH_LITERAL("contacts");
33
34class ContactDatabaseTest : public testing::Test {
35 public:
36  ContactDatabaseTest()
37      : ui_thread_(BrowserThread::UI, &message_loop_),
38        db_(NULL) {
39  }
40
41  virtual ~ContactDatabaseTest() {
42  }
43
44 protected:
45  // testing::Test implementation.
46  virtual void SetUp() OVERRIDE {
47    CHECK(temp_dir_.CreateUniqueTempDir());
48    CreateDatabase();
49  }
50
51  virtual void TearDown() OVERRIDE {
52    DestroyDatabase();
53  }
54
55 protected:
56  base::FilePath database_path() const {
57    return temp_dir_.path().Append(kDatabaseDirectoryName);
58  }
59
60  void CreateDatabase() {
61    DestroyDatabase();
62    db_ = new ContactDatabase;
63    db_->Init(database_path(),
64              base::Bind(&ContactDatabaseTest::OnDatabaseInitialized,
65                         base::Unretained(this)));
66
67    // The database will be initialized on the file thread; run the message loop
68    // until that happens.
69    message_loop_.Run();
70  }
71
72  void DestroyDatabase() {
73    if (db_) {
74      db_->DestroyOnUIThread();
75      db_ = NULL;
76    }
77  }
78
79  // Calls ContactDatabase::SaveContacts() and blocks until the operation is
80  // complete.
81  void SaveContacts(scoped_ptr<ContactPointers> contacts_to_save,
82                    scoped_ptr<ContactDatabaseInterface::ContactIds>
83                        contact_ids_to_delete,
84                    scoped_ptr<UpdateMetadata> metadata,
85                    bool is_full_update) {
86    CHECK(db_);
87    db_->SaveContacts(contacts_to_save.Pass(),
88                      contact_ids_to_delete.Pass(),
89                      metadata.Pass(),
90                      is_full_update,
91                      base::Bind(&ContactDatabaseTest::OnContactsSaved,
92                                 base::Unretained(this)));
93    message_loop_.Run();
94  }
95
96  // Calls ContactDatabase::LoadContacts() and blocks until the operation is
97  // complete.
98  void LoadContacts(scoped_ptr<ScopedVector<Contact> >* contacts_out,
99                    scoped_ptr<UpdateMetadata>* metadata_out) {
100    CHECK(db_);
101    db_->LoadContacts(base::Bind(&ContactDatabaseTest::OnContactsLoaded,
102                                 base::Unretained(this)));
103    message_loop_.Run();
104    contacts_out->swap(loaded_contacts_);
105    metadata_out->swap(loaded_metadata_);
106  }
107
108 private:
109  void OnDatabaseInitialized(bool success) {
110    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
111    CHECK(success);
112    // TODO(derat): Move google_apis::test::RunBlockingPoolTask() to a shared
113    // location and use it for these tests.
114    message_loop_.Quit();
115  }
116
117  void OnContactsSaved(bool success) {
118    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
119    CHECK(success);
120    message_loop_.Quit();
121  }
122
123  void OnContactsLoaded(bool success,
124                        scoped_ptr<ScopedVector<Contact> > contacts,
125                        scoped_ptr<UpdateMetadata> metadata) {
126    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127    CHECK(success);
128    loaded_contacts_.swap(contacts);
129    loaded_metadata_.swap(metadata);
130    message_loop_.Quit();
131  }
132
133  base::MessageLoopForUI message_loop_;
134  content::TestBrowserThread ui_thread_;
135
136  // Temporary directory where the database is saved.
137  base::ScopedTempDir temp_dir_;
138
139  // This class retains ownership of this object.
140  ContactDatabase* db_;
141
142  // Contacts and metadata returned by the most-recent
143  // ContactDatabase::LoadContacts() call.  Used to pass returned values from
144  // OnContactsLoaded() to LoadContacts().
145  scoped_ptr<ScopedVector<Contact> > loaded_contacts_;
146  scoped_ptr<UpdateMetadata> loaded_metadata_;
147
148  DISALLOW_COPY_AND_ASSIGN(ContactDatabaseTest);
149};
150
151TEST_F(ContactDatabaseTest, SaveAndReload) {
152  // Save a contact to the database and check that we get the same data back
153  // when loading it.
154  const std::string kContactId = "contact_id_1";
155  scoped_ptr<Contact> contact(new Contact);
156  InitContact(kContactId, "1", false, contact.get());
157  AddEmailAddress("email_1", Contact_AddressType_Relation_HOME,
158                  "email_label_1", true, contact.get());
159  AddEmailAddress("email_2", Contact_AddressType_Relation_WORK,
160                  "", false, contact.get());
161  AddPhoneNumber("123-456-7890", Contact_AddressType_Relation_HOME,
162                 "phone_label", true, contact.get());
163  AddPostalAddress("postal_1", Contact_AddressType_Relation_HOME,
164                   "postal_label_1", true, contact.get());
165  AddPostalAddress("postal_2", Contact_AddressType_Relation_OTHER,
166                   "postal_label_2", false, contact.get());
167  AddInstantMessagingAddress("im_1",
168                             Contact_InstantMessagingAddress_Protocol_AIM,
169                             Contact_AddressType_Relation_HOME,
170                             "im_label_1", true, contact.get());
171  SetPhoto(gfx::Size(20, 20), contact.get());
172  scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
173  contacts_to_save->push_back(contact.get());
174  scoped_ptr<ContactDatabaseInterface::ContactIds> contact_ids_to_delete(
175      new ContactDatabaseInterface::ContactIds);
176
177  const int64 kLastUpdateTime = 1234;
178  scoped_ptr<UpdateMetadata> metadata_to_save(new UpdateMetadata);
179  metadata_to_save->set_last_update_start_time(kLastUpdateTime);
180
181  SaveContacts(contacts_to_save.Pass(),
182               contact_ids_to_delete.Pass(),
183               metadata_to_save.Pass(),
184               true);
185  scoped_ptr<ScopedVector<Contact> > loaded_contacts;
186  scoped_ptr<UpdateMetadata> loaded_metadata;
187  LoadContacts(&loaded_contacts, &loaded_metadata);
188  EXPECT_EQ(VarContactsToString(1, contact.get()),
189            ContactsToString(*loaded_contacts));
190  EXPECT_EQ(kLastUpdateTime, loaded_metadata->last_update_start_time());
191
192  // Modify the contact, save it, and check that the loaded contact is also
193  // updated.
194  InitContact(kContactId, "2", false, contact.get());
195  AddEmailAddress("email_3", Contact_AddressType_Relation_OTHER,
196                  "email_label_2", true, contact.get());
197  AddPhoneNumber("phone_2", Contact_AddressType_Relation_OTHER,
198                 "phone_label_2", false, contact.get());
199  AddPostalAddress("postal_3", Contact_AddressType_Relation_HOME,
200                   "postal_label_3", true, contact.get());
201  SetPhoto(gfx::Size(64, 64), contact.get());
202  contacts_to_save.reset(new ContactPointers);
203  contacts_to_save->push_back(contact.get());
204  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
205  metadata_to_save.reset(new UpdateMetadata);
206  const int64 kNewLastUpdateTime = 5678;
207  metadata_to_save->set_last_update_start_time(kNewLastUpdateTime);
208  SaveContacts(contacts_to_save.Pass(),
209               contact_ids_to_delete.Pass(),
210               metadata_to_save.Pass(),
211               true);
212
213  LoadContacts(&loaded_contacts, &loaded_metadata);
214  EXPECT_EQ(VarContactsToString(1, contact.get()),
215            ContactsToString(*loaded_contacts));
216  EXPECT_EQ(kNewLastUpdateTime, loaded_metadata->last_update_start_time());
217}
218
219TEST_F(ContactDatabaseTest, FullAndIncrementalUpdates) {
220  // Do a full update that inserts two contacts into the database.
221  const std::string kContactId1 = "contact_id_1";
222  const std::string kSharedEmail = "foo@example.org";
223  scoped_ptr<Contact> contact1(new Contact);
224  InitContact(kContactId1, "1", false, contact1.get());
225  AddEmailAddress(kSharedEmail, Contact_AddressType_Relation_HOME,
226                  "", true, contact1.get());
227
228  const std::string kContactId2 = "contact_id_2";
229  scoped_ptr<Contact> contact2(new Contact);
230  InitContact(kContactId2, "2", false, contact2.get());
231  AddEmailAddress(kSharedEmail, Contact_AddressType_Relation_WORK,
232                  "", true, contact2.get());
233
234  scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
235  contacts_to_save->push_back(contact1.get());
236  contacts_to_save->push_back(contact2.get());
237  scoped_ptr<ContactDatabaseInterface::ContactIds> contact_ids_to_delete(
238      new ContactDatabaseInterface::ContactIds);
239  scoped_ptr<UpdateMetadata> metadata_to_save(new UpdateMetadata);
240  SaveContacts(contacts_to_save.Pass(),
241               contact_ids_to_delete.Pass(),
242               metadata_to_save.Pass(),
243               true);
244
245  scoped_ptr<ScopedVector<Contact> > loaded_contacts;
246  scoped_ptr<UpdateMetadata> loaded_metadata;
247  LoadContacts(&loaded_contacts, &loaded_metadata);
248  EXPECT_EQ(VarContactsToString(2, contact1.get(), contact2.get()),
249            ContactsToString(*loaded_contacts));
250
251  // Do an incremental update including just the second contact.
252  InitContact(kContactId2, "2b", false, contact2.get());
253  AddPostalAddress("postal_1", Contact_AddressType_Relation_HOME,
254                   "", true, contact2.get());
255  contacts_to_save.reset(new ContactPointers);
256  contacts_to_save->push_back(contact2.get());
257  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
258  metadata_to_save.reset(new UpdateMetadata);
259  SaveContacts(contacts_to_save.Pass(),
260               contact_ids_to_delete.Pass(),
261               metadata_to_save.Pass(),
262               false);
263  LoadContacts(&loaded_contacts, &loaded_metadata);
264  EXPECT_EQ(VarContactsToString(2, contact1.get(), contact2.get()),
265            ContactsToString(*loaded_contacts));
266
267  // Do an empty incremental update and check that the metadata is still
268  // updated.
269  contacts_to_save.reset(new ContactPointers);
270  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
271  metadata_to_save.reset(new UpdateMetadata);
272  const int64 kLastUpdateTime = 1234;
273  metadata_to_save->set_last_update_start_time(kLastUpdateTime);
274  SaveContacts(contacts_to_save.Pass(),
275               contact_ids_to_delete.Pass(),
276               metadata_to_save.Pass(),
277               false);
278  LoadContacts(&loaded_contacts, &loaded_metadata);
279  EXPECT_EQ(VarContactsToString(2, contact1.get(), contact2.get()),
280            ContactsToString(*loaded_contacts));
281  EXPECT_EQ(kLastUpdateTime, loaded_metadata->last_update_start_time());
282
283  // Do a full update including just the first contact.  The second contact
284  // should be removed from the database.
285  InitContact(kContactId1, "1b", false, contact1.get());
286  AddPostalAddress("postal_2", Contact_AddressType_Relation_WORK,
287                   "", true, contact1.get());
288  AddPhoneNumber("phone", Contact_AddressType_Relation_HOME,
289                 "", true, contact1.get());
290  contacts_to_save.reset(new ContactPointers);
291  contacts_to_save->push_back(contact1.get());
292  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
293  metadata_to_save.reset(new UpdateMetadata);
294  SaveContacts(contacts_to_save.Pass(),
295               contact_ids_to_delete.Pass(),
296               metadata_to_save.Pass(),
297               true);
298  LoadContacts(&loaded_contacts, &loaded_metadata);
299  EXPECT_EQ(VarContactsToString(1, contact1.get()),
300            ContactsToString(*loaded_contacts));
301
302  // Do a full update including no contacts.  The database should be cleared.
303  contacts_to_save.reset(new ContactPointers);
304  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
305  metadata_to_save.reset(new UpdateMetadata);
306  SaveContacts(contacts_to_save.Pass(),
307               contact_ids_to_delete.Pass(),
308               metadata_to_save.Pass(),
309               true);
310  LoadContacts(&loaded_contacts, &loaded_metadata);
311  EXPECT_TRUE(loaded_contacts->empty());
312}
313
314// Test that we create a new database when we encounter a corrupted one.
315TEST_F(ContactDatabaseTest, DeleteWhenCorrupt) {
316  DestroyDatabase();
317  // Overwrite all of the files in the database with a space character.
318  base::FileEnumerator enumerator(
319      database_path(), false, base::FileEnumerator::FILES);
320  for (base::FilePath path = enumerator.Next(); !path.empty();
321       path = enumerator.Next()) {
322    file_util::WriteFile(path, " ", 1);
323  }
324  CreateDatabase();
325
326  // Make sure that the resulting database is usable.
327  scoped_ptr<Contact> contact(new Contact);
328  InitContact("1", "1", false, contact.get());
329  scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
330  contacts_to_save->push_back(contact.get());
331  scoped_ptr<ContactDatabaseInterface::ContactIds> contact_ids_to_delete(
332      new ContactDatabaseInterface::ContactIds);
333  scoped_ptr<UpdateMetadata> metadata_to_save(new UpdateMetadata);
334  SaveContacts(contacts_to_save.Pass(),
335               contact_ids_to_delete.Pass(),
336               metadata_to_save.Pass(),
337               true);
338
339  scoped_ptr<ScopedVector<Contact> > loaded_contacts;
340  scoped_ptr<UpdateMetadata> loaded_metadata;
341  LoadContacts(&loaded_contacts, &loaded_metadata);
342  EXPECT_EQ(VarContactsToString(1, contact.get()),
343            ContactsToString(*loaded_contacts));
344}
345
346TEST_F(ContactDatabaseTest, DeleteRequestedContacts) {
347  // Insert two contacts into the database with a full update.
348  const std::string kContactId1 = "contact_id_1";
349  scoped_ptr<Contact> contact1(new Contact);
350  InitContact(kContactId1, "1", false, contact1.get());
351  const std::string kContactId2 = "contact_id_2";
352  scoped_ptr<Contact> contact2(new Contact);
353  InitContact(kContactId2, "2", false, contact2.get());
354
355  scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
356  contacts_to_save->push_back(contact1.get());
357  contacts_to_save->push_back(contact2.get());
358  scoped_ptr<ContactDatabaseInterface::ContactIds> contact_ids_to_delete(
359      new ContactDatabaseInterface::ContactIds);
360  scoped_ptr<UpdateMetadata> metadata_to_save(new UpdateMetadata);
361  SaveContacts(contacts_to_save.Pass(),
362               contact_ids_to_delete.Pass(),
363               metadata_to_save.Pass(),
364               true);
365
366  // Do an incremental update that inserts a third contact and deletes the first
367  // contact.
368  const std::string kContactId3 = "contact_id_3";
369  scoped_ptr<Contact> contact3(new Contact);
370  InitContact(kContactId3, "3", false, contact3.get());
371
372  contacts_to_save.reset(new ContactPointers);
373  contacts_to_save->push_back(contact3.get());
374  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
375  contact_ids_to_delete->push_back(kContactId1);
376  metadata_to_save.reset(new UpdateMetadata);
377  SaveContacts(contacts_to_save.Pass(),
378               contact_ids_to_delete.Pass(),
379               metadata_to_save.Pass(),
380               false);
381
382  // LoadContacts() should return only the second and third contacts.
383  scoped_ptr<ScopedVector<Contact> > loaded_contacts;
384  scoped_ptr<UpdateMetadata> loaded_metadata;
385  LoadContacts(&loaded_contacts, &loaded_metadata);
386  EXPECT_EQ(VarContactsToString(2, contact2.get(), contact3.get()),
387            ContactsToString(*loaded_contacts));
388
389  // Do another incremental update that deletes the second contact.
390  contacts_to_save.reset(new ContactPointers);
391  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
392  contact_ids_to_delete->push_back(kContactId2);
393  metadata_to_save.reset(new UpdateMetadata);
394  SaveContacts(contacts_to_save.Pass(),
395               contact_ids_to_delete.Pass(),
396               metadata_to_save.Pass(),
397               false);
398  LoadContacts(&loaded_contacts, &loaded_metadata);
399  EXPECT_EQ(VarContactsToString(1, contact3.get()),
400            ContactsToString(*loaded_contacts));
401
402  // Deleting a contact that isn't present should be a no-op.
403  contacts_to_save.reset(new ContactPointers);
404  contact_ids_to_delete.reset(new ContactDatabaseInterface::ContactIds);
405  contact_ids_to_delete->push_back("bogus_id");
406  metadata_to_save.reset(new UpdateMetadata);
407  SaveContacts(contacts_to_save.Pass(),
408               contact_ids_to_delete.Pass(),
409               metadata_to_save.Pass(),
410               false);
411  LoadContacts(&loaded_contacts, &loaded_metadata);
412  EXPECT_EQ(VarContactsToString(1, contact3.get()),
413            ContactsToString(*loaded_contacts));
414}
415
416}  // namespace test
417}  // namespace contacts
418