• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 <string>
6 
7 #include "base/format_macros.h"
8 #include "base/string_util.h"
9 #include "chrome/browser/sync/engine/apply_updates_command.h"
10 #include "chrome/browser/sync/engine/syncer.h"
11 #include "chrome/browser/sync/engine/syncer_util.h"
12 #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
13 #include "chrome/browser/sync/sessions/sync_session.h"
14 #include "chrome/browser/sync/syncable/directory_manager.h"
15 #include "chrome/browser/sync/syncable/nigori_util.h"
16 #include "chrome/browser/sync/syncable/syncable.h"
17 #include "chrome/browser/sync/syncable/syncable_id.h"
18 #include "chrome/test/sync/engine/syncer_command_test.h"
19 #include "chrome/test/sync/engine/test_id_factory.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 
22 namespace browser_sync {
23 
24 using sessions::SyncSession;
25 using std::string;
26 using syncable::Entry;
27 using syncable::GetEncryptedDataTypes;
28 using syncable::Id;
29 using syncable::MutableEntry;
30 using syncable::ReadTransaction;
31 using syncable::ScopedDirLookup;
32 using syncable::UNITTEST;
33 using syncable::WriteTransaction;
34 
35 // A test fixture for tests exercising ApplyUpdatesCommand.
36 class ApplyUpdatesCommandTest : public SyncerCommandTest {
37  public:
38  protected:
ApplyUpdatesCommandTest()39   ApplyUpdatesCommandTest() : next_revision_(1) {}
~ApplyUpdatesCommandTest()40   virtual ~ApplyUpdatesCommandTest() {}
41 
SetUp()42   virtual void SetUp() {
43     workers()->clear();
44     mutable_routing_info()->clear();
45     // GROUP_PASSIVE worker.
46     workers()->push_back(make_scoped_refptr(new ModelSafeWorker()));
47     (*mutable_routing_info())[syncable::BOOKMARKS] = GROUP_PASSIVE;
48     (*mutable_routing_info())[syncable::PASSWORDS] = GROUP_PASSIVE;
49     (*mutable_routing_info())[syncable::NIGORI] = GROUP_PASSIVE;
50     SyncerCommandTest::SetUp();
51   }
52 
53   // Create a new unapplied bookmark node with a parent.
CreateUnappliedNewItemWithParent(const string & item_id,const string & parent_id)54   void CreateUnappliedNewItemWithParent(const string& item_id,
55                                         const string& parent_id) {
56     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
57     ASSERT_TRUE(dir.good());
58     WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__);
59     MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM,
60         Id::CreateFromServerId(item_id));
61     ASSERT_TRUE(entry.good());
62     entry.Put(syncable::SERVER_VERSION, next_revision_++);
63     entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
64 
65     entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id);
66     entry.Put(syncable::SERVER_PARENT_ID, Id::CreateFromServerId(parent_id));
67     entry.Put(syncable::SERVER_IS_DIR, true);
68     sync_pb::EntitySpecifics default_bookmark_specifics;
69     default_bookmark_specifics.MutableExtension(sync_pb::bookmark);
70     entry.Put(syncable::SERVER_SPECIFICS, default_bookmark_specifics);
71   }
72 
73   // Create a new unapplied update without a parent.
CreateUnappliedNewItem(const string & item_id,const sync_pb::EntitySpecifics & specifics,bool is_unique)74   void CreateUnappliedNewItem(const string& item_id,
75                               const sync_pb::EntitySpecifics& specifics,
76                               bool is_unique) {
77     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
78     ASSERT_TRUE(dir.good());
79     WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__);
80     MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM,
81         Id::CreateFromServerId(item_id));
82     ASSERT_TRUE(entry.good());
83     entry.Put(syncable::SERVER_VERSION, next_revision_++);
84     entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
85     entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id);
86     entry.Put(syncable::SERVER_PARENT_ID, syncable::kNullId);
87     entry.Put(syncable::SERVER_IS_DIR, false);
88     entry.Put(syncable::SERVER_SPECIFICS, specifics);
89     if (is_unique)  // For top-level nodes.
90       entry.Put(syncable::UNIQUE_SERVER_TAG, item_id);
91   }
92 
93   // Create an unsynced item in the database.  If item_id is a local ID, it
94   // will be treated as a create-new.  Otherwise, if it's a server ID, we'll
95   // fake the server data so that it looks like it exists on the server.
96   // Returns the methandle of the created item in |metahandle_out| if not NULL.
CreateUnsyncedItem(const Id & item_id,const Id & parent_id,const string & name,bool is_folder,syncable::ModelType model_type,int64 * metahandle_out)97   void CreateUnsyncedItem(const Id& item_id,
98                           const Id& parent_id,
99                           const string& name,
100                           bool is_folder,
101                           syncable::ModelType model_type,
102                           int64* metahandle_out) {
103     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
104     ASSERT_TRUE(dir.good());
105     WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__);
106     Id predecessor_id = dir->GetLastChildId(&trans, parent_id);
107     MutableEntry entry(&trans, syncable::CREATE, parent_id, name);
108     ASSERT_TRUE(entry.good());
109     entry.Put(syncable::ID, item_id);
110     entry.Put(syncable::BASE_VERSION,
111         item_id.ServerKnows() ? next_revision_++ : 0);
112     entry.Put(syncable::IS_UNSYNCED, true);
113     entry.Put(syncable::IS_DIR, is_folder);
114     entry.Put(syncable::IS_DEL, false);
115     entry.Put(syncable::PARENT_ID, parent_id);
116     entry.PutPredecessor(predecessor_id);
117     sync_pb::EntitySpecifics default_specifics;
118     syncable::AddDefaultExtensionValue(model_type, &default_specifics);
119     entry.Put(syncable::SPECIFICS, default_specifics);
120     if (item_id.ServerKnows()) {
121       entry.Put(syncable::SERVER_SPECIFICS, default_specifics);
122       entry.Put(syncable::SERVER_IS_DIR, is_folder);
123       entry.Put(syncable::SERVER_PARENT_ID, parent_id);
124       entry.Put(syncable::SERVER_IS_DEL, false);
125     }
126     if (metahandle_out)
127       *metahandle_out = entry.Get(syncable::META_HANDLE);
128   }
129 
130   ApplyUpdatesCommand apply_updates_command_;
131   TestIdFactory id_factory_;
132  private:
133   int64 next_revision_;
134   DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest);
135 };
136 
TEST_F(ApplyUpdatesCommandTest,Simple)137 TEST_F(ApplyUpdatesCommandTest, Simple) {
138   string root_server_id = syncable::kNullId.GetServerId();
139   CreateUnappliedNewItemWithParent("parent", root_server_id);
140   CreateUnappliedNewItemWithParent("child", "parent");
141 
142   apply_updates_command_.ExecuteImpl(session());
143 
144   sessions::StatusController* status = session()->status_controller();
145 
146   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
147   EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize())
148       << "All updates should have been attempted";
149   EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
150       << "Simple update shouldn't result in conflicts";
151   EXPECT_EQ(2, status->update_progress().SuccessfullyAppliedUpdateCount())
152       << "All items should have been successfully applied";
153 }
154 
TEST_F(ApplyUpdatesCommandTest,UpdateWithChildrenBeforeParents)155 TEST_F(ApplyUpdatesCommandTest, UpdateWithChildrenBeforeParents) {
156   // Set a bunch of updates which are difficult to apply in the order
157   // they're received due to dependencies on other unseen items.
158   string root_server_id = syncable::kNullId.GetServerId();
159   CreateUnappliedNewItemWithParent("a_child_created_first", "parent");
160   CreateUnappliedNewItemWithParent("x_child_created_first", "parent");
161   CreateUnappliedNewItemWithParent("parent", root_server_id);
162   CreateUnappliedNewItemWithParent("a_child_created_second", "parent");
163   CreateUnappliedNewItemWithParent("x_child_created_second", "parent");
164 
165   apply_updates_command_.ExecuteImpl(session());
166 
167   sessions::StatusController* status = session()->status_controller();
168   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
169   EXPECT_EQ(5, status->update_progress().AppliedUpdatesSize())
170       << "All updates should have been attempted";
171   EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
172       << "Simple update shouldn't result in conflicts, even if out-of-order";
173   EXPECT_EQ(5, status->update_progress().SuccessfullyAppliedUpdateCount())
174       << "All updates should have been successfully applied";
175 }
176 
TEST_F(ApplyUpdatesCommandTest,NestedItemsWithUnknownParent)177 TEST_F(ApplyUpdatesCommandTest, NestedItemsWithUnknownParent) {
178   // We shouldn't be able to do anything with either of these items.
179   CreateUnappliedNewItemWithParent("some_item", "unknown_parent");
180   CreateUnappliedNewItemWithParent("some_other_item", "some_item");
181 
182   apply_updates_command_.ExecuteImpl(session());
183 
184   sessions::StatusController* status = session()->status_controller();
185   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
186   EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize())
187       << "All updates should have been attempted";
188   EXPECT_EQ(2, status->conflict_progress().ConflictingItemsSize())
189       << "All updates with an unknown ancestors should be in conflict";
190   EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount())
191       << "No item with an unknown ancestor should be applied";
192 }
193 
TEST_F(ApplyUpdatesCommandTest,ItemsBothKnownAndUnknown)194 TEST_F(ApplyUpdatesCommandTest, ItemsBothKnownAndUnknown) {
195   // See what happens when there's a mixture of good and bad updates.
196   string root_server_id = syncable::kNullId.GetServerId();
197   CreateUnappliedNewItemWithParent("first_unknown_item", "unknown_parent");
198   CreateUnappliedNewItemWithParent("first_known_item", root_server_id);
199   CreateUnappliedNewItemWithParent("second_unknown_item", "unknown_parent");
200   CreateUnappliedNewItemWithParent("second_known_item", "first_known_item");
201   CreateUnappliedNewItemWithParent("third_known_item", "fourth_known_item");
202   CreateUnappliedNewItemWithParent("fourth_known_item", root_server_id);
203 
204   apply_updates_command_.ExecuteImpl(session());
205 
206   sessions::StatusController* status = session()->status_controller();
207   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
208   EXPECT_EQ(6, status->update_progress().AppliedUpdatesSize())
209       << "All updates should have been attempted";
210   EXPECT_EQ(2, status->conflict_progress().ConflictingItemsSize())
211       << "The updates with unknown ancestors should be in conflict";
212   EXPECT_EQ(4, status->update_progress().SuccessfullyAppliedUpdateCount())
213       << "The updates with known ancestors should be successfully applied";
214 }
215 
TEST_F(ApplyUpdatesCommandTest,DecryptablePassword)216 TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) {
217   // Decryptable password updates should be applied.
218   Cryptographer* cryptographer;
219   {
220       // Storing the cryptographer separately is bad, but for this test we
221       // know it's safe.
222       ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
223       ASSERT_TRUE(dir.good());
224       ReadTransaction trans(dir, __FILE__, __LINE__);
225       cryptographer =
226           session()->context()->directory_manager()->GetCryptographer(&trans);
227   }
228 
229   browser_sync::KeyParams params = {"localhost", "dummy", "foobar"};
230   cryptographer->AddKey(params);
231 
232   sync_pb::EntitySpecifics specifics;
233   sync_pb::PasswordSpecificsData data;
234   data.set_origin("http://example.com");
235 
236   cryptographer->Encrypt(data,
237       specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
238   CreateUnappliedNewItem("item", specifics, false);
239 
240   apply_updates_command_.ExecuteImpl(session());
241 
242   sessions::StatusController* status = session()->status_controller();
243   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
244   EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
245       << "All updates should have been attempted";
246   EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
247       << "No update should be in conflict because they're all decryptable";
248   EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
249       << "The updates that can be decrypted should be applied";
250 }
251 
TEST_F(ApplyUpdatesCommandTest,UndecryptablePassword)252 TEST_F(ApplyUpdatesCommandTest, UndecryptablePassword) {
253   // Undecryptable password updates should not be applied.
254   sync_pb::EntitySpecifics specifics;
255   specifics.MutableExtension(sync_pb::password);
256   CreateUnappliedNewItem("item", specifics, false);
257 
258   apply_updates_command_.ExecuteImpl(session());
259 
260   sessions::StatusController* status = session()->status_controller();
261   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
262   EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
263       << "All updates should have been attempted";
264   EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize())
265       << "The updates that can't be decrypted should be in conflict";
266   EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount())
267       << "No update that can't be decrypted should be applied";
268 }
269 
TEST_F(ApplyUpdatesCommandTest,SomeUndecryptablePassword)270 TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) {
271   // Only decryptable password updates should be applied.
272   {
273     sync_pb::EntitySpecifics specifics;
274     sync_pb::PasswordSpecificsData data;
275     data.set_origin("http://example.com/1");
276     {
277       ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
278       ASSERT_TRUE(dir.good());
279       ReadTransaction trans(dir, __FILE__, __LINE__);
280       Cryptographer* cryptographer =
281           session()->context()->directory_manager()->GetCryptographer(&trans);
282 
283       KeyParams params = {"localhost", "dummy", "foobar"};
284       cryptographer->AddKey(params);
285 
286       cryptographer->Encrypt(data,
287           specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
288     }
289     CreateUnappliedNewItem("item1", specifics, false);
290   }
291   {
292     // Create a new cryptographer, independent of the one in the session.
293     Cryptographer cryptographer;
294     KeyParams params = {"localhost", "dummy", "bazqux"};
295     cryptographer.AddKey(params);
296 
297     sync_pb::EntitySpecifics specifics;
298     sync_pb::PasswordSpecificsData data;
299     data.set_origin("http://example.com/2");
300 
301     cryptographer.Encrypt(data,
302         specifics.MutableExtension(sync_pb::password)->mutable_encrypted());
303     CreateUnappliedNewItem("item2", specifics, false);
304   }
305 
306   apply_updates_command_.ExecuteImpl(session());
307 
308   sessions::StatusController* status = session()->status_controller();
309   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
310   EXPECT_EQ(2, status->update_progress().AppliedUpdatesSize())
311       << "All updates should have been attempted";
312   EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize())
313       << "The decryptable password update should be applied";
314   EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
315       << "The undecryptable password update shouldn't be applied";
316 }
317 
TEST_F(ApplyUpdatesCommandTest,NigoriUpdate)318 TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) {
319   // Storing the cryptographer separately is bad, but for this test we
320   // know it's safe.
321   Cryptographer* cryptographer;
322   syncable::ModelTypeSet encrypted_types;
323   {
324     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
325     ASSERT_TRUE(dir.good());
326     ReadTransaction trans(dir, __FILE__, __LINE__);
327     EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
328     cryptographer =
329         session()->context()->directory_manager()->GetCryptographer(&trans);
330   }
331 
332   // Nigori node updates should update the Cryptographer.
333   Cryptographer other_cryptographer;
334   KeyParams params = {"localhost", "dummy", "foobar"};
335   other_cryptographer.AddKey(params);
336 
337   sync_pb::EntitySpecifics specifics;
338   sync_pb::NigoriSpecifics* nigori =
339       specifics.MutableExtension(sync_pb::nigori);
340   other_cryptographer.GetKeys(nigori->mutable_encrypted());
341   nigori->set_encrypt_bookmarks(true);
342   encrypted_types.insert(syncable::BOOKMARKS);
343   CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI),
344                          specifics, true);
345   EXPECT_FALSE(cryptographer->has_pending_keys());
346 
347   apply_updates_command_.ExecuteImpl(session());
348 
349   sessions::StatusController* status = session()->status_controller();
350   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
351   EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
352       << "All updates should have been attempted";
353   EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
354       << "The nigori update shouldn't be in conflict";
355   EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
356       << "The nigori update should be applied";
357 
358   EXPECT_FALSE(cryptographer->is_ready());
359   EXPECT_TRUE(cryptographer->has_pending_keys());
360 }
361 
TEST_F(ApplyUpdatesCommandTest,EncryptUnsyncedChanges)362 TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) {
363   // Storing the cryptographer separately is bad, but for this test we
364   // know it's safe.
365   Cryptographer* cryptographer;
366   syncable::ModelTypeSet encrypted_types;
367   {
368     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
369     ASSERT_TRUE(dir.good());
370     ReadTransaction trans(dir, __FILE__, __LINE__);
371     EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
372     cryptographer =
373         session()->context()->directory_manager()->GetCryptographer(&trans);
374 
375     // With empty encrypted_types, this should be true.
376     EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
377 
378     Syncer::UnsyncedMetaHandles handles;
379     SyncerUtil::GetUnsyncedEntries(&trans, &handles);
380     EXPECT_TRUE(handles.empty());
381   }
382 
383   // Create unsynced bookmarks without encryption.
384   // First item is a folder
385   Id folder_id = id_factory_.NewLocalId();
386   CreateUnsyncedItem(folder_id, id_factory_.root(), "folder",
387                      true, syncable::BOOKMARKS, NULL);
388   // Next five items are children of the folder
389   size_t i;
390   size_t batch_s = 5;
391   for (i = 0; i < batch_s; ++i) {
392     CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
393                        StringPrintf("Item %"PRIuS"", i), false,
394                        syncable::BOOKMARKS, NULL);
395   }
396   // Next five items are children of the root.
397   for (; i < 2*batch_s; ++i) {
398     CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(),
399                        StringPrintf("Item %"PRIuS"", i), false,
400                        syncable::BOOKMARKS, NULL);
401   }
402 
403   KeyParams params = {"localhost", "dummy", "foobar"};
404   cryptographer->AddKey(params);
405   sync_pb::EntitySpecifics specifics;
406   sync_pb::NigoriSpecifics* nigori =
407       specifics.MutableExtension(sync_pb::nigori);
408   cryptographer->GetKeys(nigori->mutable_encrypted());
409   nigori->set_encrypt_bookmarks(true);
410   encrypted_types.insert(syncable::BOOKMARKS);
411   CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI),
412                          specifics, true);
413   EXPECT_FALSE(cryptographer->has_pending_keys());
414   EXPECT_TRUE(cryptographer->is_ready());
415 
416   {
417     // Ensure we have unsynced nodes that aren't properly encrypted.
418     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
419     ASSERT_TRUE(dir.good());
420     ReadTransaction trans(dir, __FILE__, __LINE__);
421     EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
422 
423     Syncer::UnsyncedMetaHandles handles;
424     SyncerUtil::GetUnsyncedEntries(&trans, &handles);
425     EXPECT_EQ(2*batch_s+1, handles.size());
426   }
427 
428   apply_updates_command_.ExecuteImpl(session());
429 
430   sessions::StatusController* status = session()->status_controller();
431   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
432   EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
433       << "All updates should have been attempted";
434   EXPECT_EQ(0, status->conflict_progress().ConflictingItemsSize())
435       << "The nigori update shouldn't be in conflict";
436   EXPECT_EQ(1, status->update_progress().SuccessfullyAppliedUpdateCount())
437       << "The nigori update should be applied";
438   EXPECT_FALSE(cryptographer->has_pending_keys());
439   EXPECT_TRUE(cryptographer->is_ready());
440   {
441     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
442     ASSERT_TRUE(dir.good());
443     ReadTransaction trans(dir, __FILE__, __LINE__);
444 
445     // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes
446     // should be encrypted now.
447     EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
448     EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
449 
450     Syncer::UnsyncedMetaHandles handles;
451     SyncerUtil::GetUnsyncedEntries(&trans, &handles);
452     EXPECT_EQ(2*batch_s+1, handles.size());
453   }
454 }
455 
TEST_F(ApplyUpdatesCommandTest,CannotEncryptUnsyncedChanges)456 TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) {
457   // Storing the cryptographer separately is bad, but for this test we
458   // know it's safe.
459   Cryptographer* cryptographer;
460   syncable::ModelTypeSet encrypted_types;
461   {
462     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
463     ASSERT_TRUE(dir.good());
464     ReadTransaction trans(dir, __FILE__, __LINE__);
465     EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
466     cryptographer =
467         session()->context()->directory_manager()->GetCryptographer(&trans);
468 
469     // With empty encrypted_types, this should be true.
470     EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
471 
472     Syncer::UnsyncedMetaHandles handles;
473     SyncerUtil::GetUnsyncedEntries(&trans, &handles);
474     EXPECT_TRUE(handles.empty());
475   }
476 
477   // Create unsynced bookmarks without encryption.
478   // First item is a folder
479   Id folder_id = id_factory_.NewLocalId();
480   CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", true,
481                      syncable::BOOKMARKS, NULL);
482   // Next five items are children of the folder
483   size_t i;
484   size_t batch_s = 5;
485   for (i = 0; i < batch_s; ++i) {
486     CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
487                        StringPrintf("Item %"PRIuS"", i), false,
488                        syncable::BOOKMARKS, NULL);
489   }
490   // Next five items are children of the root.
491   for (; i < 2*batch_s; ++i) {
492     CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(),
493                        StringPrintf("Item %"PRIuS"", i), false,
494                        syncable::BOOKMARKS, NULL);
495   }
496 
497   // We encrypt with new keys, triggering the local cryptographer to be unready
498   // and unable to decrypt data (once updated).
499   Cryptographer other_cryptographer;
500   KeyParams params = {"localhost", "dummy", "foobar"};
501   other_cryptographer.AddKey(params);
502   sync_pb::EntitySpecifics specifics;
503   sync_pb::NigoriSpecifics* nigori =
504       specifics.MutableExtension(sync_pb::nigori);
505   other_cryptographer.GetKeys(nigori->mutable_encrypted());
506   nigori->set_encrypt_bookmarks(true);
507   encrypted_types.insert(syncable::BOOKMARKS);
508   CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI),
509                          specifics, true);
510   EXPECT_FALSE(cryptographer->has_pending_keys());
511 
512   {
513     // Ensure we have unsynced nodes that aren't properly encrypted.
514     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
515     ASSERT_TRUE(dir.good());
516     ReadTransaction trans(dir, __FILE__, __LINE__);
517     EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
518     Syncer::UnsyncedMetaHandles handles;
519     SyncerUtil::GetUnsyncedEntries(&trans, &handles);
520     EXPECT_EQ(2*batch_s+1, handles.size());
521   }
522 
523   apply_updates_command_.ExecuteImpl(session());
524 
525   sessions::StatusController* status = session()->status_controller();
526   sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE);
527   EXPECT_EQ(1, status->update_progress().AppliedUpdatesSize())
528       << "All updates should have been attempted";
529   EXPECT_EQ(1, status->conflict_progress().ConflictingItemsSize())
530       << "The unsynced chnages trigger a conflict with the nigori update.";
531   EXPECT_EQ(0, status->update_progress().SuccessfullyAppliedUpdateCount())
532       << "The nigori update should not be applied";
533   EXPECT_FALSE(cryptographer->is_ready());
534   EXPECT_TRUE(cryptographer->has_pending_keys());
535   {
536     // Ensure the unsynced nodes are still not encrypted.
537     ScopedDirLookup dir(syncdb()->manager(), syncdb()->name());
538     ASSERT_TRUE(dir.good());
539     ReadTransaction trans(dir, __FILE__, __LINE__);
540 
541     // Since we're in conflict, the specifics don't reflect the unapplied
542     // changes.
543     EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
544     encrypted_types.clear();
545     EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans));
546 
547     Syncer::UnsyncedMetaHandles handles;
548     SyncerUtil::GetUnsyncedEntries(&trans, &handles);
549     EXPECT_EQ(2*batch_s+1, handles.size());
550   }
551 }
552 
553 }  // namespace browser_sync
554