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