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