Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(270)

Side by Side Diff: sync/engine/apply_updates_and_resolve_conflicts_command_unittest.cc

Issue 72403003: sync: Per-type update application (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase + fix typo Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 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/location.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/strings/stringprintf.h"
10 #include "sync/engine/apply_updates_and_resolve_conflicts_command.h"
11 #include "sync/engine/syncer.h"
12 #include "sync/internal_api/public/test/test_entry_factory.h"
13 #include "sync/protocol/bookmark_specifics.pb.h"
14 #include "sync/protocol/password_specifics.pb.h"
15 #include "sync/syncable/mutable_entry.h"
16 #include "sync/syncable/syncable_id.h"
17 #include "sync/syncable/syncable_read_transaction.h"
18 #include "sync/syncable/syncable_util.h"
19 #include "sync/syncable/syncable_write_transaction.h"
20 #include "sync/test/engine/fake_model_worker.h"
21 #include "sync/test/engine/syncer_command_test.h"
22 #include "sync/test/engine/test_id_factory.h"
23 #include "sync/test/fake_sync_encryption_handler.h"
24 #include "sync/util/cryptographer.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 namespace syncer {
28
29 using std::string;
30 using syncable::Id;
31 using syncable::MutableEntry;
32 using syncable::UNITTEST;
33 using syncable::WriteTransaction;
34
35 namespace {
36 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() {
37 sync_pb::EntitySpecifics result;
38 AddDefaultFieldValue(BOOKMARKS, &result);
39 return result;
40 }
41 } // namespace
42
43 // A test fixture for tests exercising ApplyUpdatesAndResolveConflictsCommand.
44 class ApplyUpdatesAndResolveConflictsCommandTest : public SyncerCommandTest {
45 public:
46 protected:
47 ApplyUpdatesAndResolveConflictsCommandTest() {}
48 virtual ~ApplyUpdatesAndResolveConflictsCommandTest() {}
49
50 virtual void SetUp() {
51 workers()->clear();
52 mutable_routing_info()->clear();
53 workers()->push_back(
54 make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
55 workers()->push_back(
56 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD)));
57 (*mutable_routing_info())[BOOKMARKS] = GROUP_UI;
58 (*mutable_routing_info())[PASSWORDS] = GROUP_PASSWORD;
59 (*mutable_routing_info())[NIGORI] = GROUP_PASSIVE;
60 SyncerCommandTest::SetUp();
61 entry_factory_.reset(new TestEntryFactory(directory()));
62 ExpectNoGroupsToChange(apply_updates_command_);
63 }
64
65 protected:
66 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesAndResolveConflictsCommandTest);
67
68 ApplyUpdatesAndResolveConflictsCommand apply_updates_command_;
69 scoped_ptr<TestEntryFactory> entry_factory_;
70 };
71
72 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, Simple) {
73 string root_server_id = syncable::GetNullId().GetServerId();
74 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
75 "parent", DefaultBookmarkSpecifics(), root_server_id);
76 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
77 "child", DefaultBookmarkSpecifics(), "parent");
78
79 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
80 apply_updates_command_.ExecuteImpl(session());
81
82 const sessions::StatusController& status = session()->status_controller();
83 EXPECT_EQ(0, status.num_encryption_conflicts())
84 << "Simple update shouldn't result in conflicts";
85 EXPECT_EQ(0, status.num_hierarchy_conflicts())
86 << "Simple update shouldn't result in conflicts";
87 EXPECT_EQ(2, status.num_updates_applied())
88 << "All items should have been successfully applied";
89 }
90
91 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest,
92 UpdateWithChildrenBeforeParents) {
93 // Set a bunch of updates which are difficult to apply in the order
94 // they're received due to dependencies on other unseen items.
95 string root_server_id = syncable::GetNullId().GetServerId();
96 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
97 "a_child_created_first", DefaultBookmarkSpecifics(), "parent");
98 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
99 "x_child_created_first", DefaultBookmarkSpecifics(), "parent");
100 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
101 "parent", DefaultBookmarkSpecifics(), root_server_id);
102 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
103 "a_child_created_second", DefaultBookmarkSpecifics(), "parent");
104 entry_factory_->CreateUnappliedNewBookmarkItemWithParent(
105 "x_child_created_second", DefaultBookmarkSpecifics(), "parent");
106
107 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
108 apply_updates_command_.ExecuteImpl(session());
109
110 const sessions::StatusController& status = session()->status_controller();
111 EXPECT_EQ(5, status.num_updates_applied())
112 << "All updates should have been successfully applied";
113 }
114
115 // Runs the ApplyUpdatesAndResolveConflictsCommand on an item that has both
116 // local and remote modifications (IS_UNSYNCED and IS_UNAPPLIED_UPDATE). We
117 // expect the command to detect that this update can't be applied because it is
118 // in a CONFLICT state.
119 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, SimpleConflict) {
120 entry_factory_->CreateUnappliedAndUnsyncedBookmarkItem("item");
121
122 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
123 apply_updates_command_.ExecuteImpl(session());
124
125 const sessions::StatusController& status = session()->status_controller();
126 EXPECT_EQ(1, status.num_server_overwrites())
127 << "Unsynced and unapplied item conflict should be resolved";
128 EXPECT_EQ(0, status.num_updates_applied())
129 << "Update should not be applied; we should override the server.";
130 }
131
132 // Runs the ApplyUpdatesAndResolveConflictsCommand on an item that has both
133 // local and remote modifications *and* the remote modification cannot be
134 // applied without violating the tree constraints. We expect the command to
135 // detect that this update can't be applied and that this situation can't be
136 // resolved with the simple conflict processing logic; it is in a
137 // CONFLICT_HIERARCHY state.
138 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, HierarchyAndSimpleConflict) {
139 // Create a simply-conflicting item. It will start with valid parent ids.
140 int64 handle = entry_factory_->CreateUnappliedAndUnsyncedBookmarkItem(
141 "orphaned_by_server");
142 {
143 // Manually set the SERVER_PARENT_ID to bad value.
144 // A bad parent indicates a hierarchy conflict.
145 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
146 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
147 ASSERT_TRUE(entry.good());
148
149 entry.PutServerParentId(TestIdFactory::MakeServer("bogus_parent"));
150 }
151
152 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
153 apply_updates_command_.ExecuteImpl(session());
154
155 const sessions::StatusController& status = session()->status_controller();
156 EXPECT_EQ(1, status.num_hierarchy_conflicts());
157 }
158
159
160 // Runs the ApplyUpdatesAndResolveConflictsCommand on an item with remote
161 // modifications that would create a directory loop if the update were applied.
162 // We expect the command to detect that this update can't be applied because it
163 // is in a CONFLICT_HIERARCHY state.
164 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest,
165 HierarchyConflictDirectoryLoop) {
166 // Item 'X' locally has parent of 'root'. Server is updating it to have
167 // parent of 'Y'.
168 {
169 // Create it as a child of root node.
170 int64 handle = entry_factory_->CreateSyncedItem("X", BOOKMARKS, true);
171
172 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
173 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
174 ASSERT_TRUE(entry.good());
175
176 // Re-parent from root to "Y"
177 entry.PutServerVersion(entry_factory_->GetNextRevision());
178 entry.PutIsUnappliedUpdate(true);
179 entry.PutServerParentId(TestIdFactory::MakeServer("Y"));
180 }
181
182 // Item 'Y' is child of 'X'.
183 entry_factory_->CreateUnsyncedItem(
184 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
185 BOOKMARKS, NULL);
186
187 // If the server's update were applied, we would have X be a child of Y, and Y
188 // as a child of X. That's a directory loop. The UpdateApplicator should
189 // prevent the update from being applied and note that this is a hierarchy
190 // conflict.
191
192 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
193 apply_updates_command_.ExecuteImpl(session());
194
195 const sessions::StatusController& status = session()->status_controller();
196
197 // This should count as a hierarchy conflict.
198 EXPECT_EQ(1, status.num_hierarchy_conflicts());
199 }
200
201 // Runs the ApplyUpdatesAndResolveConflictsCommand on a directory where the
202 // server sent us an update to add a child to a locally deleted (and unsynced)
203 // parent. We expect the command to not apply the update and to indicate the
204 // update is in a CONFLICT_HIERARCHY state.
205 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest,
206 HierarchyConflictDeletedParent) {
207 // Create a locally deleted parent item.
208 int64 parent_handle;
209 entry_factory_->CreateUnsyncedItem(
210 Id::CreateFromServerId("parent"), TestIdFactory::root(),
211 "parent", true, BOOKMARKS, &parent_handle);
212 {
213 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
214 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle);
215 entry.PutIsDel(true);
216 }
217
218 // Create an incoming child from the server.
219 entry_factory_->CreateUnappliedNewItemWithParent(
220 "child", DefaultBookmarkSpecifics(), "parent");
221
222 // The server's update may seem valid to some other client, but on this client
223 // that new item's parent no longer exists. The update should not be applied
224 // and the update applicator should indicate this is a hierarchy conflict.
225
226 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
227 apply_updates_command_.ExecuteImpl(session());
228
229 const sessions::StatusController& status = session()->status_controller();
230 EXPECT_EQ(1, status.num_hierarchy_conflicts());
231 }
232
233 // Runs the ApplyUpdatesAndResolveConflictsCommand on a directory where the
234 // server is trying to delete a folder that has a recently added (and unsynced)
235 // child. We expect the command to not apply the update because it is in a
236 // CONFLICT_HIERARCHY state.
237 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest,
238 HierarchyConflictDeleteNonEmptyDirectory) {
239 // Create a server-deleted directory.
240 {
241 // Create it as a child of root node.
242 int64 handle = entry_factory_->CreateSyncedItem("parent", BOOKMARKS, true);
243
244 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
245 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
246 ASSERT_TRUE(entry.good());
247
248 // Delete it on the server.
249 entry.PutServerVersion(entry_factory_->GetNextRevision());
250 entry.PutIsUnappliedUpdate(true);
251 entry.PutServerParentId(TestIdFactory::root());
252 entry.PutServerIsDel(true);
253 }
254
255 // Create a local child of the server-deleted directory.
256 entry_factory_->CreateUnsyncedItem(
257 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"),
258 "child", false, BOOKMARKS, NULL);
259
260 // The server's request to delete the directory must be ignored, otherwise our
261 // unsynced new child would be orphaned. This is a hierarchy conflict.
262
263 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
264 apply_updates_command_.ExecuteImpl(session());
265
266 const sessions::StatusController& status = session()->status_controller();
267 // This should count as a hierarchy conflict.
268 EXPECT_EQ(1, status.num_hierarchy_conflicts());
269 }
270
271 // Runs the ApplyUpdatesAndResolveConflictsCommand on a server-created item that
272 // has a locally unknown parent. We expect the command to not apply the update
273 // because the item is in a CONFLICT_HIERARCHY state.
274 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest,
275 HierarchyConflictUnknownParent) {
276 // We shouldn't be able to do anything with either of these items.
277 entry_factory_->CreateUnappliedNewItemWithParent(
278 "some_item", DefaultBookmarkSpecifics(), "unknown_parent");
279 entry_factory_->CreateUnappliedNewItemWithParent(
280 "some_other_item", DefaultBookmarkSpecifics(), "some_item");
281
282 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
283 apply_updates_command_.ExecuteImpl(session());
284
285 const sessions::StatusController& status = session()->status_controller();
286 EXPECT_EQ(2, status.num_hierarchy_conflicts())
287 << "All updates with an unknown ancestors should be in conflict";
288 EXPECT_EQ(0, status.num_updates_applied())
289 << "No item with an unknown ancestor should be applied";
290 }
291
292 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, ItemsBothKnownAndUnknown) {
293 // See what happens when there's a mixture of good and bad updates.
294 string root_server_id = syncable::GetNullId().GetServerId();
295 entry_factory_->CreateUnappliedNewItemWithParent(
296 "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
297 entry_factory_->CreateUnappliedNewItemWithParent(
298 "first_known_item", DefaultBookmarkSpecifics(), root_server_id);
299 entry_factory_->CreateUnappliedNewItemWithParent(
300 "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
301 entry_factory_->CreateUnappliedNewItemWithParent(
302 "second_known_item", DefaultBookmarkSpecifics(), "first_known_item");
303 entry_factory_->CreateUnappliedNewItemWithParent(
304 "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item");
305 entry_factory_->CreateUnappliedNewItemWithParent(
306 "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id);
307
308 ExpectGroupToChange(apply_updates_command_, GROUP_UI);
309 apply_updates_command_.ExecuteImpl(session());
310
311 const sessions::StatusController& status = session()->status_controller();
312 EXPECT_EQ(2, status.num_hierarchy_conflicts())
313 << "The updates with unknown ancestors should be in conflict";
314 EXPECT_EQ(4, status.num_updates_applied())
315 << "The updates with known ancestors should be successfully applied";
316 }
317
318 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, DecryptablePassword) {
319 // Decryptable password updates should be applied.
320 Cryptographer* cryptographer;
321 {
322 // Storing the cryptographer separately is bad, but for this test we
323 // know it's safe.
324 syncable::ReadTransaction trans(FROM_HERE, directory());
325 cryptographer = directory()->GetCryptographer(&trans);
326 }
327
328 KeyParams params = {"localhost", "dummy", "foobar"};
329 cryptographer->AddKey(params);
330
331 sync_pb::EntitySpecifics specifics;
332 sync_pb::PasswordSpecificsData data;
333 data.set_origin("http://example.com");
334
335 cryptographer->Encrypt(data,
336 specifics.mutable_password()->mutable_encrypted());
337 entry_factory_->CreateUnappliedNewItem("item", specifics, false);
338
339 ExpectGroupToChange(apply_updates_command_, GROUP_PASSWORD);
340 apply_updates_command_.ExecuteImpl(session());
341
342 const sessions::StatusController& status = session()->status_controller();
343 EXPECT_EQ(1, status.num_updates_applied())
344 << "The updates that can be decrypted should be applied";
345 }
346
347 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, UndecryptableData) {
348 // Undecryptable updates should not be applied.
349 sync_pb::EntitySpecifics encrypted_bookmark;
350 encrypted_bookmark.mutable_encrypted();
351 AddDefaultFieldValue(BOOKMARKS, &encrypted_bookmark);
352 string root_server_id = syncable::GetNullId().GetServerId();
353 entry_factory_->CreateUnappliedNewItemWithParent(
354 "folder", encrypted_bookmark, root_server_id);
355 entry_factory_->CreateUnappliedNewItem("item2", encrypted_bookmark, false);
356 sync_pb::EntitySpecifics encrypted_password;
357 encrypted_password.mutable_password();
358 entry_factory_->CreateUnappliedNewItem("item3", encrypted_password, false);
359
360 ExpectGroupsToChange(apply_updates_command_, GROUP_UI, GROUP_PASSWORD);
361 apply_updates_command_.ExecuteImpl(session());
362
363 const sessions::StatusController& status = session()->status_controller();
364 EXPECT_EQ(3, status.num_encryption_conflicts())
365 << "Updates that can't be decrypted should be in encryption conflict";
366 EXPECT_EQ(0, status.num_updates_applied())
367 << "No update that can't be decrypted should be applied";
368 }
369
370 TEST_F(ApplyUpdatesAndResolveConflictsCommandTest, SomeUndecryptablePassword) {
371 Cryptographer* cryptographer;
372 // Only decryptable password updates should be applied.
373 {
374 sync_pb::EntitySpecifics specifics;
375 sync_pb::PasswordSpecificsData data;
376 data.set_origin("http://example.com/1");
377 {
378 syncable::ReadTransaction trans(FROM_HERE, directory());
379 cryptographer = directory()->GetCryptographer(&trans);
380
381 KeyParams params = {"localhost", "dummy", "foobar"};
382 cryptographer->AddKey(params);
383
384 cryptographer->Encrypt(data,
385 specifics.mutable_password()->mutable_encrypted());
386 }
387 entry_factory_->CreateUnappliedNewItem("item1", specifics, false);
388 }
389 {
390 // Create a new cryptographer, independent of the one in the session.
391 Cryptographer other_cryptographer(cryptographer->encryptor());
392 KeyParams params = {"localhost", "dummy", "bazqux"};
393 other_cryptographer.AddKey(params);
394
395 sync_pb::EntitySpecifics specifics;
396 sync_pb::PasswordSpecificsData data;
397 data.set_origin("http://example.com/2");
398
399 other_cryptographer.Encrypt(data,
400 specifics.mutable_password()->mutable_encrypted());
401 entry_factory_->CreateUnappliedNewItem("item2", specifics, false);
402 }
403
404 ExpectGroupToChange(apply_updates_command_, GROUP_PASSWORD);
405 apply_updates_command_.ExecuteImpl(session());
406
407 const sessions::StatusController& status = session()->status_controller();
408 EXPECT_EQ(1, status.num_encryption_conflicts())
409 << "The updates that can't be decrypted should be in encryption "
410 << "conflict";
411 EXPECT_EQ(1, status.num_updates_applied())
412 << "The undecryptable password update shouldn't be applied";
413 }
414
415 } // namespace syncer
OLDNEW
« no previous file with comments | « sync/engine/apply_updates_and_resolve_conflicts_command.cc ('k') | sync/engine/download_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698