OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/location.h" | |
9 #include "base/stringprintf.h" | |
10 #include "chrome/browser/sync/engine/apply_updates_command.h" | |
11 #include "chrome/browser/sync/engine/nigori_util.h" | |
12 #include "chrome/browser/sync/engine/syncer.h" | |
13 #include "chrome/browser/sync/engine/syncer_util.h" | |
14 #include "chrome/browser/sync/sessions/sync_session.h" | |
15 #include "chrome/browser/sync/syncable/syncable.h" | |
16 #include "chrome/browser/sync/syncable/syncable_id.h" | |
17 #include "chrome/browser/sync/test/engine/fake_model_worker.h" | |
18 #include "chrome/browser/sync/test/engine/syncer_command_test.h" | |
19 #include "chrome/browser/sync/test/engine/test_id_factory.h" | |
20 #include "chrome/browser/sync/test/fake_encryptor.h" | |
21 #include "chrome/browser/sync/util/cryptographer.h" | |
22 #include "sync/protocol/bookmark_specifics.pb.h" | |
23 #include "sync/protocol/password_specifics.pb.h" | |
24 #include "testing/gtest/include/gtest/gtest.h" | |
25 | |
26 namespace browser_sync { | |
27 | |
28 using sessions::SyncSession; | |
29 using std::string; | |
30 using syncable::Entry; | |
31 using syncable::Id; | |
32 using syncable::MutableEntry; | |
33 using syncable::ReadTransaction; | |
34 using syncable::UNITTEST; | |
35 using syncable::WriteTransaction; | |
36 | |
37 namespace { | |
38 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { | |
39 sync_pb::EntitySpecifics result; | |
40 AddDefaultFieldValue(syncable::BOOKMARKS, &result); | |
41 return result; | |
42 } | |
43 } // namespace | |
44 | |
45 // A test fixture for tests exercising ApplyUpdatesCommand. | |
46 class ApplyUpdatesCommandTest : public SyncerCommandTest { | |
47 public: | |
48 protected: | |
49 ApplyUpdatesCommandTest() : next_revision_(1) {} | |
50 virtual ~ApplyUpdatesCommandTest() {} | |
51 | |
52 virtual void SetUp() { | |
53 workers()->clear(); | |
54 mutable_routing_info()->clear(); | |
55 workers()->push_back( | |
56 make_scoped_refptr(new FakeModelWorker(GROUP_UI))); | |
57 workers()->push_back( | |
58 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD))); | |
59 (*mutable_routing_info())[syncable::BOOKMARKS] = GROUP_UI; | |
60 (*mutable_routing_info())[syncable::PASSWORDS] = GROUP_PASSWORD; | |
61 (*mutable_routing_info())[syncable::NIGORI] = GROUP_PASSIVE; | |
62 SyncerCommandTest::SetUp(); | |
63 ExpectNoGroupsToChange(apply_updates_command_); | |
64 } | |
65 | |
66 // Create a new unapplied folder node with a parent. | |
67 void CreateUnappliedNewItemWithParent( | |
68 const string& item_id, | |
69 const sync_pb::EntitySpecifics& specifics, | |
70 const string& parent_id) { | |
71 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
72 MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM, | |
73 Id::CreateFromServerId(item_id)); | |
74 ASSERT_TRUE(entry.good()); | |
75 entry.Put(syncable::SERVER_VERSION, next_revision_++); | |
76 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
77 | |
78 entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id); | |
79 entry.Put(syncable::SERVER_PARENT_ID, Id::CreateFromServerId(parent_id)); | |
80 entry.Put(syncable::SERVER_IS_DIR, true); | |
81 entry.Put(syncable::SERVER_SPECIFICS, specifics); | |
82 } | |
83 | |
84 // Create a new unapplied update without a parent. | |
85 void CreateUnappliedNewItem(const string& item_id, | |
86 const sync_pb::EntitySpecifics& specifics, | |
87 bool is_unique) { | |
88 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
89 MutableEntry entry(&trans, syncable::CREATE_NEW_UPDATE_ITEM, | |
90 Id::CreateFromServerId(item_id)); | |
91 ASSERT_TRUE(entry.good()); | |
92 entry.Put(syncable::SERVER_VERSION, next_revision_++); | |
93 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
94 entry.Put(syncable::SERVER_NON_UNIQUE_NAME, item_id); | |
95 entry.Put(syncable::SERVER_PARENT_ID, syncable::GetNullId()); | |
96 entry.Put(syncable::SERVER_IS_DIR, false); | |
97 entry.Put(syncable::SERVER_SPECIFICS, specifics); | |
98 if (is_unique) // For top-level nodes. | |
99 entry.Put(syncable::UNIQUE_SERVER_TAG, item_id); | |
100 } | |
101 | |
102 // Create an unsynced item in the database. If item_id is a local ID, it | |
103 // will be treated as a create-new. Otherwise, if it's a server ID, we'll | |
104 // fake the server data so that it looks like it exists on the server. | |
105 // Returns the methandle of the created item in |metahandle_out| if not NULL. | |
106 void CreateUnsyncedItem(const Id& item_id, | |
107 const Id& parent_id, | |
108 const string& name, | |
109 bool is_folder, | |
110 syncable::ModelType model_type, | |
111 int64* metahandle_out) { | |
112 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
113 Id predecessor_id; | |
114 ASSERT_TRUE( | |
115 directory()->GetLastChildIdForTest(&trans, parent_id, &predecessor_id)); | |
116 MutableEntry entry(&trans, syncable::CREATE, parent_id, name); | |
117 ASSERT_TRUE(entry.good()); | |
118 entry.Put(syncable::ID, item_id); | |
119 entry.Put(syncable::BASE_VERSION, | |
120 item_id.ServerKnows() ? next_revision_++ : 0); | |
121 entry.Put(syncable::IS_UNSYNCED, true); | |
122 entry.Put(syncable::IS_DIR, is_folder); | |
123 entry.Put(syncable::IS_DEL, false); | |
124 entry.Put(syncable::PARENT_ID, parent_id); | |
125 CHECK(entry.PutPredecessor(predecessor_id)); | |
126 sync_pb::EntitySpecifics default_specifics; | |
127 syncable::AddDefaultFieldValue(model_type, &default_specifics); | |
128 entry.Put(syncable::SPECIFICS, default_specifics); | |
129 if (item_id.ServerKnows()) { | |
130 entry.Put(syncable::SERVER_SPECIFICS, default_specifics); | |
131 entry.Put(syncable::SERVER_IS_DIR, is_folder); | |
132 entry.Put(syncable::SERVER_PARENT_ID, parent_id); | |
133 entry.Put(syncable::SERVER_IS_DEL, false); | |
134 } | |
135 if (metahandle_out) | |
136 *metahandle_out = entry.Get(syncable::META_HANDLE); | |
137 } | |
138 | |
139 // Creates an item that is both unsynced an an unapplied update. Returns the | |
140 // metahandle of the created item. | |
141 int64 CreateUnappliedAndUnsyncedItem(const string& name, | |
142 syncable::ModelType model_type) { | |
143 int64 metahandle = 0; | |
144 CreateUnsyncedItem(id_factory_.MakeServer(name), id_factory_.root(), name, | |
145 false, model_type, &metahandle); | |
146 | |
147 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
148 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, metahandle); | |
149 if (!entry.good()) { | |
150 ADD_FAILURE(); | |
151 return syncable::kInvalidMetaHandle; | |
152 } | |
153 | |
154 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
155 entry.Put(syncable::SERVER_VERSION, GetNextRevision()); | |
156 | |
157 return metahandle; | |
158 } | |
159 | |
160 | |
161 // Creates an item that has neither IS_UNSYNED or IS_UNAPPLIED_UPDATE. The | |
162 // item is known to both the server and client. Returns the metahandle of | |
163 // the created item. | |
164 int64 CreateSyncedItem(const std::string& name, syncable::ModelType | |
165 model_type, bool is_folder) { | |
166 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
167 | |
168 syncable::Id parent_id(id_factory_.root()); | |
169 syncable::Id item_id(id_factory_.MakeServer(name)); | |
170 int64 version = GetNextRevision(); | |
171 | |
172 sync_pb::EntitySpecifics default_specifics; | |
173 syncable::AddDefaultFieldValue(model_type, &default_specifics); | |
174 | |
175 MutableEntry entry(&trans, syncable::CREATE, parent_id, name); | |
176 if (!entry.good()) { | |
177 ADD_FAILURE(); | |
178 return syncable::kInvalidMetaHandle; | |
179 } | |
180 | |
181 entry.Put(syncable::ID, item_id); | |
182 entry.Put(syncable::BASE_VERSION, version); | |
183 entry.Put(syncable::IS_UNSYNCED, false); | |
184 entry.Put(syncable::NON_UNIQUE_NAME, name); | |
185 entry.Put(syncable::IS_DIR, is_folder); | |
186 entry.Put(syncable::IS_DEL, false); | |
187 entry.Put(syncable::PARENT_ID, parent_id); | |
188 | |
189 if (!entry.PutPredecessor(id_factory_.root())) { | |
190 ADD_FAILURE(); | |
191 return syncable::kInvalidMetaHandle; | |
192 } | |
193 entry.Put(syncable::SPECIFICS, default_specifics); | |
194 | |
195 entry.Put(syncable::SERVER_VERSION, GetNextRevision()); | |
196 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
197 entry.Put(syncable::SERVER_NON_UNIQUE_NAME, "X"); | |
198 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.MakeServer("Y")); | |
199 entry.Put(syncable::SERVER_IS_DIR, is_folder); | |
200 entry.Put(syncable::SERVER_IS_DEL, false); | |
201 entry.Put(syncable::SERVER_SPECIFICS, default_specifics); | |
202 entry.Put(syncable::SERVER_PARENT_ID, parent_id); | |
203 | |
204 return entry.Get(syncable::META_HANDLE); | |
205 } | |
206 | |
207 int64 GetNextRevision() { | |
208 return next_revision_++; | |
209 } | |
210 | |
211 ApplyUpdatesCommand apply_updates_command_; | |
212 FakeEncryptor encryptor_; | |
213 TestIdFactory id_factory_; | |
214 private: | |
215 int64 next_revision_; | |
216 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); | |
217 }; | |
218 | |
219 TEST_F(ApplyUpdatesCommandTest, Simple) { | |
220 string root_server_id = syncable::GetNullId().GetServerId(); | |
221 CreateUnappliedNewItemWithParent("parent", | |
222 DefaultBookmarkSpecifics(), | |
223 root_server_id); | |
224 CreateUnappliedNewItemWithParent("child", | |
225 DefaultBookmarkSpecifics(), | |
226 "parent"); | |
227 | |
228 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
229 apply_updates_command_.ExecuteImpl(session()); | |
230 | |
231 sessions::StatusController* status = session()->mutable_status_controller(); | |
232 | |
233 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
234 ASSERT_TRUE(status->update_progress()); | |
235 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
236 << "All updates should have been attempted"; | |
237 ASSERT_TRUE(status->conflict_progress()); | |
238 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
239 << "Simple update shouldn't result in conflicts"; | |
240 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
241 << "Simple update shouldn't result in conflicts"; | |
242 EXPECT_EQ(0, status->conflict_progress()->HierarchyConflictingItemsSize()) | |
243 << "Simple update shouldn't result in conflicts"; | |
244 EXPECT_EQ(2, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
245 << "All items should have been successfully applied"; | |
246 } | |
247 | |
248 TEST_F(ApplyUpdatesCommandTest, UpdateWithChildrenBeforeParents) { | |
249 // Set a bunch of updates which are difficult to apply in the order | |
250 // they're received due to dependencies on other unseen items. | |
251 string root_server_id = syncable::GetNullId().GetServerId(); | |
252 CreateUnappliedNewItemWithParent("a_child_created_first", | |
253 DefaultBookmarkSpecifics(), | |
254 "parent"); | |
255 CreateUnappliedNewItemWithParent("x_child_created_first", | |
256 DefaultBookmarkSpecifics(), | |
257 "parent"); | |
258 CreateUnappliedNewItemWithParent("parent", | |
259 DefaultBookmarkSpecifics(), | |
260 root_server_id); | |
261 CreateUnappliedNewItemWithParent("a_child_created_second", | |
262 DefaultBookmarkSpecifics(), | |
263 "parent"); | |
264 CreateUnappliedNewItemWithParent("x_child_created_second", | |
265 DefaultBookmarkSpecifics(), | |
266 "parent"); | |
267 | |
268 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
269 apply_updates_command_.ExecuteImpl(session()); | |
270 | |
271 sessions::StatusController* status = session()->mutable_status_controller(); | |
272 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
273 ASSERT_TRUE(status->update_progress()); | |
274 EXPECT_EQ(5, status->update_progress()->AppliedUpdatesSize()) | |
275 << "All updates should have been attempted"; | |
276 ASSERT_TRUE(status->conflict_progress()); | |
277 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
278 << "Simple update shouldn't result in conflicts, even if out-of-order"; | |
279 EXPECT_EQ(5, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
280 << "All updates should have been successfully applied"; | |
281 } | |
282 | |
283 // Runs the ApplyUpdatesCommand on an item that has both local and remote | |
284 // modifications (IS_UNSYNCED and IS_UNAPPLIED_UPDATE). We expect the command | |
285 // to detect that this update can't be applied because it is in a CONFLICT | |
286 // state. | |
287 TEST_F(ApplyUpdatesCommandTest, SimpleConflict) { | |
288 CreateUnappliedAndUnsyncedItem("item", syncable::BOOKMARKS); | |
289 | |
290 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
291 apply_updates_command_.ExecuteImpl(session()); | |
292 | |
293 sessions::StatusController* status = session()->mutable_status_controller(); | |
294 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
295 ASSERT_TRUE(status->conflict_progress()); | |
296 EXPECT_EQ(1, status->conflict_progress()->SimpleConflictingItemsSize()) | |
297 << "Unsynced and unapplied item should be a simple conflict"; | |
298 } | |
299 | |
300 // Runs the ApplyUpdatesCommand on an item that has both local and remote | |
301 // modifications *and* the remote modification cannot be applied without | |
302 // violating the tree constraints. We expect the command to detect that this | |
303 // update can't be applied and that this situation can't be resolved with the | |
304 // simple conflict processing logic; it is in a CONFLICT_HIERARCHY state. | |
305 TEST_F(ApplyUpdatesCommandTest, HierarchyAndSimpleConflict) { | |
306 // Create a simply-conflicting item. It will start with valid parent ids. | |
307 int64 handle = CreateUnappliedAndUnsyncedItem("orphaned_by_server", | |
308 syncable::BOOKMARKS); | |
309 { | |
310 // Manually set the SERVER_PARENT_ID to bad value. | |
311 // A bad parent indicates a hierarchy conflict. | |
312 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
313 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | |
314 ASSERT_TRUE(entry.good()); | |
315 | |
316 entry.Put(syncable::SERVER_PARENT_ID, | |
317 id_factory_.MakeServer("bogus_parent")); | |
318 } | |
319 | |
320 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
321 apply_updates_command_.ExecuteImpl(session()); | |
322 | |
323 sessions::StatusController* status = session()->mutable_status_controller(); | |
324 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
325 | |
326 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); | |
327 | |
328 // An update that is both a simple conflict and a hierarchy conflict should be | |
329 // treated as a hierarchy conflict. | |
330 ASSERT_TRUE(status->conflict_progress()); | |
331 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); | |
332 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); | |
333 } | |
334 | |
335 | |
336 // Runs the ApplyUpdatesCommand on an item with remote modifications that would | |
337 // create a directory loop if the update were applied. We expect the command to | |
338 // detect that this update can't be applied because it is in a | |
339 // CONFLICT_HIERARCHY state. | |
340 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDirectoryLoop) { | |
341 // Item 'X' locally has parent of 'root'. Server is updating it to have | |
342 // parent of 'Y'. | |
343 { | |
344 // Create it as a child of root node. | |
345 int64 handle = CreateSyncedItem("X", syncable::BOOKMARKS, true); | |
346 | |
347 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
348 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | |
349 ASSERT_TRUE(entry.good()); | |
350 | |
351 // Re-parent from root to "Y" | |
352 entry.Put(syncable::SERVER_VERSION, GetNextRevision()); | |
353 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
354 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.MakeServer("Y")); | |
355 } | |
356 | |
357 // Item 'Y' is child of 'X'. | |
358 CreateUnsyncedItem(id_factory_.MakeServer("Y"), id_factory_.MakeServer("X"), | |
359 "Y", true, syncable::BOOKMARKS, NULL); | |
360 | |
361 // If the server's update were applied, we would have X be a child of Y, and Y | |
362 // as a child of X. That's a directory loop. The UpdateApplicator should | |
363 // prevent the update from being applied and note that this is a hierarchy | |
364 // conflict. | |
365 | |
366 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
367 apply_updates_command_.ExecuteImpl(session()); | |
368 | |
369 sessions::StatusController* status = session()->mutable_status_controller(); | |
370 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
371 | |
372 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); | |
373 | |
374 // This should count as a hierarchy conflict. | |
375 ASSERT_TRUE(status->conflict_progress()); | |
376 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); | |
377 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); | |
378 } | |
379 | |
380 // Runs the ApplyUpdatesCommand on a directory where the server sent us an | |
381 // update to add a child to a locally deleted (and unsynced) parent. We expect | |
382 // the command to not apply the update and to indicate the update is in a | |
383 // CONFLICT_HIERARCHY state. | |
384 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeletedParent) { | |
385 // Create a locally deleted parent item. | |
386 int64 parent_handle; | |
387 CreateUnsyncedItem(Id::CreateFromServerId("parent"), id_factory_.root(), | |
388 "parent", true, syncable::BOOKMARKS, &parent_handle); | |
389 { | |
390 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
391 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle); | |
392 entry.Put(syncable::IS_DEL, true); | |
393 } | |
394 | |
395 // Create an incoming child from the server. | |
396 CreateUnappliedNewItemWithParent("child", DefaultBookmarkSpecifics(), | |
397 "parent"); | |
398 | |
399 // The server's update may seem valid to some other client, but on this client | |
400 // that new item's parent no longer exists. The update should not be applied | |
401 // and the update applicator should indicate this is a hierarchy conflict. | |
402 | |
403 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
404 apply_updates_command_.ExecuteImpl(session()); | |
405 | |
406 sessions::StatusController* status = session()->mutable_status_controller(); | |
407 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
408 | |
409 // This should count as a hierarchy conflict. | |
410 ASSERT_TRUE(status->conflict_progress()); | |
411 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); | |
412 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); | |
413 } | |
414 | |
415 // Runs the ApplyUpdatesCommand on a directory where the server is trying to | |
416 // delete a folder that has a recently added (and unsynced) child. We expect | |
417 // the command to not apply the update because it is in a CONFLICT_HIERARCHY | |
418 // state. | |
419 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeleteNonEmptyDirectory) { | |
420 // Create a server-deleted directory. | |
421 { | |
422 // Create it as a child of root node. | |
423 int64 handle = CreateSyncedItem("parent", syncable::BOOKMARKS, true); | |
424 | |
425 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
426 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | |
427 ASSERT_TRUE(entry.good()); | |
428 | |
429 // Delete it on the server. | |
430 entry.Put(syncable::SERVER_VERSION, GetNextRevision()); | |
431 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
432 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.root()); | |
433 entry.Put(syncable::SERVER_IS_DEL, true); | |
434 } | |
435 | |
436 // Create a local child of the server-deleted directory. | |
437 CreateUnsyncedItem(id_factory_.MakeServer("child"), | |
438 id_factory_.MakeServer("parent"), "child", false, | |
439 syncable::BOOKMARKS, NULL); | |
440 | |
441 // The server's request to delete the directory must be ignored, otherwise our | |
442 // unsynced new child would be orphaned. This is a hierarchy conflict. | |
443 | |
444 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
445 apply_updates_command_.ExecuteImpl(session()); | |
446 | |
447 sessions::StatusController* status = session()->mutable_status_controller(); | |
448 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
449 | |
450 // This should count as a hierarchy conflict. | |
451 ASSERT_TRUE(status->conflict_progress()); | |
452 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); | |
453 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); | |
454 } | |
455 | |
456 // Runs the ApplyUpdatesCommand on a server-created item that has a locally | |
457 // unknown parent. We expect the command to not apply the update because the | |
458 // item is in a CONFLICT_HIERARCHY state. | |
459 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictUnknownParent) { | |
460 // We shouldn't be able to do anything with either of these items. | |
461 CreateUnappliedNewItemWithParent("some_item", | |
462 DefaultBookmarkSpecifics(), | |
463 "unknown_parent"); | |
464 CreateUnappliedNewItemWithParent("some_other_item", | |
465 DefaultBookmarkSpecifics(), | |
466 "some_item"); | |
467 | |
468 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
469 apply_updates_command_.ExecuteImpl(session()); | |
470 | |
471 sessions::StatusController* status = session()->mutable_status_controller(); | |
472 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
473 ASSERT_TRUE(status->update_progress()); | |
474 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
475 << "All updates should have been attempted"; | |
476 ASSERT_TRUE(status->conflict_progress()); | |
477 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
478 << "Updates with unknown parent should not be treated as 'simple'" | |
479 << " conflicts"; | |
480 EXPECT_EQ(2, status->conflict_progress()->HierarchyConflictingItemsSize()) | |
481 << "All updates with an unknown ancestors should be in conflict"; | |
482 EXPECT_EQ(0, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
483 << "No item with an unknown ancestor should be applied"; | |
484 } | |
485 | |
486 TEST_F(ApplyUpdatesCommandTest, ItemsBothKnownAndUnknown) { | |
487 // See what happens when there's a mixture of good and bad updates. | |
488 string root_server_id = syncable::GetNullId().GetServerId(); | |
489 CreateUnappliedNewItemWithParent("first_unknown_item", | |
490 DefaultBookmarkSpecifics(), | |
491 "unknown_parent"); | |
492 CreateUnappliedNewItemWithParent("first_known_item", | |
493 DefaultBookmarkSpecifics(), | |
494 root_server_id); | |
495 CreateUnappliedNewItemWithParent("second_unknown_item", | |
496 DefaultBookmarkSpecifics(), | |
497 "unknown_parent"); | |
498 CreateUnappliedNewItemWithParent("second_known_item", | |
499 DefaultBookmarkSpecifics(), | |
500 "first_known_item"); | |
501 CreateUnappliedNewItemWithParent("third_known_item", | |
502 DefaultBookmarkSpecifics(), | |
503 "fourth_known_item"); | |
504 CreateUnappliedNewItemWithParent("fourth_known_item", | |
505 DefaultBookmarkSpecifics(), | |
506 root_server_id); | |
507 | |
508 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | |
509 apply_updates_command_.ExecuteImpl(session()); | |
510 | |
511 sessions::StatusController* status = session()->mutable_status_controller(); | |
512 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
513 ASSERT_TRUE(status->update_progress()); | |
514 EXPECT_EQ(6, status->update_progress()->AppliedUpdatesSize()) | |
515 << "All updates should have been attempted"; | |
516 ASSERT_TRUE(status->conflict_progress()); | |
517 EXPECT_EQ(2, status->conflict_progress()->HierarchyConflictingItemsSize()) | |
518 << "The updates with unknown ancestors should be in conflict"; | |
519 EXPECT_EQ(4, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
520 << "The updates with known ancestors should be successfully applied"; | |
521 } | |
522 | |
523 TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) { | |
524 // Decryptable password updates should be applied. | |
525 Cryptographer* cryptographer; | |
526 { | |
527 // Storing the cryptographer separately is bad, but for this test we | |
528 // know it's safe. | |
529 ReadTransaction trans(FROM_HERE, directory()); | |
530 cryptographer = directory()->GetCryptographer(&trans); | |
531 } | |
532 | |
533 browser_sync::KeyParams params = {"localhost", "dummy", "foobar"}; | |
534 cryptographer->AddKey(params); | |
535 | |
536 sync_pb::EntitySpecifics specifics; | |
537 sync_pb::PasswordSpecificsData data; | |
538 data.set_origin("http://example.com"); | |
539 | |
540 cryptographer->Encrypt(data, | |
541 specifics.mutable_password()->mutable_encrypted()); | |
542 CreateUnappliedNewItem("item", specifics, false); | |
543 | |
544 ExpectGroupToChange(apply_updates_command_, GROUP_PASSWORD); | |
545 apply_updates_command_.ExecuteImpl(session()); | |
546 | |
547 sessions::StatusController* status = session()->mutable_status_controller(); | |
548 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSWORD); | |
549 ASSERT_TRUE(status->update_progress()); | |
550 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
551 << "All updates should have been attempted"; | |
552 ASSERT_TRUE(status->conflict_progress()); | |
553 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
554 << "No update should be in conflict because they're all decryptable"; | |
555 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
556 << "The updates that can be decrypted should be applied"; | |
557 } | |
558 | |
559 TEST_F(ApplyUpdatesCommandTest, UndecryptableData) { | |
560 // Undecryptable updates should not be applied. | |
561 sync_pb::EntitySpecifics encrypted_bookmark; | |
562 encrypted_bookmark.mutable_encrypted(); | |
563 AddDefaultFieldValue(syncable::BOOKMARKS, &encrypted_bookmark); | |
564 string root_server_id = syncable::GetNullId().GetServerId(); | |
565 CreateUnappliedNewItemWithParent("folder", | |
566 encrypted_bookmark, | |
567 root_server_id); | |
568 CreateUnappliedNewItem("item2", encrypted_bookmark, false); | |
569 sync_pb::EntitySpecifics encrypted_password; | |
570 encrypted_password.mutable_password(); | |
571 CreateUnappliedNewItem("item3", encrypted_password, false); | |
572 | |
573 ExpectGroupsToChange(apply_updates_command_, GROUP_UI, GROUP_PASSWORD); | |
574 apply_updates_command_.ExecuteImpl(session()); | |
575 | |
576 sessions::StatusController* status = session()->mutable_status_controller(); | |
577 EXPECT_TRUE(status->HasConflictingUpdates()) | |
578 << "Updates that can't be decrypted should trigger the syncer to have " | |
579 << "conflicting updates."; | |
580 { | |
581 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | |
582 ASSERT_TRUE(status->update_progress()); | |
583 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
584 << "All updates should have been attempted"; | |
585 ASSERT_TRUE(status->conflict_progress()); | |
586 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
587 << "The updates that can't be decrypted should not be in regular " | |
588 << "conflict"; | |
589 EXPECT_EQ(2, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
590 << "The updates that can't be decrypted should be in encryption " | |
591 << "conflict"; | |
592 EXPECT_EQ(0, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
593 << "No update that can't be decrypted should be applied"; | |
594 } | |
595 { | |
596 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSWORD); | |
597 ASSERT_TRUE(status->update_progress()); | |
598 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
599 << "All updates should have been attempted"; | |
600 ASSERT_TRUE(status->conflict_progress()); | |
601 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
602 << "The updates that can't be decrypted should not be in regular " | |
603 << "conflict"; | |
604 EXPECT_EQ(1, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
605 << "The updates that can't be decrypted should be in encryption " | |
606 << "conflict"; | |
607 EXPECT_EQ(0, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
608 << "No update that can't be decrypted should be applied"; | |
609 } | |
610 } | |
611 | |
612 TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) { | |
613 // Only decryptable password updates should be applied. | |
614 { | |
615 sync_pb::EntitySpecifics specifics; | |
616 sync_pb::PasswordSpecificsData data; | |
617 data.set_origin("http://example.com/1"); | |
618 { | |
619 ReadTransaction trans(FROM_HERE, directory()); | |
620 Cryptographer* cryptographer = directory()->GetCryptographer(&trans); | |
621 | |
622 KeyParams params = {"localhost", "dummy", "foobar"}; | |
623 cryptographer->AddKey(params); | |
624 | |
625 cryptographer->Encrypt(data, | |
626 specifics.mutable_password()->mutable_encrypted()); | |
627 } | |
628 CreateUnappliedNewItem("item1", specifics, false); | |
629 } | |
630 { | |
631 // Create a new cryptographer, independent of the one in the session. | |
632 Cryptographer cryptographer(&encryptor_); | |
633 KeyParams params = {"localhost", "dummy", "bazqux"}; | |
634 cryptographer.AddKey(params); | |
635 | |
636 sync_pb::EntitySpecifics specifics; | |
637 sync_pb::PasswordSpecificsData data; | |
638 data.set_origin("http://example.com/2"); | |
639 | |
640 cryptographer.Encrypt(data, | |
641 specifics.mutable_password()->mutable_encrypted()); | |
642 CreateUnappliedNewItem("item2", specifics, false); | |
643 } | |
644 | |
645 ExpectGroupToChange(apply_updates_command_, GROUP_PASSWORD); | |
646 apply_updates_command_.ExecuteImpl(session()); | |
647 | |
648 sessions::StatusController* status = session()->mutable_status_controller(); | |
649 EXPECT_TRUE(status->HasConflictingUpdates()) | |
650 << "Updates that can't be decrypted should trigger the syncer to have " | |
651 << "conflicting updates."; | |
652 { | |
653 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSWORD); | |
654 ASSERT_TRUE(status->update_progress()); | |
655 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
656 << "All updates should have been attempted"; | |
657 ASSERT_TRUE(status->conflict_progress()); | |
658 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
659 << "The updates that can't be decrypted should not be in regular " | |
660 << "conflict"; | |
661 EXPECT_EQ(1, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
662 << "The updates that can't be decrypted should be in encryption " | |
663 << "conflict"; | |
664 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
665 << "The undecryptable password update shouldn't be applied"; | |
666 } | |
667 } | |
668 | |
669 TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { | |
670 // Storing the cryptographer separately is bad, but for this test we | |
671 // know it's safe. | |
672 Cryptographer* cryptographer; | |
673 syncable::ModelTypeSet encrypted_types; | |
674 encrypted_types.Put(syncable::PASSWORDS); | |
675 encrypted_types.Put(syncable::NIGORI); | |
676 { | |
677 ReadTransaction trans(FROM_HERE, directory()); | |
678 cryptographer = directory()->GetCryptographer(&trans); | |
679 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
680 } | |
681 | |
682 // Nigori node updates should update the Cryptographer. | |
683 Cryptographer other_cryptographer(&encryptor_); | |
684 KeyParams params = {"localhost", "dummy", "foobar"}; | |
685 other_cryptographer.AddKey(params); | |
686 | |
687 sync_pb::EntitySpecifics specifics; | |
688 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
689 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
690 nigori->set_encrypt_bookmarks(true); | |
691 encrypted_types.Put(syncable::BOOKMARKS); | |
692 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), | |
693 specifics, true); | |
694 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
695 | |
696 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
697 apply_updates_command_.ExecuteImpl(session()); | |
698 | |
699 sessions::StatusController* status = session()->mutable_status_controller(); | |
700 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
701 ASSERT_TRUE(status->update_progress()); | |
702 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
703 << "All updates should have been attempted"; | |
704 ASSERT_TRUE(status->conflict_progress()); | |
705 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
706 << "The nigori update shouldn't be in conflict"; | |
707 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
708 << "The nigori update should be applied"; | |
709 | |
710 EXPECT_FALSE(cryptographer->is_ready()); | |
711 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
712 EXPECT_TRUE( | |
713 cryptographer->GetEncryptedTypes() | |
714 .Equals(syncable::ModelTypeSet::All())); | |
715 } | |
716 | |
717 TEST_F(ApplyUpdatesCommandTest, NigoriUpdateForDisabledTypes) { | |
718 // Storing the cryptographer separately is bad, but for this test we | |
719 // know it's safe. | |
720 Cryptographer* cryptographer; | |
721 syncable::ModelTypeSet encrypted_types; | |
722 encrypted_types.Put(syncable::PASSWORDS); | |
723 encrypted_types.Put(syncable::NIGORI); | |
724 { | |
725 ReadTransaction trans(FROM_HERE, directory()); | |
726 cryptographer = directory()->GetCryptographer(&trans); | |
727 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
728 } | |
729 | |
730 // Nigori node updates should update the Cryptographer. | |
731 Cryptographer other_cryptographer(&encryptor_); | |
732 KeyParams params = {"localhost", "dummy", "foobar"}; | |
733 other_cryptographer.AddKey(params); | |
734 | |
735 sync_pb::EntitySpecifics specifics; | |
736 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
737 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
738 nigori->set_encrypt_sessions(true); | |
739 nigori->set_encrypt_themes(true); | |
740 encrypted_types.Put(syncable::SESSIONS); | |
741 encrypted_types.Put(syncable::THEMES); | |
742 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), | |
743 specifics, true); | |
744 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
745 | |
746 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
747 apply_updates_command_.ExecuteImpl(session()); | |
748 | |
749 sessions::StatusController* status = session()->mutable_status_controller(); | |
750 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
751 ASSERT_TRUE(status->update_progress()); | |
752 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
753 << "All updates should have been attempted"; | |
754 ASSERT_TRUE(status->conflict_progress()); | |
755 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
756 << "The nigori update shouldn't be in conflict"; | |
757 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
758 << "The nigori update should be applied"; | |
759 | |
760 EXPECT_FALSE(cryptographer->is_ready()); | |
761 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
762 EXPECT_TRUE( | |
763 cryptographer->GetEncryptedTypes() | |
764 .Equals(syncable::ModelTypeSet::All())); | |
765 } | |
766 | |
767 // Create some local unsynced and unencrypted data. Apply a nigori update that | |
768 // turns on encryption for the unsynced data. Ensure we properly encrypt the | |
769 // data as part of the nigori update. Apply another nigori update with no | |
770 // changes. Ensure we ignore already-encrypted unsynced data and that nothing | |
771 // breaks. | |
772 TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { | |
773 // Storing the cryptographer separately is bad, but for this test we | |
774 // know it's safe. | |
775 Cryptographer* cryptographer; | |
776 syncable::ModelTypeSet encrypted_types; | |
777 encrypted_types.Put(syncable::PASSWORDS); | |
778 encrypted_types.Put(syncable::NIGORI); | |
779 { | |
780 ReadTransaction trans(FROM_HERE, directory()); | |
781 cryptographer = directory()->GetCryptographer(&trans); | |
782 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
783 | |
784 // With default encrypted_types, this should be true. | |
785 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
786 | |
787 Syncer::UnsyncedMetaHandles handles; | |
788 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
789 EXPECT_TRUE(handles.empty()); | |
790 } | |
791 | |
792 // Create unsynced bookmarks without encryption. | |
793 // First item is a folder | |
794 Id folder_id = id_factory_.NewLocalId(); | |
795 CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", | |
796 true, syncable::BOOKMARKS, NULL); | |
797 // Next five items are children of the folder | |
798 size_t i; | |
799 size_t batch_s = 5; | |
800 for (i = 0; i < batch_s; ++i) { | |
801 CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, | |
802 base::StringPrintf("Item %"PRIuS"", i), false, | |
803 syncable::BOOKMARKS, NULL); | |
804 } | |
805 // Next five items are children of the root. | |
806 for (; i < 2*batch_s; ++i) { | |
807 CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(), | |
808 base::StringPrintf("Item %"PRIuS"", i), false, | |
809 syncable::BOOKMARKS, NULL); | |
810 } | |
811 | |
812 KeyParams params = {"localhost", "dummy", "foobar"}; | |
813 cryptographer->AddKey(params); | |
814 sync_pb::EntitySpecifics specifics; | |
815 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
816 cryptographer->GetKeys(nigori->mutable_encrypted()); | |
817 nigori->set_encrypt_bookmarks(true); | |
818 encrypted_types.Put(syncable::BOOKMARKS); | |
819 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), | |
820 specifics, true); | |
821 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
822 EXPECT_TRUE(cryptographer->is_ready()); | |
823 | |
824 { | |
825 // Ensure we have unsynced nodes that aren't properly encrypted. | |
826 ReadTransaction trans(FROM_HERE, directory()); | |
827 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
828 | |
829 Syncer::UnsyncedMetaHandles handles; | |
830 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
831 EXPECT_EQ(2*batch_s+1, handles.size()); | |
832 } | |
833 | |
834 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
835 apply_updates_command_.ExecuteImpl(session()); | |
836 | |
837 { | |
838 sessions::StatusController* status = session()->mutable_status_controller(); | |
839 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
840 ASSERT_TRUE(status->update_progress()); | |
841 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
842 << "All updates should have been attempted"; | |
843 ASSERT_TRUE(status->conflict_progress()); | |
844 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
845 << "No updates should be in conflict"; | |
846 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
847 << "No updates should be in conflict"; | |
848 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
849 << "The nigori update should be applied"; | |
850 } | |
851 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
852 EXPECT_TRUE(cryptographer->is_ready()); | |
853 { | |
854 ReadTransaction trans(FROM_HERE, directory()); | |
855 | |
856 // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes | |
857 // should be encrypted now. | |
858 EXPECT_TRUE(syncable::ModelTypeSet::All().Equals( | |
859 cryptographer->GetEncryptedTypes())); | |
860 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
861 | |
862 Syncer::UnsyncedMetaHandles handles; | |
863 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
864 EXPECT_EQ(2*batch_s+1, handles.size()); | |
865 } | |
866 | |
867 // Simulate another nigori update that doesn't change anything. | |
868 { | |
869 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
870 MutableEntry entry(&trans, syncable::GET_BY_SERVER_TAG, | |
871 syncable::ModelTypeToRootTag(syncable::NIGORI)); | |
872 ASSERT_TRUE(entry.good()); | |
873 entry.Put(syncable::SERVER_VERSION, GetNextRevision()); | |
874 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
875 } | |
876 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
877 apply_updates_command_.ExecuteImpl(session()); | |
878 { | |
879 sessions::StatusController* status = session()->mutable_status_controller(); | |
880 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
881 ASSERT_TRUE(status->update_progress()); | |
882 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
883 << "All updates should have been attempted"; | |
884 ASSERT_TRUE(status->conflict_progress()); | |
885 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
886 << "No updates should be in conflict"; | |
887 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
888 << "No updates should be in conflict"; | |
889 EXPECT_EQ(2, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
890 << "The nigori update should be applied"; | |
891 } | |
892 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
893 EXPECT_TRUE(cryptographer->is_ready()); | |
894 { | |
895 ReadTransaction trans(FROM_HERE, directory()); | |
896 | |
897 // All our changes should still be encrypted. | |
898 EXPECT_TRUE(syncable::ModelTypeSet::All().Equals( | |
899 cryptographer->GetEncryptedTypes())); | |
900 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
901 | |
902 Syncer::UnsyncedMetaHandles handles; | |
903 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
904 EXPECT_EQ(2*batch_s+1, handles.size()); | |
905 } | |
906 } | |
907 | |
908 TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { | |
909 // Storing the cryptographer separately is bad, but for this test we | |
910 // know it's safe. | |
911 Cryptographer* cryptographer; | |
912 syncable::ModelTypeSet encrypted_types; | |
913 encrypted_types.Put(syncable::PASSWORDS); | |
914 encrypted_types.Put(syncable::NIGORI); | |
915 { | |
916 ReadTransaction trans(FROM_HERE, directory()); | |
917 cryptographer = directory()->GetCryptographer(&trans); | |
918 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
919 | |
920 // With default encrypted_types, this should be true. | |
921 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
922 | |
923 Syncer::UnsyncedMetaHandles handles; | |
924 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
925 EXPECT_TRUE(handles.empty()); | |
926 } | |
927 | |
928 // Create unsynced bookmarks without encryption. | |
929 // First item is a folder | |
930 Id folder_id = id_factory_.NewLocalId(); | |
931 CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", true, | |
932 syncable::BOOKMARKS, NULL); | |
933 // Next five items are children of the folder | |
934 size_t i; | |
935 size_t batch_s = 5; | |
936 for (i = 0; i < batch_s; ++i) { | |
937 CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, | |
938 base::StringPrintf("Item %"PRIuS"", i), false, | |
939 syncable::BOOKMARKS, NULL); | |
940 } | |
941 // Next five items are children of the root. | |
942 for (; i < 2*batch_s; ++i) { | |
943 CreateUnsyncedItem(id_factory_.NewLocalId(), id_factory_.root(), | |
944 base::StringPrintf("Item %"PRIuS"", i), false, | |
945 syncable::BOOKMARKS, NULL); | |
946 } | |
947 | |
948 // We encrypt with new keys, triggering the local cryptographer to be unready | |
949 // and unable to decrypt data (once updated). | |
950 Cryptographer other_cryptographer(&encryptor_); | |
951 KeyParams params = {"localhost", "dummy", "foobar"}; | |
952 other_cryptographer.AddKey(params); | |
953 sync_pb::EntitySpecifics specifics; | |
954 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
955 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
956 nigori->set_encrypt_bookmarks(true); | |
957 encrypted_types.Put(syncable::BOOKMARKS); | |
958 CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), | |
959 specifics, true); | |
960 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
961 | |
962 { | |
963 // Ensure we have unsynced nodes that aren't properly encrypted. | |
964 ReadTransaction trans(FROM_HERE, directory()); | |
965 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
966 Syncer::UnsyncedMetaHandles handles; | |
967 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
968 EXPECT_EQ(2*batch_s+1, handles.size()); | |
969 } | |
970 | |
971 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
972 apply_updates_command_.ExecuteImpl(session()); | |
973 | |
974 sessions::StatusController* status = session()->mutable_status_controller(); | |
975 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
976 ASSERT_TRUE(status->update_progress()); | |
977 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
978 << "All updates should have been attempted"; | |
979 ASSERT_TRUE(status->conflict_progress()); | |
980 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
981 << "The unsynced changes don't trigger a blocking conflict with the " | |
982 << "nigori update."; | |
983 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
984 << "The unsynced changes don't trigger an encryption conflict with the " | |
985 << "nigori update."; | |
986 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
987 << "The nigori update should be applied"; | |
988 EXPECT_FALSE(cryptographer->is_ready()); | |
989 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
990 { | |
991 ReadTransaction trans(FROM_HERE, directory()); | |
992 | |
993 // Since we have pending keys, we would have failed to encrypt, but the | |
994 // cryptographer should be updated. | |
995 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
996 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals( | |
997 syncable::ModelTypeSet().All())); | |
998 EXPECT_FALSE(cryptographer->is_ready()); | |
999 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
1000 | |
1001 Syncer::UnsyncedMetaHandles handles; | |
1002 SyncerUtil::GetUnsyncedEntries(&trans, &handles); | |
1003 EXPECT_EQ(2*batch_s+1, handles.size()); | |
1004 } | |
1005 } | |
1006 | |
1007 } // namespace browser_sync | |
OLD | NEW |