OLD | NEW |
| (Empty) |
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@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 base::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 | |
OLD | NEW |