OLD | NEW |
| (Empty) |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "sync/engine/sync_directory_update_handler.h" | |
6 | |
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/status_controller.h" | |
15 #include "sync/syncable/directory.h" | |
16 #include "sync/syncable/entry.h" | |
17 #include "sync/syncable/mutable_entry.h" | |
18 #include "sync/syncable/syncable_model_neutral_write_transaction.h" | |
19 #include "sync/syncable/syncable_proto_util.h" | |
20 #include "sync/syncable/syncable_read_transaction.h" | |
21 #include "sync/syncable/syncable_write_transaction.h" | |
22 #include "sync/test/engine/fake_model_worker.h" | |
23 #include "sync/test/engine/test_directory_setter_upper.h" | |
24 #include "sync/test/engine/test_id_factory.h" | |
25 #include "sync/test/engine/test_syncable_utils.h" | |
26 #include "testing/gtest/include/gtest/gtest.h" | |
27 | |
28 namespace syncer { | |
29 | |
30 using syncable::UNITTEST; | |
31 | |
32 // A test harness for tests that focus on processing updates. | |
33 // | |
34 // Update processing is what occurs when we first download updates. It converts | |
35 // the received protobuf message into information in the syncable::Directory. | |
36 // Any invalid or redundant updates will be dropped at this point. | |
37 class SyncDirectoryUpdateHandlerProcessUpdateTest : public ::testing::Test { | |
38 public: | |
39 SyncDirectoryUpdateHandlerProcessUpdateTest() | |
40 : ui_worker_(new FakeModelWorker(GROUP_UI)) { | |
41 } | |
42 | |
43 virtual ~SyncDirectoryUpdateHandlerProcessUpdateTest() {} | |
44 | |
45 virtual void SetUp() OVERRIDE { | |
46 dir_maker_.SetUp(); | |
47 } | |
48 | |
49 virtual void TearDown() OVERRIDE { | |
50 dir_maker_.TearDown(); | |
51 } | |
52 | |
53 syncable::Directory* dir() { | |
54 return dir_maker_.directory(); | |
55 } | |
56 protected: | |
57 scoped_ptr<sync_pb::SyncEntity> CreateUpdate( | |
58 const std::string& id, | |
59 const std::string& parent, | |
60 const ModelType& type); | |
61 | |
62 // This exists mostly to give tests access to the protected member function. | |
63 // Warning: This takes the syncable directory lock. | |
64 void UpdateSyncEntities( | |
65 SyncDirectoryUpdateHandler* handler, | |
66 const SyncEntityList& applicable_updates, | |
67 sessions::StatusController* status); | |
68 | |
69 // Another function to access private member functions. | |
70 void UpdateProgressMarkers( | |
71 SyncDirectoryUpdateHandler* handler, | |
72 const sync_pb::DataTypeProgressMarker& progress); | |
73 | |
74 scoped_refptr<FakeModelWorker> ui_worker() { | |
75 return ui_worker_; | |
76 } | |
77 | |
78 private: | |
79 base::MessageLoop loop_; // Needed to initialize the directory. | |
80 TestDirectorySetterUpper dir_maker_; | |
81 scoped_refptr<FakeModelWorker> ui_worker_; | |
82 }; | |
83 | |
84 scoped_ptr<sync_pb::SyncEntity> | |
85 SyncDirectoryUpdateHandlerProcessUpdateTest::CreateUpdate( | |
86 const std::string& id, | |
87 const std::string& parent, | |
88 const ModelType& type) { | |
89 scoped_ptr<sync_pb::SyncEntity> e(new sync_pb::SyncEntity()); | |
90 e->set_id_string(id); | |
91 e->set_parent_id_string(parent); | |
92 e->set_non_unique_name(id); | |
93 e->set_name(id); | |
94 e->set_version(1000); | |
95 AddDefaultFieldValue(type, e->mutable_specifics()); | |
96 return e.Pass(); | |
97 } | |
98 | |
99 void SyncDirectoryUpdateHandlerProcessUpdateTest::UpdateSyncEntities( | |
100 SyncDirectoryUpdateHandler* handler, | |
101 const SyncEntityList& applicable_updates, | |
102 sessions::StatusController* status) { | |
103 syncable::ModelNeutralWriteTransaction trans(FROM_HERE, UNITTEST, dir()); | |
104 handler->UpdateSyncEntities(&trans, applicable_updates, status); | |
105 } | |
106 | |
107 void SyncDirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers( | |
108 SyncDirectoryUpdateHandler* handler, | |
109 const sync_pb::DataTypeProgressMarker& progress) { | |
110 handler->UpdateProgressMarker(progress); | |
111 } | |
112 | |
113 static const char kCacheGuid[] = "IrcjZ2jyzHDV9Io4+zKcXQ=="; | |
114 | |
115 // Test that the bookmark tag is set on newly downloaded items. | |
116 TEST_F(SyncDirectoryUpdateHandlerProcessUpdateTest, NewBookmarkTag) { | |
117 SyncDirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker()); | |
118 sync_pb::GetUpdatesResponse gu_response; | |
119 sessions::StatusController status; | |
120 | |
121 // Add a bookmark item to the update message. | |
122 std::string root = syncable::GetNullId().GetServerId(); | |
123 syncable::Id server_id = syncable::Id::CreateFromServerId("b1"); | |
124 scoped_ptr<sync_pb::SyncEntity> e = | |
125 CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS); | |
126 e->set_originator_cache_guid( | |
127 std::string(kCacheGuid, arraysize(kCacheGuid)-1)); | |
128 syncable::Id client_id = syncable::Id::CreateFromClientString("-2"); | |
129 e->set_originator_client_item_id(client_id.GetServerId()); | |
130 e->set_position_in_parent(0); | |
131 | |
132 // Add it to the applicable updates list. | |
133 SyncEntityList bookmark_updates; | |
134 bookmark_updates.push_back(e.get()); | |
135 | |
136 // Process the update. | |
137 UpdateSyncEntities(&handler, bookmark_updates, &status); | |
138 | |
139 syncable::ReadTransaction trans(FROM_HERE, dir()); | |
140 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id); | |
141 ASSERT_TRUE(entry.good()); | |
142 EXPECT_TRUE(UniquePosition::IsValidSuffix(entry.GetUniqueBookmarkTag())); | |
143 EXPECT_TRUE(entry.GetServerUniquePosition().IsValid()); | |
144 | |
145 // If this assertion fails, that might indicate that the algorithm used to | |
146 // generate bookmark tags has been modified. This could have implications for | |
147 // bookmark ordering. Please make sure you know what you're doing if you | |
148 // intend to make such a change. | |
149 EXPECT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", entry.GetUniqueBookmarkTag()); | |
150 } | |
151 | |
152 // Test the receipt of a type root node. | |
153 TEST_F(SyncDirectoryUpdateHandlerProcessUpdateTest, | |
154 ReceiveServerCreatedBookmarkFolders) { | |
155 SyncDirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker()); | |
156 sync_pb::GetUpdatesResponse gu_response; | |
157 sessions::StatusController status; | |
158 | |
159 // Create an update that mimics the bookmark root. | |
160 syncable::Id server_id = syncable::Id::CreateFromServerId("xyz"); | |
161 std::string root = syncable::GetNullId().GetServerId(); | |
162 scoped_ptr<sync_pb::SyncEntity> e = | |
163 CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS); | |
164 e->set_server_defined_unique_tag("google_chrome_bookmarks"); | |
165 e->set_folder(true); | |
166 | |
167 // Add it to the applicable updates list. | |
168 SyncEntityList bookmark_updates; | |
169 bookmark_updates.push_back(e.get()); | |
170 | |
171 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e)); | |
172 | |
173 // Process it. | |
174 UpdateSyncEntities(&handler, bookmark_updates, &status); | |
175 | |
176 // Verify the results. | |
177 syncable::ReadTransaction trans(FROM_HERE, dir()); | |
178 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id); | |
179 ASSERT_TRUE(entry.good()); | |
180 | |
181 EXPECT_FALSE(entry.ShouldMaintainPosition()); | |
182 EXPECT_FALSE(entry.GetUniquePosition().IsValid()); | |
183 EXPECT_FALSE(entry.GetServerUniquePosition().IsValid()); | |
184 EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty()); | |
185 } | |
186 | |
187 // Test the receipt of a non-bookmark item. | |
188 TEST_F(SyncDirectoryUpdateHandlerProcessUpdateTest, ReceiveNonBookmarkItem) { | |
189 SyncDirectoryUpdateHandler handler(dir(), PREFERENCES, ui_worker()); | |
190 sync_pb::GetUpdatesResponse gu_response; | |
191 sessions::StatusController status; | |
192 | |
193 std::string root = syncable::GetNullId().GetServerId(); | |
194 syncable::Id server_id = syncable::Id::CreateFromServerId("xyz"); | |
195 scoped_ptr<sync_pb::SyncEntity> e = | |
196 CreateUpdate(SyncableIdToProto(server_id), root, PREFERENCES); | |
197 e->set_server_defined_unique_tag("9PGRuKdX5sHyGMB17CvYTXuC43I="); | |
198 | |
199 // Add it to the applicable updates list. | |
200 SyncEntityList autofill_updates; | |
201 autofill_updates.push_back(e.get()); | |
202 | |
203 EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e)); | |
204 | |
205 // Process it. | |
206 UpdateSyncEntities(&handler, autofill_updates, &status); | |
207 | |
208 syncable::ReadTransaction trans(FROM_HERE, dir()); | |
209 syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id); | |
210 ASSERT_TRUE(entry.good()); | |
211 | |
212 EXPECT_FALSE(entry.ShouldMaintainPosition()); | |
213 EXPECT_FALSE(entry.GetUniquePosition().IsValid()); | |
214 EXPECT_FALSE(entry.GetServerUniquePosition().IsValid()); | |
215 EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty()); | |
216 } | |
217 | |
218 // Tests the setting of progress markers. | |
219 TEST_F(SyncDirectoryUpdateHandlerProcessUpdateTest, ProcessNewProgressMarkers) { | |
220 SyncDirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker()); | |
221 | |
222 sync_pb::DataTypeProgressMarker progress; | |
223 progress.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS)); | |
224 progress.set_token("token"); | |
225 | |
226 UpdateProgressMarkers(&handler, progress); | |
227 | |
228 sync_pb::DataTypeProgressMarker saved; | |
229 dir()->GetDownloadProgress(BOOKMARKS, &saved); | |
230 | |
231 EXPECT_EQ(progress.token(), saved.token()); | |
232 EXPECT_EQ(progress.data_type_id(), saved.data_type_id()); | |
233 } | |
234 | |
235 // A test harness for tests that focus on applying updates. | |
236 // | |
237 // Update application is performed when we want to take updates that were | |
238 // previously downloaded, processed, and stored in our syncable::Directory | |
239 // and use them to update our local state (both the Directory's local state | |
240 // and the model's local state, though these tests focus only on the Directory's | |
241 // local state). | |
242 // | |
243 // This is kept separate from the update processing test in part for historical | |
244 // reasons, and in part because these tests may require a bit more infrastrcture | |
245 // in the future. Update application should happen on a different thread a lot | |
246 // of the time so these tests may end up requiring more infrastructure than the | |
247 // update processing tests. Currently, we're bypassing most of those issues by | |
248 // using FakeModelWorkers, so there's not much difference between the two test | |
249 // harnesses. | |
250 class SyncDirectoryUpdateHandlerApplyUpdateTest : public ::testing::Test { | |
251 public: | |
252 SyncDirectoryUpdateHandlerApplyUpdateTest() | |
253 : ui_worker_(new FakeModelWorker(GROUP_UI)), | |
254 password_worker_(new FakeModelWorker(GROUP_PASSWORD)), | |
255 passive_worker_(new FakeModelWorker(GROUP_PASSIVE)), | |
256 update_handler_map_deleter_(&update_handler_map_) {} | |
257 | |
258 virtual void SetUp() OVERRIDE { | |
259 dir_maker_.SetUp(); | |
260 entry_factory_.reset(new TestEntryFactory(directory())); | |
261 | |
262 update_handler_map_.insert(std::make_pair( | |
263 BOOKMARKS, | |
264 new SyncDirectoryUpdateHandler(directory(), BOOKMARKS, ui_worker_))); | |
265 update_handler_map_.insert(std::make_pair( | |
266 PASSWORDS, | |
267 new SyncDirectoryUpdateHandler(directory(), | |
268 PASSWORDS, | |
269 password_worker_))); | |
270 } | |
271 | |
272 virtual void TearDown() OVERRIDE { | |
273 dir_maker_.TearDown(); | |
274 } | |
275 | |
276 protected: | |
277 void ApplyBookmarkUpdates(sessions::StatusController* status) { | |
278 update_handler_map_[BOOKMARKS]->ApplyUpdates(status); | |
279 } | |
280 | |
281 void ApplyPasswordUpdates(sessions::StatusController* status) { | |
282 update_handler_map_[PASSWORDS]->ApplyUpdates(status); | |
283 } | |
284 | |
285 TestEntryFactory* entry_factory() { | |
286 return entry_factory_.get(); | |
287 } | |
288 | |
289 syncable::Directory* directory() { | |
290 return dir_maker_.directory(); | |
291 } | |
292 | |
293 private: | |
294 typedef std::map<ModelType, SyncDirectoryUpdateHandler*> UpdateHandlerMap; | |
295 | |
296 base::MessageLoop loop_; // Needed to initialize the directory. | |
297 TestDirectorySetterUpper dir_maker_; | |
298 scoped_ptr<TestEntryFactory> entry_factory_; | |
299 | |
300 scoped_refptr<FakeModelWorker> ui_worker_; | |
301 scoped_refptr<FakeModelWorker> password_worker_; | |
302 scoped_refptr<FakeModelWorker> passive_worker_; | |
303 | |
304 UpdateHandlerMap update_handler_map_; | |
305 STLValueDeleter<UpdateHandlerMap> update_handler_map_deleter_; | |
306 }; | |
307 | |
308 namespace { | |
309 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { | |
310 sync_pb::EntitySpecifics result; | |
311 AddDefaultFieldValue(BOOKMARKS, &result); | |
312 return result; | |
313 } | |
314 } // namespace | |
315 | |
316 // Test update application for a few bookmark items. | |
317 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, SimpleBookmark) { | |
318 sessions::StatusController status; | |
319 | |
320 std::string root_server_id = syncable::GetNullId().GetServerId(); | |
321 int64 parent_handle = | |
322 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
323 "parent", DefaultBookmarkSpecifics(), root_server_id); | |
324 int64 child_handle = | |
325 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
326 "child", DefaultBookmarkSpecifics(), "parent"); | |
327 | |
328 ApplyBookmarkUpdates(&status); | |
329 | |
330 EXPECT_EQ(0, status.num_encryption_conflicts()) | |
331 << "Simple update shouldn't result in conflicts"; | |
332 EXPECT_EQ(0, status.num_hierarchy_conflicts()) | |
333 << "Simple update shouldn't result in conflicts"; | |
334 EXPECT_EQ(2, status.num_updates_applied()) | |
335 << "All items should have been successfully applied"; | |
336 | |
337 { | |
338 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
339 | |
340 syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle); | |
341 syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle); | |
342 | |
343 ASSERT_TRUE(parent.good()); | |
344 ASSERT_TRUE(child.good()); | |
345 | |
346 EXPECT_FALSE(parent.GetIsUnsynced()); | |
347 EXPECT_FALSE(parent.GetIsUnappliedUpdate()); | |
348 EXPECT_FALSE(child.GetIsUnsynced()); | |
349 EXPECT_FALSE(child.GetIsUnappliedUpdate()); | |
350 } | |
351 } | |
352 | |
353 // Test that the applicator can handle updates delivered out of order. | |
354 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, | |
355 BookmarkChildrenBeforeParent) { | |
356 // Start with some bookmarks whose parents are unknown. | |
357 std::string root_server_id = syncable::GetNullId().GetServerId(); | |
358 int64 a_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
359 "a_child_created_first", DefaultBookmarkSpecifics(), "parent"); | |
360 int64 x_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
361 "x_child_created_first", DefaultBookmarkSpecifics(), "parent"); | |
362 | |
363 // Update application will fail. | |
364 sessions::StatusController status1; | |
365 ApplyBookmarkUpdates(&status1); | |
366 EXPECT_EQ(0, status1.num_updates_applied()); | |
367 EXPECT_EQ(2, status1.num_hierarchy_conflicts()); | |
368 | |
369 { | |
370 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
371 | |
372 syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle); | |
373 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle); | |
374 | |
375 ASSERT_TRUE(a.good()); | |
376 ASSERT_TRUE(x.good()); | |
377 | |
378 EXPECT_TRUE(a.GetIsUnappliedUpdate()); | |
379 EXPECT_TRUE(x.GetIsUnappliedUpdate()); | |
380 } | |
381 | |
382 // Now add their parent and a few siblings. | |
383 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
384 "parent", DefaultBookmarkSpecifics(), root_server_id); | |
385 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
386 "a_child_created_second", DefaultBookmarkSpecifics(), "parent"); | |
387 entry_factory()->CreateUnappliedNewBookmarkItemWithParent( | |
388 "x_child_created_second", DefaultBookmarkSpecifics(), "parent"); | |
389 | |
390 // Update application will succeed. | |
391 sessions::StatusController status2; | |
392 ApplyBookmarkUpdates(&status2); | |
393 EXPECT_EQ(5, status2.num_updates_applied()) | |
394 << "All updates should have been successfully applied"; | |
395 | |
396 { | |
397 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
398 | |
399 syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle); | |
400 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle); | |
401 | |
402 ASSERT_TRUE(a.good()); | |
403 ASSERT_TRUE(x.good()); | |
404 | |
405 EXPECT_FALSE(a.GetIsUnappliedUpdate()); | |
406 EXPECT_FALSE(x.GetIsUnappliedUpdate()); | |
407 } | |
408 } | |
409 | |
410 // Try to apply changes on an item that is both IS_UNSYNCED and | |
411 // IS_UNAPPLIED_UPDATE. Conflict resolution should be performed. | |
412 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, SimpleBookmarkConflict) { | |
413 int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x"); | |
414 | |
415 int original_server_version = -10; | |
416 { | |
417 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
418 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); | |
419 original_server_version = e.GetServerVersion(); | |
420 ASSERT_NE(original_server_version, e.GetBaseVersion()); | |
421 EXPECT_TRUE(e.GetIsUnsynced()); | |
422 } | |
423 | |
424 sessions::StatusController status; | |
425 ApplyBookmarkUpdates(&status); | |
426 EXPECT_EQ(1, status.num_server_overwrites()) | |
427 << "Unsynced and unapplied item conflict should be resolved"; | |
428 EXPECT_EQ(0, status.num_updates_applied()) | |
429 << "Update should not be applied; we should override the server."; | |
430 | |
431 { | |
432 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
433 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); | |
434 ASSERT_TRUE(e.good()); | |
435 EXPECT_EQ(original_server_version, e.GetServerVersion()); | |
436 EXPECT_EQ(original_server_version, e.GetBaseVersion()); | |
437 EXPECT_FALSE(e.GetIsUnappliedUpdate()); | |
438 | |
439 // The unsynced flag will remain set until we successfully commit the item. | |
440 EXPECT_TRUE(e.GetIsUnsynced()); | |
441 } | |
442 } | |
443 | |
444 // Create a simple conflict that is also a hierarchy conflict. If we were to | |
445 // follow the normal "server wins" logic, we'd end up violating hierarchy | |
446 // constraints. The hierarchy conflict must take precedence. We can not allow | |
447 // the update to be applied. The item must remain in the conflict state. | |
448 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, HierarchyAndSimpleConflict) { | |
449 // Create a simply-conflicting item. It will start with valid parent ids. | |
450 int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem( | |
451 "orphaned_by_server"); | |
452 { | |
453 // Manually set the SERVER_PARENT_ID to bad value. | |
454 // A bad parent indicates a hierarchy conflict. | |
455 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
456 syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | |
457 ASSERT_TRUE(entry.good()); | |
458 | |
459 entry.PutServerParentId(TestIdFactory::MakeServer("bogus_parent")); | |
460 } | |
461 | |
462 sessions::StatusController status; | |
463 ApplyBookmarkUpdates(&status); | |
464 EXPECT_EQ(0, status.num_updates_applied()); | |
465 EXPECT_EQ(0, status.num_server_overwrites()); | |
466 EXPECT_EQ(1, status.num_hierarchy_conflicts()); | |
467 | |
468 { | |
469 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
470 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); | |
471 ASSERT_TRUE(e.good()); | |
472 EXPECT_TRUE(e.GetIsUnappliedUpdate()); | |
473 EXPECT_TRUE(e.GetIsUnsynced()); | |
474 } | |
475 } | |
476 | |
477 // Attempt to apply an udpate that would create a bookmark folder loop. This | |
478 // application should fail. | |
479 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, BookmarkFolderLoop) { | |
480 // Item 'X' locally has parent of 'root'. Server is updating it to have | |
481 // parent of 'Y'. | |
482 | |
483 // Create it as a child of root node. | |
484 int64 handle = entry_factory()->CreateSyncedItem("X", BOOKMARKS, true); | |
485 | |
486 { | |
487 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
488 syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | |
489 ASSERT_TRUE(entry.good()); | |
490 | |
491 // Re-parent from root to "Y" | |
492 entry.PutServerVersion(entry_factory()->GetNextRevision()); | |
493 entry.PutIsUnappliedUpdate(true); | |
494 entry.PutServerParentId(TestIdFactory::MakeServer("Y")); | |
495 } | |
496 | |
497 // Item 'Y' is child of 'X'. | |
498 entry_factory()->CreateUnsyncedItem( | |
499 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true, | |
500 BOOKMARKS, NULL); | |
501 | |
502 // If the server's update were applied, we would have X be a child of Y, and Y | |
503 // as a child of X. That's a directory loop. The UpdateApplicator should | |
504 // prevent the update from being applied and note that this is a hierarchy | |
505 // conflict. | |
506 | |
507 sessions::StatusController status; | |
508 ApplyBookmarkUpdates(&status); | |
509 | |
510 // This should count as a hierarchy conflict. | |
511 EXPECT_EQ(1, status.num_hierarchy_conflicts()); | |
512 | |
513 { | |
514 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
515 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); | |
516 ASSERT_TRUE(e.good()); | |
517 EXPECT_TRUE(e.GetIsUnappliedUpdate()); | |
518 EXPECT_FALSE(e.GetIsUnsynced()); | |
519 } | |
520 } | |
521 | |
522 // Test update application where the update has been orphaned by a local folder | |
523 // deletion. The update application attempt should fail. | |
524 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, | |
525 HierarchyConflictDeletedParent) { | |
526 // Create a locally deleted parent item. | |
527 int64 parent_handle; | |
528 entry_factory()->CreateUnsyncedItem( | |
529 syncable::Id::CreateFromServerId("parent"), TestIdFactory::root(), | |
530 "parent", true, BOOKMARKS, &parent_handle); | |
531 { | |
532 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
533 syncable::MutableEntry entry(&trans, | |
534 syncable::GET_BY_HANDLE, | |
535 parent_handle); | |
536 entry.PutIsDel(true); | |
537 } | |
538 | |
539 // Create an incoming child from the server. | |
540 int64 child_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
541 "child", DefaultBookmarkSpecifics(), "parent"); | |
542 | |
543 // The server's update may seem valid to some other client, but on this client | |
544 // that new item's parent no longer exists. The update should not be applied | |
545 // and the update applicator should indicate this is a hierarchy conflict. | |
546 | |
547 sessions::StatusController status; | |
548 ApplyBookmarkUpdates(&status); | |
549 EXPECT_EQ(1, status.num_hierarchy_conflicts()); | |
550 | |
551 { | |
552 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
553 syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle); | |
554 ASSERT_TRUE(child.good()); | |
555 EXPECT_TRUE(child.GetIsUnappliedUpdate()); | |
556 EXPECT_FALSE(child.GetIsUnsynced()); | |
557 } | |
558 } | |
559 | |
560 // Attempt to apply an update that deletes a folder where the folder has | |
561 // locally-created children. The update application should fail. | |
562 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, | |
563 HierarchyConflictDeleteNonEmptyDirectory) { | |
564 // Create a server-deleted folder as a child of root node. | |
565 int64 parent_handle = | |
566 entry_factory()->CreateSyncedItem("parent", BOOKMARKS, true); | |
567 { | |
568 syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
569 syncable::MutableEntry entry(&trans, | |
570 syncable::GET_BY_HANDLE, | |
571 parent_handle); | |
572 ASSERT_TRUE(entry.good()); | |
573 | |
574 // Delete it on the server. | |
575 entry.PutServerVersion(entry_factory()->GetNextRevision()); | |
576 entry.PutIsUnappliedUpdate(true); | |
577 entry.PutServerParentId(TestIdFactory::root()); | |
578 entry.PutServerIsDel(true); | |
579 } | |
580 | |
581 // Create a local child of the server-deleted directory. | |
582 entry_factory()->CreateUnsyncedItem( | |
583 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"), | |
584 "child", false, BOOKMARKS, NULL); | |
585 | |
586 // The server's request to delete the directory must be ignored, otherwise our | |
587 // unsynced new child would be orphaned. This is a hierarchy conflict. | |
588 | |
589 sessions::StatusController status; | |
590 ApplyBookmarkUpdates(&status); | |
591 | |
592 // This should count as a hierarchy conflict. | |
593 EXPECT_EQ(1, status.num_hierarchy_conflicts()); | |
594 | |
595 { | |
596 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
597 syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle); | |
598 ASSERT_TRUE(parent.good()); | |
599 EXPECT_TRUE(parent.GetIsUnappliedUpdate()); | |
600 EXPECT_FALSE(parent.GetIsUnsynced()); | |
601 } | |
602 } | |
603 | |
604 // Attempt to apply updates where the updated item's parent is not known to this | |
605 // client. The update application attempt should fail. | |
606 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, | |
607 HierarchyConflictUnknownParent) { | |
608 // We shouldn't be able to do anything with either of these items. | |
609 int64 x_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
610 "some_item", DefaultBookmarkSpecifics(), "unknown_parent"); | |
611 int64 y_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
612 "some_other_item", DefaultBookmarkSpecifics(), "some_item"); | |
613 | |
614 sessions::StatusController status; | |
615 ApplyBookmarkUpdates(&status); | |
616 | |
617 EXPECT_EQ(2, status.num_hierarchy_conflicts()) | |
618 << "All updates with an unknown ancestors should be in conflict"; | |
619 EXPECT_EQ(0, status.num_updates_applied()) | |
620 << "No item with an unknown ancestor should be applied"; | |
621 | |
622 { | |
623 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
624 syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle); | |
625 syncable::Entry y(&trans, syncable::GET_BY_HANDLE, y_handle); | |
626 ASSERT_TRUE(x.good()); | |
627 ASSERT_TRUE(y.good()); | |
628 EXPECT_TRUE(x.GetIsUnappliedUpdate()); | |
629 EXPECT_TRUE(y.GetIsUnappliedUpdate()); | |
630 EXPECT_FALSE(x.GetIsUnsynced()); | |
631 EXPECT_FALSE(y.GetIsUnsynced()); | |
632 } | |
633 } | |
634 | |
635 // Attempt application of a mix of items. Some update application attempts will | |
636 // fail due to hierarchy conflicts. Others should succeed. | |
637 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, ItemsBothKnownAndUnknown) { | |
638 // See what happens when there's a mixture of good and bad updates. | |
639 std::string root_server_id = syncable::GetNullId().GetServerId(); | |
640 int64 u1_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
641 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent"); | |
642 int64 k1_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
643 "first_known_item", DefaultBookmarkSpecifics(), root_server_id); | |
644 int64 u2_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
645 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent"); | |
646 int64 k2_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
647 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item"); | |
648 int64 k3_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
649 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item"); | |
650 int64 k4_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
651 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id); | |
652 | |
653 sessions::StatusController status; | |
654 ApplyBookmarkUpdates(&status); | |
655 | |
656 EXPECT_EQ(2, status.num_hierarchy_conflicts()) | |
657 << "The updates with unknown ancestors should be in conflict"; | |
658 EXPECT_EQ(4, status.num_updates_applied()) | |
659 << "The updates with known ancestors should be successfully applied"; | |
660 | |
661 { | |
662 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
663 syncable::Entry u1(&trans, syncable::GET_BY_HANDLE, u1_handle); | |
664 syncable::Entry u2(&trans, syncable::GET_BY_HANDLE, u2_handle); | |
665 syncable::Entry k1(&trans, syncable::GET_BY_HANDLE, k1_handle); | |
666 syncable::Entry k2(&trans, syncable::GET_BY_HANDLE, k2_handle); | |
667 syncable::Entry k3(&trans, syncable::GET_BY_HANDLE, k3_handle); | |
668 syncable::Entry k4(&trans, syncable::GET_BY_HANDLE, k4_handle); | |
669 ASSERT_TRUE(u1.good()); | |
670 ASSERT_TRUE(u2.good()); | |
671 ASSERT_TRUE(k1.good()); | |
672 ASSERT_TRUE(k2.good()); | |
673 ASSERT_TRUE(k3.good()); | |
674 ASSERT_TRUE(k4.good()); | |
675 EXPECT_TRUE(u1.GetIsUnappliedUpdate()); | |
676 EXPECT_TRUE(u2.GetIsUnappliedUpdate()); | |
677 EXPECT_FALSE(k1.GetIsUnappliedUpdate()); | |
678 EXPECT_FALSE(k2.GetIsUnappliedUpdate()); | |
679 EXPECT_FALSE(k3.GetIsUnappliedUpdate()); | |
680 EXPECT_FALSE(k4.GetIsUnappliedUpdate()); | |
681 } | |
682 } | |
683 | |
684 // Attempt application of password upates where the passphrase is known. | |
685 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, DecryptablePassword) { | |
686 // Decryptable password updates should be applied. | |
687 Cryptographer* cryptographer; | |
688 { | |
689 // Storing the cryptographer separately is bad, but for this test we | |
690 // know it's safe. | |
691 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
692 cryptographer = directory()->GetCryptographer(&trans); | |
693 } | |
694 | |
695 KeyParams params = {"localhost", "dummy", "foobar"}; | |
696 cryptographer->AddKey(params); | |
697 | |
698 sync_pb::EntitySpecifics specifics; | |
699 sync_pb::PasswordSpecificsData data; | |
700 data.set_origin("http://example.com"); | |
701 | |
702 cryptographer->Encrypt(data, | |
703 specifics.mutable_password()->mutable_encrypted()); | |
704 int64 handle = | |
705 entry_factory()->CreateUnappliedNewItem("item", specifics, false); | |
706 | |
707 sessions::StatusController status; | |
708 ApplyPasswordUpdates(&status); | |
709 | |
710 EXPECT_EQ(1, status.num_updates_applied()) | |
711 << "The updates that can be decrypted should be applied"; | |
712 | |
713 { | |
714 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
715 syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle); | |
716 ASSERT_TRUE(e.good()); | |
717 EXPECT_FALSE(e.GetIsUnappliedUpdate()); | |
718 EXPECT_FALSE(e.GetIsUnsynced()); | |
719 } | |
720 } | |
721 | |
722 // Attempt application of encrypted items when the passphrase is not known. | |
723 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, UndecryptableData) { | |
724 // Undecryptable updates should not be applied. | |
725 sync_pb::EntitySpecifics encrypted_bookmark; | |
726 encrypted_bookmark.mutable_encrypted(); | |
727 AddDefaultFieldValue(BOOKMARKS, &encrypted_bookmark); | |
728 std::string root_server_id = syncable::GetNullId().GetServerId(); | |
729 int64 folder_handle = entry_factory()->CreateUnappliedNewItemWithParent( | |
730 "folder", | |
731 encrypted_bookmark, | |
732 root_server_id); | |
733 int64 bookmark_handle = entry_factory()->CreateUnappliedNewItem( | |
734 "item2", | |
735 encrypted_bookmark, | |
736 false); | |
737 sync_pb::EntitySpecifics encrypted_password; | |
738 encrypted_password.mutable_password(); | |
739 int64 password_handle = entry_factory()->CreateUnappliedNewItem( | |
740 "item3", | |
741 encrypted_password, | |
742 false); | |
743 | |
744 sessions::StatusController status; | |
745 ApplyBookmarkUpdates(&status); | |
746 ApplyPasswordUpdates(&status); | |
747 | |
748 EXPECT_EQ(3, status.num_encryption_conflicts()) | |
749 << "Updates that can't be decrypted should be in encryption conflict"; | |
750 EXPECT_EQ(0, status.num_updates_applied()) | |
751 << "No update that can't be decrypted should be applied"; | |
752 | |
753 { | |
754 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
755 syncable::Entry folder(&trans, syncable::GET_BY_HANDLE, folder_handle); | |
756 syncable::Entry bm(&trans, syncable::GET_BY_HANDLE, bookmark_handle); | |
757 syncable::Entry pw(&trans, syncable::GET_BY_HANDLE, password_handle); | |
758 ASSERT_TRUE(folder.good()); | |
759 ASSERT_TRUE(bm.good()); | |
760 ASSERT_TRUE(pw.good()); | |
761 EXPECT_TRUE(folder.GetIsUnappliedUpdate()); | |
762 EXPECT_TRUE(bm.GetIsUnappliedUpdate()); | |
763 EXPECT_TRUE(pw.GetIsUnappliedUpdate()); | |
764 } | |
765 } | |
766 | |
767 // Test a mix of decryptable and undecryptable updates. | |
768 TEST_F(SyncDirectoryUpdateHandlerApplyUpdateTest, SomeUndecryptablePassword) { | |
769 Cryptographer* cryptographer; | |
770 | |
771 int64 decryptable_handle = -1; | |
772 int64 undecryptable_handle = -1; | |
773 | |
774 // Only decryptable password updates should be applied. | |
775 { | |
776 sync_pb::EntitySpecifics specifics; | |
777 sync_pb::PasswordSpecificsData data; | |
778 data.set_origin("http://example.com/1"); | |
779 { | |
780 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
781 cryptographer = directory()->GetCryptographer(&trans); | |
782 | |
783 KeyParams params = {"localhost", "dummy", "foobar"}; | |
784 cryptographer->AddKey(params); | |
785 | |
786 cryptographer->Encrypt(data, | |
787 specifics.mutable_password()->mutable_encrypted()); | |
788 } | |
789 decryptable_handle = | |
790 entry_factory()->CreateUnappliedNewItem("item1", specifics, false); | |
791 } | |
792 { | |
793 // Create a new cryptographer, independent of the one in the session. | |
794 Cryptographer other_cryptographer(cryptographer->encryptor()); | |
795 KeyParams params = {"localhost", "dummy", "bazqux"}; | |
796 other_cryptographer.AddKey(params); | |
797 | |
798 sync_pb::EntitySpecifics specifics; | |
799 sync_pb::PasswordSpecificsData data; | |
800 data.set_origin("http://example.com/2"); | |
801 | |
802 other_cryptographer.Encrypt(data, | |
803 specifics.mutable_password()->mutable_encrypted()); | |
804 undecryptable_handle = | |
805 entry_factory()->CreateUnappliedNewItem("item2", specifics, false); | |
806 } | |
807 | |
808 sessions::StatusController status; | |
809 ApplyPasswordUpdates(&status); | |
810 | |
811 EXPECT_EQ(1, status.num_encryption_conflicts()) | |
812 << "The updates that can't be decrypted should be in encryption " | |
813 << "conflict"; | |
814 EXPECT_EQ(1, status.num_updates_applied()) | |
815 << "The undecryptable password update shouldn't be applied"; | |
816 | |
817 { | |
818 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
819 syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, decryptable_handle); | |
820 syncable::Entry e2(&trans, syncable::GET_BY_HANDLE, undecryptable_handle); | |
821 ASSERT_TRUE(e1.good()); | |
822 ASSERT_TRUE(e2.good()); | |
823 EXPECT_FALSE(e1.GetIsUnappliedUpdate()); | |
824 EXPECT_TRUE(e2.GetIsUnappliedUpdate()); | |
825 } | |
826 } | |
827 | |
828 } // namespace syncer | |
OLD | NEW |