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.
5 #include "sync/engine/directory_update_handler.h"
7 #include "base/compiler_specific.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/stl_util.h"
10 #include "sync/engine/syncer_proto_util.h"
11 #include "sync/internal_api/public/base/model_type.h"
12 #include "sync/internal_api/public/test/test_entry_factory.h"
13 #include "sync/protocol/sync.pb.h"
14 #include "sync/sessions/directory_type_debug_info_emitter.h"
15 #include "sync/sessions/status_controller.h"
16 #include "sync/syncable/directory.h"
17 #include "sync/syncable/entry.h"
18 #include "sync/syncable/mutable_entry.h"
19 #include "sync/syncable/syncable_model_neutral_write_transaction.h"
20 #include "sync/syncable/syncable_proto_util.h"
21 #include "sync/syncable/syncable_read_transaction.h"
22 #include "sync/syncable/syncable_write_transaction.h"
23 #include "sync/test/engine/fake_model_worker.h"
24 #include "sync/test/engine/test_directory_setter_upper.h"
25 #include "sync/test/engine/test_id_factory.h"
26 #include "sync/test/engine/test_syncable_utils.h"
27 #include "testing/gtest/include/gtest/gtest.h"
31 using syncable::UNITTEST;
33 static const int64 kDefaultVersion = 1000;
35 // A test harness for tests that focus on processing updates.
37 // Update processing is what occurs when we first download updates. It converts
38 // the received protobuf message into information in the syncable::Directory.
39 // Any invalid or redundant updates will be dropped at this point.
40 class DirectoryUpdateHandlerProcessUpdateTest : public ::testing::Test {
42 DirectoryUpdateHandlerProcessUpdateTest()
43 : ui_worker_(new FakeModelWorker(GROUP_UI)) {
46 virtual ~DirectoryUpdateHandlerProcessUpdateTest() {}
48 virtual void SetUp() OVERRIDE {
52 virtual void TearDown() OVERRIDE {
53 dir_maker_.TearDown();
56 syncable::Directory* dir() {
57 return dir_maker_.directory();
61 scoped_ptr<sync_pb::SyncEntity> CreateUpdate(
62 const std::string& id,
63 const std::string& parent,
64 const ModelType& type);
66 // This exists mostly to give tests access to the protected member function.
67 // Warning: This takes the syncable directory lock.
68 void UpdateSyncEntities(
69 DirectoryUpdateHandler* handler,
70 const SyncEntityList& applicable_updates,
71 sessions::StatusController* status);
73 // Another function to access private member functions.
74 void UpdateProgressMarkers(
75 DirectoryUpdateHandler* handler,
76 const sync_pb::DataTypeProgressMarker& progress);
78 scoped_refptr<FakeModelWorker> ui_worker() {
82 bool EntryExists(const std::string& id) {
83 syncable::ReadTransaction trans(FROM_HERE, dir());
84 syncable::Entry e(&trans, syncable::GET_BY_ID,
85 syncable::Id::CreateFromServerId(id));
86 return e.good() && !e.GetIsDel();
90 base::MessageLoop loop_; // Needed to initialize the directory.
91 TestDirectorySetterUpper dir_maker_;
92 scoped_refptr<FakeModelWorker> ui_worker_;
95 scoped_ptr<sync_pb::SyncEntity>
96 DirectoryUpdateHandlerProcessUpdateTest::CreateUpdate(
97 const std::string& id,
98 const std::string& parent,
99 const ModelType& type) {
100 scoped_ptr<sync_pb::SyncEntity> e(new sync_pb::SyncEntity());
101 e->set_id_string(id);
102 e->set_parent_id_string(parent);
103 e->set_non_unique_name(id);
105 e->set_version(kDefaultVersion);
106 AddDefaultFieldValue(type, e->mutable_specifics());
110 void DirectoryUpdateHandlerProcessUpdateTest::UpdateSyncEntities(
111 DirectoryUpdateHandler* handler,
112 const SyncEntityList& applicable_updates,
113 sessions::StatusController* status) {
114 syncable::ModelNeutralWriteTransaction trans(FROM_HERE, UNITTEST, dir());
115 handler->UpdateSyncEntities(&trans, applicable_updates, status);
118 void DirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers(
119 DirectoryUpdateHandler* handler,
120 const sync_pb::DataTypeProgressMarker& progress) {
121 handler->UpdateProgressMarker(progress);
124 static const char kCacheGuid[] = "IrcjZ2jyzHDV9Io4+zKcXQ==";
126 // Test that the bookmark tag is set on newly downloaded items.
127 TEST_F(DirectoryUpdateHandlerProcessUpdateTest, NewBookmarkTag) {
128 DirectoryTypeDebugInfoEmitter emitter;
129 DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
130 sync_pb::GetUpdatesResponse gu_response;
131 sessions::StatusController status;
133 // Add a bookmark item to the update message.
134 std::string root = syncable::GetNullId().GetServerId();
135 syncable::Id server_id = syncable::Id::CreateFromServerId("b1");
136 scoped_ptr<sync_pb::SyncEntity> e =
137 CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS);
138 e->set_originator_cache_guid(
139 std::string(kCacheGuid, arraysize(kCacheGuid)-1));
140 syncable::Id client_id = syncable::Id::CreateFromClientString("-2");
141 e->set_originator_client_item_id(client_id.GetServerId());
142 e->set_position_in_parent(0);
144 // Add it to the applicable updates list.
145 SyncEntityList bookmark_updates;
146 bookmark_updates.push_back(e.get());
148 // Process the update.
149 UpdateSyncEntities(&handler, bookmark_updates, &status);
151 syncable::ReadTransaction trans(FROM_HERE, dir());
152 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
153 ASSERT_TRUE(entry.good());
154 EXPECT_TRUE(UniquePosition::IsValidSuffix(entry.GetUniqueBookmarkTag()));
155 EXPECT_TRUE(entry.GetServerUniquePosition().IsValid());
157 // If this assertion fails, that might indicate that the algorithm used to
158 // generate bookmark tags has been modified. This could have implications for
159 // bookmark ordering. Please make sure you know what you're doing if you
160 // intend to make such a change.
161 EXPECT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", entry.GetUniqueBookmarkTag());
164 // Test the receipt of a type root node.
165 TEST_F(DirectoryUpdateHandlerProcessUpdateTest,
166 ReceiveServerCreatedBookmarkFolders) {
167 DirectoryTypeDebugInfoEmitter emitter;
168 DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
169 sync_pb::GetUpdatesResponse gu_response;
170 sessions::StatusController status;
172 // Create an update that mimics the bookmark root.
173 syncable::Id server_id = syncable::Id::CreateFromServerId("xyz");
174 std::string root = syncable::GetNullId().GetServerId();
175 scoped_ptr<sync_pb::SyncEntity> e =
176 CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS);
177 e->set_server_defined_unique_tag("google_chrome_bookmarks");
180 // Add it to the applicable updates list.
181 SyncEntityList bookmark_updates;
182 bookmark_updates.push_back(e.get());
184 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e));
187 UpdateSyncEntities(&handler, bookmark_updates, &status);
189 // Verify the results.
190 syncable::ReadTransaction trans(FROM_HERE, dir());
191 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
192 ASSERT_TRUE(entry.good());
194 EXPECT_FALSE(entry.ShouldMaintainPosition());
195 EXPECT_FALSE(entry.GetUniquePosition().IsValid());
196 EXPECT_FALSE(entry.GetServerUniquePosition().IsValid());
197 EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty());
200 // Test the receipt of a non-bookmark item.
201 TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ReceiveNonBookmarkItem) {
202 DirectoryTypeDebugInfoEmitter emitter;
203 DirectoryUpdateHandler handler(dir(), AUTOFILL, ui_worker(), &emitter);
204 sync_pb::GetUpdatesResponse gu_response;
205 sessions::StatusController status;
207 std::string root = syncable::GetNullId().GetServerId();
208 syncable::Id server_id = syncable::Id::CreateFromServerId("xyz");
209 scoped_ptr<sync_pb::SyncEntity> e =
210 CreateUpdate(SyncableIdToProto(server_id), root, AUTOFILL);
211 e->set_server_defined_unique_tag("9PGRuKdX5sHyGMB17CvYTXuC43I=");
213 // Add it to the applicable updates list.
214 SyncEntityList autofill_updates;
215 autofill_updates.push_back(e.get());
217 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e));
220 UpdateSyncEntities(&handler, autofill_updates, &status);
222 syncable::ReadTransaction trans(FROM_HERE, dir());
223 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
224 ASSERT_TRUE(entry.good());
226 EXPECT_FALSE(entry.ShouldMaintainPosition());
227 EXPECT_FALSE(entry.GetUniquePosition().IsValid());
228 EXPECT_FALSE(entry.GetServerUniquePosition().IsValid());
229 EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty());
232 // Tests the setting of progress markers.
233 TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ProcessNewProgressMarkers) {
234 DirectoryTypeDebugInfoEmitter emitter;
235 DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
237 sync_pb::DataTypeProgressMarker progress;
238 progress.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS));
239 progress.set_token("token");
241 UpdateProgressMarkers(&handler, progress);
243 sync_pb::DataTypeProgressMarker saved;
244 dir()->GetDownloadProgress(BOOKMARKS, &saved);
246 EXPECT_EQ(progress.token(), saved.token());
247 EXPECT_EQ(progress.data_type_id(), saved.data_type_id());
250 TEST_F(DirectoryUpdateHandlerProcessUpdateTest, GarbageCollectionByVersion) {
251 DirectoryTypeDebugInfoEmitter emitter;
252 DirectoryUpdateHandler handler(dir(), SYNCED_NOTIFICATIONS,
253 ui_worker(), &emitter);
254 sessions::StatusController status;
256 sync_pb::DataTypeProgressMarker progress;
257 progress.set_data_type_id(
258 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
259 progress.set_token("token");
260 progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 10);
262 sync_pb::DataTypeContext context;
263 context.set_data_type_id(
264 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
265 context.set_context("context");
266 context.set_version(1);
268 scoped_ptr<sync_pb::SyncEntity> type_root =
269 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
270 syncable::GetNullId().GetServerId(),
271 SYNCED_NOTIFICATIONS);
272 type_root->set_server_defined_unique_tag(
273 ModelTypeToRootTag(SYNCED_NOTIFICATIONS));
274 type_root->set_folder(true);
276 scoped_ptr<sync_pb::SyncEntity> e1 =
277 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
278 type_root->id_string(),
279 SYNCED_NOTIFICATIONS);
281 scoped_ptr<sync_pb::SyncEntity> e2 =
282 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
283 type_root->id_string(),
284 SYNCED_NOTIFICATIONS);
285 e2->set_version(kDefaultVersion + 100);
287 // Add to the applicable updates list.
288 SyncEntityList updates;
289 updates.push_back(type_root.get());
290 updates.push_back(e1.get());
291 updates.push_back(e2.get());
293 // Process and apply updates.
296 handler.ProcessGetUpdatesResponse(progress, context, updates, &status));
297 handler.ApplyUpdates(&status);
299 // Verify none is deleted because they are unapplied during GC.
300 EXPECT_TRUE(EntryExists(type_root->id_string()));
301 EXPECT_TRUE(EntryExists(e1->id_string()));
302 EXPECT_TRUE(EntryExists(e2->id_string()));
304 // Process and apply again. Old entry is deleted but not root.
305 progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 20);
307 handler.ProcessGetUpdatesResponse(
308 progress, context, SyncEntityList(), &status));
309 handler.ApplyUpdates(&status);
310 EXPECT_TRUE(EntryExists(type_root->id_string()));
311 EXPECT_FALSE(EntryExists(e1->id_string()));
312 EXPECT_TRUE(EntryExists(e2->id_string()));
315 TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ContextVersion) {
316 DirectoryTypeDebugInfoEmitter emitter;
317 DirectoryUpdateHandler handler(dir(), SYNCED_NOTIFICATIONS,
318 ui_worker(), &emitter);
319 sessions::StatusController status;
320 int field_number = GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS);
322 sync_pb::DataTypeProgressMarker progress;
323 progress.set_data_type_id(
324 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
325 progress.set_token("token");
327 sync_pb::DataTypeContext old_context;
328 old_context.set_version(1);
329 old_context.set_context("data");
330 old_context.set_data_type_id(field_number);
332 scoped_ptr<sync_pb::SyncEntity> type_root =
333 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
334 syncable::GetNullId().GetServerId(),
335 SYNCED_NOTIFICATIONS);
336 type_root->set_server_defined_unique_tag(
337 ModelTypeToRootTag(SYNCED_NOTIFICATIONS));
338 type_root->set_folder(true);
339 scoped_ptr<sync_pb::SyncEntity> e1 =
340 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
341 type_root->id_string(),
342 SYNCED_NOTIFICATIONS);
344 SyncEntityList updates;
345 updates.push_back(type_root.get());
346 updates.push_back(e1.get());
348 // The first response should be processed fine.
350 handler.ProcessGetUpdatesResponse(
351 progress, old_context, updates, &status));
352 handler.ApplyUpdates(&status);
354 EXPECT_TRUE(EntryExists(type_root->id_string()));
355 EXPECT_TRUE(EntryExists(e1->id_string()));
358 sync_pb::DataTypeContext dir_context;
359 syncable::ReadTransaction trans(FROM_HERE, dir());
360 trans.directory()->GetDataTypeContext(
361 &trans, SYNCED_NOTIFICATIONS, &dir_context);
362 EXPECT_EQ(old_context.SerializeAsString(), dir_context.SerializeAsString());
365 sync_pb::DataTypeContext new_context;
366 new_context.set_version(0);
367 new_context.set_context("old");
368 new_context.set_data_type_id(field_number);
370 scoped_ptr<sync_pb::SyncEntity> e2 =
371 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
372 type_root->id_string(),
373 SYNCED_NOTIFICATIONS);
375 updates.push_back(e2.get());
377 // The second response, with an old context version, should result in an
378 // error and the updates should be dropped.
379 EXPECT_EQ(DATATYPE_TRIGGERED_RETRY,
380 handler.ProcessGetUpdatesResponse(
381 progress, new_context, updates, &status));
382 handler.ApplyUpdates(&status);
384 EXPECT_FALSE(EntryExists(e2->id_string()));
387 sync_pb::DataTypeContext dir_context;
388 syncable::ReadTransaction trans(FROM_HERE, dir());
389 trans.directory()->GetDataTypeContext(
390 &trans, SYNCED_NOTIFICATIONS, &dir_context);
391 EXPECT_EQ(old_context.SerializeAsString(), dir_context.SerializeAsString());
395 // A test harness for tests that focus on applying updates.
397 // Update application is performed when we want to take updates that were
398 // previously downloaded, processed, and stored in our syncable::Directory
399 // and use them to update our local state (both the Directory's local state
400 // and the model's local state, though these tests focus only on the Directory's
403 // This is kept separate from the update processing test in part for historical
404 // reasons, and in part because these tests may require a bit more infrastrcture
405 // in the future. Update application should happen on a different thread a lot
406 // of the time so these tests may end up requiring more infrastructure than the
407 // update processing tests. Currently, we're bypassing most of those issues by
408 // using FakeModelWorkers, so there's not much difference between the two test
410 class DirectoryUpdateHandlerApplyUpdateTest : public ::testing::Test {
412 DirectoryUpdateHandlerApplyUpdateTest()
413 : ui_worker_(new FakeModelWorker(GROUP_UI)),
414 password_worker_(new FakeModelWorker(GROUP_PASSWORD)),
415 passive_worker_(new FakeModelWorker(GROUP_PASSIVE)),
416 update_handler_map_deleter_(&update_handler_map_) {}
418 virtual void SetUp() OVERRIDE {
420 entry_factory_.reset(new TestEntryFactory(directory()));
422 update_handler_map_.insert(std::make_pair(
424 new DirectoryUpdateHandler(directory(), BOOKMARKS,
425 ui_worker_, &bookmarks_emitter_)));
426 update_handler_map_.insert(std::make_pair(
428 new DirectoryUpdateHandler(directory(),
431 &passwords_emitter_)));
434 virtual void TearDown() OVERRIDE {
435 dir_maker_.TearDown();
439 void ApplyBookmarkUpdates(sessions::StatusController* status) {
440 update_handler_map_[BOOKMARKS]->ApplyUpdates(status);
443 void ApplyPasswordUpdates(sessions::StatusController* status) {
444 update_handler_map_[PASSWORDS]->ApplyUpdates(status);
447 TestEntryFactory* entry_factory() {
448 return entry_factory_.get();
451 syncable::Directory* directory() {
452 return dir_maker_.directory();
456 typedef std::map<ModelType, UpdateHandler*> UpdateHandlerMap;
458 base::MessageLoop loop_; // Needed to initialize the directory.
459 TestDirectorySetterUpper dir_maker_;
460 scoped_ptr<TestEntryFactory> entry_factory_;
462 scoped_refptr<FakeModelWorker> ui_worker_;
463 scoped_refptr<FakeModelWorker> password_worker_;
464 scoped_refptr<FakeModelWorker> passive_worker_;
466 DirectoryTypeDebugInfoEmitter bookmarks_emitter_;
467 DirectoryTypeDebugInfoEmitter passwords_emitter_;
469 UpdateHandlerMap update_handler_map_;
470 STLValueDeleter<UpdateHandlerMap> update_handler_map_deleter_;
474 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() {
475 sync_pb::EntitySpecifics result;
476 AddDefaultFieldValue(BOOKMARKS, &result);
481 // Test update application for a few bookmark items.
482 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmark) {
483 sessions::StatusController status;
485 std::string root_server_id = syncable::GetNullId().GetServerId();
486 int64 parent_handle =
487 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
488 "parent", DefaultBookmarkSpecifics(), root_server_id);
490 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
491 "child", DefaultBookmarkSpecifics(), "parent");
493 ApplyBookmarkUpdates(&status);
495 EXPECT_EQ(0, status.num_encryption_conflicts())
496 << "Simple update shouldn't result in conflicts";
497 EXPECT_EQ(0, status.num_hierarchy_conflicts())
498 << "Simple update shouldn't result in conflicts";
499 EXPECT_EQ(2, status.num_updates_applied())
500 << "All items should have been successfully applied";
503 syncable::ReadTransaction trans(FROM_HERE, directory());
505 syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle);
506 syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle);
508 ASSERT_TRUE(parent.good());
509 ASSERT_TRUE(child.good());
511 EXPECT_FALSE(parent.GetIsUnsynced());
512 EXPECT_FALSE(parent.GetIsUnappliedUpdate());
513 EXPECT_FALSE(child.GetIsUnsynced());
514 EXPECT_FALSE(child.GetIsUnappliedUpdate());
518 // Test that the applicator can handle updates delivered out of order.
519 TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
520 BookmarkChildrenBeforeParent) {
521 // Start with some bookmarks whose parents are unknown.
522 std::string root_server_id = syncable::GetNullId().GetServerId();
523 int64 a_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
524 "a_child_created_first", DefaultBookmarkSpecifics(), "parent");
525 int64 x_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
526 "x_child_created_first", DefaultBookmarkSpecifics(), "parent");
528 // Update application will fail.
529 sessions::StatusController status1;
530 ApplyBookmarkUpdates(&status1);
531 EXPECT_EQ(0, status1.num_updates_applied());
532 EXPECT_EQ(2, status1.num_hierarchy_conflicts());
535 syncable::ReadTransaction trans(FROM_HERE, directory());
537 syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle);
538 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
540 ASSERT_TRUE(a.good());
541 ASSERT_TRUE(x.good());
543 EXPECT_TRUE(a.GetIsUnappliedUpdate());
544 EXPECT_TRUE(x.GetIsUnappliedUpdate());
547 // Now add their parent and a few siblings.
548 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
549 "parent", DefaultBookmarkSpecifics(), root_server_id);
550 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
551 "a_child_created_second", DefaultBookmarkSpecifics(), "parent");
552 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
553 "x_child_created_second", DefaultBookmarkSpecifics(), "parent");
555 // Update application will succeed.
556 sessions::StatusController status2;
557 ApplyBookmarkUpdates(&status2);
558 EXPECT_EQ(5, status2.num_updates_applied())
559 << "All updates should have been successfully applied";
562 syncable::ReadTransaction trans(FROM_HERE, directory());
564 syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle);
565 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
567 ASSERT_TRUE(a.good());
568 ASSERT_TRUE(x.good());
570 EXPECT_FALSE(a.GetIsUnappliedUpdate());
571 EXPECT_FALSE(x.GetIsUnappliedUpdate());
575 // Try to apply changes on an item that is both IS_UNSYNCED and
576 // IS_UNAPPLIED_UPDATE. Conflict resolution should be performed.
577 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmarkConflict) {
578 int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x");
580 int original_server_version = -10;
582 syncable::ReadTransaction trans(FROM_HERE, directory());
583 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
584 original_server_version = e.GetServerVersion();
585 ASSERT_NE(original_server_version, e.GetBaseVersion());
586 EXPECT_TRUE(e.GetIsUnsynced());
589 sessions::StatusController status;
590 ApplyBookmarkUpdates(&status);
591 EXPECT_EQ(1, status.num_server_overwrites())
592 << "Unsynced and unapplied item conflict should be resolved";
593 EXPECT_EQ(0, status.num_updates_applied())
594 << "Update should not be applied; we should override the server.";
597 syncable::ReadTransaction trans(FROM_HERE, directory());
598 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
599 ASSERT_TRUE(e.good());
600 EXPECT_EQ(original_server_version, e.GetServerVersion());
601 EXPECT_EQ(original_server_version, e.GetBaseVersion());
602 EXPECT_FALSE(e.GetIsUnappliedUpdate());
604 // The unsynced flag will remain set until we successfully commit the item.
605 EXPECT_TRUE(e.GetIsUnsynced());
609 // Create a simple conflict that is also a hierarchy conflict. If we were to
610 // follow the normal "server wins" logic, we'd end up violating hierarchy
611 // constraints. The hierarchy conflict must take precedence. We can not allow
612 // the update to be applied. The item must remain in the conflict state.
613 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, HierarchyAndSimpleConflict) {
614 // Create a simply-conflicting item. It will start with valid parent ids.
615 int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem(
616 "orphaned_by_server");
618 // Manually set the SERVER_PARENT_ID to bad value.
619 // A bad parent indicates a hierarchy conflict.
620 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
621 syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
622 ASSERT_TRUE(entry.good());
624 entry.PutServerParentId(TestIdFactory::MakeServer("bogus_parent"));
627 sessions::StatusController status;
628 ApplyBookmarkUpdates(&status);
629 EXPECT_EQ(0, status.num_updates_applied());
630 EXPECT_EQ(0, status.num_server_overwrites());
631 EXPECT_EQ(1, status.num_hierarchy_conflicts());
634 syncable::ReadTransaction trans(FROM_HERE, directory());
635 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
636 ASSERT_TRUE(e.good());
637 EXPECT_TRUE(e.GetIsUnappliedUpdate());
638 EXPECT_TRUE(e.GetIsUnsynced());
642 // Attempt to apply an udpate that would create a bookmark folder loop. This
643 // application should fail.
644 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, BookmarkFolderLoop) {
645 // Item 'X' locally has parent of 'root'. Server is updating it to have
648 // Create it as a child of root node.
649 int64 handle = entry_factory()->CreateSyncedItem("X", BOOKMARKS, true);
652 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
653 syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
654 ASSERT_TRUE(entry.good());
656 // Re-parent from root to "Y"
657 entry.PutServerVersion(entry_factory()->GetNextRevision());
658 entry.PutIsUnappliedUpdate(true);
659 entry.PutServerParentId(TestIdFactory::MakeServer("Y"));
662 // Item 'Y' is child of 'X'.
663 entry_factory()->CreateUnsyncedItem(
664 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
667 // If the server's update were applied, we would have X be a child of Y, and Y
668 // as a child of X. That's a directory loop. The UpdateApplicator should
669 // prevent the update from being applied and note that this is a hierarchy
672 sessions::StatusController status;
673 ApplyBookmarkUpdates(&status);
675 // This should count as a hierarchy conflict.
676 EXPECT_EQ(1, status.num_hierarchy_conflicts());
679 syncable::ReadTransaction trans(FROM_HERE, directory());
680 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
681 ASSERT_TRUE(e.good());
682 EXPECT_TRUE(e.GetIsUnappliedUpdate());
683 EXPECT_FALSE(e.GetIsUnsynced());
687 // Test update application where the update has been orphaned by a local folder
688 // deletion. The update application attempt should fail.
689 TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
690 HierarchyConflictDeletedParent) {
691 // Create a locally deleted parent item.
693 entry_factory()->CreateUnsyncedItem(
694 syncable::Id::CreateFromServerId("parent"), TestIdFactory::root(),
695 "parent", true, BOOKMARKS, &parent_handle);
697 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
698 syncable::MutableEntry entry(&trans,
699 syncable::GET_BY_HANDLE,
701 entry.PutIsDel(true);
704 // Create an incoming child from the server.
705 int64 child_handle = entry_factory()->CreateUnappliedNewItemWithParent(
706 "child", DefaultBookmarkSpecifics(), "parent");
708 // The server's update may seem valid to some other client, but on this client
709 // that new item's parent no longer exists. The update should not be applied
710 // and the update applicator should indicate this is a hierarchy conflict.
712 sessions::StatusController status;
713 ApplyBookmarkUpdates(&status);
714 EXPECT_EQ(1, status.num_hierarchy_conflicts());
717 syncable::ReadTransaction trans(FROM_HERE, directory());
718 syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle);
719 ASSERT_TRUE(child.good());
720 EXPECT_TRUE(child.GetIsUnappliedUpdate());
721 EXPECT_FALSE(child.GetIsUnsynced());
725 // Attempt to apply an update that deletes a folder where the folder has
726 // locally-created children. The update application should fail.
727 TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
728 HierarchyConflictDeleteNonEmptyDirectory) {
729 // Create a server-deleted folder as a child of root node.
730 int64 parent_handle =
731 entry_factory()->CreateSyncedItem("parent", BOOKMARKS, true);
733 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
734 syncable::MutableEntry entry(&trans,
735 syncable::GET_BY_HANDLE,
737 ASSERT_TRUE(entry.good());
739 // Delete it on the server.
740 entry.PutServerVersion(entry_factory()->GetNextRevision());
741 entry.PutIsUnappliedUpdate(true);
742 entry.PutServerParentId(TestIdFactory::root());
743 entry.PutServerIsDel(true);
746 // Create a local child of the server-deleted directory.
747 entry_factory()->CreateUnsyncedItem(
748 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"),
749 "child", false, BOOKMARKS, NULL);
751 // The server's request to delete the directory must be ignored, otherwise our
752 // unsynced new child would be orphaned. This is a hierarchy conflict.
754 sessions::StatusController status;
755 ApplyBookmarkUpdates(&status);
757 // This should count as a hierarchy conflict.
758 EXPECT_EQ(1, status.num_hierarchy_conflicts());
761 syncable::ReadTransaction trans(FROM_HERE, directory());
762 syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle);
763 ASSERT_TRUE(parent.good());
764 EXPECT_TRUE(parent.GetIsUnappliedUpdate());
765 EXPECT_FALSE(parent.GetIsUnsynced());
769 // Attempt to apply updates where the updated item's parent is not known to this
770 // client. The update application attempt should fail.
771 TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
772 HierarchyConflictUnknownParent) {
773 // We shouldn't be able to do anything with either of these items.
774 int64 x_handle = entry_factory()->CreateUnappliedNewItemWithParent(
775 "some_item", DefaultBookmarkSpecifics(), "unknown_parent");
776 int64 y_handle = entry_factory()->CreateUnappliedNewItemWithParent(
777 "some_other_item", DefaultBookmarkSpecifics(), "some_item");
779 sessions::StatusController status;
780 ApplyBookmarkUpdates(&status);
782 EXPECT_EQ(2, status.num_hierarchy_conflicts())
783 << "All updates with an unknown ancestors should be in conflict";
784 EXPECT_EQ(0, status.num_updates_applied())
785 << "No item with an unknown ancestor should be applied";
788 syncable::ReadTransaction trans(FROM_HERE, directory());
789 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
790 syncable::Entry y(&trans, syncable::GET_BY_HANDLE, y_handle);
791 ASSERT_TRUE(x.good());
792 ASSERT_TRUE(y.good());
793 EXPECT_TRUE(x.GetIsUnappliedUpdate());
794 EXPECT_TRUE(y.GetIsUnappliedUpdate());
795 EXPECT_FALSE(x.GetIsUnsynced());
796 EXPECT_FALSE(y.GetIsUnsynced());
800 // Attempt application of a mix of items. Some update application attempts will
801 // fail due to hierarchy conflicts. Others should succeed.
802 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, ItemsBothKnownAndUnknown) {
803 // See what happens when there's a mixture of good and bad updates.
804 std::string root_server_id = syncable::GetNullId().GetServerId();
805 int64 u1_handle = entry_factory()->CreateUnappliedNewItemWithParent(
806 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
807 int64 k1_handle = entry_factory()->CreateUnappliedNewItemWithParent(
808 "first_known_item", DefaultBookmarkSpecifics(), root_server_id);
809 int64 u2_handle = entry_factory()->CreateUnappliedNewItemWithParent(
810 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
811 int64 k2_handle = entry_factory()->CreateUnappliedNewItemWithParent(
812 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item");
813 int64 k3_handle = entry_factory()->CreateUnappliedNewItemWithParent(
814 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item");
815 int64 k4_handle = entry_factory()->CreateUnappliedNewItemWithParent(
816 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id);
818 sessions::StatusController status;
819 ApplyBookmarkUpdates(&status);
821 EXPECT_EQ(2, status.num_hierarchy_conflicts())
822 << "The updates with unknown ancestors should be in conflict";
823 EXPECT_EQ(4, status.num_updates_applied())
824 << "The updates with known ancestors should be successfully applied";
827 syncable::ReadTransaction trans(FROM_HERE, directory());
828 syncable::Entry u1(&trans, syncable::GET_BY_HANDLE, u1_handle);
829 syncable::Entry u2(&trans, syncable::GET_BY_HANDLE, u2_handle);
830 syncable::Entry k1(&trans, syncable::GET_BY_HANDLE, k1_handle);
831 syncable::Entry k2(&trans, syncable::GET_BY_HANDLE, k2_handle);
832 syncable::Entry k3(&trans, syncable::GET_BY_HANDLE, k3_handle);
833 syncable::Entry k4(&trans, syncable::GET_BY_HANDLE, k4_handle);
834 ASSERT_TRUE(u1.good());
835 ASSERT_TRUE(u2.good());
836 ASSERT_TRUE(k1.good());
837 ASSERT_TRUE(k2.good());
838 ASSERT_TRUE(k3.good());
839 ASSERT_TRUE(k4.good());
840 EXPECT_TRUE(u1.GetIsUnappliedUpdate());
841 EXPECT_TRUE(u2.GetIsUnappliedUpdate());
842 EXPECT_FALSE(k1.GetIsUnappliedUpdate());
843 EXPECT_FALSE(k2.GetIsUnappliedUpdate());
844 EXPECT_FALSE(k3.GetIsUnappliedUpdate());
845 EXPECT_FALSE(k4.GetIsUnappliedUpdate());
849 // Attempt application of password upates where the passphrase is known.
850 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, DecryptablePassword) {
851 // Decryptable password updates should be applied.
852 Cryptographer* cryptographer;
854 // Storing the cryptographer separately is bad, but for this test we
856 syncable::ReadTransaction trans(FROM_HERE, directory());
857 cryptographer = directory()->GetCryptographer(&trans);
860 KeyParams params = {"localhost", "dummy", "foobar"};
861 cryptographer->AddKey(params);
863 sync_pb::EntitySpecifics specifics;
864 sync_pb::PasswordSpecificsData data;
865 data.set_origin("http://example.com");
867 cryptographer->Encrypt(data,
868 specifics.mutable_password()->mutable_encrypted());
870 entry_factory()->CreateUnappliedNewItem("item", specifics, false);
872 sessions::StatusController status;
873 ApplyPasswordUpdates(&status);
875 EXPECT_EQ(1, status.num_updates_applied())
876 << "The updates that can be decrypted should be applied";
879 syncable::ReadTransaction trans(FROM_HERE, directory());
880 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
881 ASSERT_TRUE(e.good());
882 EXPECT_FALSE(e.GetIsUnappliedUpdate());
883 EXPECT_FALSE(e.GetIsUnsynced());
887 // Attempt application of encrypted items when the passphrase is not known.
888 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, UndecryptableData) {
889 // Undecryptable updates should not be applied.
890 sync_pb::EntitySpecifics encrypted_bookmark;
891 encrypted_bookmark.mutable_encrypted();
892 AddDefaultFieldValue(BOOKMARKS, &encrypted_bookmark);
893 std::string root_server_id = syncable::GetNullId().GetServerId();
894 int64 folder_handle = entry_factory()->CreateUnappliedNewItemWithParent(
898 int64 bookmark_handle = entry_factory()->CreateUnappliedNewItem(
902 sync_pb::EntitySpecifics encrypted_password;
903 encrypted_password.mutable_password();
904 int64 password_handle = entry_factory()->CreateUnappliedNewItem(
909 sessions::StatusController status;
910 ApplyBookmarkUpdates(&status);
911 ApplyPasswordUpdates(&status);
913 EXPECT_EQ(3, status.num_encryption_conflicts())
914 << "Updates that can't be decrypted should be in encryption conflict";
915 EXPECT_EQ(0, status.num_updates_applied())
916 << "No update that can't be decrypted should be applied";
919 syncable::ReadTransaction trans(FROM_HERE, directory());
920 syncable::Entry folder(&trans, syncable::GET_BY_HANDLE, folder_handle);
921 syncable::Entry bm(&trans, syncable::GET_BY_HANDLE, bookmark_handle);
922 syncable::Entry pw(&trans, syncable::GET_BY_HANDLE, password_handle);
923 ASSERT_TRUE(folder.good());
924 ASSERT_TRUE(bm.good());
925 ASSERT_TRUE(pw.good());
926 EXPECT_TRUE(folder.GetIsUnappliedUpdate());
927 EXPECT_TRUE(bm.GetIsUnappliedUpdate());
928 EXPECT_TRUE(pw.GetIsUnappliedUpdate());
932 // Test a mix of decryptable and undecryptable updates.
933 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SomeUndecryptablePassword) {
934 Cryptographer* cryptographer;
936 int64 decryptable_handle = -1;
937 int64 undecryptable_handle = -1;
939 // Only decryptable password updates should be applied.
941 sync_pb::EntitySpecifics specifics;
942 sync_pb::PasswordSpecificsData data;
943 data.set_origin("http://example.com/1");
945 syncable::ReadTransaction trans(FROM_HERE, directory());
946 cryptographer = directory()->GetCryptographer(&trans);
948 KeyParams params = {"localhost", "dummy", "foobar"};
949 cryptographer->AddKey(params);
951 cryptographer->Encrypt(data,
952 specifics.mutable_password()->mutable_encrypted());
955 entry_factory()->CreateUnappliedNewItem("item1", specifics, false);
958 // Create a new cryptographer, independent of the one in the session.
959 Cryptographer other_cryptographer(cryptographer->encryptor());
960 KeyParams params = {"localhost", "dummy", "bazqux"};
961 other_cryptographer.AddKey(params);
963 sync_pb::EntitySpecifics specifics;
964 sync_pb::PasswordSpecificsData data;
965 data.set_origin("http://example.com/2");
967 other_cryptographer.Encrypt(data,
968 specifics.mutable_password()->mutable_encrypted());
969 undecryptable_handle =
970 entry_factory()->CreateUnappliedNewItem("item2", specifics, false);
973 sessions::StatusController status;
974 ApplyPasswordUpdates(&status);
976 EXPECT_EQ(1, status.num_encryption_conflicts())
977 << "The updates that can't be decrypted should be in encryption "
979 EXPECT_EQ(1, status.num_updates_applied())
980 << "The undecryptable password update shouldn't be applied";
983 syncable::ReadTransaction trans(FROM_HERE, directory());
984 syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, decryptable_handle);
985 syncable::Entry e2(&trans, syncable::GET_BY_HANDLE, undecryptable_handle);
986 ASSERT_TRUE(e1.good());
987 ASSERT_TRUE(e2.good());
988 EXPECT_FALSE(e1.GetIsUnappliedUpdate());
989 EXPECT_TRUE(e2.GetIsUnappliedUpdate());
993 } // namespace syncer