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 // Used in the construction of DirectoryTypeDebugInfoEmitters.
91 ObserverList
<TypeDebugInfoObserver
> type_observers_
;
94 base::MessageLoop loop_
; // Needed to initialize the directory.
95 TestDirectorySetterUpper dir_maker_
;
96 scoped_refptr
<FakeModelWorker
> ui_worker_
;
99 scoped_ptr
<sync_pb::SyncEntity
>
100 DirectoryUpdateHandlerProcessUpdateTest::CreateUpdate(
101 const std::string
& id
,
102 const std::string
& parent
,
103 const ModelType
& type
) {
104 scoped_ptr
<sync_pb::SyncEntity
> e(new sync_pb::SyncEntity());
105 e
->set_id_string(id
);
106 e
->set_parent_id_string(parent
);
107 e
->set_non_unique_name(id
);
109 e
->set_version(kDefaultVersion
);
110 AddDefaultFieldValue(type
, e
->mutable_specifics());
114 void DirectoryUpdateHandlerProcessUpdateTest::UpdateSyncEntities(
115 DirectoryUpdateHandler
* handler
,
116 const SyncEntityList
& applicable_updates
,
117 sessions::StatusController
* status
) {
118 syncable::ModelNeutralWriteTransaction
trans(FROM_HERE
, UNITTEST
, dir());
119 handler
->UpdateSyncEntities(&trans
, applicable_updates
, status
);
122 void DirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers(
123 DirectoryUpdateHandler
* handler
,
124 const sync_pb::DataTypeProgressMarker
& progress
) {
125 handler
->UpdateProgressMarker(progress
);
128 static const char kCacheGuid
[] = "IrcjZ2jyzHDV9Io4+zKcXQ==";
130 // Test that the bookmark tag is set on newly downloaded items.
131 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, NewBookmarkTag
) {
132 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
133 DirectoryUpdateHandler
handler(dir(), BOOKMARKS
, ui_worker(), &emitter
);
134 sync_pb::GetUpdatesResponse gu_response
;
135 sessions::StatusController status
;
137 // Add a bookmark item to the update message.
138 std::string root
= syncable::GetNullId().GetServerId();
139 syncable::Id server_id
= syncable::Id::CreateFromServerId("b1");
140 scoped_ptr
<sync_pb::SyncEntity
> e
=
141 CreateUpdate(SyncableIdToProto(server_id
), root
, BOOKMARKS
);
142 e
->set_originator_cache_guid(
143 std::string(kCacheGuid
, arraysize(kCacheGuid
)-1));
144 syncable::Id client_id
= syncable::Id::CreateFromClientString("-2");
145 e
->set_originator_client_item_id(client_id
.GetServerId());
146 e
->set_position_in_parent(0);
148 // Add it to the applicable updates list.
149 SyncEntityList bookmark_updates
;
150 bookmark_updates
.push_back(e
.get());
152 // Process the update.
153 UpdateSyncEntities(&handler
, bookmark_updates
, &status
);
155 syncable::ReadTransaction
trans(FROM_HERE
, dir());
156 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, server_id
);
157 ASSERT_TRUE(entry
.good());
158 EXPECT_TRUE(UniquePosition::IsValidSuffix(entry
.GetUniqueBookmarkTag()));
159 EXPECT_TRUE(entry
.GetServerUniquePosition().IsValid());
161 // If this assertion fails, that might indicate that the algorithm used to
162 // generate bookmark tags has been modified. This could have implications for
163 // bookmark ordering. Please make sure you know what you're doing if you
164 // intend to make such a change.
165 EXPECT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", entry
.GetUniqueBookmarkTag());
168 // Test the receipt of a type root node.
169 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
,
170 ReceiveServerCreatedBookmarkFolders
) {
171 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
172 DirectoryUpdateHandler
handler(dir(), BOOKMARKS
, ui_worker(), &emitter
);
173 sync_pb::GetUpdatesResponse gu_response
;
174 sessions::StatusController status
;
176 // Create an update that mimics the bookmark root.
177 syncable::Id server_id
= syncable::Id::CreateFromServerId("xyz");
178 std::string root
= syncable::GetNullId().GetServerId();
179 scoped_ptr
<sync_pb::SyncEntity
> e
=
180 CreateUpdate(SyncableIdToProto(server_id
), root
, BOOKMARKS
);
181 e
->set_server_defined_unique_tag("google_chrome_bookmarks");
184 // Add it to the applicable updates list.
185 SyncEntityList bookmark_updates
;
186 bookmark_updates
.push_back(e
.get());
188 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e
));
191 UpdateSyncEntities(&handler
, bookmark_updates
, &status
);
193 // Verify the results.
194 syncable::ReadTransaction
trans(FROM_HERE
, dir());
195 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, server_id
);
196 ASSERT_TRUE(entry
.good());
198 EXPECT_FALSE(entry
.ShouldMaintainPosition());
199 EXPECT_FALSE(entry
.GetUniquePosition().IsValid());
200 EXPECT_FALSE(entry
.GetServerUniquePosition().IsValid());
201 EXPECT_TRUE(entry
.GetUniqueBookmarkTag().empty());
204 // Test the receipt of a non-bookmark item.
205 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, ReceiveNonBookmarkItem
) {
206 DirectoryTypeDebugInfoEmitter
emitter(AUTOFILL
, &type_observers_
);
207 DirectoryUpdateHandler
handler(dir(), AUTOFILL
, ui_worker(), &emitter
);
208 sync_pb::GetUpdatesResponse gu_response
;
209 sessions::StatusController status
;
211 std::string root
= syncable::GetNullId().GetServerId();
212 syncable::Id server_id
= syncable::Id::CreateFromServerId("xyz");
213 scoped_ptr
<sync_pb::SyncEntity
> e
=
214 CreateUpdate(SyncableIdToProto(server_id
), root
, AUTOFILL
);
215 e
->set_server_defined_unique_tag("9PGRuKdX5sHyGMB17CvYTXuC43I=");
217 // Add it to the applicable updates list.
218 SyncEntityList autofill_updates
;
219 autofill_updates
.push_back(e
.get());
221 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e
));
224 UpdateSyncEntities(&handler
, autofill_updates
, &status
);
226 syncable::ReadTransaction
trans(FROM_HERE
, dir());
227 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, server_id
);
228 ASSERT_TRUE(entry
.good());
230 EXPECT_FALSE(entry
.ShouldMaintainPosition());
231 EXPECT_FALSE(entry
.GetUniquePosition().IsValid());
232 EXPECT_FALSE(entry
.GetServerUniquePosition().IsValid());
233 EXPECT_TRUE(entry
.GetUniqueBookmarkTag().empty());
236 // Tests the setting of progress markers.
237 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, ProcessNewProgressMarkers
) {
238 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
239 DirectoryUpdateHandler
handler(dir(), BOOKMARKS
, ui_worker(), &emitter
);
241 sync_pb::DataTypeProgressMarker progress
;
242 progress
.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS
));
243 progress
.set_token("token");
245 UpdateProgressMarkers(&handler
, progress
);
247 sync_pb::DataTypeProgressMarker saved
;
248 dir()->GetDownloadProgress(BOOKMARKS
, &saved
);
250 EXPECT_EQ(progress
.token(), saved
.token());
251 EXPECT_EQ(progress
.data_type_id(), saved
.data_type_id());
254 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, GarbageCollectionByVersion
) {
255 DirectoryTypeDebugInfoEmitter
emitter(SYNCED_NOTIFICATIONS
, &type_observers_
);
256 DirectoryUpdateHandler
handler(dir(), SYNCED_NOTIFICATIONS
,
257 ui_worker(), &emitter
);
258 sessions::StatusController status
;
260 sync_pb::DataTypeProgressMarker progress
;
261 progress
.set_data_type_id(
262 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
));
263 progress
.set_token("token");
264 progress
.mutable_gc_directive()->set_version_watermark(kDefaultVersion
+ 10);
266 sync_pb::DataTypeContext context
;
267 context
.set_data_type_id(
268 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
));
269 context
.set_context("context");
270 context
.set_version(1);
272 scoped_ptr
<sync_pb::SyncEntity
> type_root
=
273 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
274 syncable::GetNullId().GetServerId(),
275 SYNCED_NOTIFICATIONS
);
276 type_root
->set_server_defined_unique_tag(
277 ModelTypeToRootTag(SYNCED_NOTIFICATIONS
));
278 type_root
->set_folder(true);
280 scoped_ptr
<sync_pb::SyncEntity
> e1
=
281 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
282 type_root
->id_string(),
283 SYNCED_NOTIFICATIONS
);
285 scoped_ptr
<sync_pb::SyncEntity
> e2
=
286 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
287 type_root
->id_string(),
288 SYNCED_NOTIFICATIONS
);
289 e2
->set_version(kDefaultVersion
+ 100);
291 // Add to the applicable updates list.
292 SyncEntityList updates
;
293 updates
.push_back(type_root
.get());
294 updates
.push_back(e1
.get());
295 updates
.push_back(e2
.get());
297 // Process and apply updates.
300 handler
.ProcessGetUpdatesResponse(progress
, context
, updates
, &status
));
301 handler
.ApplyUpdates(&status
);
303 // Verify none is deleted because they are unapplied during GC.
304 EXPECT_TRUE(EntryExists(type_root
->id_string()));
305 EXPECT_TRUE(EntryExists(e1
->id_string()));
306 EXPECT_TRUE(EntryExists(e2
->id_string()));
308 // Process and apply again. Old entry is deleted but not root.
309 progress
.mutable_gc_directive()->set_version_watermark(kDefaultVersion
+ 20);
311 handler
.ProcessGetUpdatesResponse(
312 progress
, context
, SyncEntityList(), &status
));
313 handler
.ApplyUpdates(&status
);
314 EXPECT_TRUE(EntryExists(type_root
->id_string()));
315 EXPECT_FALSE(EntryExists(e1
->id_string()));
316 EXPECT_TRUE(EntryExists(e2
->id_string()));
319 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, ContextVersion
) {
320 DirectoryTypeDebugInfoEmitter
emitter(SYNCED_NOTIFICATIONS
, &type_observers_
);
321 DirectoryUpdateHandler
handler(dir(), SYNCED_NOTIFICATIONS
,
322 ui_worker(), &emitter
);
323 sessions::StatusController status
;
324 int field_number
= GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
);
326 sync_pb::DataTypeProgressMarker progress
;
327 progress
.set_data_type_id(
328 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
));
329 progress
.set_token("token");
331 sync_pb::DataTypeContext old_context
;
332 old_context
.set_version(1);
333 old_context
.set_context("data");
334 old_context
.set_data_type_id(field_number
);
336 scoped_ptr
<sync_pb::SyncEntity
> type_root
=
337 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
338 syncable::GetNullId().GetServerId(),
339 SYNCED_NOTIFICATIONS
);
340 type_root
->set_server_defined_unique_tag(
341 ModelTypeToRootTag(SYNCED_NOTIFICATIONS
));
342 type_root
->set_folder(true);
343 scoped_ptr
<sync_pb::SyncEntity
> e1
=
344 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
345 type_root
->id_string(),
346 SYNCED_NOTIFICATIONS
);
348 SyncEntityList updates
;
349 updates
.push_back(type_root
.get());
350 updates
.push_back(e1
.get());
352 // The first response should be processed fine.
354 handler
.ProcessGetUpdatesResponse(
355 progress
, old_context
, updates
, &status
));
356 handler
.ApplyUpdates(&status
);
358 EXPECT_TRUE(EntryExists(type_root
->id_string()));
359 EXPECT_TRUE(EntryExists(e1
->id_string()));
362 sync_pb::DataTypeContext dir_context
;
363 syncable::ReadTransaction
trans(FROM_HERE
, dir());
364 trans
.directory()->GetDataTypeContext(
365 &trans
, SYNCED_NOTIFICATIONS
, &dir_context
);
366 EXPECT_EQ(old_context
.SerializeAsString(), dir_context
.SerializeAsString());
369 sync_pb::DataTypeContext new_context
;
370 new_context
.set_version(0);
371 new_context
.set_context("old");
372 new_context
.set_data_type_id(field_number
);
374 scoped_ptr
<sync_pb::SyncEntity
> e2
=
375 CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
376 type_root
->id_string(),
377 SYNCED_NOTIFICATIONS
);
379 updates
.push_back(e2
.get());
381 // The second response, with an old context version, should result in an
382 // error and the updates should be dropped.
383 EXPECT_EQ(DATATYPE_TRIGGERED_RETRY
,
384 handler
.ProcessGetUpdatesResponse(
385 progress
, new_context
, updates
, &status
));
386 handler
.ApplyUpdates(&status
);
388 EXPECT_FALSE(EntryExists(e2
->id_string()));
391 sync_pb::DataTypeContext dir_context
;
392 syncable::ReadTransaction
trans(FROM_HERE
, dir());
393 trans
.directory()->GetDataTypeContext(
394 &trans
, SYNCED_NOTIFICATIONS
, &dir_context
);
395 EXPECT_EQ(old_context
.SerializeAsString(), dir_context
.SerializeAsString());
399 // A test harness for tests that focus on applying updates.
401 // Update application is performed when we want to take updates that were
402 // previously downloaded, processed, and stored in our syncable::Directory
403 // and use them to update our local state (both the Directory's local state
404 // and the model's local state, though these tests focus only on the Directory's
407 // This is kept separate from the update processing test in part for historical
408 // reasons, and in part because these tests may require a bit more infrastrcture
409 // in the future. Update application should happen on a different thread a lot
410 // of the time so these tests may end up requiring more infrastructure than the
411 // update processing tests. Currently, we're bypassing most of those issues by
412 // using FakeModelWorkers, so there's not much difference between the two test
414 class DirectoryUpdateHandlerApplyUpdateTest
: public ::testing::Test
{
416 DirectoryUpdateHandlerApplyUpdateTest()
417 : ui_worker_(new FakeModelWorker(GROUP_UI
)),
418 password_worker_(new FakeModelWorker(GROUP_PASSWORD
)),
419 passive_worker_(new FakeModelWorker(GROUP_PASSIVE
)),
420 bookmarks_emitter_(BOOKMARKS
, &type_observers_
),
421 passwords_emitter_(PASSWORDS
, &type_observers_
),
422 update_handler_map_deleter_(&update_handler_map_
) {}
424 virtual void SetUp() OVERRIDE
{
426 entry_factory_
.reset(new TestEntryFactory(directory()));
428 update_handler_map_
.insert(std::make_pair(
430 new DirectoryUpdateHandler(directory(), BOOKMARKS
,
431 ui_worker_
, &bookmarks_emitter_
)));
432 update_handler_map_
.insert(std::make_pair(
434 new DirectoryUpdateHandler(directory(),
437 &passwords_emitter_
)));
440 virtual void TearDown() OVERRIDE
{
441 dir_maker_
.TearDown();
444 const UpdateCounters
& GetBookmarksUpdateCounters() {
445 return bookmarks_emitter_
.GetUpdateCounters();
448 const UpdateCounters
& GetPasswordsUpdateCounters() {
449 return passwords_emitter_
.GetUpdateCounters();
453 void ApplyBookmarkUpdates(sessions::StatusController
* status
) {
454 update_handler_map_
[BOOKMARKS
]->ApplyUpdates(status
);
457 void ApplyPasswordUpdates(sessions::StatusController
* status
) {
458 update_handler_map_
[PASSWORDS
]->ApplyUpdates(status
);
461 TestEntryFactory
* entry_factory() {
462 return entry_factory_
.get();
465 syncable::Directory
* directory() {
466 return dir_maker_
.directory();
470 typedef std::map
<ModelType
, UpdateHandler
*> UpdateHandlerMap
;
472 base::MessageLoop loop_
; // Needed to initialize the directory.
473 TestDirectorySetterUpper dir_maker_
;
474 scoped_ptr
<TestEntryFactory
> entry_factory_
;
476 scoped_refptr
<FakeModelWorker
> ui_worker_
;
477 scoped_refptr
<FakeModelWorker
> password_worker_
;
478 scoped_refptr
<FakeModelWorker
> passive_worker_
;
480 ObserverList
<TypeDebugInfoObserver
> type_observers_
;
481 DirectoryTypeDebugInfoEmitter bookmarks_emitter_
;
482 DirectoryTypeDebugInfoEmitter passwords_emitter_
;
484 UpdateHandlerMap update_handler_map_
;
485 STLValueDeleter
<UpdateHandlerMap
> update_handler_map_deleter_
;
489 sync_pb::EntitySpecifics
DefaultBookmarkSpecifics() {
490 sync_pb::EntitySpecifics result
;
491 AddDefaultFieldValue(BOOKMARKS
, &result
);
496 // Test update application for a few bookmark items.
497 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, SimpleBookmark
) {
498 sessions::StatusController status
;
500 std::string root_server_id
= syncable::GetNullId().GetServerId();
501 int64 parent_handle
=
502 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
503 "parent", DefaultBookmarkSpecifics(), root_server_id
);
505 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
506 "child", DefaultBookmarkSpecifics(), "parent");
508 ApplyBookmarkUpdates(&status
);
510 const UpdateCounters
& counter
= GetBookmarksUpdateCounters();
511 EXPECT_EQ(0, counter
.num_encryption_conflict_application_failures
)
512 << "Simple update shouldn't result in conflicts";
513 EXPECT_EQ(0, counter
.num_hierarchy_conflict_application_failures
)
514 << "Simple update shouldn't result in conflicts";
515 EXPECT_EQ(2, counter
.num_updates_applied
)
516 << "All items should have been successfully applied";
519 syncable::ReadTransaction
trans(FROM_HERE
, directory());
521 syncable::Entry
parent(&trans
, syncable::GET_BY_HANDLE
, parent_handle
);
522 syncable::Entry
child(&trans
, syncable::GET_BY_HANDLE
, child_handle
);
524 ASSERT_TRUE(parent
.good());
525 ASSERT_TRUE(child
.good());
527 EXPECT_FALSE(parent
.GetIsUnsynced());
528 EXPECT_FALSE(parent
.GetIsUnappliedUpdate());
529 EXPECT_FALSE(child
.GetIsUnsynced());
530 EXPECT_FALSE(child
.GetIsUnappliedUpdate());
534 // Test that the applicator can handle updates delivered out of order.
535 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
536 BookmarkChildrenBeforeParent
) {
537 // Start with some bookmarks whose parents are unknown.
538 std::string root_server_id
= syncable::GetNullId().GetServerId();
539 int64 a_handle
= entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
540 "a_child_created_first", DefaultBookmarkSpecifics(), "parent");
541 int64 x_handle
= entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
542 "x_child_created_first", DefaultBookmarkSpecifics(), "parent");
544 // Update application will fail.
545 sessions::StatusController status1
;
546 ApplyBookmarkUpdates(&status1
);
547 EXPECT_EQ(0, status1
.num_updates_applied());
548 EXPECT_EQ(2, status1
.num_hierarchy_conflicts());
551 syncable::ReadTransaction
trans(FROM_HERE
, directory());
553 syncable::Entry
a(&trans
, syncable::GET_BY_HANDLE
, a_handle
);
554 syncable::Entry
x(&trans
, syncable::GET_BY_HANDLE
, x_handle
);
556 ASSERT_TRUE(a
.good());
557 ASSERT_TRUE(x
.good());
559 EXPECT_TRUE(a
.GetIsUnappliedUpdate());
560 EXPECT_TRUE(x
.GetIsUnappliedUpdate());
563 // Now add their parent and a few siblings.
564 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
565 "parent", DefaultBookmarkSpecifics(), root_server_id
);
566 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
567 "a_child_created_second", DefaultBookmarkSpecifics(), "parent");
568 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
569 "x_child_created_second", DefaultBookmarkSpecifics(), "parent");
571 // Update application will succeed.
572 sessions::StatusController status2
;
573 ApplyBookmarkUpdates(&status2
);
574 EXPECT_EQ(5, status2
.num_updates_applied())
575 << "All updates should have been successfully applied";
578 syncable::ReadTransaction
trans(FROM_HERE
, directory());
580 syncable::Entry
a(&trans
, syncable::GET_BY_HANDLE
, a_handle
);
581 syncable::Entry
x(&trans
, syncable::GET_BY_HANDLE
, x_handle
);
583 ASSERT_TRUE(a
.good());
584 ASSERT_TRUE(x
.good());
586 EXPECT_FALSE(a
.GetIsUnappliedUpdate());
587 EXPECT_FALSE(x
.GetIsUnappliedUpdate());
591 // Try to apply changes on an item that is both IS_UNSYNCED and
592 // IS_UNAPPLIED_UPDATE. Conflict resolution should be performed.
593 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, SimpleBookmarkConflict
) {
594 int64 handle
= entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x");
596 int original_server_version
= -10;
598 syncable::ReadTransaction
trans(FROM_HERE
, directory());
599 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
600 original_server_version
= e
.GetServerVersion();
601 ASSERT_NE(original_server_version
, e
.GetBaseVersion());
602 EXPECT_TRUE(e
.GetIsUnsynced());
605 sessions::StatusController status
;
606 ApplyBookmarkUpdates(&status
);
608 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
609 EXPECT_EQ(1, counters
.num_server_overwrites
)
610 << "Unsynced and unapplied item conflict should be resolved";
611 EXPECT_EQ(0, counters
.num_updates_applied
)
612 << "Update should not be applied; we should override the server.";
615 syncable::ReadTransaction
trans(FROM_HERE
, directory());
616 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
617 ASSERT_TRUE(e
.good());
618 EXPECT_EQ(original_server_version
, e
.GetServerVersion());
619 EXPECT_EQ(original_server_version
, e
.GetBaseVersion());
620 EXPECT_FALSE(e
.GetIsUnappliedUpdate());
622 // The unsynced flag will remain set until we successfully commit the item.
623 EXPECT_TRUE(e
.GetIsUnsynced());
627 // Create a simple conflict that is also a hierarchy conflict. If we were to
628 // follow the normal "server wins" logic, we'd end up violating hierarchy
629 // constraints. The hierarchy conflict must take precedence. We can not allow
630 // the update to be applied. The item must remain in the conflict state.
631 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, HierarchyAndSimpleConflict
) {
632 // Create a simply-conflicting item. It will start with valid parent ids.
633 int64 handle
= entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem(
634 "orphaned_by_server");
636 // Manually set the SERVER_PARENT_ID to bad value.
637 // A bad parent indicates a hierarchy conflict.
638 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
639 syncable::MutableEntry
entry(&trans
, syncable::GET_BY_HANDLE
, handle
);
640 ASSERT_TRUE(entry
.good());
642 entry
.PutServerParentId(TestIdFactory::MakeServer("bogus_parent"));
645 sessions::StatusController status
;
646 ApplyBookmarkUpdates(&status
);
648 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
649 EXPECT_EQ(0, counters
.num_updates_applied
);
650 EXPECT_EQ(0, counters
.num_server_overwrites
);
651 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
654 syncable::ReadTransaction
trans(FROM_HERE
, directory());
655 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
656 ASSERT_TRUE(e
.good());
657 EXPECT_TRUE(e
.GetIsUnappliedUpdate());
658 EXPECT_TRUE(e
.GetIsUnsynced());
662 // Attempt to apply an udpate that would create a bookmark folder loop. This
663 // application should fail.
664 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, BookmarkFolderLoop
) {
665 // Item 'X' locally has parent of 'root'. Server is updating it to have
668 // Create it as a child of root node.
669 int64 handle
= entry_factory()->CreateSyncedItem("X", BOOKMARKS
, true);
672 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
673 syncable::MutableEntry
entry(&trans
, syncable::GET_BY_HANDLE
, handle
);
674 ASSERT_TRUE(entry
.good());
676 // Re-parent from root to "Y"
677 entry
.PutServerVersion(entry_factory()->GetNextRevision());
678 entry
.PutIsUnappliedUpdate(true);
679 entry
.PutServerParentId(TestIdFactory::MakeServer("Y"));
682 // Item 'Y' is child of 'X'.
683 entry_factory()->CreateUnsyncedItem(
684 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
687 // If the server's update were applied, we would have X be a child of Y, and Y
688 // as a child of X. That's a directory loop. The UpdateApplicator should
689 // prevent the update from being applied and note that this is a hierarchy
692 sessions::StatusController status
;
693 ApplyBookmarkUpdates(&status
);
695 // This should count as a hierarchy conflict.
696 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
697 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
700 syncable::ReadTransaction
trans(FROM_HERE
, directory());
701 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
702 ASSERT_TRUE(e
.good());
703 EXPECT_TRUE(e
.GetIsUnappliedUpdate());
704 EXPECT_FALSE(e
.GetIsUnsynced());
708 // Test update application where the update has been orphaned by a local folder
709 // deletion. The update application attempt should fail.
710 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
711 HierarchyConflictDeletedParent
) {
712 // Create a locally deleted parent item.
714 entry_factory()->CreateUnsyncedItem(
715 syncable::Id::CreateFromServerId("parent"), TestIdFactory::root(),
716 "parent", true, BOOKMARKS
, &parent_handle
);
718 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
719 syncable::MutableEntry
entry(&trans
,
720 syncable::GET_BY_HANDLE
,
722 entry
.PutIsDel(true);
725 // Create an incoming child from the server.
726 int64 child_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
727 "child", DefaultBookmarkSpecifics(), "parent");
729 // The server's update may seem valid to some other client, but on this client
730 // that new item's parent no longer exists. The update should not be applied
731 // and the update applicator should indicate this is a hierarchy conflict.
733 sessions::StatusController status
;
734 ApplyBookmarkUpdates(&status
);
735 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
736 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
739 syncable::ReadTransaction
trans(FROM_HERE
, directory());
740 syncable::Entry
child(&trans
, syncable::GET_BY_HANDLE
, child_handle
);
741 ASSERT_TRUE(child
.good());
742 EXPECT_TRUE(child
.GetIsUnappliedUpdate());
743 EXPECT_FALSE(child
.GetIsUnsynced());
747 // Attempt to apply an update that deletes a folder where the folder has
748 // locally-created children. The update application should fail.
749 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
750 HierarchyConflictDeleteNonEmptyDirectory
) {
751 // Create a server-deleted folder as a child of root node.
752 int64 parent_handle
=
753 entry_factory()->CreateSyncedItem("parent", BOOKMARKS
, true);
755 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
756 syncable::MutableEntry
entry(&trans
,
757 syncable::GET_BY_HANDLE
,
759 ASSERT_TRUE(entry
.good());
761 // Delete it on the server.
762 entry
.PutServerVersion(entry_factory()->GetNextRevision());
763 entry
.PutIsUnappliedUpdate(true);
764 entry
.PutServerParentId(TestIdFactory::root());
765 entry
.PutServerIsDel(true);
768 // Create a local child of the server-deleted directory.
769 entry_factory()->CreateUnsyncedItem(
770 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"),
771 "child", false, BOOKMARKS
, NULL
);
773 // The server's request to delete the directory must be ignored, otherwise our
774 // unsynced new child would be orphaned. This is a hierarchy conflict.
776 sessions::StatusController status
;
777 ApplyBookmarkUpdates(&status
);
779 // This should count as a hierarchy conflict.
780 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
781 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
784 syncable::ReadTransaction
trans(FROM_HERE
, directory());
785 syncable::Entry
parent(&trans
, syncable::GET_BY_HANDLE
, parent_handle
);
786 ASSERT_TRUE(parent
.good());
787 EXPECT_TRUE(parent
.GetIsUnappliedUpdate());
788 EXPECT_FALSE(parent
.GetIsUnsynced());
792 // Attempt to apply updates where the updated item's parent is not known to this
793 // client. The update application attempt should fail.
794 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
795 HierarchyConflictUnknownParent
) {
796 // We shouldn't be able to do anything with either of these items.
797 int64 x_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
798 "some_item", DefaultBookmarkSpecifics(), "unknown_parent");
799 int64 y_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
800 "some_other_item", DefaultBookmarkSpecifics(), "some_item");
802 sessions::StatusController status
;
803 ApplyBookmarkUpdates(&status
);
805 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
806 EXPECT_EQ(2, counters
.num_hierarchy_conflict_application_failures
)
807 << "All updates with an unknown ancestors should be in conflict";
808 EXPECT_EQ(0, counters
.num_updates_applied
)
809 << "No item with an unknown ancestor should be applied";
812 syncable::ReadTransaction
trans(FROM_HERE
, directory());
813 syncable::Entry
x(&trans
, syncable::GET_BY_HANDLE
, x_handle
);
814 syncable::Entry
y(&trans
, syncable::GET_BY_HANDLE
, y_handle
);
815 ASSERT_TRUE(x
.good());
816 ASSERT_TRUE(y
.good());
817 EXPECT_TRUE(x
.GetIsUnappliedUpdate());
818 EXPECT_TRUE(y
.GetIsUnappliedUpdate());
819 EXPECT_FALSE(x
.GetIsUnsynced());
820 EXPECT_FALSE(y
.GetIsUnsynced());
824 // Attempt application of a mix of items. Some update application attempts will
825 // fail due to hierarchy conflicts. Others should succeed.
826 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, ItemsBothKnownAndUnknown
) {
827 // See what happens when there's a mixture of good and bad updates.
828 std::string root_server_id
= syncable::GetNullId().GetServerId();
829 int64 u1_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
830 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
831 int64 k1_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
832 "first_known_item", DefaultBookmarkSpecifics(), root_server_id
);
833 int64 u2_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
834 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
835 int64 k2_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
836 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item");
837 int64 k3_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
838 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item");
839 int64 k4_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
840 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id
);
842 sessions::StatusController status
;
843 ApplyBookmarkUpdates(&status
);
845 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
846 EXPECT_EQ(2, counters
.num_hierarchy_conflict_application_failures
)
847 << "The updates with unknown ancestors should be in conflict";
848 EXPECT_EQ(4, counters
.num_updates_applied
)
849 << "The updates with known ancestors should be successfully applied";
852 syncable::ReadTransaction
trans(FROM_HERE
, directory());
853 syncable::Entry
u1(&trans
, syncable::GET_BY_HANDLE
, u1_handle
);
854 syncable::Entry
u2(&trans
, syncable::GET_BY_HANDLE
, u2_handle
);
855 syncable::Entry
k1(&trans
, syncable::GET_BY_HANDLE
, k1_handle
);
856 syncable::Entry
k2(&trans
, syncable::GET_BY_HANDLE
, k2_handle
);
857 syncable::Entry
k3(&trans
, syncable::GET_BY_HANDLE
, k3_handle
);
858 syncable::Entry
k4(&trans
, syncable::GET_BY_HANDLE
, k4_handle
);
859 ASSERT_TRUE(u1
.good());
860 ASSERT_TRUE(u2
.good());
861 ASSERT_TRUE(k1
.good());
862 ASSERT_TRUE(k2
.good());
863 ASSERT_TRUE(k3
.good());
864 ASSERT_TRUE(k4
.good());
865 EXPECT_TRUE(u1
.GetIsUnappliedUpdate());
866 EXPECT_TRUE(u2
.GetIsUnappliedUpdate());
867 EXPECT_FALSE(k1
.GetIsUnappliedUpdate());
868 EXPECT_FALSE(k2
.GetIsUnappliedUpdate());
869 EXPECT_FALSE(k3
.GetIsUnappliedUpdate());
870 EXPECT_FALSE(k4
.GetIsUnappliedUpdate());
874 // Attempt application of password upates where the passphrase is known.
875 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, DecryptablePassword
) {
876 // Decryptable password updates should be applied.
877 Cryptographer
* cryptographer
;
879 // Storing the cryptographer separately is bad, but for this test we
881 syncable::ReadTransaction
trans(FROM_HERE
, directory());
882 cryptographer
= directory()->GetCryptographer(&trans
);
885 KeyParams params
= {"localhost", "dummy", "foobar"};
886 cryptographer
->AddKey(params
);
888 sync_pb::EntitySpecifics specifics
;
889 sync_pb::PasswordSpecificsData data
;
890 data
.set_origin("http://example.com");
892 cryptographer
->Encrypt(data
,
893 specifics
.mutable_password()->mutable_encrypted());
895 entry_factory()->CreateUnappliedNewItem("item", specifics
, false);
897 sessions::StatusController status
;
898 ApplyPasswordUpdates(&status
);
900 const UpdateCounters
& counters
= GetPasswordsUpdateCounters();
901 EXPECT_EQ(1, counters
.num_updates_applied
)
902 << "The updates that can be decrypted should be applied";
905 syncable::ReadTransaction
trans(FROM_HERE
, directory());
906 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
907 ASSERT_TRUE(e
.good());
908 EXPECT_FALSE(e
.GetIsUnappliedUpdate());
909 EXPECT_FALSE(e
.GetIsUnsynced());
913 // Attempt application of encrypted items when the passphrase is not known.
914 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, UndecryptableData
) {
915 // Undecryptable updates should not be applied.
916 sync_pb::EntitySpecifics encrypted_bookmark
;
917 encrypted_bookmark
.mutable_encrypted();
918 AddDefaultFieldValue(BOOKMARKS
, &encrypted_bookmark
);
919 std::string root_server_id
= syncable::GetNullId().GetServerId();
920 int64 folder_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
924 int64 bookmark_handle
= entry_factory()->CreateUnappliedNewItem(
928 sync_pb::EntitySpecifics encrypted_password
;
929 encrypted_password
.mutable_password();
930 int64 password_handle
= entry_factory()->CreateUnappliedNewItem(
935 sessions::StatusController status
;
936 ApplyBookmarkUpdates(&status
);
937 ApplyPasswordUpdates(&status
);
939 const UpdateCounters
& bm_counters
= GetBookmarksUpdateCounters();
940 EXPECT_EQ(2, bm_counters
.num_encryption_conflict_application_failures
)
941 << "Updates that can't be decrypted should be in encryption conflict";
942 EXPECT_EQ(0, bm_counters
.num_updates_applied
)
943 << "No update that can't be decrypted should be applied";
945 const UpdateCounters
& pw_counters
= GetPasswordsUpdateCounters();
946 EXPECT_EQ(1, pw_counters
.num_encryption_conflict_application_failures
)
947 << "Updates that can't be decrypted should be in encryption conflict";
948 EXPECT_EQ(0, pw_counters
.num_updates_applied
)
949 << "No update that can't be decrypted should be applied";
952 syncable::ReadTransaction
trans(FROM_HERE
, directory());
953 syncable::Entry
folder(&trans
, syncable::GET_BY_HANDLE
, folder_handle
);
954 syncable::Entry
bm(&trans
, syncable::GET_BY_HANDLE
, bookmark_handle
);
955 syncable::Entry
pw(&trans
, syncable::GET_BY_HANDLE
, password_handle
);
956 ASSERT_TRUE(folder
.good());
957 ASSERT_TRUE(bm
.good());
958 ASSERT_TRUE(pw
.good());
959 EXPECT_TRUE(folder
.GetIsUnappliedUpdate());
960 EXPECT_TRUE(bm
.GetIsUnappliedUpdate());
961 EXPECT_TRUE(pw
.GetIsUnappliedUpdate());
965 // Test a mix of decryptable and undecryptable updates.
966 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, SomeUndecryptablePassword
) {
967 Cryptographer
* cryptographer
;
969 int64 decryptable_handle
= -1;
970 int64 undecryptable_handle
= -1;
972 // Only decryptable password updates should be applied.
974 sync_pb::EntitySpecifics specifics
;
975 sync_pb::PasswordSpecificsData data
;
976 data
.set_origin("http://example.com/1");
978 syncable::ReadTransaction
trans(FROM_HERE
, directory());
979 cryptographer
= directory()->GetCryptographer(&trans
);
981 KeyParams params
= {"localhost", "dummy", "foobar"};
982 cryptographer
->AddKey(params
);
984 cryptographer
->Encrypt(data
,
985 specifics
.mutable_password()->mutable_encrypted());
988 entry_factory()->CreateUnappliedNewItem("item1", specifics
, false);
991 // Create a new cryptographer, independent of the one in the session.
992 Cryptographer
other_cryptographer(cryptographer
->encryptor());
993 KeyParams params
= {"localhost", "dummy", "bazqux"};
994 other_cryptographer
.AddKey(params
);
996 sync_pb::EntitySpecifics specifics
;
997 sync_pb::PasswordSpecificsData data
;
998 data
.set_origin("http://example.com/2");
1000 other_cryptographer
.Encrypt(data
,
1001 specifics
.mutable_password()->mutable_encrypted());
1002 undecryptable_handle
=
1003 entry_factory()->CreateUnappliedNewItem("item2", specifics
, false);
1006 sessions::StatusController status
;
1007 ApplyPasswordUpdates(&status
);
1009 const UpdateCounters
& counters
= GetPasswordsUpdateCounters();
1010 EXPECT_EQ(1, counters
.num_encryption_conflict_application_failures
)
1011 << "The updates that can't be decrypted should be in encryption "
1013 EXPECT_EQ(1, counters
.num_updates_applied
)
1014 << "The undecryptable password update shouldn't be applied";
1017 syncable::ReadTransaction
trans(FROM_HERE
, directory());
1018 syncable::Entry
e1(&trans
, syncable::GET_BY_HANDLE
, decryptable_handle
);
1019 syncable::Entry
e2(&trans
, syncable::GET_BY_HANDLE
, undecryptable_handle
);
1020 ASSERT_TRUE(e1
.good());
1021 ASSERT_TRUE(e2
.good());
1022 EXPECT_FALSE(e1
.GetIsUnappliedUpdate());
1023 EXPECT_TRUE(e2
.GetIsUnappliedUpdate());
1027 } // namespace syncer