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 24 using content::BrowserThread; 25 26 namespace contacts { 27 namespace test { 28 29 // Name of the directory created within a temporary directory to store the 30 // contacts database. 31 const base::FilePath::CharType kDatabaseDirectoryName[] = 32 FILE_PATH_LITERAL("contacts"); 33 34 class 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 151 TEST_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 219 TEST_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 (at) 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. 315 TEST_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 346 TEST_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