• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "sync/engine/directory_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/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"
28 
29 namespace syncer {
30 
31 using syncable::UNITTEST;
32 
33 static const int64 kDefaultVersion = 1000;
34 
35 // A test harness for tests that focus on processing updates.
36 //
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 {
41  public:
DirectoryUpdateHandlerProcessUpdateTest()42   DirectoryUpdateHandlerProcessUpdateTest()
43       : ui_worker_(new FakeModelWorker(GROUP_UI)) {
44   }
45 
~DirectoryUpdateHandlerProcessUpdateTest()46   virtual ~DirectoryUpdateHandlerProcessUpdateTest() {}
47 
SetUp()48   virtual void SetUp() OVERRIDE {
49     dir_maker_.SetUp();
50   }
51 
TearDown()52   virtual void TearDown() OVERRIDE {
53     dir_maker_.TearDown();
54   }
55 
dir()56   syncable::Directory* dir() {
57     return dir_maker_.directory();
58   }
59 
60  protected:
61   scoped_ptr<sync_pb::SyncEntity> CreateUpdate(
62       const std::string& id,
63       const std::string& parent,
64       const ModelType& type);
65 
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);
72 
73   // Another function to access private member functions.
74   void UpdateProgressMarkers(
75       DirectoryUpdateHandler* handler,
76       const sync_pb::DataTypeProgressMarker& progress);
77 
ui_worker()78   scoped_refptr<FakeModelWorker> ui_worker() {
79     return ui_worker_;
80   }
81 
EntryExists(const std::string & id)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();
87   }
88 
89  protected:
90   // Used in the construction of DirectoryTypeDebugInfoEmitters.
91   ObserverList<TypeDebugInfoObserver> type_observers_;
92 
93  private:
94   base::MessageLoop loop_;  // Needed to initialize the directory.
95   TestDirectorySetterUpper dir_maker_;
96   scoped_refptr<FakeModelWorker> ui_worker_;
97 };
98 
99 scoped_ptr<sync_pb::SyncEntity>
CreateUpdate(const std::string & id,const std::string & parent,const ModelType & type)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);
108   e->set_name(id);
109   e->set_version(kDefaultVersion);
110   AddDefaultFieldValue(type, e->mutable_specifics());
111   return e.Pass();
112 }
113 
UpdateSyncEntities(DirectoryUpdateHandler * handler,const SyncEntityList & applicable_updates,sessions::StatusController * status)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);
120 }
121 
UpdateProgressMarkers(DirectoryUpdateHandler * handler,const sync_pb::DataTypeProgressMarker & progress)122 void DirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers(
123     DirectoryUpdateHandler* handler,
124     const sync_pb::DataTypeProgressMarker& progress) {
125   handler->UpdateProgressMarker(progress);
126 }
127 
128 static const char kCacheGuid[] = "IrcjZ2jyzHDV9Io4+zKcXQ==";
129 
130 // Test that the bookmark tag is set on newly downloaded items.
TEST_F(DirectoryUpdateHandlerProcessUpdateTest,NewBookmarkTag)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;
136 
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);
147 
148   // Add it to the applicable updates list.
149   SyncEntityList bookmark_updates;
150   bookmark_updates.push_back(e.get());
151 
152   // Process the update.
153   UpdateSyncEntities(&handler, bookmark_updates, &status);
154 
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());
160 
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());
166 }
167 
168 // Test the receipt of a type root node.
TEST_F(DirectoryUpdateHandlerProcessUpdateTest,ReceiveServerCreatedBookmarkFolders)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;
175 
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");
182   e->set_folder(true);
183 
184   // Add it to the applicable updates list.
185   SyncEntityList bookmark_updates;
186   bookmark_updates.push_back(e.get());
187 
188   EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e));
189 
190   // Process it.
191   UpdateSyncEntities(&handler, bookmark_updates, &status);
192 
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());
197 
198   EXPECT_FALSE(entry.ShouldMaintainPosition());
199   EXPECT_FALSE(entry.GetUniquePosition().IsValid());
200   EXPECT_FALSE(entry.GetServerUniquePosition().IsValid());
201   EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty());
202 }
203 
204 // Test the receipt of a non-bookmark item.
TEST_F(DirectoryUpdateHandlerProcessUpdateTest,ReceiveNonBookmarkItem)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;
210 
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=");
216 
217   // Add it to the applicable updates list.
218   SyncEntityList autofill_updates;
219   autofill_updates.push_back(e.get());
220 
221   EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e));
222 
223   // Process it.
224   UpdateSyncEntities(&handler, autofill_updates, &status);
225 
226   syncable::ReadTransaction trans(FROM_HERE, dir());
227   syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
228   ASSERT_TRUE(entry.good());
229 
230   EXPECT_FALSE(entry.ShouldMaintainPosition());
231   EXPECT_FALSE(entry.GetUniquePosition().IsValid());
232   EXPECT_FALSE(entry.GetServerUniquePosition().IsValid());
233   EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty());
234 }
235 
236 // Tests the setting of progress markers.
TEST_F(DirectoryUpdateHandlerProcessUpdateTest,ProcessNewProgressMarkers)237 TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ProcessNewProgressMarkers) {
238   DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
239   DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
240 
241   sync_pb::DataTypeProgressMarker progress;
242   progress.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS));
243   progress.set_token("token");
244 
245   UpdateProgressMarkers(&handler, progress);
246 
247   sync_pb::DataTypeProgressMarker saved;
248   dir()->GetDownloadProgress(BOOKMARKS, &saved);
249 
250   EXPECT_EQ(progress.token(), saved.token());
251   EXPECT_EQ(progress.data_type_id(), saved.data_type_id());
252 }
253 
TEST_F(DirectoryUpdateHandlerProcessUpdateTest,GarbageCollectionByVersion)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;
259 
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);
265 
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);
271 
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);
279 
280   scoped_ptr<sync_pb::SyncEntity> e1 =
281       CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
282                    type_root->id_string(),
283                    SYNCED_NOTIFICATIONS);
284 
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);
290 
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());
296 
297   // Process and apply updates.
298   EXPECT_EQ(
299       SYNCER_OK,
300       handler.ProcessGetUpdatesResponse(progress, context, updates, &status));
301   handler.ApplyUpdates(&status);
302 
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()));
307 
308   // Process and apply again. Old entry is deleted but not root.
309   progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 20);
310   EXPECT_EQ(SYNCER_OK,
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()));
317 }
318 
TEST_F(DirectoryUpdateHandlerProcessUpdateTest,ContextVersion)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);
325 
326   sync_pb::DataTypeProgressMarker progress;
327   progress.set_data_type_id(
328       GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
329   progress.set_token("token");
330 
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);
335 
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);
347 
348   SyncEntityList updates;
349   updates.push_back(type_root.get());
350   updates.push_back(e1.get());
351 
352   // The first response should be processed fine.
353   EXPECT_EQ(SYNCER_OK,
354             handler.ProcessGetUpdatesResponse(
355                 progress, old_context, updates, &status));
356   handler.ApplyUpdates(&status);
357 
358   EXPECT_TRUE(EntryExists(type_root->id_string()));
359   EXPECT_TRUE(EntryExists(e1->id_string()));
360 
361   {
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());
367   }
368 
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);
373 
374   scoped_ptr<sync_pb::SyncEntity> e2 =
375       CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
376                    type_root->id_string(),
377                    SYNCED_NOTIFICATIONS);
378   updates.clear();
379   updates.push_back(e2.get());
380 
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);
387 
388   EXPECT_FALSE(EntryExists(e2->id_string()));
389 
390   {
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());
396   }
397 }
398 
399 // A test harness for tests that focus on applying updates.
400 //
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
405 // local state).
406 //
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
413 // harnesses.
414 class DirectoryUpdateHandlerApplyUpdateTest : public ::testing::Test {
415  public:
DirectoryUpdateHandlerApplyUpdateTest()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_) {}
423 
SetUp()424   virtual void SetUp() OVERRIDE {
425     dir_maker_.SetUp();
426     entry_factory_.reset(new TestEntryFactory(directory()));
427 
428     update_handler_map_.insert(std::make_pair(
429         BOOKMARKS,
430         new DirectoryUpdateHandler(directory(), BOOKMARKS,
431                                    ui_worker_, &bookmarks_emitter_)));
432     update_handler_map_.insert(std::make_pair(
433         PASSWORDS,
434         new DirectoryUpdateHandler(directory(),
435                                    PASSWORDS,
436                                    password_worker_,
437                                    &passwords_emitter_)));
438   }
439 
TearDown()440   virtual void TearDown() OVERRIDE {
441     dir_maker_.TearDown();
442   }
443 
GetBookmarksUpdateCounters()444   const UpdateCounters& GetBookmarksUpdateCounters() {
445     return bookmarks_emitter_.GetUpdateCounters();
446   }
447 
GetPasswordsUpdateCounters()448   const UpdateCounters& GetPasswordsUpdateCounters() {
449     return passwords_emitter_.GetUpdateCounters();
450   }
451 
452  protected:
ApplyBookmarkUpdates(sessions::StatusController * status)453   void ApplyBookmarkUpdates(sessions::StatusController* status) {
454     update_handler_map_[BOOKMARKS]->ApplyUpdates(status);
455   }
456 
ApplyPasswordUpdates(sessions::StatusController * status)457   void ApplyPasswordUpdates(sessions::StatusController* status) {
458     update_handler_map_[PASSWORDS]->ApplyUpdates(status);
459   }
460 
entry_factory()461   TestEntryFactory* entry_factory() {
462     return entry_factory_.get();
463   }
464 
directory()465   syncable::Directory* directory() {
466     return dir_maker_.directory();
467   }
468 
469  private:
470   typedef std::map<ModelType, UpdateHandler*> UpdateHandlerMap;
471 
472   base::MessageLoop loop_;  // Needed to initialize the directory.
473   TestDirectorySetterUpper dir_maker_;
474   scoped_ptr<TestEntryFactory> entry_factory_;
475 
476   scoped_refptr<FakeModelWorker> ui_worker_;
477   scoped_refptr<FakeModelWorker> password_worker_;
478   scoped_refptr<FakeModelWorker> passive_worker_;
479 
480   ObserverList<TypeDebugInfoObserver> type_observers_;
481   DirectoryTypeDebugInfoEmitter bookmarks_emitter_;
482   DirectoryTypeDebugInfoEmitter passwords_emitter_;
483 
484   UpdateHandlerMap update_handler_map_;
485   STLValueDeleter<UpdateHandlerMap> update_handler_map_deleter_;
486 };
487 
488 namespace {
DefaultBookmarkSpecifics()489 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() {
490   sync_pb::EntitySpecifics result;
491   AddDefaultFieldValue(BOOKMARKS, &result);
492   return result;
493 }
494 } // namespace
495 
496 // Test update application for a few bookmark items.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,SimpleBookmark)497 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmark) {
498   sessions::StatusController status;
499 
500   std::string root_server_id = syncable::GetNullId().GetServerId();
501   int64 parent_handle =
502       entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
503           "parent", DefaultBookmarkSpecifics(), root_server_id);
504   int64 child_handle =
505       entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
506           "child", DefaultBookmarkSpecifics(), "parent");
507 
508   ApplyBookmarkUpdates(&status);
509 
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";
517 
518   {
519     syncable::ReadTransaction trans(FROM_HERE, directory());
520 
521     syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle);
522     syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle);
523 
524     ASSERT_TRUE(parent.good());
525     ASSERT_TRUE(child.good());
526 
527     EXPECT_FALSE(parent.GetIsUnsynced());
528     EXPECT_FALSE(parent.GetIsUnappliedUpdate());
529     EXPECT_FALSE(child.GetIsUnsynced());
530     EXPECT_FALSE(child.GetIsUnappliedUpdate());
531   }
532 }
533 
534 // Test that the applicator can handle updates delivered out of order.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,BookmarkChildrenBeforeParent)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");
543 
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());
549 
550   {
551     syncable::ReadTransaction trans(FROM_HERE, directory());
552 
553     syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle);
554     syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
555 
556     ASSERT_TRUE(a.good());
557     ASSERT_TRUE(x.good());
558 
559     EXPECT_TRUE(a.GetIsUnappliedUpdate());
560     EXPECT_TRUE(x.GetIsUnappliedUpdate());
561   }
562 
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");
570 
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";
576 
577   {
578     syncable::ReadTransaction trans(FROM_HERE, directory());
579 
580     syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle);
581     syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
582 
583     ASSERT_TRUE(a.good());
584     ASSERT_TRUE(x.good());
585 
586     EXPECT_FALSE(a.GetIsUnappliedUpdate());
587     EXPECT_FALSE(x.GetIsUnappliedUpdate());
588   }
589 }
590 
591 // Try to apply changes on an item that is both IS_UNSYNCED and
592 // IS_UNAPPLIED_UPDATE.  Conflict resolution should be performed.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,SimpleBookmarkConflict)593 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmarkConflict) {
594   int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x");
595 
596   int original_server_version = -10;
597   {
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());
603   }
604 
605   sessions::StatusController status;
606   ApplyBookmarkUpdates(&status);
607 
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.";
613 
614   {
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());
621 
622     // The unsynced flag will remain set until we successfully commit the item.
623     EXPECT_TRUE(e.GetIsUnsynced());
624   }
625 }
626 
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.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,HierarchyAndSimpleConflict)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");
635   {
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());
641 
642     entry.PutServerParentId(TestIdFactory::MakeServer("bogus_parent"));
643   }
644 
645   sessions::StatusController status;
646   ApplyBookmarkUpdates(&status);
647 
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);
652 
653   {
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());
659   }
660 }
661 
662 // Attempt to apply an udpate that would create a bookmark folder loop.  This
663 // application should fail.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,BookmarkFolderLoop)664 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, BookmarkFolderLoop) {
665   // Item 'X' locally has parent of 'root'.  Server is updating it to have
666   // parent of 'Y'.
667 
668   // Create it as a child of root node.
669   int64 handle = entry_factory()->CreateSyncedItem("X", BOOKMARKS, true);
670 
671   {
672     syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
673     syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
674     ASSERT_TRUE(entry.good());
675 
676     // Re-parent from root to "Y"
677     entry.PutServerVersion(entry_factory()->GetNextRevision());
678     entry.PutIsUnappliedUpdate(true);
679     entry.PutServerParentId(TestIdFactory::MakeServer("Y"));
680   }
681 
682   // Item 'Y' is child of 'X'.
683   entry_factory()->CreateUnsyncedItem(
684       TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
685       BOOKMARKS, NULL);
686 
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
690   // conflict.
691 
692   sessions::StatusController status;
693   ApplyBookmarkUpdates(&status);
694 
695   // This should count as a hierarchy conflict.
696   const UpdateCounters& counters = GetBookmarksUpdateCounters();
697   EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
698 
699   {
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());
705   }
706 }
707 
708 // Test update application where the update has been orphaned by a local folder
709 // deletion.  The update application attempt should fail.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,HierarchyConflictDeletedParent)710 TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
711        HierarchyConflictDeletedParent) {
712   // Create a locally deleted parent item.
713   int64 parent_handle;
714   entry_factory()->CreateUnsyncedItem(
715       syncable::Id::CreateFromServerId("parent"), TestIdFactory::root(),
716       "parent", true, BOOKMARKS, &parent_handle);
717   {
718     syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
719     syncable::MutableEntry entry(&trans,
720                                  syncable::GET_BY_HANDLE,
721                                  parent_handle);
722     entry.PutIsDel(true);
723   }
724 
725   // Create an incoming child from the server.
726   int64 child_handle = entry_factory()->CreateUnappliedNewItemWithParent(
727       "child", DefaultBookmarkSpecifics(), "parent");
728 
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.
732 
733   sessions::StatusController status;
734   ApplyBookmarkUpdates(&status);
735   const UpdateCounters& counters = GetBookmarksUpdateCounters();
736   EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
737 
738   {
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());
744   }
745 }
746 
747 // Attempt to apply an update that deletes a folder where the folder has
748 // locally-created children.  The update application should fail.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,HierarchyConflictDeleteNonEmptyDirectory)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);
754   {
755     syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
756     syncable::MutableEntry entry(&trans,
757                                  syncable::GET_BY_HANDLE,
758                                  parent_handle);
759     ASSERT_TRUE(entry.good());
760 
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);
766   }
767 
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);
772 
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.
775 
776   sessions::StatusController status;
777   ApplyBookmarkUpdates(&status);
778 
779   // This should count as a hierarchy conflict.
780   const UpdateCounters& counters = GetBookmarksUpdateCounters();
781   EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
782 
783   {
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());
789   }
790 }
791 
792 // Attempt to apply updates where the updated item's parent is not known to this
793 // client.  The update application attempt should fail.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,HierarchyConflictUnknownParent)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");
801 
802   sessions::StatusController status;
803   ApplyBookmarkUpdates(&status);
804 
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";
810 
811   {
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());
821   }
822 }
823 
824 // Attempt application of a mix of items.  Some update application attempts will
825 // fail due to hierarchy conflicts.  Others should succeed.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,ItemsBothKnownAndUnknown)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);
841 
842   sessions::StatusController status;
843   ApplyBookmarkUpdates(&status);
844 
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";
850 
851   {
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());
871   }
872 }
873 
874 // Attempt application of password upates where the passphrase is known.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,DecryptablePassword)875 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, DecryptablePassword) {
876   // Decryptable password updates should be applied.
877   Cryptographer* cryptographer;
878   {
879     // Storing the cryptographer separately is bad, but for this test we
880     // know it's safe.
881     syncable::ReadTransaction trans(FROM_HERE, directory());
882     cryptographer = directory()->GetCryptographer(&trans);
883   }
884 
885   KeyParams params = {"localhost", "dummy", "foobar"};
886   cryptographer->AddKey(params);
887 
888   sync_pb::EntitySpecifics specifics;
889   sync_pb::PasswordSpecificsData data;
890   data.set_origin("http://example.com");
891 
892   cryptographer->Encrypt(data,
893                          specifics.mutable_password()->mutable_encrypted());
894   int64 handle =
895       entry_factory()->CreateUnappliedNewItem("item", specifics, false);
896 
897   sessions::StatusController status;
898   ApplyPasswordUpdates(&status);
899 
900   const UpdateCounters& counters = GetPasswordsUpdateCounters();
901   EXPECT_EQ(1, counters.num_updates_applied)
902       << "The updates that can be decrypted should be applied";
903 
904   {
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());
910   }
911 }
912 
913 // Attempt application of encrypted items when the passphrase is not known.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,UndecryptableData)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(
921       "folder",
922       encrypted_bookmark,
923       root_server_id);
924   int64 bookmark_handle = entry_factory()->CreateUnappliedNewItem(
925       "item2",
926       encrypted_bookmark,
927       false);
928   sync_pb::EntitySpecifics encrypted_password;
929   encrypted_password.mutable_password();
930   int64 password_handle = entry_factory()->CreateUnappliedNewItem(
931       "item3",
932       encrypted_password,
933       false);
934 
935   sessions::StatusController status;
936   ApplyBookmarkUpdates(&status);
937   ApplyPasswordUpdates(&status);
938 
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";
944 
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";
950 
951   {
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());
962   }
963 }
964 
965 // Test a mix of decryptable and undecryptable updates.
TEST_F(DirectoryUpdateHandlerApplyUpdateTest,SomeUndecryptablePassword)966 TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SomeUndecryptablePassword) {
967   Cryptographer* cryptographer;
968 
969   int64 decryptable_handle = -1;
970   int64 undecryptable_handle = -1;
971 
972   // Only decryptable password updates should be applied.
973   {
974     sync_pb::EntitySpecifics specifics;
975     sync_pb::PasswordSpecificsData data;
976     data.set_origin("http://example.com/1");
977     {
978       syncable::ReadTransaction trans(FROM_HERE, directory());
979       cryptographer = directory()->GetCryptographer(&trans);
980 
981       KeyParams params = {"localhost", "dummy", "foobar"};
982       cryptographer->AddKey(params);
983 
984       cryptographer->Encrypt(data,
985           specifics.mutable_password()->mutable_encrypted());
986     }
987     decryptable_handle =
988         entry_factory()->CreateUnappliedNewItem("item1", specifics, false);
989   }
990   {
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);
995 
996     sync_pb::EntitySpecifics specifics;
997     sync_pb::PasswordSpecificsData data;
998     data.set_origin("http://example.com/2");
999 
1000     other_cryptographer.Encrypt(data,
1001         specifics.mutable_password()->mutable_encrypted());
1002     undecryptable_handle =
1003         entry_factory()->CreateUnappliedNewItem("item2", specifics, false);
1004   }
1005 
1006   sessions::StatusController status;
1007   ApplyPasswordUpdates(&status);
1008 
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 "
1012       << "conflict";
1013   EXPECT_EQ(1, counters.num_updates_applied)
1014       << "The undecryptable password update shouldn't be applied";
1015 
1016   {
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());
1024   }
1025 }
1026 
1027 }  // namespace syncer
1028