1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "sync/engine/directory_update_handler.h"
6
7#include "base/compiler_specific.h"
8#include "base/message_loop/message_loop.h"
9#include "base/stl_util.h"
10#include "sync/engine/syncer_proto_util.h"
11#include "sync/internal_api/public/base/attachment_id_proto.h"
12#include "sync/internal_api/public/base/model_type.h"
13#include "sync/internal_api/public/test/test_entry_factory.h"
14#include "sync/protocol/sync.pb.h"
15#include "sync/sessions/directory_type_debug_info_emitter.h"
16#include "sync/sessions/status_controller.h"
17#include "sync/syncable/directory.h"
18#include "sync/syncable/entry.h"
19#include "sync/syncable/mutable_entry.h"
20#include "sync/syncable/syncable_model_neutral_write_transaction.h"
21#include "sync/syncable/syncable_proto_util.h"
22#include "sync/syncable/syncable_read_transaction.h"
23#include "sync/syncable/syncable_write_transaction.h"
24#include "sync/test/engine/fake_model_worker.h"
25#include "sync/test/engine/test_directory_setter_upper.h"
26#include "sync/test/engine/test_id_factory.h"
27#include "sync/test/engine/test_syncable_utils.h"
28#include "testing/gtest/include/gtest/gtest.h"
29
30namespace syncer {
31
32using syncable::UNITTEST;
33
34static const int64 kDefaultVersion = 1000;
35
36// A test harness for tests that focus on processing updates.
37//
38// Update processing is what occurs when we first download updates.  It converts
39// the received protobuf message into information in the syncable::Directory.
40// Any invalid or redundant updates will be dropped at this point.
41class DirectoryUpdateHandlerProcessUpdateTest : public ::testing::Test {
42 public:
43  DirectoryUpdateHandlerProcessUpdateTest()
44      : ui_worker_(new FakeModelWorker(GROUP_UI)) {
45  }
46
47  virtual ~DirectoryUpdateHandlerProcessUpdateTest() {}
48
49  virtual void SetUp() OVERRIDE {
50    dir_maker_.SetUp();
51  }
52
53  virtual void TearDown() OVERRIDE {
54    dir_maker_.TearDown();
55  }
56
57  syncable::Directory* dir() {
58    return dir_maker_.directory();
59  }
60
61 protected:
62  scoped_ptr<sync_pb::SyncEntity> CreateUpdate(
63      const std::string& id,
64      const std::string& parent,
65      const ModelType& type);
66
67  // This exists mostly to give tests access to the protected member function.
68  // Warning: This takes the syncable directory lock.
69  void UpdateSyncEntities(
70      DirectoryUpdateHandler* handler,
71      const SyncEntityList& applicable_updates,
72      sessions::StatusController* status);
73
74  // Another function to access private member functions.
75  void UpdateProgressMarkers(
76      DirectoryUpdateHandler* handler,
77      const sync_pb::DataTypeProgressMarker& progress);
78
79  scoped_refptr<FakeModelWorker> ui_worker() {
80    return ui_worker_;
81  }
82
83  bool EntryExists(const std::string& id) {
84    syncable::ReadTransaction trans(FROM_HERE, dir());
85    syncable::Entry e(&trans, syncable::GET_BY_ID,
86                      syncable::Id::CreateFromServerId(id));
87    return e.good() && !e.GetIsDel();
88  }
89
90 protected:
91  // Used in the construction of DirectoryTypeDebugInfoEmitters.
92  ObserverList<TypeDebugInfoObserver> type_observers_;
93
94 private:
95  base::MessageLoop loop_;  // Needed to initialize the directory.
96  TestDirectorySetterUpper dir_maker_;
97  scoped_refptr<FakeModelWorker> ui_worker_;
98};
99
100scoped_ptr<sync_pb::SyncEntity>
101DirectoryUpdateHandlerProcessUpdateTest::CreateUpdate(
102    const std::string& id,
103    const std::string& parent,
104    const ModelType& type) {
105  scoped_ptr<sync_pb::SyncEntity> e(new sync_pb::SyncEntity());
106  e->set_id_string(id);
107  e->set_parent_id_string(parent);
108  e->set_non_unique_name(id);
109  e->set_name(id);
110  e->set_version(kDefaultVersion);
111  AddDefaultFieldValue(type, e->mutable_specifics());
112  return e.Pass();
113}
114
115void DirectoryUpdateHandlerProcessUpdateTest::UpdateSyncEntities(
116    DirectoryUpdateHandler* handler,
117    const SyncEntityList& applicable_updates,
118    sessions::StatusController* status) {
119  syncable::ModelNeutralWriteTransaction trans(FROM_HERE, UNITTEST, dir());
120  handler->UpdateSyncEntities(&trans, applicable_updates, status);
121}
122
123void DirectoryUpdateHandlerProcessUpdateTest::UpdateProgressMarkers(
124    DirectoryUpdateHandler* handler,
125    const sync_pb::DataTypeProgressMarker& progress) {
126  handler->UpdateProgressMarker(progress);
127}
128
129static const char kCacheGuid[] = "IrcjZ2jyzHDV9Io4+zKcXQ==";
130
131// Test that the bookmark tag is set on newly downloaded items.
132TEST_F(DirectoryUpdateHandlerProcessUpdateTest, NewBookmarkTag) {
133  DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
134  DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
135  sync_pb::GetUpdatesResponse gu_response;
136  sessions::StatusController status;
137
138  // Add a bookmark item to the update message.
139  std::string root = syncable::GetNullId().GetServerId();
140  syncable::Id server_id = syncable::Id::CreateFromServerId("b1");
141  scoped_ptr<sync_pb::SyncEntity> e =
142      CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS);
143  e->set_originator_cache_guid(
144      std::string(kCacheGuid, arraysize(kCacheGuid)-1));
145  syncable::Id client_id = syncable::Id::CreateFromClientString("-2");
146  e->set_originator_client_item_id(client_id.GetServerId());
147  e->set_position_in_parent(0);
148
149  // Add it to the applicable updates list.
150  SyncEntityList bookmark_updates;
151  bookmark_updates.push_back(e.get());
152
153  // Process the update.
154  UpdateSyncEntities(&handler, bookmark_updates, &status);
155
156  syncable::ReadTransaction trans(FROM_HERE, dir());
157  syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
158  ASSERT_TRUE(entry.good());
159  EXPECT_TRUE(UniquePosition::IsValidSuffix(entry.GetUniqueBookmarkTag()));
160  EXPECT_TRUE(entry.GetServerUniquePosition().IsValid());
161
162  // If this assertion fails, that might indicate that the algorithm used to
163  // generate bookmark tags has been modified.  This could have implications for
164  // bookmark ordering.  Please make sure you know what you're doing if you
165  // intend to make such a change.
166  EXPECT_EQ("6wHRAb3kbnXV5GHrejp4/c1y5tw=", entry.GetUniqueBookmarkTag());
167}
168
169// Test the receipt of a type root node.
170TEST_F(DirectoryUpdateHandlerProcessUpdateTest,
171       ReceiveServerCreatedBookmarkFolders) {
172  DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
173  DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
174  sync_pb::GetUpdatesResponse gu_response;
175  sessions::StatusController status;
176
177  // Create an update that mimics the bookmark root.
178  syncable::Id server_id = syncable::Id::CreateFromServerId("xyz");
179  std::string root = syncable::GetNullId().GetServerId();
180  scoped_ptr<sync_pb::SyncEntity> e =
181      CreateUpdate(SyncableIdToProto(server_id), root, BOOKMARKS);
182  e->set_server_defined_unique_tag("google_chrome_bookmarks");
183  e->set_folder(true);
184
185  // Add it to the applicable updates list.
186  SyncEntityList bookmark_updates;
187  bookmark_updates.push_back(e.get());
188
189  EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e));
190
191  // Process it.
192  UpdateSyncEntities(&handler, bookmark_updates, &status);
193
194  // Verify the results.
195  syncable::ReadTransaction trans(FROM_HERE, dir());
196  syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
197  ASSERT_TRUE(entry.good());
198
199  EXPECT_FALSE(entry.ShouldMaintainPosition());
200  EXPECT_FALSE(entry.GetUniquePosition().IsValid());
201  EXPECT_FALSE(entry.GetServerUniquePosition().IsValid());
202  EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty());
203}
204
205// Test the receipt of a non-bookmark item.
206TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ReceiveNonBookmarkItem) {
207  DirectoryTypeDebugInfoEmitter emitter(AUTOFILL, &type_observers_);
208  DirectoryUpdateHandler handler(dir(), AUTOFILL, ui_worker(), &emitter);
209  sync_pb::GetUpdatesResponse gu_response;
210  sessions::StatusController status;
211
212  std::string root = syncable::GetNullId().GetServerId();
213  syncable::Id server_id = syncable::Id::CreateFromServerId("xyz");
214  scoped_ptr<sync_pb::SyncEntity> e =
215      CreateUpdate(SyncableIdToProto(server_id), root, AUTOFILL);
216  e->set_server_defined_unique_tag("9PGRuKdX5sHyGMB17CvYTXuC43I=");
217
218  // Add it to the applicable updates list.
219  SyncEntityList autofill_updates;
220  autofill_updates.push_back(e.get());
221
222  EXPECT_FALSE(SyncerProtoUtil::ShouldMaintainPosition(*e));
223
224  // Process it.
225  UpdateSyncEntities(&handler, autofill_updates, &status);
226
227  syncable::ReadTransaction trans(FROM_HERE, dir());
228  syncable::Entry entry(&trans, syncable::GET_BY_ID, server_id);
229  ASSERT_TRUE(entry.good());
230
231  EXPECT_FALSE(entry.ShouldMaintainPosition());
232  EXPECT_FALSE(entry.GetUniquePosition().IsValid());
233  EXPECT_FALSE(entry.GetServerUniquePosition().IsValid());
234  EXPECT_TRUE(entry.GetUniqueBookmarkTag().empty());
235}
236
237// Tests the setting of progress markers.
238TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ProcessNewProgressMarkers) {
239  DirectoryTypeDebugInfoEmitter emitter(BOOKMARKS, &type_observers_);
240  DirectoryUpdateHandler handler(dir(), BOOKMARKS, ui_worker(), &emitter);
241
242  sync_pb::DataTypeProgressMarker progress;
243  progress.set_data_type_id(GetSpecificsFieldNumberFromModelType(BOOKMARKS));
244  progress.set_token("token");
245
246  UpdateProgressMarkers(&handler, progress);
247
248  sync_pb::DataTypeProgressMarker saved;
249  dir()->GetDownloadProgress(BOOKMARKS, &saved);
250
251  EXPECT_EQ(progress.token(), saved.token());
252  EXPECT_EQ(progress.data_type_id(), saved.data_type_id());
253}
254
255TEST_F(DirectoryUpdateHandlerProcessUpdateTest, GarbageCollectionByVersion) {
256  DirectoryTypeDebugInfoEmitter emitter(SYNCED_NOTIFICATIONS, &type_observers_);
257  DirectoryUpdateHandler handler(dir(), SYNCED_NOTIFICATIONS,
258                                 ui_worker(), &emitter);
259  sessions::StatusController status;
260
261  sync_pb::DataTypeProgressMarker progress;
262  progress.set_data_type_id(
263      GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
264  progress.set_token("token");
265  progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 10);
266
267  sync_pb::DataTypeContext context;
268  context.set_data_type_id(
269      GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
270  context.set_context("context");
271  context.set_version(1);
272
273  scoped_ptr<sync_pb::SyncEntity> type_root =
274      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
275                   syncable::GetNullId().GetServerId(),
276                   SYNCED_NOTIFICATIONS);
277  type_root->set_server_defined_unique_tag(
278      ModelTypeToRootTag(SYNCED_NOTIFICATIONS));
279  type_root->set_folder(true);
280
281  scoped_ptr<sync_pb::SyncEntity> e1 =
282      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
283                   type_root->id_string(),
284                   SYNCED_NOTIFICATIONS);
285
286  scoped_ptr<sync_pb::SyncEntity> e2 =
287      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
288                   type_root->id_string(),
289                   SYNCED_NOTIFICATIONS);
290  e2->set_version(kDefaultVersion + 100);
291
292  // Add to the applicable updates list.
293  SyncEntityList updates;
294  updates.push_back(type_root.get());
295  updates.push_back(e1.get());
296  updates.push_back(e2.get());
297
298  // Process and apply updates.
299  EXPECT_EQ(
300      SYNCER_OK,
301      handler.ProcessGetUpdatesResponse(progress, context, updates, &status));
302  handler.ApplyUpdates(&status);
303
304  // Verify none is deleted because they are unapplied during GC.
305  EXPECT_TRUE(EntryExists(type_root->id_string()));
306  EXPECT_TRUE(EntryExists(e1->id_string()));
307  EXPECT_TRUE(EntryExists(e2->id_string()));
308
309  // Process and apply again. Old entry is deleted but not root.
310  progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 20);
311  EXPECT_EQ(SYNCER_OK,
312            handler.ProcessGetUpdatesResponse(
313                progress, context, SyncEntityList(), &status));
314  handler.ApplyUpdates(&status);
315  EXPECT_TRUE(EntryExists(type_root->id_string()));
316  EXPECT_FALSE(EntryExists(e1->id_string()));
317  EXPECT_TRUE(EntryExists(e2->id_string()));
318}
319
320TEST_F(DirectoryUpdateHandlerProcessUpdateTest, ContextVersion) {
321  DirectoryTypeDebugInfoEmitter emitter(SYNCED_NOTIFICATIONS, &type_observers_);
322  DirectoryUpdateHandler handler(dir(), SYNCED_NOTIFICATIONS,
323                                 ui_worker(), &emitter);
324  sessions::StatusController status;
325  int field_number = GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS);
326
327  sync_pb::DataTypeProgressMarker progress;
328  progress.set_data_type_id(
329      GetSpecificsFieldNumberFromModelType(SYNCED_NOTIFICATIONS));
330  progress.set_token("token");
331
332  sync_pb::DataTypeContext old_context;
333  old_context.set_version(1);
334  old_context.set_context("data");
335  old_context.set_data_type_id(field_number);
336
337  scoped_ptr<sync_pb::SyncEntity> type_root =
338      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
339                   syncable::GetNullId().GetServerId(),
340                   SYNCED_NOTIFICATIONS);
341  type_root->set_server_defined_unique_tag(
342      ModelTypeToRootTag(SYNCED_NOTIFICATIONS));
343  type_root->set_folder(true);
344  scoped_ptr<sync_pb::SyncEntity> e1 =
345      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
346                   type_root->id_string(),
347                   SYNCED_NOTIFICATIONS);
348
349  SyncEntityList updates;
350  updates.push_back(type_root.get());
351  updates.push_back(e1.get());
352
353  // The first response should be processed fine.
354  EXPECT_EQ(SYNCER_OK,
355            handler.ProcessGetUpdatesResponse(
356                progress, old_context, updates, &status));
357  handler.ApplyUpdates(&status);
358
359  EXPECT_TRUE(EntryExists(type_root->id_string()));
360  EXPECT_TRUE(EntryExists(e1->id_string()));
361
362  {
363    sync_pb::DataTypeContext dir_context;
364    syncable::ReadTransaction trans(FROM_HERE, dir());
365    trans.directory()->GetDataTypeContext(
366        &trans, SYNCED_NOTIFICATIONS, &dir_context);
367    EXPECT_EQ(old_context.SerializeAsString(), dir_context.SerializeAsString());
368  }
369
370  sync_pb::DataTypeContext new_context;
371  new_context.set_version(0);
372  new_context.set_context("old");
373  new_context.set_data_type_id(field_number);
374
375  scoped_ptr<sync_pb::SyncEntity> e2 =
376      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e2")),
377                   type_root->id_string(),
378                   SYNCED_NOTIFICATIONS);
379  updates.clear();
380  updates.push_back(e2.get());
381
382  // The second response, with an old context version, should result in an
383  // error and the updates should be dropped.
384  EXPECT_EQ(DATATYPE_TRIGGERED_RETRY,
385            handler.ProcessGetUpdatesResponse(
386                progress, new_context, updates, &status));
387  handler.ApplyUpdates(&status);
388
389  EXPECT_FALSE(EntryExists(e2->id_string()));
390
391  {
392    sync_pb::DataTypeContext dir_context;
393    syncable::ReadTransaction trans(FROM_HERE, dir());
394    trans.directory()->GetDataTypeContext(
395        &trans, SYNCED_NOTIFICATIONS, &dir_context);
396    EXPECT_EQ(old_context.SerializeAsString(), dir_context.SerializeAsString());
397  }
398}
399
400// See that updates containing attachment metadata are applied
401// (i.e. server_attachment_metadata is copied to attachment_metadata).
402TEST_F(DirectoryUpdateHandlerProcessUpdateTest,
403       ProcessAndApplyUpdatesWithAttachments) {
404  DirectoryTypeDebugInfoEmitter emitter(ARTICLES, &type_observers_);
405  DirectoryUpdateHandler handler(dir(), ARTICLES, ui_worker(), &emitter);
406  sessions::StatusController status;
407
408  sync_pb::DataTypeProgressMarker progress;
409  progress.set_data_type_id(GetSpecificsFieldNumberFromModelType(ARTICLES));
410  progress.set_token("token");
411  progress.mutable_gc_directive()->set_version_watermark(kDefaultVersion + 10);
412
413  sync_pb::DataTypeContext context;
414  context.set_data_type_id(GetSpecificsFieldNumberFromModelType(ARTICLES));
415  context.set_context("context");
416  context.set_version(1);
417
418  scoped_ptr<sync_pb::SyncEntity> type_root =
419      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("root")),
420                   syncable::GetNullId().GetServerId(),
421                   ARTICLES);
422  type_root->set_server_defined_unique_tag(ModelTypeToRootTag(ARTICLES));
423  type_root->set_folder(true);
424
425  scoped_ptr<sync_pb::SyncEntity> e1 =
426      CreateUpdate(SyncableIdToProto(syncable::Id::CreateFromServerId("e1")),
427                   type_root->id_string(),
428                   ARTICLES);
429  sync_pb::AttachmentIdProto* attachment_id = e1->add_attachment_id();
430  *attachment_id = CreateAttachmentIdProto();
431
432  SyncEntityList updates;
433  updates.push_back(type_root.get());
434  updates.push_back(e1.get());
435
436  // Process and apply updates.
437  EXPECT_EQ(
438      SYNCER_OK,
439      handler.ProcessGetUpdatesResponse(progress, context, updates, &status));
440  handler.ApplyUpdates(&status);
441
442  ASSERT_TRUE(EntryExists(type_root->id_string()));
443  ASSERT_TRUE(EntryExists(e1->id_string()));
444  {
445    syncable::ReadTransaction trans(FROM_HERE, dir());
446    syncable::Entry e(&trans,
447                      syncable::GET_BY_ID,
448                      syncable::Id::CreateFromServerId(e1->id_string()));
449
450    // See that the attachment_metadata is correct.
451    sync_pb::AttachmentMetadata attachment_metadata = e.GetAttachmentMetadata();
452    ASSERT_EQ(1, attachment_metadata.record_size());
453    ASSERT_EQ(attachment_id->SerializeAsString(),
454              attachment_metadata.record(0).id().SerializeAsString());
455    ASSERT_TRUE(attachment_metadata.record(0).is_on_server());
456
457    // See that attachment_metadata and server_attachment_metadata are equal.
458    ASSERT_EQ(attachment_metadata.SerializeAsString(),
459              e.GetServerAttachmentMetadata().SerializeAsString());
460  }
461}
462
463// A test harness for tests that focus on applying updates.
464//
465// Update application is performed when we want to take updates that were
466// previously downloaded, processed, and stored in our syncable::Directory
467// and use them to update our local state (both the Directory's local state
468// and the model's local state, though these tests focus only on the Directory's
469// local state).
470//
471// This is kept separate from the update processing test in part for historical
472// reasons, and in part because these tests may require a bit more infrastrcture
473// in the future.  Update application should happen on a different thread a lot
474// of the time so these tests may end up requiring more infrastructure than the
475// update processing tests.  Currently, we're bypassing most of those issues by
476// using FakeModelWorkers, so there's not much difference between the two test
477// harnesses.
478class DirectoryUpdateHandlerApplyUpdateTest : public ::testing::Test {
479 public:
480  DirectoryUpdateHandlerApplyUpdateTest()
481      : ui_worker_(new FakeModelWorker(GROUP_UI)),
482        password_worker_(new FakeModelWorker(GROUP_PASSWORD)),
483        passive_worker_(new FakeModelWorker(GROUP_PASSIVE)),
484        bookmarks_emitter_(BOOKMARKS, &type_observers_),
485        passwords_emitter_(PASSWORDS, &type_observers_),
486        articles_emitter_(ARTICLES, &type_observers_),
487        update_handler_map_deleter_(&update_handler_map_) {}
488
489  virtual void SetUp() OVERRIDE {
490    dir_maker_.SetUp();
491    entry_factory_.reset(new TestEntryFactory(directory()));
492
493    update_handler_map_.insert(std::make_pair(
494        BOOKMARKS,
495        new DirectoryUpdateHandler(directory(), BOOKMARKS,
496                                   ui_worker_, &bookmarks_emitter_)));
497    update_handler_map_.insert(std::make_pair(
498        PASSWORDS,
499        new DirectoryUpdateHandler(directory(),
500                                   PASSWORDS,
501                                   password_worker_,
502                                   &passwords_emitter_)));
503    update_handler_map_.insert(std::make_pair(
504        ARTICLES,
505        new DirectoryUpdateHandler(
506            directory(), ARTICLES, ui_worker_, &articles_emitter_)));
507  }
508
509  virtual void TearDown() OVERRIDE {
510    dir_maker_.TearDown();
511  }
512
513  const UpdateCounters& GetBookmarksUpdateCounters() {
514    return bookmarks_emitter_.GetUpdateCounters();
515  }
516
517  const UpdateCounters& GetPasswordsUpdateCounters() {
518    return passwords_emitter_.GetUpdateCounters();
519  }
520
521  const UpdateCounters& GetArticlesUpdateCounters() {
522    return articles_emitter_.GetUpdateCounters();
523  }
524
525 protected:
526  void ApplyBookmarkUpdates(sessions::StatusController* status) {
527    update_handler_map_[BOOKMARKS]->ApplyUpdates(status);
528  }
529
530  void ApplyPasswordUpdates(sessions::StatusController* status) {
531    update_handler_map_[PASSWORDS]->ApplyUpdates(status);
532  }
533
534  void ApplyArticlesUpdates(sessions::StatusController* status) {
535    update_handler_map_[ARTICLES]->ApplyUpdates(status);
536  }
537
538  TestEntryFactory* entry_factory() {
539    return entry_factory_.get();
540  }
541
542  syncable::Directory* directory() {
543    return dir_maker_.directory();
544  }
545
546 private:
547  typedef std::map<ModelType, UpdateHandler*> UpdateHandlerMap;
548
549  base::MessageLoop loop_;  // Needed to initialize the directory.
550  TestDirectorySetterUpper dir_maker_;
551  scoped_ptr<TestEntryFactory> entry_factory_;
552
553  scoped_refptr<FakeModelWorker> ui_worker_;
554  scoped_refptr<FakeModelWorker> password_worker_;
555  scoped_refptr<FakeModelWorker> passive_worker_;
556
557  ObserverList<TypeDebugInfoObserver> type_observers_;
558  DirectoryTypeDebugInfoEmitter bookmarks_emitter_;
559  DirectoryTypeDebugInfoEmitter passwords_emitter_;
560  DirectoryTypeDebugInfoEmitter articles_emitter_;
561
562  UpdateHandlerMap update_handler_map_;
563  STLValueDeleter<UpdateHandlerMap> update_handler_map_deleter_;
564};
565
566namespace {
567sync_pb::EntitySpecifics DefaultBookmarkSpecifics() {
568  sync_pb::EntitySpecifics result;
569  AddDefaultFieldValue(BOOKMARKS, &result);
570  return result;
571}
572} // namespace
573
574// Test update application for a few bookmark items.
575TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmark) {
576  sessions::StatusController status;
577
578  std::string root_server_id = syncable::GetNullId().GetServerId();
579  int64 parent_handle =
580      entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
581          "parent", DefaultBookmarkSpecifics(), root_server_id);
582  int64 child_handle =
583      entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
584          "child", DefaultBookmarkSpecifics(), "parent");
585
586  ApplyBookmarkUpdates(&status);
587
588  const UpdateCounters& counter = GetBookmarksUpdateCounters();
589  EXPECT_EQ(0, counter.num_encryption_conflict_application_failures)
590      << "Simple update shouldn't result in conflicts";
591  EXPECT_EQ(0, counter.num_hierarchy_conflict_application_failures)
592      << "Simple update shouldn't result in conflicts";
593  EXPECT_EQ(2, counter.num_updates_applied)
594      << "All items should have been successfully applied";
595
596  {
597    syncable::ReadTransaction trans(FROM_HERE, directory());
598
599    syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle);
600    syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle);
601
602    ASSERT_TRUE(parent.good());
603    ASSERT_TRUE(child.good());
604
605    EXPECT_FALSE(parent.GetIsUnsynced());
606    EXPECT_FALSE(parent.GetIsUnappliedUpdate());
607    EXPECT_FALSE(child.GetIsUnsynced());
608    EXPECT_FALSE(child.GetIsUnappliedUpdate());
609  }
610}
611
612// Test that the applicator can handle updates delivered out of order.
613TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
614       BookmarkChildrenBeforeParent) {
615  // Start with some bookmarks whose parents are unknown.
616  std::string root_server_id = syncable::GetNullId().GetServerId();
617  int64 a_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
618      "a_child_created_first", DefaultBookmarkSpecifics(), "parent");
619  int64 x_handle = entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
620      "x_child_created_first", DefaultBookmarkSpecifics(), "parent");
621
622  // Update application will fail.
623  sessions::StatusController status1;
624  ApplyBookmarkUpdates(&status1);
625  EXPECT_EQ(0, status1.num_updates_applied());
626  EXPECT_EQ(2, status1.num_hierarchy_conflicts());
627
628  {
629    syncable::ReadTransaction trans(FROM_HERE, directory());
630
631    syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle);
632    syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
633
634    ASSERT_TRUE(a.good());
635    ASSERT_TRUE(x.good());
636
637    EXPECT_TRUE(a.GetIsUnappliedUpdate());
638    EXPECT_TRUE(x.GetIsUnappliedUpdate());
639  }
640
641  // Now add their parent and a few siblings.
642  entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
643      "parent", DefaultBookmarkSpecifics(), root_server_id);
644  entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
645      "a_child_created_second", DefaultBookmarkSpecifics(), "parent");
646  entry_factory()->CreateUnappliedNewBookmarkItemWithParent(
647      "x_child_created_second", DefaultBookmarkSpecifics(), "parent");
648
649  // Update application will succeed.
650  sessions::StatusController status2;
651  ApplyBookmarkUpdates(&status2);
652  EXPECT_EQ(5, status2.num_updates_applied())
653      << "All updates should have been successfully applied";
654
655  {
656    syncable::ReadTransaction trans(FROM_HERE, directory());
657
658    syncable::Entry a(&trans, syncable::GET_BY_HANDLE, a_handle);
659    syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
660
661    ASSERT_TRUE(a.good());
662    ASSERT_TRUE(x.good());
663
664    EXPECT_FALSE(a.GetIsUnappliedUpdate());
665    EXPECT_FALSE(x.GetIsUnappliedUpdate());
666  }
667}
668
669// Try to apply changes on an item that is both IS_UNSYNCED and
670// IS_UNAPPLIED_UPDATE.  Conflict resolution should be performed.
671TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SimpleBookmarkConflict) {
672  int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem("x");
673
674  int original_server_version = -10;
675  {
676    syncable::ReadTransaction trans(FROM_HERE, directory());
677    syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
678    original_server_version = e.GetServerVersion();
679    ASSERT_NE(original_server_version, e.GetBaseVersion());
680    EXPECT_TRUE(e.GetIsUnsynced());
681  }
682
683  sessions::StatusController status;
684  ApplyBookmarkUpdates(&status);
685
686  const UpdateCounters& counters = GetBookmarksUpdateCounters();
687  EXPECT_EQ(1, counters.num_server_overwrites)
688      << "Unsynced and unapplied item conflict should be resolved";
689  EXPECT_EQ(0, counters.num_updates_applied)
690      << "Update should not be applied; we should override the server.";
691
692  {
693    syncable::ReadTransaction trans(FROM_HERE, directory());
694    syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
695    ASSERT_TRUE(e.good());
696    EXPECT_EQ(original_server_version, e.GetServerVersion());
697    EXPECT_EQ(original_server_version, e.GetBaseVersion());
698    EXPECT_FALSE(e.GetIsUnappliedUpdate());
699
700    // The unsynced flag will remain set until we successfully commit the item.
701    EXPECT_TRUE(e.GetIsUnsynced());
702  }
703}
704
705// Create a simple conflict that is also a hierarchy conflict.  If we were to
706// follow the normal "server wins" logic, we'd end up violating hierarchy
707// constraints.  The hierarchy conflict must take precedence.  We can not allow
708// the update to be applied.  The item must remain in the conflict state.
709TEST_F(DirectoryUpdateHandlerApplyUpdateTest, HierarchyAndSimpleConflict) {
710  // Create a simply-conflicting item.  It will start with valid parent ids.
711  int64 handle = entry_factory()->CreateUnappliedAndUnsyncedBookmarkItem(
712      "orphaned_by_server");
713  {
714    // Manually set the SERVER_PARENT_ID to bad value.
715    // A bad parent indicates a hierarchy conflict.
716    syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
717    syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
718    ASSERT_TRUE(entry.good());
719
720    entry.PutServerParentId(TestIdFactory::MakeServer("bogus_parent"));
721  }
722
723  sessions::StatusController status;
724  ApplyBookmarkUpdates(&status);
725
726  const UpdateCounters& counters = GetBookmarksUpdateCounters();
727  EXPECT_EQ(0, counters.num_updates_applied);
728  EXPECT_EQ(0, counters.num_server_overwrites);
729  EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
730
731  {
732    syncable::ReadTransaction trans(FROM_HERE, directory());
733    syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
734    ASSERT_TRUE(e.good());
735    EXPECT_TRUE(e.GetIsUnappliedUpdate());
736    EXPECT_TRUE(e.GetIsUnsynced());
737  }
738}
739
740// Attempt to apply an udpate that would create a bookmark folder loop.  This
741// application should fail.
742TEST_F(DirectoryUpdateHandlerApplyUpdateTest, BookmarkFolderLoop) {
743  // Item 'X' locally has parent of 'root'.  Server is updating it to have
744  // parent of 'Y'.
745
746  // Create it as a child of root node.
747  int64 handle = entry_factory()->CreateSyncedItem("X", BOOKMARKS, true);
748
749  {
750    syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
751    syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle);
752    ASSERT_TRUE(entry.good());
753
754    // Re-parent from root to "Y"
755    entry.PutServerVersion(entry_factory()->GetNextRevision());
756    entry.PutIsUnappliedUpdate(true);
757    entry.PutServerParentId(TestIdFactory::MakeServer("Y"));
758  }
759
760  // Item 'Y' is child of 'X'.
761  entry_factory()->CreateUnsyncedItem(
762      TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true,
763      BOOKMARKS, NULL);
764
765  // If the server's update were applied, we would have X be a child of Y, and Y
766  // as a child of X.  That's a directory loop.  The UpdateApplicator should
767  // prevent the update from being applied and note that this is a hierarchy
768  // conflict.
769
770  sessions::StatusController status;
771  ApplyBookmarkUpdates(&status);
772
773  // This should count as a hierarchy conflict.
774  const UpdateCounters& counters = GetBookmarksUpdateCounters();
775  EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
776
777  {
778    syncable::ReadTransaction trans(FROM_HERE, directory());
779    syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
780    ASSERT_TRUE(e.good());
781    EXPECT_TRUE(e.GetIsUnappliedUpdate());
782    EXPECT_FALSE(e.GetIsUnsynced());
783  }
784}
785
786// Test update application where the update has been orphaned by a local folder
787// deletion.  The update application attempt should fail.
788TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
789       HierarchyConflictDeletedParent) {
790  // Create a locally deleted parent item.
791  int64 parent_handle;
792  entry_factory()->CreateUnsyncedItem(
793      syncable::Id::CreateFromServerId("parent"), TestIdFactory::root(),
794      "parent", true, BOOKMARKS, &parent_handle);
795  {
796    syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
797    syncable::MutableEntry entry(&trans,
798                                 syncable::GET_BY_HANDLE,
799                                 parent_handle);
800    entry.PutIsDel(true);
801  }
802
803  // Create an incoming child from the server.
804  int64 child_handle = entry_factory()->CreateUnappliedNewItemWithParent(
805      "child", DefaultBookmarkSpecifics(), "parent");
806
807  // The server's update may seem valid to some other client, but on this client
808  // that new item's parent no longer exists.  The update should not be applied
809  // and the update applicator should indicate this is a hierarchy conflict.
810
811  sessions::StatusController status;
812  ApplyBookmarkUpdates(&status);
813  const UpdateCounters& counters = GetBookmarksUpdateCounters();
814  EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
815
816  {
817    syncable::ReadTransaction trans(FROM_HERE, directory());
818    syncable::Entry child(&trans, syncable::GET_BY_HANDLE, child_handle);
819    ASSERT_TRUE(child.good());
820    EXPECT_TRUE(child.GetIsUnappliedUpdate());
821    EXPECT_FALSE(child.GetIsUnsynced());
822  }
823}
824
825// Attempt to apply an update that deletes a folder where the folder has
826// locally-created children.  The update application should fail.
827TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
828       HierarchyConflictDeleteNonEmptyDirectory) {
829  // Create a server-deleted folder as a child of root node.
830  int64 parent_handle =
831      entry_factory()->CreateSyncedItem("parent", BOOKMARKS, true);
832  {
833    syncable::WriteTransaction trans(FROM_HERE, UNITTEST, directory());
834    syncable::MutableEntry entry(&trans,
835                                 syncable::GET_BY_HANDLE,
836                                 parent_handle);
837    ASSERT_TRUE(entry.good());
838
839    // Delete it on the server.
840    entry.PutServerVersion(entry_factory()->GetNextRevision());
841    entry.PutIsUnappliedUpdate(true);
842    entry.PutServerParentId(TestIdFactory::root());
843    entry.PutServerIsDel(true);
844  }
845
846  // Create a local child of the server-deleted directory.
847  entry_factory()->CreateUnsyncedItem(
848      TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"),
849      "child", false, BOOKMARKS, NULL);
850
851  // The server's request to delete the directory must be ignored, otherwise our
852  // unsynced new child would be orphaned.  This is a hierarchy conflict.
853
854  sessions::StatusController status;
855  ApplyBookmarkUpdates(&status);
856
857  // This should count as a hierarchy conflict.
858  const UpdateCounters& counters = GetBookmarksUpdateCounters();
859  EXPECT_EQ(1, counters.num_hierarchy_conflict_application_failures);
860
861  {
862    syncable::ReadTransaction trans(FROM_HERE, directory());
863    syncable::Entry parent(&trans, syncable::GET_BY_HANDLE, parent_handle);
864    ASSERT_TRUE(parent.good());
865    EXPECT_TRUE(parent.GetIsUnappliedUpdate());
866    EXPECT_FALSE(parent.GetIsUnsynced());
867  }
868}
869
870// Attempt to apply updates where the updated item's parent is not known to this
871// client.  The update application attempt should fail.
872TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
873       HierarchyConflictUnknownParent) {
874  // We shouldn't be able to do anything with either of these items.
875  int64 x_handle = entry_factory()->CreateUnappliedNewItemWithParent(
876      "some_item", DefaultBookmarkSpecifics(), "unknown_parent");
877  int64 y_handle = entry_factory()->CreateUnappliedNewItemWithParent(
878      "some_other_item", DefaultBookmarkSpecifics(), "some_item");
879
880  sessions::StatusController status;
881  ApplyBookmarkUpdates(&status);
882
883  const UpdateCounters& counters = GetBookmarksUpdateCounters();
884  EXPECT_EQ(2, counters.num_hierarchy_conflict_application_failures)
885      << "All updates with an unknown ancestors should be in conflict";
886  EXPECT_EQ(0, counters.num_updates_applied)
887      << "No item with an unknown ancestor should be applied";
888
889  {
890    syncable::ReadTransaction trans(FROM_HERE, directory());
891    syncable::Entry x(&trans, syncable::GET_BY_HANDLE, x_handle);
892    syncable::Entry y(&trans, syncable::GET_BY_HANDLE, y_handle);
893    ASSERT_TRUE(x.good());
894    ASSERT_TRUE(y.good());
895    EXPECT_TRUE(x.GetIsUnappliedUpdate());
896    EXPECT_TRUE(y.GetIsUnappliedUpdate());
897    EXPECT_FALSE(x.GetIsUnsynced());
898    EXPECT_FALSE(y.GetIsUnsynced());
899  }
900}
901
902// Attempt application of a mix of items.  Some update application attempts will
903// fail due to hierarchy conflicts.  Others should succeed.
904TEST_F(DirectoryUpdateHandlerApplyUpdateTest, ItemsBothKnownAndUnknown) {
905  // See what happens when there's a mixture of good and bad updates.
906  std::string root_server_id = syncable::GetNullId().GetServerId();
907  int64 u1_handle = entry_factory()->CreateUnappliedNewItemWithParent(
908      "first_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
909  int64 k1_handle = entry_factory()->CreateUnappliedNewItemWithParent(
910      "first_known_item", DefaultBookmarkSpecifics(), root_server_id);
911  int64 u2_handle = entry_factory()->CreateUnappliedNewItemWithParent(
912      "second_unknown_item", DefaultBookmarkSpecifics(), "unknown_parent");
913  int64 k2_handle = entry_factory()->CreateUnappliedNewItemWithParent(
914      "second_known_item", DefaultBookmarkSpecifics(), "first_known_item");
915  int64 k3_handle = entry_factory()->CreateUnappliedNewItemWithParent(
916      "third_known_item", DefaultBookmarkSpecifics(), "fourth_known_item");
917  int64 k4_handle = entry_factory()->CreateUnappliedNewItemWithParent(
918      "fourth_known_item", DefaultBookmarkSpecifics(), root_server_id);
919
920  sessions::StatusController status;
921  ApplyBookmarkUpdates(&status);
922
923  const UpdateCounters& counters = GetBookmarksUpdateCounters();
924  EXPECT_EQ(2, counters.num_hierarchy_conflict_application_failures)
925      << "The updates with unknown ancestors should be in conflict";
926  EXPECT_EQ(4, counters.num_updates_applied)
927      << "The updates with known ancestors should be successfully applied";
928
929  {
930    syncable::ReadTransaction trans(FROM_HERE, directory());
931    syncable::Entry u1(&trans, syncable::GET_BY_HANDLE, u1_handle);
932    syncable::Entry u2(&trans, syncable::GET_BY_HANDLE, u2_handle);
933    syncable::Entry k1(&trans, syncable::GET_BY_HANDLE, k1_handle);
934    syncable::Entry k2(&trans, syncable::GET_BY_HANDLE, k2_handle);
935    syncable::Entry k3(&trans, syncable::GET_BY_HANDLE, k3_handle);
936    syncable::Entry k4(&trans, syncable::GET_BY_HANDLE, k4_handle);
937    ASSERT_TRUE(u1.good());
938    ASSERT_TRUE(u2.good());
939    ASSERT_TRUE(k1.good());
940    ASSERT_TRUE(k2.good());
941    ASSERT_TRUE(k3.good());
942    ASSERT_TRUE(k4.good());
943    EXPECT_TRUE(u1.GetIsUnappliedUpdate());
944    EXPECT_TRUE(u2.GetIsUnappliedUpdate());
945    EXPECT_FALSE(k1.GetIsUnappliedUpdate());
946    EXPECT_FALSE(k2.GetIsUnappliedUpdate());
947    EXPECT_FALSE(k3.GetIsUnappliedUpdate());
948    EXPECT_FALSE(k4.GetIsUnappliedUpdate());
949  }
950}
951
952// Attempt application of password upates where the passphrase is known.
953TEST_F(DirectoryUpdateHandlerApplyUpdateTest, DecryptablePassword) {
954  // Decryptable password updates should be applied.
955  Cryptographer* cryptographer;
956  {
957    // Storing the cryptographer separately is bad, but for this test we
958    // know it's safe.
959    syncable::ReadTransaction trans(FROM_HERE, directory());
960    cryptographer = directory()->GetCryptographer(&trans);
961  }
962
963  KeyParams params = {"localhost", "dummy", "foobar"};
964  cryptographer->AddKey(params);
965
966  sync_pb::EntitySpecifics specifics;
967  sync_pb::PasswordSpecificsData data;
968  data.set_origin("http://example.com");
969
970  cryptographer->Encrypt(data,
971                         specifics.mutable_password()->mutable_encrypted());
972  int64 handle =
973      entry_factory()->CreateUnappliedNewItem("item", specifics, false);
974
975  sessions::StatusController status;
976  ApplyPasswordUpdates(&status);
977
978  const UpdateCounters& counters = GetPasswordsUpdateCounters();
979  EXPECT_EQ(1, counters.num_updates_applied)
980      << "The updates that can be decrypted should be applied";
981
982  {
983    syncable::ReadTransaction trans(FROM_HERE, directory());
984    syncable::Entry e(&trans, syncable::GET_BY_HANDLE, handle);
985    ASSERT_TRUE(e.good());
986    EXPECT_FALSE(e.GetIsUnappliedUpdate());
987    EXPECT_FALSE(e.GetIsUnsynced());
988  }
989}
990
991// Attempt application of encrypted items when the passphrase is not known.
992TEST_F(DirectoryUpdateHandlerApplyUpdateTest, UndecryptableData) {
993  // Undecryptable updates should not be applied.
994  sync_pb::EntitySpecifics encrypted_bookmark;
995  encrypted_bookmark.mutable_encrypted();
996  AddDefaultFieldValue(BOOKMARKS, &encrypted_bookmark);
997  std::string root_server_id = syncable::GetNullId().GetServerId();
998  int64 folder_handle = entry_factory()->CreateUnappliedNewItemWithParent(
999      "folder",
1000      encrypted_bookmark,
1001      root_server_id);
1002  int64 bookmark_handle = entry_factory()->CreateUnappliedNewItem(
1003      "item2",
1004      encrypted_bookmark,
1005      false);
1006  sync_pb::EntitySpecifics encrypted_password;
1007  encrypted_password.mutable_password();
1008  int64 password_handle = entry_factory()->CreateUnappliedNewItem(
1009      "item3",
1010      encrypted_password,
1011      false);
1012
1013  sessions::StatusController status;
1014  ApplyBookmarkUpdates(&status);
1015  ApplyPasswordUpdates(&status);
1016
1017  const UpdateCounters& bm_counters = GetBookmarksUpdateCounters();
1018  EXPECT_EQ(2, bm_counters.num_encryption_conflict_application_failures)
1019      << "Updates that can't be decrypted should be in encryption conflict";
1020  EXPECT_EQ(0, bm_counters.num_updates_applied)
1021      << "No update that can't be decrypted should be applied";
1022
1023  const UpdateCounters& pw_counters = GetPasswordsUpdateCounters();
1024  EXPECT_EQ(1, pw_counters.num_encryption_conflict_application_failures)
1025      << "Updates that can't be decrypted should be in encryption conflict";
1026  EXPECT_EQ(0, pw_counters.num_updates_applied)
1027      << "No update that can't be decrypted should be applied";
1028
1029  {
1030    syncable::ReadTransaction trans(FROM_HERE, directory());
1031    syncable::Entry folder(&trans, syncable::GET_BY_HANDLE, folder_handle);
1032    syncable::Entry bm(&trans, syncable::GET_BY_HANDLE, bookmark_handle);
1033    syncable::Entry pw(&trans, syncable::GET_BY_HANDLE, password_handle);
1034    ASSERT_TRUE(folder.good());
1035    ASSERT_TRUE(bm.good());
1036    ASSERT_TRUE(pw.good());
1037    EXPECT_TRUE(folder.GetIsUnappliedUpdate());
1038    EXPECT_TRUE(bm.GetIsUnappliedUpdate());
1039    EXPECT_TRUE(pw.GetIsUnappliedUpdate());
1040  }
1041}
1042
1043// Test a mix of decryptable and undecryptable updates.
1044TEST_F(DirectoryUpdateHandlerApplyUpdateTest, SomeUndecryptablePassword) {
1045  Cryptographer* cryptographer;
1046
1047  int64 decryptable_handle = -1;
1048  int64 undecryptable_handle = -1;
1049
1050  // Only decryptable password updates should be applied.
1051  {
1052    sync_pb::EntitySpecifics specifics;
1053    sync_pb::PasswordSpecificsData data;
1054    data.set_origin("http://example.com/1");
1055    {
1056      syncable::ReadTransaction trans(FROM_HERE, directory());
1057      cryptographer = directory()->GetCryptographer(&trans);
1058
1059      KeyParams params = {"localhost", "dummy", "foobar"};
1060      cryptographer->AddKey(params);
1061
1062      cryptographer->Encrypt(data,
1063          specifics.mutable_password()->mutable_encrypted());
1064    }
1065    decryptable_handle =
1066        entry_factory()->CreateUnappliedNewItem("item1", specifics, false);
1067  }
1068  {
1069    // Create a new cryptographer, independent of the one in the session.
1070    Cryptographer other_cryptographer(cryptographer->encryptor());
1071    KeyParams params = {"localhost", "dummy", "bazqux"};
1072    other_cryptographer.AddKey(params);
1073
1074    sync_pb::EntitySpecifics specifics;
1075    sync_pb::PasswordSpecificsData data;
1076    data.set_origin("http://example.com/2");
1077
1078    other_cryptographer.Encrypt(data,
1079        specifics.mutable_password()->mutable_encrypted());
1080    undecryptable_handle =
1081        entry_factory()->CreateUnappliedNewItem("item2", specifics, false);
1082  }
1083
1084  sessions::StatusController status;
1085  ApplyPasswordUpdates(&status);
1086
1087  const UpdateCounters& counters = GetPasswordsUpdateCounters();
1088  EXPECT_EQ(1, counters.num_encryption_conflict_application_failures)
1089      << "The updates that can't be decrypted should be in encryption "
1090      << "conflict";
1091  EXPECT_EQ(1, counters.num_updates_applied)
1092      << "The undecryptable password update shouldn't be applied";
1093
1094  {
1095    syncable::ReadTransaction trans(FROM_HERE, directory());
1096    syncable::Entry e1(&trans, syncable::GET_BY_HANDLE, decryptable_handle);
1097    syncable::Entry e2(&trans, syncable::GET_BY_HANDLE, undecryptable_handle);
1098    ASSERT_TRUE(e1.good());
1099    ASSERT_TRUE(e2.good());
1100    EXPECT_FALSE(e1.GetIsUnappliedUpdate());
1101    EXPECT_TRUE(e2.GetIsUnappliedUpdate());
1102  }
1103}
1104
1105TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
1106       SimpleConflictDifferentAttachmentMetadata) {
1107  const bool is_folder = false;
1108  sync_pb::EntitySpecifics specifics;
1109  *specifics.mutable_article() = sync_pb::ArticleSpecifics();
1110  int64 handle = entry_factory()->CreateSyncedItem("art1", ARTICLES, is_folder);
1111
1112  sync_pb::AttachmentIdProto local_attachment_id = CreateAttachmentIdProto();
1113  sync_pb::AttachmentIdProto server_attachment_id = CreateAttachmentIdProto();
1114
1115  // Add an attachment to the local attachment metadata.
1116  sync_pb::AttachmentMetadata local_metadata;
1117  sync_pb::AttachmentMetadataRecord* local_record = local_metadata.add_record();
1118  *local_record->mutable_id() = local_attachment_id;
1119  local_record->set_is_on_server(true);
1120  entry_factory()->SetLocalAttachmentMetadataForItem(handle, local_metadata);
1121
1122  // Add a different attachment to the server attachment metadata.
1123  sync_pb::AttachmentMetadata server_metadata;
1124  sync_pb::AttachmentMetadataRecord* server_record =
1125      server_metadata.add_record();
1126  *server_record->mutable_id() = server_attachment_id;
1127  server_record->set_is_on_server(true);
1128  entry_factory()->SetServerAttachmentMetadataForItem(handle, server_metadata);
1129
1130  // At this point we have a simple conflict.  The server says art1 should have
1131  // server_attachment_id, but the local sync engine says it should have
1132  // local_attachment_id.
1133
1134  sessions::StatusController status;
1135  ApplyArticlesUpdates(&status);
1136
1137  // See that the server won.
1138  const UpdateCounters& counters = GetArticlesUpdateCounters();
1139  EXPECT_EQ(1, counters.num_updates_applied);
1140  EXPECT_EQ(1, counters.num_local_overwrites);
1141  EXPECT_EQ(0, counters.num_server_overwrites);
1142  local_metadata = entry_factory()->GetLocalAttachmentMetadataForItem(handle);
1143  EXPECT_EQ(server_metadata.SerializeAsString(),
1144            local_metadata.SerializeAsString());
1145}
1146
1147TEST_F(DirectoryUpdateHandlerApplyUpdateTest,
1148       SimpleConflictSameAttachmentMetadataDifferentOrder) {
1149  const bool is_folder = false;
1150  sync_pb::EntitySpecifics specifics;
1151  *specifics.mutable_article() = sync_pb::ArticleSpecifics();
1152  int64 handle = entry_factory()->CreateSyncedItem("art1", ARTICLES, is_folder);
1153
1154  sync_pb::AttachmentIdProto id1 = CreateAttachmentIdProto();
1155  sync_pb::AttachmentIdProto id2 = CreateAttachmentIdProto();
1156
1157  // Add id1, then id2 to the local attachment metadata.
1158  sync_pb::AttachmentMetadata local_metadata;
1159  sync_pb::AttachmentMetadataRecord* record = local_metadata.add_record();
1160  *record->mutable_id() = id1;
1161  record->set_is_on_server(true);
1162  record = local_metadata.add_record();
1163  *record->mutable_id() = id2;
1164  record->set_is_on_server(true);
1165  entry_factory()->SetLocalAttachmentMetadataForItem(handle, local_metadata);
1166
1167  // Add id1 and id2 to the server attachment metadata, but in reverse order.
1168  sync_pb::AttachmentMetadata server_metadata;
1169  record = server_metadata.add_record();
1170  *record->mutable_id() = id2;
1171  record->set_is_on_server(true);
1172  record = local_metadata.add_record();
1173  *record->mutable_id() = id1;
1174  record->set_is_on_server(true);
1175  entry_factory()->SetServerAttachmentMetadataForItem(handle, server_metadata);
1176
1177  // At this point we have a (false) conflict.
1178
1179  sessions::StatusController status;
1180  ApplyArticlesUpdates(&status);
1181
1182  // See that the server won.
1183  const UpdateCounters& counters = GetArticlesUpdateCounters();
1184  EXPECT_EQ(1, counters.num_updates_applied);
1185  EXPECT_EQ(1, counters.num_local_overwrites);
1186  EXPECT_EQ(0, counters.num_server_overwrites);
1187  local_metadata = entry_factory()->GetLocalAttachmentMetadataForItem(handle);
1188  EXPECT_EQ(server_metadata.SerializeAsString(),
1189            local_metadata.SerializeAsString());
1190}
1191
1192}  // namespace syncer
1193