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/attachment_id_proto.h"
12 #include "sync/internal_api/public/base/model_type.h"
13 #include "sync/internal_api/public/test/test_entry_factory.h"
14 #include "sync/protocol/sync.pb.h"
15 #include "sync/sessions/directory_type_debug_info_emitter.h"
16 #include "sync/sessions/status_controller.h"
17 #include "sync/syncable/directory.h"
18 #include "sync/syncable/entry.h"
19 #include "sync/syncable/mutable_entry.h"
20 #include "sync/syncable/syncable_model_neutral_write_transaction.h"
21 #include "sync/syncable/syncable_proto_util.h"
22 #include "sync/syncable/syncable_read_transaction.h"
23 #include "sync/syncable/syncable_write_transaction.h"
24 #include "sync/test/engine/fake_model_worker.h"
25 #include "sync/test/engine/test_directory_setter_upper.h"
26 #include "sync/test/engine/test_id_factory.h"
27 #include "sync/test/engine/test_syncable_utils.h"
28 #include "testing/gtest/include/gtest/gtest.h"
33 using syncable::UNITTEST
;
35 static const int64 kDefaultVersion
= 1000;
37 // A test harness for tests that focus on processing updates.
39 // Update processing is what occurs when we first download updates. It converts
40 // the received protobuf message into information in the syncable::Directory.
41 // Any invalid or redundant updates will be dropped at this point.
42 class DirectoryUpdateHandlerProcessUpdateTest
: public ::testing::Test
{
44 DirectoryUpdateHandlerProcessUpdateTest()
45 : ui_worker_(new FakeModelWorker(GROUP_UI
)) {
48 ~DirectoryUpdateHandlerProcessUpdateTest() override
{}
50 void SetUp() override
{ dir_maker_
.SetUp(); }
52 void TearDown() override
{ dir_maker_
.TearDown(); }
54 syncable::Directory
* dir() {
55 return dir_maker_
.directory();
59 scoped_ptr
<sync_pb::SyncEntity
> CreateUpdate(
60 const std::string
& id
,
61 const std::string
& parent
,
62 const ModelType
& type
);
64 // This exists mostly to give tests access to the protected member function.
65 // Warning: This takes the syncable directory lock.
66 void UpdateSyncEntities(
67 DirectoryUpdateHandler
* handler
,
68 const SyncEntityList
& applicable_updates
,
69 sessions::StatusController
* status
);
71 // Another function to access private member functions.
72 void UpdateProgressMarkers(
73 DirectoryUpdateHandler
* handler
,
74 const sync_pb::DataTypeProgressMarker
& progress
);
76 scoped_refptr
<FakeModelWorker
> ui_worker() {
80 bool EntryExists(const std::string
& id
) {
81 syncable::ReadTransaction
trans(FROM_HERE
, dir());
82 syncable::Entry
e(&trans
, syncable::GET_BY_ID
,
83 Id::CreateFromServerId(id
));
84 return e
.good() && !e
.GetIsDel();
87 bool TypeRootExists(ModelType model_type
) {
88 syncable::ReadTransaction
trans(FROM_HERE
, dir());
89 syncable::Entry
e(&trans
, syncable::GET_TYPE_ROOT
, model_type
);
90 return e
.good() && !e
.GetIsDel();
94 // Used in the construction of DirectoryTypeDebugInfoEmitters.
95 ObserverList
<TypeDebugInfoObserver
> type_observers_
;
98 base::MessageLoop loop_
; // Needed to initialize the directory.
99 TestDirectorySetterUpper dir_maker_
;
100 scoped_refptr
<FakeModelWorker
> ui_worker_
;
103 scoped_ptr
<sync_pb::SyncEntity
>
104 DirectoryUpdateHandlerProcessUpdateTest::CreateUpdate(
105 const std::string
& id
,
106 const std::string
& parent
,
107 const ModelType
& type
) {
108 scoped_ptr
<sync_pb::SyncEntity
> e(new sync_pb::SyncEntity());
109 e
->set_id_string(id
);
110 e
->set_parent_id_string(parent
);
111 e
->set_non_unique_name(id
);
113 e
->set_version(kDefaultVersion
);
114 AddDefaultFieldValue(type
, e
->mutable_specifics());
118 void DirectoryUpdateHandlerProcessUpdateTest::UpdateSyncEntities(
119 DirectoryUpdateHandler
* handler
,
120 const SyncEntityList
& applicable_updates
,
121 sessions::StatusController
* status
) {
122 syncable::ModelNeutralWriteTransaction
trans(FROM_HERE
, UNITTEST
, dir());
123 handler
->UpdateSyncEntities(&trans
, applicable_updates
, status
);
126 void DirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers(
127 DirectoryUpdateHandler
* handler
,
128 const sync_pb::DataTypeProgressMarker
& progress
) {
129 handler
->UpdateProgressMarker(progress
);
132 static const char kCacheGuid
[] = "IrcjZ2jyzHDV9Io4+zKcXQ==";
134 // Test that the bookmark tag is set on newly downloaded items.
135 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, NewBookmarkTag
) {
136 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
137 DirectoryUpdateHandler
handler(dir(), BOOKMARKS
, ui_worker(), &emitter
);
138 sync_pb::GetUpdatesResponse gu_response
;
139 sessions::StatusController status
;
141 // Add a bookmark item to the update message.
142 std::string root
= Id::GetRoot().GetServerId();
143 Id server_id
= Id::CreateFromServerId("b1");
144 scoped_ptr
<sync_pb::SyncEntity
> e
=
145 CreateUpdate(SyncableIdToProto(server_id
), root
, BOOKMARKS
);
146 e
->set_originator_cache_guid(
147 std::string(kCacheGuid
, arraysize(kCacheGuid
)-1));
148 Id client_id
= Id::CreateFromClientString("-2");
149 e
->set_originator_client_item_id(client_id
.GetServerId());
150 e
->set_position_in_parent(0);
152 // Add it to the applicable updates list.
153 SyncEntityList bookmark_updates
;
154 bookmark_updates
.push_back(e
.get());
156 // Process the update.
157 UpdateSyncEntities(&handler
, bookmark_updates
, &status
);
159 syncable::ReadTransaction
trans(FROM_HERE
, dir());
160 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, server_id
);
161 ASSERT_TRUE(entry
.good());
162 EXPECT_TRUE(UniquePosition::IsValidSuffix(entry
.GetUniqueBookmarkTag()));
163 EXPECT_TRUE(entry
.GetServerUniquePosition().IsValid());
165 // If this assertion fails, that might indicate that the algorithm used to
166 // generate bookmark tags has been modified. This could have implications for
167 // bookmark ordering. Please make sure you know what you're doing if you
168 // intend to make such a change.
169 EXPECT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", entry
.GetUniqueBookmarkTag());
172 // Test the receipt of a type root node.
173 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
,
174 ReceiveServerCreatedBookmarkFolders
) {
175 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
176 DirectoryUpdateHandler
handler(dir(), BOOKMARKS
, ui_worker(), &emitter
);
177 sync_pb::GetUpdatesResponse gu_response
;
178 sessions::StatusController status
;
180 // Create an update that mimics the bookmark root.
181 Id server_id
= Id::CreateFromServerId("xyz");
182 std::string root
= Id::GetRoot().GetServerId();
183 scoped_ptr
<sync_pb::SyncEntity
> e
=
184 CreateUpdate(SyncableIdToProto(server_id
), root
, BOOKMARKS
);
185 e
->set_server_defined_unique_tag("google_chrome_bookmarks");
188 // Add it to the applicable updates list.
189 SyncEntityList bookmark_updates
;
190 bookmark_updates
.push_back(e
.get());
192 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e
));
195 UpdateSyncEntities(&handler
, bookmark_updates
, &status
);
197 // Verify the results.
198 syncable::ReadTransaction
trans(FROM_HERE
, dir());
199 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, server_id
);
200 ASSERT_TRUE(entry
.good());
202 EXPECT_FALSE(entry
.ShouldMaintainPosition());
203 EXPECT_FALSE(entry
.GetUniquePosition().IsValid());
204 EXPECT_FALSE(entry
.GetServerUniquePosition().IsValid());
205 EXPECT_TRUE(entry
.GetUniqueBookmarkTag().empty());
208 // Test the receipt of a non-bookmark item.
209 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, ReceiveNonBookmarkItem
) {
210 DirectoryTypeDebugInfoEmitter
emitter(AUTOFILL
, &type_observers_
);
211 DirectoryUpdateHandler
handler(dir(), AUTOFILL
, ui_worker(), &emitter
);
212 sync_pb::GetUpdatesResponse gu_response
;
213 sessions::StatusController status
;
215 std::string root
= Id::GetRoot().GetServerId();
216 Id server_id
= Id::CreateFromServerId("xyz");
217 scoped_ptr
<sync_pb::SyncEntity
> e
=
218 CreateUpdate(SyncableIdToProto(server_id
), root
, AUTOFILL
);
219 e
->set_server_defined_unique_tag("9PGRuKdX5sHyGMB17CvYTXuC43I=");
221 // Add it to the applicable updates list.
222 SyncEntityList autofill_updates
;
223 autofill_updates
.push_back(e
.get());
225 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e
));
228 UpdateSyncEntities(&handler
, autofill_updates
, &status
);
230 syncable::ReadTransaction
trans(FROM_HERE
, dir());
231 syncable::Entry
entry(&trans
, syncable::GET_BY_ID
, server_id
);
232 ASSERT_TRUE(entry
.good());
234 EXPECT_FALSE(entry
.ShouldMaintainPosition());
235 EXPECT_FALSE(entry
.GetUniquePosition().IsValid());
236 EXPECT_FALSE(entry
.GetServerUniquePosition().IsValid());
237 EXPECT_TRUE(entry
.GetUniqueBookmarkTag().empty());
240 // Tests the setting of progress markers.
241 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, ProcessNewProgressMarkers
) {
242 DirectoryTypeDebugInfoEmitter
emitter(BOOKMARKS
, &type_observers_
);
243 DirectoryUpdateHandler
handler(dir(), BOOKMARKS
, ui_worker(), &emitter
);
245 sync_pb::DataTypeProgressMarker progress
;
246 progress
.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS
));
247 progress
.set_token("token");
249 UpdateProgressMarkers(&handler
, progress
);
251 sync_pb::DataTypeProgressMarker saved
;
252 dir()->GetDownloadProgress(BOOKMARKS
, &saved
);
254 EXPECT_EQ(progress
.token(), saved
.token());
255 EXPECT_EQ(progress
.data_type_id(), saved
.data_type_id());
258 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, GarbageCollectionByVersion
) {
259 DirectoryTypeDebugInfoEmitter
emitter(SYNCED_NOTIFICATIONS
, &type_observers_
);
260 DirectoryUpdateHandler
handler(dir(), SYNCED_NOTIFICATIONS
,
261 ui_worker(), &emitter
);
262 sessions::StatusController status
;
264 sync_pb::DataTypeProgressMarker progress
;
265 progress
.set_data_type_id(
266 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
));
267 progress
.set_token("token");
268 progress
.mutable_gc_directive()->set_version_watermark(kDefaultVersion
+ 10);
270 sync_pb::DataTypeContext context
;
271 context
.set_data_type_id(
272 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
));
273 context
.set_context("context");
274 context
.set_version(1);
276 scoped_ptr
<sync_pb::SyncEntity
> e1
=
277 CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")), "",
278 SYNCED_NOTIFICATIONS
);
280 scoped_ptr
<sync_pb::SyncEntity
> e2
=
281 CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e2")), "",
282 SYNCED_NOTIFICATIONS
);
283 e2
->set_version(kDefaultVersion
+ 100);
285 // Add to the applicable updates list.
286 SyncEntityList updates
;
287 updates
.push_back(e1
.get());
288 updates
.push_back(e2
.get());
290 // Process and apply updates.
293 handler
.ProcessGetUpdatesResponse(progress
, context
, updates
, &status
));
294 handler
.ApplyUpdates(&status
);
296 // Verify none is deleted because they are unapplied during GC.
297 EXPECT_TRUE(TypeRootExists(SYNCED_NOTIFICATIONS
));
298 EXPECT_TRUE(EntryExists(e1
->id_string()));
299 EXPECT_TRUE(EntryExists(e2
->id_string()));
301 // Process and apply again. Old entry is deleted but not root.
302 progress
.mutable_gc_directive()->set_version_watermark(kDefaultVersion
+ 20);
304 handler
.ProcessGetUpdatesResponse(
305 progress
, context
, SyncEntityList(), &status
));
306 handler
.ApplyUpdates(&status
);
307 EXPECT_FALSE(EntryExists(e1
->id_string()));
308 EXPECT_TRUE(EntryExists(e2
->id_string()));
311 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
, ContextVersion
) {
312 DirectoryTypeDebugInfoEmitter
emitter(SYNCED_NOTIFICATIONS
, &type_observers_
);
313 DirectoryUpdateHandler
handler(dir(), SYNCED_NOTIFICATIONS
,
314 ui_worker(), &emitter
);
315 sessions::StatusController status
;
316 int field_number
= GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
);
318 sync_pb::DataTypeProgressMarker progress
;
319 progress
.set_data_type_id(
320 GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS
));
321 progress
.set_token("token");
323 sync_pb::DataTypeContext old_context
;
324 old_context
.set_version(1);
325 old_context
.set_context("data");
326 old_context
.set_data_type_id(field_number
);
328 scoped_ptr
<sync_pb::SyncEntity
> e1
=
329 CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e1")), "",
330 SYNCED_NOTIFICATIONS
);
332 SyncEntityList updates
;
333 updates
.push_back(e1
.get());
335 // The first response should be processed fine.
337 handler
.ProcessGetUpdatesResponse(
338 progress
, old_context
, updates
, &status
));
339 handler
.ApplyUpdates(&status
);
341 // The PREFERENCES root should be auto-created.
342 EXPECT_TRUE(TypeRootExists(SYNCED_NOTIFICATIONS
));
344 EXPECT_TRUE(EntryExists(e1
->id_string()));
347 sync_pb::DataTypeContext dir_context
;
348 syncable::ReadTransaction
trans(FROM_HERE
, dir());
349 trans
.directory()->GetDataTypeContext(
350 &trans
, SYNCED_NOTIFICATIONS
, &dir_context
);
351 EXPECT_EQ(old_context
.SerializeAsString(), dir_context
.SerializeAsString());
354 sync_pb::DataTypeContext new_context
;
355 new_context
.set_version(0);
356 new_context
.set_context("old");
357 new_context
.set_data_type_id(field_number
);
359 scoped_ptr
<sync_pb::SyncEntity
> e2
=
360 CreateUpdate(SyncableIdToProto(Id::CreateFromServerId("e2")), "",
361 SYNCED_NOTIFICATIONS
);
363 updates
.push_back(e2
.get());
365 // The second response, with an old context version, should result in an
366 // error and the updates should be dropped.
367 EXPECT_EQ(DATATYPE_TRIGGERED_RETRY
,
368 handler
.ProcessGetUpdatesResponse(
369 progress
, new_context
, updates
, &status
));
370 handler
.ApplyUpdates(&status
);
372 EXPECT_FALSE(EntryExists(e2
->id_string()));
375 sync_pb::DataTypeContext dir_context
;
376 syncable::ReadTransaction
trans(FROM_HERE
, dir());
377 trans
.directory()->GetDataTypeContext(
378 &trans
, SYNCED_NOTIFICATIONS
, &dir_context
);
379 EXPECT_EQ(old_context
.SerializeAsString(), dir_context
.SerializeAsString());
383 // See that updates containing attachment metadata are applied
384 // (i.e. server_attachment_metadata is copied to attachment_metadata).
385 TEST_F(DirectoryUpdateHandlerProcessUpdateTest
,
386 ProcessAndApplyUpdatesWithAttachments
) {
387 DirectoryTypeDebugInfoEmitter
emitter(ARTICLES
, &type_observers_
);
388 DirectoryUpdateHandler
handler(dir(), ARTICLES
, ui_worker(), &emitter
);
389 sessions::StatusController status
;
391 sync_pb::DataTypeProgressMarker progress
;
392 progress
.set_data_type_id(GetSpecificsFieldNumberFromModelType(ARTICLES
));
393 progress
.set_token("token");
394 progress
.mutable_gc_directive()->set_version_watermark(kDefaultVersion
+ 10);
396 sync_pb::DataTypeContext context
;
397 context
.set_data_type_id(GetSpecificsFieldNumberFromModelType(ARTICLES
));
398 context
.set_context("context");
399 context
.set_version(1);
401 scoped_ptr
<sync_pb::SyncEntity
> e1
= CreateUpdate(
402 SyncableIdToProto(Id::CreateFromServerId("e1")), "", ARTICLES
);
403 sync_pb::AttachmentIdProto
* attachment_id
= e1
->add_attachment_id();
404 *attachment_id
= CreateAttachmentIdProto();
406 SyncEntityList updates
;
407 updates
.push_back(e1
.get());
409 // Process and apply updates.
412 handler
.ProcessGetUpdatesResponse(progress
, context
, updates
, &status
));
413 handler
.ApplyUpdates(&status
);
415 ASSERT_TRUE(TypeRootExists(ARTICLES
));
416 ASSERT_TRUE(EntryExists(e1
->id_string()));
418 syncable::ReadTransaction
trans(FROM_HERE
, dir());
419 syncable::Entry
e(&trans
,
421 Id::CreateFromServerId(e1
->id_string()));
423 // See that the attachment_metadata is correct.
424 sync_pb::AttachmentMetadata attachment_metadata
= e
.GetAttachmentMetadata();
425 ASSERT_EQ(1, attachment_metadata
.record_size());
426 ASSERT_EQ(attachment_id
->SerializeAsString(),
427 attachment_metadata
.record(0).id().SerializeAsString());
428 ASSERT_TRUE(attachment_metadata
.record(0).is_on_server());
430 // See that attachment_metadata and server_attachment_metadata are equal.
431 ASSERT_EQ(attachment_metadata
.SerializeAsString(),
432 e
.GetServerAttachmentMetadata().SerializeAsString());
436 // A test harness for tests that focus on applying updates.
438 // Update application is performed when we want to take updates that were
439 // previously downloaded, processed, and stored in our syncable::Directory
440 // and use them to update our local state (both the Directory's local state
441 // and the model's local state, though these tests focus only on the Directory's
444 // This is kept separate from the update processing test in part for historical
445 // reasons, and in part because these tests may require a bit more infrastrcture
446 // in the future. Update application should happen on a different thread a lot
447 // of the time so these tests may end up requiring more infrastructure than the
448 // update processing tests. Currently, we're bypassing most of those issues by
449 // using FakeModelWorkers, so there's not much difference between the two test
451 class DirectoryUpdateHandlerApplyUpdateTest
: public ::testing::Test
{
453 DirectoryUpdateHandlerApplyUpdateTest()
454 : ui_worker_(new FakeModelWorker(GROUP_UI
)),
455 password_worker_(new FakeModelWorker(GROUP_PASSWORD
)),
456 passive_worker_(new FakeModelWorker(GROUP_PASSIVE
)),
457 bookmarks_emitter_(BOOKMARKS
, &type_observers_
),
458 passwords_emitter_(PASSWORDS
, &type_observers_
),
459 articles_emitter_(ARTICLES
, &type_observers_
),
460 update_handler_map_deleter_(&update_handler_map_
) {}
462 void SetUp() override
{
464 entry_factory_
.reset(new TestEntryFactory(directory()));
466 update_handler_map_
.insert(std::make_pair(
468 new DirectoryUpdateHandler(directory(), BOOKMARKS
,
469 ui_worker_
, &bookmarks_emitter_
)));
470 update_handler_map_
.insert(std::make_pair(
472 new DirectoryUpdateHandler(directory(),
475 &passwords_emitter_
)));
476 update_handler_map_
.insert(std::make_pair(
478 new DirectoryUpdateHandler(
479 directory(), ARTICLES
, ui_worker_
, &articles_emitter_
)));
482 void TearDown() override
{ dir_maker_
.TearDown(); }
484 const UpdateCounters
& GetBookmarksUpdateCounters() {
485 return bookmarks_emitter_
.GetUpdateCounters();
488 const UpdateCounters
& GetPasswordsUpdateCounters() {
489 return passwords_emitter_
.GetUpdateCounters();
492 const UpdateCounters
& GetArticlesUpdateCounters() {
493 return articles_emitter_
.GetUpdateCounters();
497 void ApplyBookmarkUpdates(sessions::StatusController
* status
) {
498 update_handler_map_
[BOOKMARKS
]->ApplyUpdates(status
);
501 void ApplyPasswordUpdates(sessions::StatusController
* status
) {
502 update_handler_map_
[PASSWORDS
]->ApplyUpdates(status
);
505 void ApplyArticlesUpdates(sessions::StatusController
* status
) {
506 update_handler_map_
[ARTICLES
]->ApplyUpdates(status
);
509 TestEntryFactory
* entry_factory() {
510 return entry_factory_
.get();
513 syncable::Directory
* directory() {
514 return dir_maker_
.directory();
518 typedef std::map
<ModelType
, UpdateHandler
*> UpdateHandlerMap
;
520 base::MessageLoop loop_
; // Needed to initialize the directory.
521 TestDirectorySetterUpper dir_maker_
;
522 scoped_ptr
<TestEntryFactory
> entry_factory_
;
524 scoped_refptr
<FakeModelWorker
> ui_worker_
;
525 scoped_refptr
<FakeModelWorker
> password_worker_
;
526 scoped_refptr
<FakeModelWorker
> passive_worker_
;
528 ObserverList
<TypeDebugInfoObserver
> type_observers_
;
529 DirectoryTypeDebugInfoEmitter bookmarks_emitter_
;
530 DirectoryTypeDebugInfoEmitter passwords_emitter_
;
531 DirectoryTypeDebugInfoEmitter articles_emitter_
;
533 UpdateHandlerMap update_handler_map_
;
534 STLValueDeleter
<UpdateHandlerMap
> update_handler_map_deleter_
;
538 sync_pb::EntitySpecifics
DefaultBookmarkSpecifics() {
539 sync_pb::EntitySpecifics result
;
540 AddDefaultFieldValue(BOOKMARKS
, &result
);
545 // Test update application for a few bookmark items.
546 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, SimpleBookmark
) {
547 sessions::StatusController status
;
549 std::string root_server_id
= Id::GetRoot().GetServerId();
550 int64 parent_handle
=
551 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
552 "parent", DefaultBookmarkSpecifics(), root_server_id
);
554 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
555 "child", DefaultBookmarkSpecifics(), "parent");
557 ApplyBookmarkUpdates(&status
);
559 const UpdateCounters
& counter
= GetBookmarksUpdateCounters();
560 EXPECT_EQ(0, counter
.num_encryption_conflict_application_failures
)
561 << "Simple update shouldn't result in conflicts";
562 EXPECT_EQ(0, counter
.num_hierarchy_conflict_application_failures
)
563 << "Simple update shouldn't result in conflicts";
564 EXPECT_EQ(2, counter
.num_updates_applied
)
565 << "All items should have been successfully applied";
568 syncable::ReadTransaction
trans(FROM_HERE
, directory());
570 syncable::Entry
parent(&trans
, syncable::GET_BY_HANDLE
, parent_handle
);
571 syncable::Entry
child(&trans
, syncable::GET_BY_HANDLE
, child_handle
);
573 ASSERT_TRUE(parent
.good());
574 ASSERT_TRUE(child
.good());
576 EXPECT_FALSE(parent
.GetIsUnsynced());
577 EXPECT_FALSE(parent
.GetIsUnappliedUpdate());
578 EXPECT_FALSE(child
.GetIsUnsynced());
579 EXPECT_FALSE(child
.GetIsUnappliedUpdate());
583 // Test that the applicator can handle updates delivered out of order.
584 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
585 BookmarkChildrenBeforeParent
) {
586 // Start with some bookmarks whose parents are unknown.
587 std::string root_server_id
= Id::GetRoot().GetServerId();
588 int64 a_handle
= entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
589 "a_child_created_first", DefaultBookmarkSpecifics(), "parent");
590 int64 x_handle
= entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
591 "x_child_created_first", DefaultBookmarkSpecifics(), "parent");
593 // Update application will fail.
594 sessions::StatusController status1
;
595 ApplyBookmarkUpdates(&status1
);
596 EXPECT_EQ(0, status1
.num_updates_applied());
597 EXPECT_EQ(2, status1
.num_hierarchy_conflicts());
600 syncable::ReadTransaction
trans(FROM_HERE
, directory());
602 syncable::Entry
a(&trans
, syncable::GET_BY_HANDLE
, a_handle
);
603 syncable::Entry
x(&trans
, syncable::GET_BY_HANDLE
, x_handle
);
605 ASSERT_TRUE(a
.good());
606 ASSERT_TRUE(x
.good());
608 EXPECT_TRUE(a
.GetIsUnappliedUpdate());
609 EXPECT_TRUE(x
.GetIsUnappliedUpdate());
612 // Now add their parent and a few siblings.
613 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
614 "parent", DefaultBookmarkSpecifics(), root_server_id
);
615 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
616 "a_child_created_second", DefaultBookmarkSpecifics(), "parent");
617 entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
618 "x_child_created_second", DefaultBookmarkSpecifics(), "parent");
620 // Update application will succeed.
621 sessions::StatusController status2
;
622 ApplyBookmarkUpdates(&status2
);
623 EXPECT_EQ(5, status2
.num_updates_applied())
624 << "All updates should have been successfully applied";
627 syncable::ReadTransaction
trans(FROM_HERE
, directory());
629 syncable::Entry
a(&trans
, syncable::GET_BY_HANDLE
, a_handle
);
630 syncable::Entry
x(&trans
, syncable::GET_BY_HANDLE
, x_handle
);
632 ASSERT_TRUE(a
.good());
633 ASSERT_TRUE(x
.good());
635 EXPECT_FALSE(a
.GetIsUnappliedUpdate());
636 EXPECT_FALSE(x
.GetIsUnappliedUpdate());
640 // Try to apply changes on an item that is both IS_UNSYNCED and
641 // IS_UNAPPLIED_UPDATE. Conflict resolution should be performed.
642 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, SimpleBookmarkConflict
) {
643 int64 handle
= entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x");
645 int original_server_version
= -10;
647 syncable::ReadTransaction
trans(FROM_HERE
, directory());
648 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
649 original_server_version
= e
.GetServerVersion();
650 ASSERT_NE(original_server_version
, e
.GetBaseVersion());
651 EXPECT_TRUE(e
.GetIsUnsynced());
654 sessions::StatusController status
;
655 ApplyBookmarkUpdates(&status
);
657 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
658 EXPECT_EQ(1, counters
.num_server_overwrites
)
659 << "Unsynced and unapplied item conflict should be resolved";
660 EXPECT_EQ(0, counters
.num_updates_applied
)
661 << "Update should not be applied; we should override the server.";
664 syncable::ReadTransaction
trans(FROM_HERE
, directory());
665 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
666 ASSERT_TRUE(e
.good());
667 EXPECT_EQ(original_server_version
, e
.GetServerVersion());
668 EXPECT_EQ(original_server_version
, e
.GetBaseVersion());
669 EXPECT_FALSE(e
.GetIsUnappliedUpdate());
671 // The unsynced flag will remain set until we successfully commit the item.
672 EXPECT_TRUE(e
.GetIsUnsynced());
676 // Create a simple conflict that is also a hierarchy conflict. If we were to
677 // follow the normal "server wins" logic, we'd end up violating hierarchy
678 // constraints. The hierarchy conflict must take precedence. We can not allow
679 // the update to be applied. The item must remain in the conflict state.
680 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, HierarchyAndSimpleConflict
) {
681 // Create a simply-conflicting item. It will start with valid parent ids.
682 int64 handle
= entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem(
683 "orphaned_by_server");
685 // Manually set the SERVER_PARENT_ID to bad value.
686 // A bad parent indicates a hierarchy conflict.
687 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
688 syncable::MutableEntry
entry(&trans
, syncable::GET_BY_HANDLE
, handle
);
689 ASSERT_TRUE(entry
.good());
691 entry
.PutServerParentId(TestIdFactory::MakeServer("bogus_parent"));
694 sessions::StatusController status
;
695 ApplyBookmarkUpdates(&status
);
697 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
698 EXPECT_EQ(0, counters
.num_updates_applied
);
699 EXPECT_EQ(0, counters
.num_server_overwrites
);
700 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
703 syncable::ReadTransaction
trans(FROM_HERE
, directory());
704 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
705 ASSERT_TRUE(e
.good());
706 EXPECT_TRUE(e
.GetIsUnappliedUpdate());
707 EXPECT_TRUE(e
.GetIsUnsynced());
711 // Attempt to apply an udpate that would create a bookmark folder loop. This
712 // application should fail.
713 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, BookmarkFolderLoop
) {
714 // Item 'X' locally has parent of 'root'. Server is updating it to have
717 // Create it as a child of root node.
718 int64 handle
= entry_factory()->CreateSyncedItem("X", BOOKMARKS
, true);
721 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
722 syncable::MutableEntry
entry(&trans
, syncable::GET_BY_HANDLE
, handle
);
723 ASSERT_TRUE(entry
.good());
725 // Re-parent from root to "Y"
726 entry
.PutServerVersion(entry_factory()->GetNextRevision());
727 entry
.PutIsUnappliedUpdate(true);
728 entry
.PutServerParentId(TestIdFactory::MakeServer("Y"));
731 // Item 'Y' is child of 'X'.
732 entry_factory()->CreateUnsyncedItem(
733 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
736 // If the server's update were applied, we would have X be a child of Y, and Y
737 // as a child of X. That's a directory loop. The UpdateApplicator should
738 // prevent the update from being applied and note that this is a hierarchy
741 sessions::StatusController status
;
742 ApplyBookmarkUpdates(&status
);
744 // This should count as a hierarchy conflict.
745 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
746 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
749 syncable::ReadTransaction
trans(FROM_HERE
, directory());
750 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
751 ASSERT_TRUE(e
.good());
752 EXPECT_TRUE(e
.GetIsUnappliedUpdate());
753 EXPECT_FALSE(e
.GetIsUnsynced());
757 // Test update application where the update has been orphaned by a local folder
758 // deletion. The update application attempt should fail.
759 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
760 HierarchyConflictDeletedParent
) {
761 // Create a locally deleted parent item.
763 entry_factory()->CreateUnsyncedItem(
764 Id::CreateFromServerId("parent"), TestIdFactory::root(),
765 "parent", true, BOOKMARKS
, &parent_handle
);
767 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
768 syncable::MutableEntry
entry(&trans
,
769 syncable::GET_BY_HANDLE
,
771 entry
.PutIsDel(true);
774 // Create an incoming child from the server.
775 int64 child_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
776 "child", DefaultBookmarkSpecifics(), "parent");
778 // The server's update may seem valid to some other client, but on this client
779 // that new item's parent no longer exists. The update should not be applied
780 // and the update applicator should indicate this is a hierarchy conflict.
782 sessions::StatusController status
;
783 ApplyBookmarkUpdates(&status
);
784 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
785 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
788 syncable::ReadTransaction
trans(FROM_HERE
, directory());
789 syncable::Entry
child(&trans
, syncable::GET_BY_HANDLE
, child_handle
);
790 ASSERT_TRUE(child
.good());
791 EXPECT_TRUE(child
.GetIsUnappliedUpdate());
792 EXPECT_FALSE(child
.GetIsUnsynced());
796 // Attempt to apply an update that deletes a folder where the folder has
797 // locally-created children. The update application should fail.
798 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
799 HierarchyConflictDeleteNonEmptyDirectory
) {
800 // Create a server-deleted folder as a child of root node.
801 int64 parent_handle
=
802 entry_factory()->CreateSyncedItem("parent", BOOKMARKS
, true);
804 syncable::WriteTransaction
trans(FROM_HERE
, UNITTEST
, directory());
805 syncable::MutableEntry
entry(&trans
,
806 syncable::GET_BY_HANDLE
,
808 ASSERT_TRUE(entry
.good());
810 // Delete it on the server.
811 entry
.PutServerVersion(entry_factory()->GetNextRevision());
812 entry
.PutIsUnappliedUpdate(true);
813 entry
.PutServerParentId(TestIdFactory::root());
814 entry
.PutServerIsDel(true);
817 // Create a local child of the server-deleted directory.
818 entry_factory()->CreateUnsyncedItem(
819 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"),
820 "child", false, BOOKMARKS
, NULL
);
822 // The server's request to delete the directory must be ignored, otherwise our
823 // unsynced new child would be orphaned. This is a hierarchy conflict.
825 sessions::StatusController status
;
826 ApplyBookmarkUpdates(&status
);
828 // This should count as a hierarchy conflict.
829 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
830 EXPECT_EQ(1, counters
.num_hierarchy_conflict_application_failures
);
833 syncable::ReadTransaction
trans(FROM_HERE
, directory());
834 syncable::Entry
parent(&trans
, syncable::GET_BY_HANDLE
, parent_handle
);
835 ASSERT_TRUE(parent
.good());
836 EXPECT_TRUE(parent
.GetIsUnappliedUpdate());
837 EXPECT_FALSE(parent
.GetIsUnsynced());
841 // Attempt to apply updates where the updated item's parent is not known to this
842 // client. The update application attempt should fail.
843 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
844 HierarchyConflictUnknownParent
) {
845 // We shouldn't be able to do anything with either of these items.
846 int64 x_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
847 "some_item", DefaultBookmarkSpecifics(), "unknown_parent");
848 int64 y_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
849 "some_other_item", DefaultBookmarkSpecifics(), "some_item");
851 sessions::StatusController status
;
852 ApplyBookmarkUpdates(&status
);
854 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
855 EXPECT_EQ(2, counters
.num_hierarchy_conflict_application_failures
)
856 << "All updates with an unknown ancestors should be in conflict";
857 EXPECT_EQ(0, counters
.num_updates_applied
)
858 << "No item with an unknown ancestor should be applied";
861 syncable::ReadTransaction
trans(FROM_HERE
, directory());
862 syncable::Entry
x(&trans
, syncable::GET_BY_HANDLE
, x_handle
);
863 syncable::Entry
y(&trans
, syncable::GET_BY_HANDLE
, y_handle
);
864 ASSERT_TRUE(x
.good());
865 ASSERT_TRUE(y
.good());
866 EXPECT_TRUE(x
.GetIsUnappliedUpdate());
867 EXPECT_TRUE(y
.GetIsUnappliedUpdate());
868 EXPECT_FALSE(x
.GetIsUnsynced());
869 EXPECT_FALSE(y
.GetIsUnsynced());
873 // Attempt application of a mix of items. Some update application attempts will
874 // fail due to hierarchy conflicts. Others should succeed.
875 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, ItemsBothKnownAndUnknown
) {
876 // See what happens when there's a mixture of good and bad updates.
877 std::string root_server_id
= Id::GetRoot().GetServerId();
878 int64 u1_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
879 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
880 int64 k1_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
881 "first_known_item", DefaultBookmarkSpecifics(), root_server_id
);
882 int64 u2_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
883 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
884 int64 k2_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
885 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item");
886 int64 k3_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
887 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item");
888 int64 k4_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
889 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id
);
891 sessions::StatusController status
;
892 ApplyBookmarkUpdates(&status
);
894 const UpdateCounters
& counters
= GetBookmarksUpdateCounters();
895 EXPECT_EQ(2, counters
.num_hierarchy_conflict_application_failures
)
896 << "The updates with unknown ancestors should be in conflict";
897 EXPECT_EQ(4, counters
.num_updates_applied
)
898 << "The updates with known ancestors should be successfully applied";
901 syncable::ReadTransaction
trans(FROM_HERE
, directory());
902 syncable::Entry
u1(&trans
, syncable::GET_BY_HANDLE
, u1_handle
);
903 syncable::Entry
u2(&trans
, syncable::GET_BY_HANDLE
, u2_handle
);
904 syncable::Entry
k1(&trans
, syncable::GET_BY_HANDLE
, k1_handle
);
905 syncable::Entry
k2(&trans
, syncable::GET_BY_HANDLE
, k2_handle
);
906 syncable::Entry
k3(&trans
, syncable::GET_BY_HANDLE
, k3_handle
);
907 syncable::Entry
k4(&trans
, syncable::GET_BY_HANDLE
, k4_handle
);
908 ASSERT_TRUE(u1
.good());
909 ASSERT_TRUE(u2
.good());
910 ASSERT_TRUE(k1
.good());
911 ASSERT_TRUE(k2
.good());
912 ASSERT_TRUE(k3
.good());
913 ASSERT_TRUE(k4
.good());
914 EXPECT_TRUE(u1
.GetIsUnappliedUpdate());
915 EXPECT_TRUE(u2
.GetIsUnappliedUpdate());
916 EXPECT_FALSE(k1
.GetIsUnappliedUpdate());
917 EXPECT_FALSE(k2
.GetIsUnappliedUpdate());
918 EXPECT_FALSE(k3
.GetIsUnappliedUpdate());
919 EXPECT_FALSE(k4
.GetIsUnappliedUpdate());
923 // Attempt application of password upates where the passphrase is known.
924 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, DecryptablePassword
) {
925 // Decryptable password updates should be applied.
926 Cryptographer
* cryptographer
;
928 // Storing the cryptographer separately is bad, but for this test we
930 syncable::ReadTransaction
trans(FROM_HERE
, directory());
931 cryptographer
= directory()->GetCryptographer(&trans
);
934 KeyParams params
= {"localhost", "dummy", "foobar"};
935 cryptographer
->AddKey(params
);
937 sync_pb::EntitySpecifics specifics
;
938 sync_pb::PasswordSpecificsData data
;
939 data
.set_origin("http://example.com");
941 cryptographer
->Encrypt(data
,
942 specifics
.mutable_password()->mutable_encrypted());
944 entry_factory()->CreateUnappliedNewItem("item", specifics
, false);
946 sessions::StatusController status
;
947 ApplyPasswordUpdates(&status
);
949 const UpdateCounters
& counters
= GetPasswordsUpdateCounters();
950 EXPECT_EQ(1, counters
.num_updates_applied
)
951 << "The updates that can be decrypted should be applied";
954 syncable::ReadTransaction
trans(FROM_HERE
, directory());
955 syncable::Entry
e(&trans
, syncable::GET_BY_HANDLE
, handle
);
956 ASSERT_TRUE(e
.good());
957 EXPECT_FALSE(e
.GetIsUnappliedUpdate());
958 EXPECT_FALSE(e
.GetIsUnsynced());
962 // Attempt application of encrypted items when the passphrase is not known.
963 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, UndecryptableData
) {
964 // Undecryptable updates should not be applied.
965 sync_pb::EntitySpecifics encrypted_bookmark
;
966 encrypted_bookmark
.mutable_encrypted();
967 AddDefaultFieldValue(BOOKMARKS
, &encrypted_bookmark
);
968 std::string root_server_id
= Id::GetRoot().GetServerId();
969 int64 folder_handle
= entry_factory()->CreateUnappliedNewItemWithParent(
973 int64 bookmark_handle
= entry_factory()->CreateUnappliedNewItem(
977 sync_pb::EntitySpecifics encrypted_password
;
978 encrypted_password
.mutable_password();
979 int64 password_handle
= entry_factory()->CreateUnappliedNewItem(
984 sessions::StatusController status
;
985 ApplyBookmarkUpdates(&status
);
986 ApplyPasswordUpdates(&status
);
988 const UpdateCounters
& bm_counters
= GetBookmarksUpdateCounters();
989 EXPECT_EQ(2, bm_counters
.num_encryption_conflict_application_failures
)
990 << "Updates that can't be decrypted should be in encryption conflict";
991 EXPECT_EQ(0, bm_counters
.num_updates_applied
)
992 << "No update that can't be decrypted should be applied";
994 const UpdateCounters
& pw_counters
= GetPasswordsUpdateCounters();
995 EXPECT_EQ(1, pw_counters
.num_encryption_conflict_application_failures
)
996 << "Updates that can't be decrypted should be in encryption conflict";
997 EXPECT_EQ(0, pw_counters
.num_updates_applied
)
998 << "No update that can't be decrypted should be applied";
1001 syncable::ReadTransaction
trans(FROM_HERE
, directory());
1002 syncable::Entry
folder(&trans
, syncable::GET_BY_HANDLE
, folder_handle
);
1003 syncable::Entry
bm(&trans
, syncable::GET_BY_HANDLE
, bookmark_handle
);
1004 syncable::Entry
pw(&trans
, syncable::GET_BY_HANDLE
, password_handle
);
1005 ASSERT_TRUE(folder
.good());
1006 ASSERT_TRUE(bm
.good());
1007 ASSERT_TRUE(pw
.good());
1008 EXPECT_TRUE(folder
.GetIsUnappliedUpdate());
1009 EXPECT_TRUE(bm
.GetIsUnappliedUpdate());
1010 EXPECT_TRUE(pw
.GetIsUnappliedUpdate());
1014 // Test a mix of decryptable and undecryptable updates.
1015 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
, SomeUndecryptablePassword
) {
1016 Cryptographer
* cryptographer
;
1018 int64 decryptable_handle
= -1;
1019 int64 undecryptable_handle
= -1;
1021 // Only decryptable password updates should be applied.
1023 sync_pb::EntitySpecifics specifics
;
1024 sync_pb::PasswordSpecificsData data
;
1025 data
.set_origin("http://example.com/1");
1027 syncable::ReadTransaction
trans(FROM_HERE
, directory());
1028 cryptographer
= directory()->GetCryptographer(&trans
);
1030 KeyParams params
= {"localhost", "dummy", "foobar"};
1031 cryptographer
->AddKey(params
);
1033 cryptographer
->Encrypt(data
,
1034 specifics
.mutable_password()->mutable_encrypted());
1036 decryptable_handle
=
1037 entry_factory()->CreateUnappliedNewItem("item1", specifics
, false);
1040 // Create a new cryptographer, independent of the one in the session.
1041 Cryptographer
other_cryptographer(cryptographer
->encryptor());
1042 KeyParams params
= {"localhost", "dummy", "bazqux"};
1043 other_cryptographer
.AddKey(params
);
1045 sync_pb::EntitySpecifics specifics
;
1046 sync_pb::PasswordSpecificsData data
;
1047 data
.set_origin("http://example.com/2");
1049 other_cryptographer
.Encrypt(data
,
1050 specifics
.mutable_password()->mutable_encrypted());
1051 undecryptable_handle
=
1052 entry_factory()->CreateUnappliedNewItem("item2", specifics
, false);
1055 sessions::StatusController status
;
1056 ApplyPasswordUpdates(&status
);
1058 const UpdateCounters
& counters
= GetPasswordsUpdateCounters();
1059 EXPECT_EQ(1, counters
.num_encryption_conflict_application_failures
)
1060 << "The updates that can't be decrypted should be in encryption "
1062 EXPECT_EQ(1, counters
.num_updates_applied
)
1063 << "The undecryptable password update shouldn't be applied";
1066 syncable::ReadTransaction
trans(FROM_HERE
, directory());
1067 syncable::Entry
e1(&trans
, syncable::GET_BY_HANDLE
, decryptable_handle
);
1068 syncable::Entry
e2(&trans
, syncable::GET_BY_HANDLE
, undecryptable_handle
);
1069 ASSERT_TRUE(e1
.good());
1070 ASSERT_TRUE(e2
.good());
1071 EXPECT_FALSE(e1
.GetIsUnappliedUpdate());
1072 EXPECT_TRUE(e2
.GetIsUnappliedUpdate());
1076 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
1077 SimpleConflictDifferentAttachmentMetadata
) {
1078 const bool is_folder
= false;
1079 sync_pb::EntitySpecifics specifics
;
1080 *specifics
.mutable_article() = sync_pb::ArticleSpecifics();
1081 int64 handle
= entry_factory()->CreateSyncedItem("art1", ARTICLES
, is_folder
);
1083 sync_pb::AttachmentIdProto local_attachment_id
= CreateAttachmentIdProto();
1084 sync_pb::AttachmentIdProto server_attachment_id
= CreateAttachmentIdProto();
1086 // Add an attachment to the local attachment metadata.
1087 sync_pb::AttachmentMetadata local_metadata
;
1088 sync_pb::AttachmentMetadataRecord
* local_record
= local_metadata
.add_record();
1089 *local_record
->mutable_id() = local_attachment_id
;
1090 local_record
->set_is_on_server(true);
1091 entry_factory()->SetLocalAttachmentMetadataForItem(handle
, local_metadata
);
1093 // Add a different attachment to the server attachment metadata.
1094 sync_pb::AttachmentMetadata server_metadata
;
1095 sync_pb::AttachmentMetadataRecord
* server_record
=
1096 server_metadata
.add_record();
1097 *server_record
->mutable_id() = server_attachment_id
;
1098 server_record
->set_is_on_server(true);
1099 entry_factory()->SetServerAttachmentMetadataForItem(handle
, server_metadata
);
1101 // At this point we have a simple conflict. The server says art1 should have
1102 // server_attachment_id, but the local sync engine says it should have
1103 // local_attachment_id.
1105 sessions::StatusController status
;
1106 ApplyArticlesUpdates(&status
);
1108 // See that the server won.
1109 const UpdateCounters
& counters
= GetArticlesUpdateCounters();
1110 EXPECT_EQ(1, counters
.num_updates_applied
);
1111 EXPECT_EQ(1, counters
.num_local_overwrites
);
1112 EXPECT_EQ(0, counters
.num_server_overwrites
);
1113 local_metadata
= entry_factory()->GetLocalAttachmentMetadataForItem(handle
);
1114 EXPECT_EQ(server_metadata
.SerializeAsString(),
1115 local_metadata
.SerializeAsString());
1118 TEST_F(DirectoryUpdateHandlerApplyUpdateTest
,
1119 SimpleConflictSameAttachmentMetadataDifferentOrder
) {
1120 const bool is_folder
= false;
1121 sync_pb::EntitySpecifics specifics
;
1122 *specifics
.mutable_article() = sync_pb::ArticleSpecifics();
1123 int64 handle
= entry_factory()->CreateSyncedItem("art1", ARTICLES
, is_folder
);
1125 sync_pb::AttachmentIdProto id1
= CreateAttachmentIdProto();
1126 sync_pb::AttachmentIdProto id2
= CreateAttachmentIdProto();
1128 // Add id1, then id2 to the local attachment metadata.
1129 sync_pb::AttachmentMetadata local_metadata
;
1130 sync_pb::AttachmentMetadataRecord
* record
= local_metadata
.add_record();
1131 *record
->mutable_id() = id1
;
1132 record
->set_is_on_server(true);
1133 record
= local_metadata
.add_record();
1134 *record
->mutable_id() = id2
;
1135 record
->set_is_on_server(true);
1136 entry_factory()->SetLocalAttachmentMetadataForItem(handle
, local_metadata
);
1138 // Add id1 and id2 to the server attachment metadata, but in reverse order.
1139 sync_pb::AttachmentMetadata server_metadata
;
1140 record
= server_metadata
.add_record();
1141 *record
->mutable_id() = id2
;
1142 record
->set_is_on_server(true);
1143 record
= local_metadata
.add_record();
1144 *record
->mutable_id() = id1
;
1145 record
->set_is_on_server(true);
1146 entry_factory()->SetServerAttachmentMetadataForItem(handle
, server_metadata
);
1148 // At this point we have a (false) conflict.
1150 sessions::StatusController status
;
1151 ApplyArticlesUpdates(&status
);
1153 // See that the server won.
1154 const UpdateCounters
& counters
= GetArticlesUpdateCounters();
1155 EXPECT_EQ(1, counters
.num_updates_applied
);
1156 EXPECT_EQ(1, counters
.num_local_overwrites
);
1157 EXPECT_EQ(0, counters
.num_server_overwrites
);
1158 local_metadata
= entry_factory()->GetLocalAttachmentMetadataForItem(handle
);
1159 EXPECT_EQ(server_metadata
.SerializeAsString(),
1160 local_metadata
.SerializeAsString());
1163 } // namespace syncer