1 // Copyright 2014 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 "sync/engine/directory_commit_contribution.h" 6 7 #include "base/message_loop/message_loop.h" 8 #include "sync/internal_api/public/base/attachment_id_proto.h" 9 #include "sync/sessions/status_controller.h" 10 #include "sync/syncable/entry.h" 11 #include "sync/syncable/mutable_entry.h" 12 #include "sync/syncable/syncable_read_transaction.h" 13 #include "sync/syncable/syncable_write_transaction.h" 14 #include "sync/test/engine/test_directory_setter_upper.h" 15 #include "sync/test/engine/test_id_factory.h" 16 #include "sync/test/engine/test_syncable_utils.h" 17 #include "testing/gtest/include/gtest/gtest.h" 18 19 namespace syncer { 20 21 class DirectoryCommitContributionTest : public ::testing::Test { 22 public: 23 virtual void SetUp() OVERRIDE { 24 dir_maker_.SetUp(); 25 26 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 27 CreateTypeRoot(&trans, dir(), PREFERENCES); 28 CreateTypeRoot(&trans, dir(), EXTENSIONS); 29 CreateTypeRoot(&trans, dir(), ARTICLES); 30 CreateTypeRoot(&trans, dir(), BOOKMARKS); 31 } 32 33 virtual void TearDown() OVERRIDE { 34 dir_maker_.TearDown(); 35 } 36 37 protected: 38 int64 CreateUnsyncedItemWithAttachments( 39 syncable::WriteTransaction* trans, 40 ModelType type, 41 const std::string& tag, 42 const sync_pb::AttachmentMetadata& attachment_metadata) { 43 syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type); 44 syncable::MutableEntry entry( 45 trans, 46 syncable::CREATE, 47 type, 48 parent_entry.GetId(), 49 tag); 50 if (attachment_metadata.record_size() > 0) { 51 entry.PutAttachmentMetadata(attachment_metadata); 52 } 53 entry.PutIsUnsynced(true); 54 return entry.GetMetahandle(); 55 } 56 57 int64 CreateUnsyncedItem(syncable::WriteTransaction* trans, 58 ModelType type, 59 const std::string& tag) { 60 return CreateUnsyncedItemWithAttachments( 61 trans, type, tag, sync_pb::AttachmentMetadata()); 62 } 63 64 int64 CreateSyncedItem(syncable::WriteTransaction* trans, 65 ModelType type, 66 const std::string& tag) { 67 syncable::Entry parent_entry(trans, syncable::GET_TYPE_ROOT, type); 68 syncable::MutableEntry entry( 69 trans, 70 syncable::CREATE, 71 type, 72 parent_entry.GetId(), 73 tag); 74 75 entry.PutId(syncable::Id::CreateFromServerId( 76 id_factory_.NewServerId().GetServerId())); 77 entry.PutBaseVersion(10); 78 entry.PutServerVersion(10); 79 entry.PutIsUnappliedUpdate(false); 80 entry.PutIsUnsynced(false); 81 entry.PutIsDel(false); 82 entry.PutServerIsDel(false); 83 84 return entry.GetMetahandle(); 85 } 86 87 void CreateSuccessfulCommitResponse( 88 const sync_pb::SyncEntity& entity, 89 sync_pb::CommitResponse::EntryResponse* response) { 90 response->set_response_type(sync_pb::CommitResponse::SUCCESS); 91 response->set_non_unique_name(entity.name()); 92 response->set_version(entity.version() + 1); 93 response->set_parent_id_string(entity.parent_id_string()); 94 95 if (entity.id_string()[0] == '-') // Look for the - in 'c-1234' style IDs. 96 response->set_id_string(id_factory_.NewServerId().GetServerId()); 97 else 98 response->set_id_string(entity.id_string()); 99 } 100 101 syncable::Directory* dir() { 102 return dir_maker_.directory(); 103 } 104 105 TestIdFactory id_factory_; 106 107 // Used in construction of DirectoryTypeDebugInfoEmitters. 108 ObserverList<TypeDebugInfoObserver> type_observers_; 109 110 private: 111 base::MessageLoop loop_; // Neeed to initialize the directory. 112 TestDirectorySetterUpper dir_maker_; 113 }; 114 115 // Verify that the DirectoryCommitContribution contains only entries of its 116 // specified type. 117 TEST_F(DirectoryCommitContributionTest, GatherByTypes) { 118 int64 pref1; 119 { 120 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 121 pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 122 CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 123 CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 124 } 125 126 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 127 scoped_ptr<DirectoryCommitContribution> cc( 128 DirectoryCommitContribution::Build(dir(), PREFERENCES, 5, &emitter)); 129 ASSERT_EQ(2U, cc->GetNumEntries()); 130 131 const std::vector<int64>& metahandles = cc->metahandles_; 132 EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) != 133 metahandles.end()); 134 EXPECT_TRUE(std::find(metahandles.begin(), metahandles.end(), pref1) != 135 metahandles.end()); 136 137 cc->CleanUp(); 138 } 139 140 // Verify that the DirectoryCommitContributionTest builder function 141 // truncates if necessary. 142 TEST_F(DirectoryCommitContributionTest, GatherAndTruncate) { 143 int64 pref1; 144 int64 pref2; 145 { 146 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 147 pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 148 pref2 = CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 149 CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 150 } 151 152 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 153 scoped_ptr<DirectoryCommitContribution> cc( 154 DirectoryCommitContribution::Build(dir(), PREFERENCES, 1, &emitter)); 155 ASSERT_EQ(1U, cc->GetNumEntries()); 156 157 int64 only_metahandle = cc->metahandles_[0]; 158 EXPECT_TRUE(only_metahandle == pref1 || only_metahandle == pref2); 159 160 cc->CleanUp(); 161 } 162 163 // Sanity check for building commits from DirectoryCommitContributions. 164 // This test makes two CommitContribution objects of different types and uses 165 // them to initialize a commit message. Then it checks that the contents of the 166 // commit message match those of the directory they came from. 167 TEST_F(DirectoryCommitContributionTest, PrepareCommit) { 168 { 169 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 170 CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 171 CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 172 CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 173 } 174 175 DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_); 176 DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_); 177 scoped_ptr<DirectoryCommitContribution> pref_cc( 178 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1)); 179 scoped_ptr<DirectoryCommitContribution> ext_cc( 180 DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2)); 181 182 sync_pb::ClientToServerMessage message; 183 pref_cc->AddToCommitMessage(&message); 184 ext_cc->AddToCommitMessage(&message); 185 186 const sync_pb::CommitMessage& commit_message = message.commit(); 187 188 std::set<syncable::Id> ids_for_commit; 189 ASSERT_EQ(3, commit_message.entries_size()); 190 for (int i = 0; i < commit_message.entries_size(); ++i) { 191 const sync_pb::SyncEntity& entity = commit_message.entries(i); 192 // The entities in this test have client-style IDs since they've never been 193 // committed before, so we must use CreateFromClientString to re-create them 194 // from the commit message. 195 ids_for_commit.insert(syncable::Id::CreateFromClientString( 196 entity.id_string())); 197 } 198 199 ASSERT_EQ(3U, ids_for_commit.size()); 200 { 201 syncable::ReadTransaction trans(FROM_HERE, dir()); 202 for (std::set<syncable::Id>::iterator it = ids_for_commit.begin(); 203 it != ids_for_commit.end(); ++it) { 204 SCOPED_TRACE(it->value()); 205 syncable::Entry entry(&trans, syncable::GET_BY_ID, *it); 206 ASSERT_TRUE(entry.good()); 207 EXPECT_TRUE(entry.GetSyncing()); 208 } 209 } 210 211 pref_cc->CleanUp(); 212 ext_cc->CleanUp(); 213 } 214 215 // Check that deletion requests include a model type. 216 // This was not always the case, but was implemented to allow us to loosen some 217 // other restrictions in the protocol. 218 TEST_F(DirectoryCommitContributionTest, DeletedItemsWithSpecifics) { 219 int64 pref1; 220 { 221 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 222 pref1 = CreateSyncedItem(&trans, PREFERENCES, "pref1"); 223 syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, pref1); 224 e1.PutIsDel(true); 225 e1.PutIsUnsynced(true); 226 } 227 228 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 229 scoped_ptr<DirectoryCommitContribution> pref_cc( 230 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter)); 231 ASSERT_TRUE(pref_cc); 232 233 sync_pb::ClientToServerMessage message; 234 pref_cc->AddToCommitMessage(&message); 235 236 const sync_pb::CommitMessage& commit_message = message.commit(); 237 ASSERT_EQ(1, commit_message.entries_size()); 238 EXPECT_TRUE( 239 commit_message.entries(0).specifics().has_preference()); 240 241 pref_cc->CleanUp(); 242 } 243 244 // As ususal, bookmarks are special. Bookmark deletion is special. 245 // Deleted bookmarks include a valid "is folder" bit and their full specifics 246 // (especially the meta info, which is what server really wants). 247 TEST_F(DirectoryCommitContributionTest, DeletedBookmarksWithSpecifics) { 248 int64 bm1; 249 { 250 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 251 bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1"); 252 syncable::MutableEntry e1(&trans, syncable::GET_BY_HANDLE, bm1); 253 254 e1.PutIsDir(true); 255 e1.PutServerIsDir(true); 256 257 sync_pb::EntitySpecifics specifics; 258 sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark(); 259 bm_specifics->set_url("http://www.chrome.com"); 260 bm_specifics->set_title("Chrome"); 261 sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info(); 262 meta_info->set_key("K"); 263 meta_info->set_value("V"); 264 e1.PutSpecifics(specifics); 265 266 e1.PutIsDel(true); 267 e1.PutIsUnsynced(true); 268 } 269 270 DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); 271 scoped_ptr<DirectoryCommitContribution> bm_cc( 272 DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter)); 273 ASSERT_TRUE(bm_cc); 274 275 sync_pb::ClientToServerMessage message; 276 bm_cc->AddToCommitMessage(&message); 277 278 const sync_pb::CommitMessage& commit_message = message.commit(); 279 ASSERT_EQ(1, commit_message.entries_size()); 280 281 const sync_pb::SyncEntity& entity = commit_message.entries(0); 282 EXPECT_TRUE(entity.has_folder()); 283 ASSERT_TRUE(entity.specifics().has_bookmark()); 284 ASSERT_EQ(1, entity.specifics().bookmark().meta_info_size()); 285 EXPECT_EQ("K", entity.specifics().bookmark().meta_info(0).key()); 286 EXPECT_EQ("V", entity.specifics().bookmark().meta_info(0).value()); 287 288 bm_cc->CleanUp(); 289 } 290 291 // Test that bookmarks support hierarchy. 292 TEST_F(DirectoryCommitContributionTest, HierarchySupport_Bookmark) { 293 294 // Create a normal-looking bookmark item. 295 int64 bm1; 296 { 297 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 298 bm1 = CreateSyncedItem(&trans, BOOKMARKS, "bm1"); 299 syncable::MutableEntry e(&trans, syncable::GET_BY_HANDLE, bm1); 300 301 sync_pb::EntitySpecifics specifics; 302 sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark(); 303 bm_specifics->set_url("http://www.chrome.com"); 304 bm_specifics->set_title("Chrome"); 305 e.PutSpecifics(specifics); 306 307 e.PutIsDel(false); 308 e.PutIsUnsynced(true); 309 310 EXPECT_TRUE(e.ShouldMaintainHierarchy()); 311 } 312 313 DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_); 314 scoped_ptr<DirectoryCommitContribution> bm_cc( 315 DirectoryCommitContribution::Build(dir(), BOOKMARKS, 25, &emitter)); 316 317 sync_pb::ClientToServerMessage message; 318 bm_cc->AddToCommitMessage(&message); 319 const sync_pb::CommitMessage& commit_message = message.commit(); 320 bm_cc->CleanUp(); 321 322 ASSERT_EQ(1, commit_message.entries_size()); 323 EXPECT_TRUE(commit_message.entries(0).has_parent_id_string()); 324 EXPECT_FALSE(commit_message.entries(0).parent_id_string().empty()); 325 } 326 327 // Test that preferences do not support hierarchy. 328 TEST_F(DirectoryCommitContributionTest, HierarchySupport_Preferences) { 329 // Create a normal-looking prefs item. 330 int64 pref1; 331 { 332 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 333 pref1 = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 334 syncable::MutableEntry e(&trans, syncable::GET_BY_HANDLE, pref1); 335 336 EXPECT_FALSE(e.ShouldMaintainHierarchy()); 337 } 338 339 DirectoryTypeDebugInfoEmitter emitter(PREFERENCES, &type_observers_); 340 scoped_ptr<DirectoryCommitContribution> pref_cc( 341 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter)); 342 343 sync_pb::ClientToServerMessage message; 344 pref_cc->AddToCommitMessage(&message); 345 const sync_pb::CommitMessage& commit_message = message.commit(); 346 pref_cc->CleanUp(); 347 348 ASSERT_EQ(1, commit_message.entries_size()); 349 EXPECT_FALSE(commit_message.entries(0).has_parent_id_string()); 350 EXPECT_TRUE(commit_message.entries(0).parent_id_string().empty()); 351 } 352 353 void AddAttachment(sync_pb::AttachmentMetadata* metadata, bool is_on_server) { 354 sync_pb::AttachmentMetadataRecord record; 355 *record.mutable_id() = CreateAttachmentIdProto(); 356 record.set_is_on_server(is_on_server); 357 *metadata->add_record() = record; 358 } 359 360 // Creates some unsynced items, pretends to commit them, and hands back a 361 // specially crafted response to the syncer in order to test commit response 362 // processing. The response simulates a succesful commit scenario. 363 TEST_F(DirectoryCommitContributionTest, ProcessCommitResponse) { 364 int64 pref1_handle; 365 int64 pref2_handle; 366 int64 ext1_handle; 367 { 368 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 369 pref1_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref1"); 370 pref2_handle = CreateUnsyncedItem(&trans, PREFERENCES, "pref2"); 371 ext1_handle = CreateUnsyncedItem(&trans, EXTENSIONS, "extension1"); 372 } 373 374 DirectoryTypeDebugInfoEmitter emitter1(PREFERENCES, &type_observers_); 375 DirectoryTypeDebugInfoEmitter emitter2(EXTENSIONS, &type_observers_); 376 scoped_ptr<DirectoryCommitContribution> pref_cc( 377 DirectoryCommitContribution::Build(dir(), PREFERENCES, 25, &emitter1)); 378 scoped_ptr<DirectoryCommitContribution> ext_cc( 379 DirectoryCommitContribution::Build(dir(), EXTENSIONS, 25, &emitter2)); 380 381 sync_pb::ClientToServerMessage message; 382 pref_cc->AddToCommitMessage(&message); 383 ext_cc->AddToCommitMessage(&message); 384 385 const sync_pb::CommitMessage& commit_message = message.commit(); 386 ASSERT_EQ(3, commit_message.entries_size()); 387 388 sync_pb::ClientToServerResponse response; 389 for (int i = 0; i < commit_message.entries_size(); ++i) { 390 sync_pb::SyncEntity entity = commit_message.entries(i); 391 sync_pb::CommitResponse_EntryResponse* entry_response = 392 response.mutable_commit()->add_entryresponse(); 393 CreateSuccessfulCommitResponse(entity, entry_response); 394 } 395 396 sessions::StatusController status; 397 398 // Process these in reverse order. Just because we can. 399 ext_cc->ProcessCommitResponse(response, &status); 400 pref_cc->ProcessCommitResponse(response, &status); 401 402 { 403 syncable::ReadTransaction trans(FROM_HERE, dir()); 404 syncable::Entry p1(&trans, syncable::GET_BY_HANDLE, pref1_handle); 405 EXPECT_TRUE(p1.GetId().ServerKnows()); 406 EXPECT_FALSE(p1.GetSyncing()); 407 EXPECT_LT(0, p1.GetServerVersion()); 408 409 syncable::Entry p2(&trans, syncable::GET_BY_HANDLE, pref2_handle); 410 EXPECT_TRUE(p2.GetId().ServerKnows()); 411 EXPECT_FALSE(p2.GetSyncing()); 412 EXPECT_LT(0, p2.GetServerVersion()); 413 414 syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, ext1_handle); 415 EXPECT_TRUE(e1.GetId().ServerKnows()); 416 EXPECT_FALSE(e1.GetSyncing()); 417 EXPECT_LT(0, e1.GetServerVersion()); 418 } 419 420 pref_cc->CleanUp(); 421 ext_cc->CleanUp(); 422 } 423 424 // Creates some unsynced items with attachments and verifies that only items 425 // where all attachments have been uploaded to the server are eligible to be 426 // committed. 427 TEST_F(DirectoryCommitContributionTest, ProcessCommitResponseWithAttachments) { 428 int64 art1_handle; 429 int64 art2_handle; 430 int64 art3_handle; 431 { 432 syncable::WriteTransaction trans(FROM_HERE, syncable::UNITTEST, dir()); 433 434 // art1 has two attachments, both have been uploaded to the server. art1 is 435 // eligible to be committed. 436 sync_pb::AttachmentMetadata art1_attachments; 437 AddAttachment(&art1_attachments, true /* is_on_server */); 438 AddAttachment(&art1_attachments, true /* is_on_server */); 439 art1_handle = CreateUnsyncedItemWithAttachments( 440 &trans, ARTICLES, "art1", art1_attachments); 441 442 // art2 has two attachments, one of which has been uploaded to the 443 // server. art2 is not eligible to be committed. 444 sync_pb::AttachmentMetadata art2_attachments; 445 AddAttachment(&art2_attachments, false /* is_on_server */); 446 AddAttachment(&art2_attachments, true /* is_on_server */); 447 art2_handle = CreateUnsyncedItemWithAttachments( 448 &trans, ARTICLES, "art2", art2_attachments); 449 450 // art3 has two attachments, neither of which have been uploaded to the 451 // server. art2 is not eligible to be committed. 452 sync_pb::AttachmentMetadata art3_attachments; 453 AddAttachment(&art3_attachments, false /* is_on_server */); 454 AddAttachment(&art3_attachments, false /* is_on_server */); 455 art3_handle = CreateUnsyncedItemWithAttachments( 456 &trans, ARTICLES, "art3", art3_attachments); 457 } 458 459 DirectoryTypeDebugInfoEmitter emitter(ARTICLES, &type_observers_); 460 scoped_ptr<DirectoryCommitContribution> art_cc( 461 DirectoryCommitContribution::Build(dir(), ARTICLES, 25, &emitter)); 462 463 // Only art1 is ready. 464 EXPECT_EQ(1U, art_cc->GetNumEntries()); 465 466 sync_pb::ClientToServerMessage message; 467 art_cc->AddToCommitMessage(&message); 468 469 const sync_pb::CommitMessage& commit_message = message.commit(); 470 ASSERT_EQ(1, commit_message.entries_size()); 471 472 sync_pb::ClientToServerResponse response; 473 for (int i = 0; i < commit_message.entries_size(); ++i) { 474 sync_pb::SyncEntity entity = commit_message.entries(i); 475 sync_pb::CommitResponse_EntryResponse* entry_response = 476 response.mutable_commit()->add_entryresponse(); 477 CreateSuccessfulCommitResponse(entity, entry_response); 478 } 479 480 sessions::StatusController status; 481 art_cc->ProcessCommitResponse(response, &status); 482 { 483 syncable::ReadTransaction trans(FROM_HERE, dir()); 484 485 syncable::Entry a1(&trans, syncable::GET_BY_HANDLE, art1_handle); 486 EXPECT_TRUE(a1.GetId().ServerKnows()); 487 EXPECT_FALSE(a1.GetSyncing()); 488 EXPECT_LT(0, a1.GetServerVersion()); 489 490 syncable::Entry a2(&trans, syncable::GET_BY_HANDLE, art2_handle); 491 EXPECT_FALSE(a2.GetId().ServerKnows()); 492 EXPECT_FALSE(a2.GetSyncing()); 493 EXPECT_EQ(0, a2.GetServerVersion()); 494 495 syncable::Entry a3(&trans, syncable::GET_BY_HANDLE, art3_handle); 496 EXPECT_FALSE(a3.GetId().ServerKnows()); 497 EXPECT_FALSE(a3.GetSyncing()); 498 EXPECT_EQ(0, a3.GetServerVersion()); 499 } 500 501 art_cc->CleanUp(); 502 } 503 504 } // namespace syncer 505