Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / sync / engine / model_type_sync_worker_impl_unittest.cc
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/model_type_sync_worker_impl.h"
6
7 #include "base/strings/stringprintf.h"
8 #include "sync/engine/commit_contribution.h"
9 #include "sync/engine/model_type_sync_proxy.h"
10 #include "sync/internal_api/public/base/model_type.h"
11 #include "sync/internal_api/public/non_blocking_sync_common.h"
12 #include "sync/protocol/sync.pb.h"
13 #include "sync/sessions/status_controller.h"
14 #include "sync/syncable/syncable_util.h"
15 #include "sync/test/engine/mock_model_type_sync_proxy.h"
16 #include "sync/test/engine/mock_nudge_handler.h"
17 #include "sync/test/engine/single_type_mock_server.h"
18 #include "sync/test/fake_encryptor.h"
19
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 static const std::string kTypeParentId = "PrefsRootNodeID";
23 static const syncer::ModelType kModelType = syncer::PREFERENCES;
24
25 // Special constant value taken from cryptographer.cc.
26 const char kNigoriKeyName[] = "nigori-key";
27
28 namespace syncer {
29
30 // Tests the ModelTypeSyncWorkerImpl.
31 //
32 // This class passes messages between the model thread and sync server.
33 // As such, its code is subject to lots of different race conditions.  This
34 // test harness lets us exhaustively test all possible races.  We try to
35 // focus on just a few interesting cases.
36 //
37 // Inputs:
38 // - Initial data type state from the model thread.
39 // - Commit requests from the model thread.
40 // - Update responses from the server.
41 // - Commit responses from the server.
42 // - The cryptographer, if encryption is enabled.
43 //
44 // Outputs:
45 // - Commit requests to the server.
46 // - Commit responses to the model thread.
47 // - Update responses to the model thread.
48 // - Nudges to the sync scheduler.
49 //
50 // We use the MockModelTypeSyncProxy to stub out all communication
51 // with the model thread.  That interface is synchronous, which makes it
52 // much easier to test races.
53 //
54 // The interface with the server is built around "pulling" data from this
55 // class, so we don't have to mock out any of it.  We wrap it with some
56 // convenience functions to we can emulate server behavior.
57 class ModelTypeSyncWorkerImplTest : public ::testing::Test {
58  public:
59   ModelTypeSyncWorkerImplTest();
60   virtual ~ModelTypeSyncWorkerImplTest();
61
62   // One of these Initialize functions should be called at the beginning of
63   // each test.
64
65   // Initializes with no data type state.  We will be unable to perform any
66   // significant server action until we receive an update response that
67   // contains the type root node for this type.
68   void FirstInitialize();
69
70   // Initializes with some existing data type state.  Allows us to start
71   // committing items right away.
72   void NormalInitialize();
73
74   // Initialize with some saved pending updates from the model thread.
75   void InitializeWithPendingUpdates(
76       const UpdateResponseDataList& initial_pending_updates);
77
78   // Initialize with a custom initial DataTypeState and pending updates.
79   void InitializeWithState(const DataTypeState& state,
80                            const UpdateResponseDataList& pending_updates);
81
82   // Introduce a new key that the local cryptographer can't decrypt.
83   void NewForeignEncryptionKey();
84
85   // Update the local cryptographer with all relevant keys.
86   void UpdateLocalCryptographer();
87
88   // Use the Nth nigori instance to encrypt incoming updates.
89   // The default value, zero, indicates no encryption.
90   void SetUpdateEncryptionFilter(int n);
91
92   // Modifications on the model thread that get sent to the worker under test.
93   void CommitRequest(const std::string& tag, const std::string& value);
94   void DeleteRequest(const std::string& tag);
95
96   // Pretends to receive update messages from the server.
97   void TriggerTypeRootUpdateFromServer();
98   void TriggerUpdateFromServer(int64 version_offset,
99                                const std::string& tag,
100                                const std::string& value);
101   void TriggerTombstoneFromServer(int64 version_offset, const std::string& tag);
102
103   // Delivers specified protos as updates.
104   //
105   // Does not update mock server state.  Should be used as a last resort when
106   // writing test cases that require entities that don't fit the normal sync
107   // protocol.  Try to use the other, higher level methods if possible.
108   void DeliverRawUpdates(const SyncEntityList& update_list);
109
110   // By default, this harness behaves as if all tasks posted to the model
111   // thread are executed immediately.  However, this is not necessarily true.
112   // The model's TaskRunner has a queue, and the tasks we post to it could
113   // linger there for a while.  In the meantime, the model thread could
114   // continue posting tasks to the worker based on its stale state.
115   //
116   // If you want to test those race cases, then these functions are for you.
117   void SetModelThreadIsSynchronous(bool is_synchronous);
118   void PumpModelThread();
119
120   // Returns true if the |worker_| is ready to commit something.
121   bool WillCommit();
122
123   // Pretend to successfully commit all outstanding unsynced items.
124   // It is safe to call this only if WillCommit() returns true.
125   void DoSuccessfulCommit();
126
127   // Read commit messages the worker_ sent to the emulated server.
128   size_t GetNumCommitMessagesOnServer() const;
129   sync_pb::ClientToServerMessage GetNthCommitMessageOnServer(size_t n) const;
130
131   // Read the latest version of sync entities committed to the emulated server.
132   bool HasCommitEntityOnServer(const std::string& tag) const;
133   sync_pb::SyncEntity GetLatestCommitEntityOnServer(
134       const std::string& tag) const;
135
136   // Read the latest update messages received on the model thread.
137   // Note that if the model thread is in non-blocking mode, this data will not
138   // be updated until the response is actually processed by the model thread.
139   size_t GetNumModelThreadUpdateResponses() const;
140   UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const;
141   UpdateResponseDataList GetNthModelThreadPendingUpdates(size_t n) const;
142   DataTypeState GetNthModelThreadUpdateState(size_t n) const;
143
144   // Reads the latest update response datas on the model thread.
145   // Note that if the model thread is in non-blocking mode, this data will not
146   // be updated until the response is actually processed by the model thread.
147   bool HasUpdateResponseOnModelThread(const std::string& tag) const;
148   UpdateResponseData GetUpdateResponseOnModelThread(
149       const std::string& tag) const;
150
151   // Read the latest commit messages received on the model thread.
152   // Note that if the model thread is in non-blocking mode, this data will not
153   // be updated until the response is actually processed by the model thread.
154   size_t GetNumModelThreadCommitResponses() const;
155   CommitResponseDataList GetNthModelThreadCommitResponse(size_t n) const;
156   DataTypeState GetNthModelThreadCommitState(size_t n) const;
157
158   // Reads the latest commit response datas on the model thread.
159   // Note that if the model thread is in non-blocking mode, this data will not
160   // be updated until the response is actually processed by the model thread.
161   bool HasCommitResponseOnModelThread(const std::string& tag) const;
162   CommitResponseData GetCommitResponseOnModelThread(
163       const std::string& tag) const;
164
165   // Returns the number of commit nudges sent to the mock nudge handler.
166   int GetNumCommitNudges() const;
167
168   // Returns the number of initial sync nudges sent to the mock nudge handler.
169   int GetNumInitialDownloadNudges() const;
170
171   // Returns the name of the encryption key in the cryptographer last passed to
172   // the ModelTypeSyncWorker.  Returns an empty string if no crypgorapher is
173   // in use.  See also: UpdateLocalCryptographer().
174   std::string GetLocalCryptographerKeyName() const;
175
176   // Helpers for building various messages and structures.
177   static std::string GenerateTagHash(const std::string& tag);
178   static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag,
179                                                     const std::string& value);
180
181   // Returns a set of KeyParams for the cryptographer.  Each input 'n' value
182   // results in a different set of parameters.
183   static KeyParams GetNthKeyParams(int n);
184
185   // Returns the name for the given Nigori.
186   //
187   // Uses some 'white-box' knowledge to mimic the names that a real sync client
188   // would generate.  It's probably not necessary to do so, but it can't hurt.
189   static std::string GetNigoriName(const Nigori& nigori);
190
191   // Modifies the input/output parameter |specifics| by encrypting it with
192   // a Nigori intialized with the specified KeyParams.
193   static void EncryptUpdate(const KeyParams& params,
194                             sync_pb::EntitySpecifics* specifics);
195
196  private:
197   // An encryptor for our cryptographer.
198   FakeEncryptor fake_encryptor_;
199
200   // The cryptographer itself.  NULL if we're not encrypting the type.
201   scoped_ptr<Cryptographer> cryptographer_;
202
203   // The number of the most recent foreign encryption key known to our
204   // cryptographer.  Note that not all of these will be decryptable.
205   int foreign_encryption_key_index_;
206
207   // The number of the encryption key used to encrypt incoming updates.  A zero
208   // value implies no encryption.
209   int update_encryption_filter_index_;
210
211   // The ModelTypeSyncWorkerImpl being tested.
212   scoped_ptr<ModelTypeSyncWorkerImpl> worker_;
213
214   // Non-owned, possibly NULL pointer.  This object belongs to the
215   // ModelTypeSyncWorkerImpl under test.
216   MockModelTypeSyncProxy* mock_type_sync_proxy_;
217
218   // A mock that emulates enough of the sync server that it can be used
219   // a single UpdateHandler and CommitContributor pair.  In this test
220   // harness, the |worker_| is both of them.
221   SingleTypeMockServer mock_server_;
222
223   // A mock to track the number of times the ModelTypeSyncWorker requests to
224   // sync.
225   MockNudgeHandler mock_nudge_handler_;
226 };
227
228 ModelTypeSyncWorkerImplTest::ModelTypeSyncWorkerImplTest()
229     : foreign_encryption_key_index_(0),
230       update_encryption_filter_index_(0),
231       mock_type_sync_proxy_(NULL),
232       mock_server_(kModelType) {
233 }
234
235 ModelTypeSyncWorkerImplTest::~ModelTypeSyncWorkerImplTest() {
236 }
237
238 void ModelTypeSyncWorkerImplTest::FirstInitialize() {
239   DataTypeState initial_state;
240   initial_state.progress_marker.set_data_type_id(
241       GetSpecificsFieldNumberFromModelType(kModelType));
242   initial_state.next_client_id = 0;
243
244   InitializeWithState(initial_state, UpdateResponseDataList());
245 }
246
247 void ModelTypeSyncWorkerImplTest::NormalInitialize() {
248   InitializeWithPendingUpdates(UpdateResponseDataList());
249 }
250
251 void ModelTypeSyncWorkerImplTest::InitializeWithPendingUpdates(
252     const UpdateResponseDataList& initial_pending_updates) {
253   DataTypeState initial_state;
254   initial_state.progress_marker.set_data_type_id(
255       GetSpecificsFieldNumberFromModelType(kModelType));
256   initial_state.progress_marker.set_token("some_saved_progress_token");
257
258   initial_state.next_client_id = 10;
259   initial_state.type_root_id = kTypeParentId;
260   initial_state.initial_sync_done = true;
261
262   InitializeWithState(initial_state, initial_pending_updates);
263
264   mock_nudge_handler_.ClearCounters();
265 }
266
267 void ModelTypeSyncWorkerImplTest::InitializeWithState(
268     const DataTypeState& state,
269     const UpdateResponseDataList& initial_pending_updates) {
270   DCHECK(!worker_);
271
272   // We don't get to own this object.  The |worker_| keeps a scoped_ptr to it.
273   mock_type_sync_proxy_ = new MockModelTypeSyncProxy();
274   scoped_ptr<ModelTypeSyncProxy> proxy(mock_type_sync_proxy_);
275
276   scoped_ptr<Cryptographer> cryptographer_copy;
277   if (cryptographer_) {
278     cryptographer_copy.reset(new Cryptographer(*cryptographer_));
279   }
280
281   worker_.reset(new ModelTypeSyncWorkerImpl(kModelType,
282                                             state,
283                                             initial_pending_updates,
284                                             cryptographer_copy.Pass(),
285                                             &mock_nudge_handler_,
286                                             proxy.Pass()));
287 }
288
289 void ModelTypeSyncWorkerImplTest::NewForeignEncryptionKey() {
290   if (!cryptographer_) {
291     cryptographer_.reset(new Cryptographer(&fake_encryptor_));
292   }
293
294   foreign_encryption_key_index_++;
295
296   sync_pb::NigoriKeyBag bag;
297
298   for (int i = 0; i <= foreign_encryption_key_index_; ++i) {
299     Nigori nigori;
300     KeyParams params = GetNthKeyParams(i);
301     nigori.InitByDerivation(params.hostname, params.username, params.password);
302
303     sync_pb::NigoriKey* key = bag.add_key();
304
305     key->set_name(GetNigoriName(nigori));
306     nigori.ExportKeys(key->mutable_user_key(),
307                       key->mutable_encryption_key(),
308                       key->mutable_mac_key());
309   }
310
311   // Re-create the last nigori from that loop.
312   Nigori last_nigori;
313   KeyParams params = GetNthKeyParams(foreign_encryption_key_index_);
314   last_nigori.InitByDerivation(
315       params.hostname, params.username, params.password);
316
317   // Serialize and encrypt the bag with the last nigori.
318   std::string serialized_bag;
319   bag.SerializeToString(&serialized_bag);
320
321   sync_pb::EncryptedData encrypted;
322   encrypted.set_key_name(GetNigoriName(last_nigori));
323   last_nigori.Encrypt(serialized_bag, encrypted.mutable_blob());
324
325   // Update the cryptographer with new pending keys.
326   cryptographer_->SetPendingKeys(encrypted);
327
328   // Update the worker with the latest cryptographer.
329   if (worker_) {
330     worker_->UpdateCryptographer(
331         make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_)));
332   }
333 }
334
335 void ModelTypeSyncWorkerImplTest::UpdateLocalCryptographer() {
336   if (!cryptographer_) {
337     cryptographer_.reset(new Cryptographer(&fake_encryptor_));
338   }
339
340   KeyParams params = GetNthKeyParams(foreign_encryption_key_index_);
341   bool success = cryptographer_->DecryptPendingKeys(params);
342   DCHECK(success);
343
344   // Update the worker with the latest cryptographer.
345   if (worker_) {
346     worker_->UpdateCryptographer(
347         make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_)));
348   }
349 }
350
351 void ModelTypeSyncWorkerImplTest::SetUpdateEncryptionFilter(int n) {
352   update_encryption_filter_index_ = n;
353 }
354
355 void ModelTypeSyncWorkerImplTest::CommitRequest(const std::string& name,
356                                                 const std::string& value) {
357   const std::string tag_hash = GenerateTagHash(name);
358   CommitRequestData data = mock_type_sync_proxy_->CommitRequest(
359       tag_hash, GenerateSpecifics(name, value));
360   CommitRequestDataList list;
361   list.push_back(data);
362   worker_->EnqueueForCommit(list);
363 }
364
365 void ModelTypeSyncWorkerImplTest::DeleteRequest(const std::string& tag) {
366   const std::string tag_hash = GenerateTagHash(tag);
367   CommitRequestData data = mock_type_sync_proxy_->DeleteRequest(tag_hash);
368   CommitRequestDataList list;
369   list.push_back(data);
370   worker_->EnqueueForCommit(list);
371 }
372
373 void ModelTypeSyncWorkerImplTest::TriggerTypeRootUpdateFromServer() {
374   sync_pb::SyncEntity entity = mock_server_.TypeRootUpdate();
375   SyncEntityList entity_list;
376   entity_list.push_back(&entity);
377
378   sessions::StatusController dummy_status;
379
380   worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
381                                      mock_server_.GetContext(),
382                                      entity_list,
383                                      &dummy_status);
384   worker_->ApplyUpdates(&dummy_status);
385 }
386
387 void ModelTypeSyncWorkerImplTest::TriggerUpdateFromServer(
388     int64 version_offset,
389     const std::string& tag,
390     const std::string& value) {
391   sync_pb::SyncEntity entity = mock_server_.UpdateFromServer(
392       version_offset, GenerateTagHash(tag), GenerateSpecifics(tag, value));
393
394   if (update_encryption_filter_index_ != 0) {
395     EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_),
396                   entity.mutable_specifics());
397   }
398
399   SyncEntityList entity_list;
400   entity_list.push_back(&entity);
401
402   sessions::StatusController dummy_status;
403
404   worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
405                                      mock_server_.GetContext(),
406                                      entity_list,
407                                      &dummy_status);
408   worker_->ApplyUpdates(&dummy_status);
409 }
410
411 void ModelTypeSyncWorkerImplTest::DeliverRawUpdates(
412     const SyncEntityList& list) {
413   sessions::StatusController dummy_status;
414   worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
415                                      mock_server_.GetContext(),
416                                      list,
417                                      &dummy_status);
418   worker_->ApplyUpdates(&dummy_status);
419 }
420
421 void ModelTypeSyncWorkerImplTest::TriggerTombstoneFromServer(
422     int64 version_offset,
423     const std::string& tag) {
424   sync_pb::SyncEntity entity =
425       mock_server_.TombstoneFromServer(version_offset, GenerateTagHash(tag));
426
427   if (update_encryption_filter_index_ != 0) {
428     EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_),
429                   entity.mutable_specifics());
430   }
431
432   SyncEntityList entity_list;
433   entity_list.push_back(&entity);
434
435   sessions::StatusController dummy_status;
436
437   worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
438                                      mock_server_.GetContext(),
439                                      entity_list,
440                                      &dummy_status);
441   worker_->ApplyUpdates(&dummy_status);
442 }
443
444 void ModelTypeSyncWorkerImplTest::SetModelThreadIsSynchronous(
445     bool is_synchronous) {
446   mock_type_sync_proxy_->SetSynchronousExecution(is_synchronous);
447 }
448
449 void ModelTypeSyncWorkerImplTest::PumpModelThread() {
450   mock_type_sync_proxy_->RunQueuedTasks();
451 }
452
453 bool ModelTypeSyncWorkerImplTest::WillCommit() {
454   scoped_ptr<CommitContribution> contribution(
455       worker_->GetContribution(INT_MAX));
456
457   if (contribution) {
458     contribution->CleanUp();  // Gracefully abort the commit.
459     return true;
460   } else {
461     return false;
462   }
463 }
464
465 // Conveniently, this is all one big synchronous operation.  The sync thread
466 // remains blocked while the commit is in progress, so we don't need to worry
467 // about other tasks being run between the time when the commit request is
468 // issued and the time when the commit response is received.
469 void ModelTypeSyncWorkerImplTest::DoSuccessfulCommit() {
470   DCHECK(WillCommit());
471   scoped_ptr<CommitContribution> contribution(
472       worker_->GetContribution(INT_MAX));
473
474   sync_pb::ClientToServerMessage message;
475   contribution->AddToCommitMessage(&message);
476
477   sync_pb::ClientToServerResponse response =
478       mock_server_.DoSuccessfulCommit(message);
479
480   sessions::StatusController dummy_status;
481   contribution->ProcessCommitResponse(response, &dummy_status);
482   contribution->CleanUp();
483 }
484
485 size_t ModelTypeSyncWorkerImplTest::GetNumCommitMessagesOnServer() const {
486   return mock_server_.GetNumCommitMessages();
487 }
488
489 sync_pb::ClientToServerMessage
490 ModelTypeSyncWorkerImplTest::GetNthCommitMessageOnServer(size_t n) const {
491   DCHECK_LT(n, GetNumCommitMessagesOnServer());
492   return mock_server_.GetNthCommitMessage(n);
493 }
494
495 bool ModelTypeSyncWorkerImplTest::HasCommitEntityOnServer(
496     const std::string& tag) const {
497   const std::string tag_hash = GenerateTagHash(tag);
498   return mock_server_.HasCommitEntity(tag_hash);
499 }
500
501 sync_pb::SyncEntity ModelTypeSyncWorkerImplTest::GetLatestCommitEntityOnServer(
502     const std::string& tag) const {
503   DCHECK(HasCommitEntityOnServer(tag));
504   const std::string tag_hash = GenerateTagHash(tag);
505   return mock_server_.GetLastCommittedEntity(tag_hash);
506 }
507
508 size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadUpdateResponses() const {
509   return mock_type_sync_proxy_->GetNumUpdateResponses();
510 }
511
512 UpdateResponseDataList
513 ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateResponse(size_t n) const {
514   DCHECK_LT(n, GetNumModelThreadUpdateResponses());
515   return mock_type_sync_proxy_->GetNthUpdateResponse(n);
516 }
517
518 UpdateResponseDataList
519 ModelTypeSyncWorkerImplTest::GetNthModelThreadPendingUpdates(size_t n) const {
520   DCHECK_LT(n, GetNumModelThreadUpdateResponses());
521   return mock_type_sync_proxy_->GetNthPendingUpdates(n);
522 }
523
524 DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateState(
525     size_t n) const {
526   DCHECK_LT(n, GetNumModelThreadUpdateResponses());
527   return mock_type_sync_proxy_->GetNthTypeStateReceivedInUpdateResponse(n);
528 }
529
530 bool ModelTypeSyncWorkerImplTest::HasUpdateResponseOnModelThread(
531     const std::string& tag) const {
532   const std::string tag_hash = GenerateTagHash(tag);
533   return mock_type_sync_proxy_->HasUpdateResponse(tag_hash);
534 }
535
536 UpdateResponseData ModelTypeSyncWorkerImplTest::GetUpdateResponseOnModelThread(
537     const std::string& tag) const {
538   const std::string tag_hash = GenerateTagHash(tag);
539   return mock_type_sync_proxy_->GetUpdateResponse(tag_hash);
540 }
541
542 size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadCommitResponses() const {
543   return mock_type_sync_proxy_->GetNumCommitResponses();
544 }
545
546 CommitResponseDataList
547 ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitResponse(size_t n) const {
548   DCHECK_LT(n, GetNumModelThreadCommitResponses());
549   return mock_type_sync_proxy_->GetNthCommitResponse(n);
550 }
551
552 DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitState(
553     size_t n) const {
554   DCHECK_LT(n, GetNumModelThreadCommitResponses());
555   return mock_type_sync_proxy_->GetNthTypeStateReceivedInCommitResponse(n);
556 }
557
558 bool ModelTypeSyncWorkerImplTest::HasCommitResponseOnModelThread(
559     const std::string& tag) const {
560   const std::string tag_hash = GenerateTagHash(tag);
561   return mock_type_sync_proxy_->HasCommitResponse(tag_hash);
562 }
563
564 CommitResponseData ModelTypeSyncWorkerImplTest::GetCommitResponseOnModelThread(
565     const std::string& tag) const {
566   DCHECK(HasCommitResponseOnModelThread(tag));
567   const std::string tag_hash = GenerateTagHash(tag);
568   return mock_type_sync_proxy_->GetCommitResponse(tag_hash);
569 }
570
571 int ModelTypeSyncWorkerImplTest::GetNumCommitNudges() const {
572   return mock_nudge_handler_.GetNumCommitNudges();
573 }
574
575 int ModelTypeSyncWorkerImplTest::GetNumInitialDownloadNudges() const {
576   return mock_nudge_handler_.GetNumInitialDownloadNudges();
577 }
578
579 std::string ModelTypeSyncWorkerImplTest::GetLocalCryptographerKeyName() const {
580   if (!cryptographer_) {
581     return std::string();
582   }
583
584   return cryptographer_->GetDefaultNigoriKeyName();
585 }
586
587 // static.
588 std::string ModelTypeSyncWorkerImplTest::GenerateTagHash(
589     const std::string& tag) {
590   const std::string& client_tag_hash =
591       syncable::GenerateSyncableHash(kModelType, tag);
592   return client_tag_hash;
593 }
594
595 // static.
596 sync_pb::EntitySpecifics ModelTypeSyncWorkerImplTest::GenerateSpecifics(
597     const std::string& tag,
598     const std::string& value) {
599   sync_pb::EntitySpecifics specifics;
600   specifics.mutable_preference()->set_name(tag);
601   specifics.mutable_preference()->set_value(value);
602   return specifics;
603 }
604
605 // static.
606 std::string ModelTypeSyncWorkerImplTest::GetNigoriName(const Nigori& nigori) {
607   std::string name;
608   if (!nigori.Permute(Nigori::Password, kNigoriKeyName, &name)) {
609     NOTREACHED();
610     return std::string();
611   }
612
613   return name;
614 }
615
616 // static.
617 KeyParams ModelTypeSyncWorkerImplTest::GetNthKeyParams(int n) {
618   KeyParams params;
619   params.hostname = std::string("localhost");
620   params.username = std::string("userX");
621   params.password = base::StringPrintf("pw%02d", n);
622   return params;
623 }
624
625 // static.
626 void ModelTypeSyncWorkerImplTest::EncryptUpdate(
627     const KeyParams& params,
628     sync_pb::EntitySpecifics* specifics) {
629   Nigori nigori;
630   nigori.InitByDerivation(params.hostname, params.username, params.password);
631
632   sync_pb::EntitySpecifics original_specifics = *specifics;
633   std::string plaintext;
634   original_specifics.SerializeToString(&plaintext);
635
636   std::string encrypted;
637   nigori.Encrypt(plaintext, &encrypted);
638
639   specifics->Clear();
640   AddDefaultFieldValue(kModelType, specifics);
641   specifics->mutable_encrypted()->set_key_name(GetNigoriName(nigori));
642   specifics->mutable_encrypted()->set_blob(encrypted);
643 }
644
645 // Requests a commit and verifies the messages sent to the client and server as
646 // a result.
647 //
648 // This test performs sanity checks on most of the fields in these messages.
649 // For the most part this is checking that the test code behaves as expected
650 // and the |worker_| doesn't mess up its simple task of moving around these
651 // values.  It makes sense to have one or two tests that are this thorough, but
652 // we shouldn't be this verbose in all tests.
653 TEST_F(ModelTypeSyncWorkerImplTest, SimpleCommit) {
654   NormalInitialize();
655
656   EXPECT_FALSE(WillCommit());
657   EXPECT_EQ(0U, GetNumCommitMessagesOnServer());
658   EXPECT_EQ(0U, GetNumModelThreadCommitResponses());
659
660   CommitRequest("tag1", "value1");
661
662   EXPECT_EQ(1, GetNumCommitNudges());
663
664   ASSERT_TRUE(WillCommit());
665   DoSuccessfulCommit();
666
667   const std::string& client_tag_hash = GenerateTagHash("tag1");
668
669   // Exhaustively verify the SyncEntity sent in the commit message.
670   ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
671   EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
672   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
673   const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
674   EXPECT_FALSE(entity.id_string().empty());
675   EXPECT_EQ(kTypeParentId, entity.parent_id_string());
676   EXPECT_EQ(kUncommittedVersion, entity.version());
677   EXPECT_NE(0, entity.mtime());
678   EXPECT_NE(0, entity.ctime());
679   EXPECT_FALSE(entity.name().empty());
680   EXPECT_EQ(client_tag_hash, entity.client_defined_unique_tag());
681   EXPECT_EQ("tag1", entity.specifics().preference().name());
682   EXPECT_FALSE(entity.deleted());
683   EXPECT_EQ("value1", entity.specifics().preference().value());
684
685   // Exhaustively verify the commit response returned to the model thread.
686   ASSERT_EQ(1U, GetNumModelThreadCommitResponses());
687   EXPECT_EQ(1U, GetNthModelThreadCommitResponse(0).size());
688   ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
689   const CommitResponseData& commit_response =
690       GetCommitResponseOnModelThread("tag1");
691
692   // The ID changes in a commit response to initial commit.
693   EXPECT_FALSE(commit_response.id.empty());
694   EXPECT_NE(entity.id_string(), commit_response.id);
695
696   EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash);
697   EXPECT_LT(0, commit_response.response_version);
698 }
699
700 TEST_F(ModelTypeSyncWorkerImplTest, SimpleDelete) {
701   NormalInitialize();
702
703   // We can't delete an entity that was never committed.
704   // Step 1 is to create and commit a new entity.
705   CommitRequest("tag1", "value1");
706   EXPECT_EQ(1, GetNumCommitNudges());
707   ASSERT_TRUE(WillCommit());
708   DoSuccessfulCommit();
709
710   ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
711   const CommitResponseData& initial_commit_response =
712       GetCommitResponseOnModelThread("tag1");
713   int64 base_version = initial_commit_response.response_version;
714
715   // Now that we have an entity, we can delete it.
716   DeleteRequest("tag1");
717   ASSERT_TRUE(WillCommit());
718   DoSuccessfulCommit();
719
720   // Verify the SyncEntity sent in the commit message.
721   ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
722   EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
723   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
724   const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
725   EXPECT_FALSE(entity.id_string().empty());
726   EXPECT_EQ(GenerateTagHash("tag1"), entity.client_defined_unique_tag());
727   EXPECT_EQ(base_version, entity.version());
728   EXPECT_TRUE(entity.deleted());
729
730   // Deletions should contain enough specifics to identify the type.
731   EXPECT_TRUE(entity.has_specifics());
732   EXPECT_EQ(kModelType, GetModelTypeFromSpecifics(entity.specifics()));
733
734   // Verify the commit response returned to the model thread.
735   ASSERT_EQ(2U, GetNumModelThreadCommitResponses());
736   EXPECT_EQ(1U, GetNthModelThreadCommitResponse(1).size());
737   ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
738   const CommitResponseData& commit_response =
739       GetCommitResponseOnModelThread("tag1");
740
741   EXPECT_EQ(entity.id_string(), commit_response.id);
742   EXPECT_EQ(entity.client_defined_unique_tag(),
743             commit_response.client_tag_hash);
744   EXPECT_EQ(entity.version(), commit_response.response_version);
745 }
746
747 // The server doesn't like it when we try to delete an entity it's never heard
748 // of before.  This test helps ensure we avoid that scenario.
749 TEST_F(ModelTypeSyncWorkerImplTest, NoDeleteUncommitted) {
750   NormalInitialize();
751
752   // Request the commit of a new, never-before-seen item.
753   CommitRequest("tag1", "value1");
754   EXPECT_TRUE(WillCommit());
755   EXPECT_EQ(1, GetNumCommitNudges());
756
757   // Request a deletion of that item before we've had a chance to commit it.
758   DeleteRequest("tag1");
759   EXPECT_FALSE(WillCommit());
760   EXPECT_EQ(2, GetNumCommitNudges());
761 }
762
763 // Verifies the sending of an "initial sync done" signal.
764 TEST_F(ModelTypeSyncWorkerImplTest, SendInitialSyncDone) {
765   FirstInitialize();  // Initialize with no saved sync state.
766   EXPECT_EQ(0U, GetNumModelThreadUpdateResponses());
767   EXPECT_EQ(1, GetNumInitialDownloadNudges());
768
769   // Receive an update response that contains only the type root node.
770   TriggerTypeRootUpdateFromServer();
771
772   // Two updates:
773   // - One triggered by process updates to forward the type root ID.
774   // - One triggered by apply updates, which the worker interprets to mean
775   //   "initial sync done".  This triggers a model thread update, too.
776   EXPECT_EQ(2U, GetNumModelThreadUpdateResponses());
777
778   // The type root and initial sync done updates both contain no entities.
779   EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(0).size());
780   EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(1).size());
781
782   const DataTypeState& state = GetNthModelThreadUpdateState(1);
783   EXPECT_FALSE(state.progress_marker.token().empty());
784   EXPECT_FALSE(state.type_root_id.empty());
785   EXPECT_TRUE(state.initial_sync_done);
786 }
787
788 // Commit two new entities in two separate commit messages.
789 TEST_F(ModelTypeSyncWorkerImplTest, TwoNewItemsCommittedSeparately) {
790   NormalInitialize();
791
792   // Commit the first of two entities.
793   CommitRequest("tag1", "value1");
794   EXPECT_EQ(1, GetNumCommitNudges());
795   ASSERT_TRUE(WillCommit());
796   DoSuccessfulCommit();
797   ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
798   EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
799   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
800   const sync_pb::SyncEntity& tag1_entity =
801       GetLatestCommitEntityOnServer("tag1");
802
803   // Commit the second of two entities.
804   CommitRequest("tag2", "value2");
805   EXPECT_EQ(2, GetNumCommitNudges());
806   ASSERT_TRUE(WillCommit());
807   DoSuccessfulCommit();
808   ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
809   EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
810   ASSERT_TRUE(HasCommitEntityOnServer("tag2"));
811   const sync_pb::SyncEntity& tag2_entity =
812       GetLatestCommitEntityOnServer("tag2");
813
814   EXPECT_FALSE(WillCommit());
815
816   // The IDs assigned by the |worker_| should be unique.
817   EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string());
818
819   // Check that the committed specifics values are sane.
820   EXPECT_EQ(tag1_entity.specifics().preference().value(), "value1");
821   EXPECT_EQ(tag2_entity.specifics().preference().value(), "value2");
822
823   // There should have been two separate commit responses sent to the model
824   // thread.  They should be uninteresting, so we don't bother inspecting them.
825   EXPECT_EQ(2U, GetNumModelThreadCommitResponses());
826 }
827
828 // Test normal update receipt code path.
829 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUpdates) {
830   NormalInitialize();
831
832   const std::string& tag_hash = GenerateTagHash("tag1");
833
834   TriggerUpdateFromServer(10, "tag1", "value1");
835
836   ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
837   UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
838   ASSERT_EQ(1U, updates_list.size());
839
840   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
841   UpdateResponseData update = GetUpdateResponseOnModelThread("tag1");
842
843   EXPECT_FALSE(update.id.empty());
844   EXPECT_EQ(tag_hash, update.client_tag_hash);
845   EXPECT_LT(0, update.response_version);
846   EXPECT_FALSE(update.ctime.is_null());
847   EXPECT_FALSE(update.mtime.is_null());
848   EXPECT_FALSE(update.non_unique_name.empty());
849   EXPECT_FALSE(update.deleted);
850   EXPECT_EQ("tag1", update.specifics.preference().name());
851   EXPECT_EQ("value1", update.specifics.preference().value());
852 }
853
854 // Test commit of encrypted updates.
855 TEST_F(ModelTypeSyncWorkerImplTest, EncryptedCommit) {
856   NormalInitialize();
857
858   ASSERT_EQ(0U, GetNumModelThreadUpdateResponses());
859
860   NewForeignEncryptionKey();
861   UpdateLocalCryptographer();
862
863   ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
864   EXPECT_EQ(GetLocalCryptographerKeyName(),
865             GetNthModelThreadUpdateState(0).encryption_key_name);
866
867   // Normal commit request stuff.
868   CommitRequest("tag1", "value1");
869   DoSuccessfulCommit();
870   ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
871   EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
872   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
873   const sync_pb::SyncEntity& tag1_entity =
874       GetLatestCommitEntityOnServer("tag1");
875
876   EXPECT_TRUE(tag1_entity.specifics().has_encrypted());
877
878   // The title should be overwritten.
879   EXPECT_EQ(tag1_entity.name(), "encrypted");
880
881   // The type should be set, but there should be no non-encrypted contents.
882   EXPECT_TRUE(tag1_entity.specifics().has_preference());
883   EXPECT_FALSE(tag1_entity.specifics().preference().has_name());
884   EXPECT_FALSE(tag1_entity.specifics().preference().has_value());
885 }
886
887 // Test items are not committed when encryption is required but unavailable.
888 TEST_F(ModelTypeSyncWorkerImplTest, EncryptionBlocksCommits) {
889   NormalInitialize();
890
891   CommitRequest("tag1", "value1");
892   EXPECT_TRUE(WillCommit());
893
894   // We know encryption is in use on this account, but don't have the necessary
895   // encryption keys.  The worker should refuse to commit.
896   NewForeignEncryptionKey();
897   EXPECT_FALSE(WillCommit());
898
899   // Once the cryptographer is returned to a normal state, we should be able to
900   // commit again.
901   EXPECT_EQ(1, GetNumCommitNudges());
902   UpdateLocalCryptographer();
903   EXPECT_EQ(2, GetNumCommitNudges());
904   EXPECT_TRUE(WillCommit());
905
906   // Verify the committed entity was properly encrypted.
907   DoSuccessfulCommit();
908   ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
909   EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
910   ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
911   const sync_pb::SyncEntity& tag1_entity =
912       GetLatestCommitEntityOnServer("tag1");
913   EXPECT_TRUE(tag1_entity.specifics().has_encrypted());
914   EXPECT_EQ(tag1_entity.name(), "encrypted");
915   EXPECT_TRUE(tag1_entity.specifics().has_preference());
916   EXPECT_FALSE(tag1_entity.specifics().preference().has_name());
917   EXPECT_FALSE(tag1_entity.specifics().preference().has_value());
918 }
919
920 // Test the receipt of decryptable entities.
921 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveDecryptableEntities) {
922   NormalInitialize();
923
924   // Create a new Nigori and allow the cryptographer to decrypt it.
925   NewForeignEncryptionKey();
926   UpdateLocalCryptographer();
927
928   // First, receive an unencrypted entry.
929   TriggerUpdateFromServer(10, "tag1", "value1");
930
931   // Test some basic properties regarding the update.
932   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
933   UpdateResponseData update1 = GetUpdateResponseOnModelThread("tag1");
934   EXPECT_EQ("tag1", update1.specifics.preference().name());
935   EXPECT_EQ("value1", update1.specifics.preference().value());
936   EXPECT_TRUE(update1.encryption_key_name.empty());
937
938   // Set received updates to be encrypted using the new nigori.
939   SetUpdateEncryptionFilter(1);
940
941   // This next update will be encrypted.
942   TriggerUpdateFromServer(10, "tag2", "value2");
943
944   // Test its basic features and the value of encryption_key_name.
945   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag2"));
946   UpdateResponseData update2 = GetUpdateResponseOnModelThread("tag2");
947   EXPECT_EQ("tag2", update2.specifics.preference().name());
948   EXPECT_EQ("value2", update2.specifics.preference().value());
949   EXPECT_FALSE(update2.encryption_key_name.empty());
950 }
951
952 // Test initializing a ModelTypeSyncWorker with a cryptographer at startup.
953 TEST_F(ModelTypeSyncWorkerImplTest, InitializeWithCryptographer) {
954   // Set up some encryption state.
955   NewForeignEncryptionKey();
956   UpdateLocalCryptographer();
957
958   // Then initialize.
959   NormalInitialize();
960
961   // The worker should tell the model thread about encryption as soon as
962   // possible, so that it will have the chance to re-encrypt local data if
963   // necessary.
964   ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
965   EXPECT_EQ(GetLocalCryptographerKeyName(),
966             GetNthModelThreadUpdateState(0).encryption_key_name);
967 }
968
969 // Receive updates that are initially undecryptable, then ensure they get
970 // delivered to the model thread when decryption becomes possible.
971 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUndecryptableEntries) {
972   NormalInitialize();
973
974   // Receive a new foreign encryption key that we can't decrypt.
975   NewForeignEncryptionKey();
976
977   // Receive an encrypted with that new key, which we can't access.
978   SetUpdateEncryptionFilter(1);
979   TriggerUpdateFromServer(10, "tag1", "value1");
980
981   // At this point, the cryptographer does not have access to the key, so the
982   // updates will be undecryptable.  They'll be transfered to the model thread
983   // for safe-keeping as pending updates.
984   ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
985   UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
986   EXPECT_EQ(0U, updates_list.size());
987   UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0);
988   EXPECT_EQ(1U, pending_updates.size());
989
990   // The update will be delivered as soon as decryption becomes possible.
991   UpdateLocalCryptographer();
992   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
993   UpdateResponseData update = GetUpdateResponseOnModelThread("tag1");
994   EXPECT_EQ("tag1", update.specifics.preference().name());
995   EXPECT_EQ("value1", update.specifics.preference().value());
996   EXPECT_FALSE(update.encryption_key_name.empty());
997 }
998
999 // Ensure that even encrypted updates can cause conflicts.
1000 TEST_F(ModelTypeSyncWorkerImplTest, EncryptedUpdateOverridesPendingCommit) {
1001   NormalInitialize();
1002
1003   // Prepeare to commit an item.
1004   CommitRequest("tag1", "value1");
1005   EXPECT_TRUE(WillCommit());
1006
1007   // Receive an encrypted update for that item.
1008   SetUpdateEncryptionFilter(1);
1009   TriggerUpdateFromServer(10, "tag1", "value1");
1010
1011   // The pending commit state should be cleared.
1012   EXPECT_FALSE(WillCommit());
1013
1014   // The encrypted update will be delivered to the model thread.
1015   ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
1016   UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
1017   EXPECT_EQ(0U, updates_list.size());
1018   UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0);
1019   EXPECT_EQ(1U, pending_updates.size());
1020 }
1021
1022 // Test decryption of pending updates saved across a restart.
1023 TEST_F(ModelTypeSyncWorkerImplTest, RestorePendingEntries) {
1024   // Create a fake pending update.
1025   UpdateResponseData update;
1026
1027   update.client_tag_hash = GenerateTagHash("tag1");
1028   update.id = "SomeID";
1029   update.response_version = 100;
1030   update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10);
1031   update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11);
1032   update.non_unique_name = "encrypted";
1033   update.deleted = false;
1034
1035   update.specifics = GenerateSpecifics("tag1", "value1");
1036   EncryptUpdate(GetNthKeyParams(1), &(update.specifics));
1037
1038   // Inject the update during ModelTypeSyncWorker initialization.
1039   UpdateResponseDataList saved_pending_updates;
1040   saved_pending_updates.push_back(update);
1041   InitializeWithPendingUpdates(saved_pending_updates);
1042
1043   // Update will be undecryptable at first.
1044   EXPECT_EQ(0U, GetNumModelThreadUpdateResponses());
1045   ASSERT_FALSE(HasUpdateResponseOnModelThread("tag1"));
1046
1047   // Update the cryptographer so it can decrypt that update.
1048   NewForeignEncryptionKey();
1049   UpdateLocalCryptographer();
1050
1051   // Verify the item gets decrypted and sent back to the model thread.
1052   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
1053 }
1054
1055 // Test decryption of pending updates saved across a restart.  This test
1056 // differs from the previous one in that the restored updates can be decrypted
1057 // immediately after the ModelTypeSyncWorker is constructed.
1058 TEST_F(ModelTypeSyncWorkerImplTest, RestoreApplicableEntries) {
1059   // Update the cryptographer so it can decrypt that update.
1060   NewForeignEncryptionKey();
1061   UpdateLocalCryptographer();
1062
1063   // Create a fake pending update.
1064   UpdateResponseData update;
1065   update.client_tag_hash = GenerateTagHash("tag1");
1066   update.id = "SomeID";
1067   update.response_version = 100;
1068   update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10);
1069   update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11);
1070   update.non_unique_name = "encrypted";
1071   update.deleted = false;
1072
1073   update.specifics = GenerateSpecifics("tag1", "value1");
1074   EncryptUpdate(GetNthKeyParams(1), &(update.specifics));
1075
1076   // Inject the update during ModelTypeSyncWorker initialization.
1077   UpdateResponseDataList saved_pending_updates;
1078   saved_pending_updates.push_back(update);
1079   InitializeWithPendingUpdates(saved_pending_updates);
1080
1081   // Verify the item gets decrypted and sent back to the model thread.
1082   ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
1083 }
1084
1085 // Test that undecryptable updates provide sufficient reason to not commit.
1086 //
1087 // This should be rare in practice.  Usually the cryptographer will be in an
1088 // unusable state when we receive undecryptable updates, and that alone will be
1089 // enough to prevent all commits.
1090 TEST_F(ModelTypeSyncWorkerImplTest, CommitBlockedByPending) {
1091   NormalInitialize();
1092
1093   // Prepeare to commit an item.
1094   CommitRequest("tag1", "value1");
1095   EXPECT_TRUE(WillCommit());
1096
1097   // Receive an encrypted update for that item.
1098   SetUpdateEncryptionFilter(1);
1099   TriggerUpdateFromServer(10, "tag1", "value1");
1100
1101   // The pending commit state should be cleared.
1102   EXPECT_FALSE(WillCommit());
1103
1104   // The pending update will be delivered to the model thread.
1105   HasUpdateResponseOnModelThread("tag1");
1106
1107   // Pretend the update arrived too late to prevent another commit request.
1108   CommitRequest("tag1", "value2");
1109
1110   EXPECT_FALSE(WillCommit());
1111 }
1112
1113 // Verify that corrupted encrypted updates don't cause crashes.
1114 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveCorruptEncryption) {
1115   // Initialize the worker with basic encryption state.
1116   NormalInitialize();
1117   NewForeignEncryptionKey();
1118   UpdateLocalCryptographer();
1119
1120   // Manually create an update.
1121   sync_pb::SyncEntity entity;
1122   entity.set_client_defined_unique_tag(GenerateTagHash("tag1"));
1123   entity.set_id_string("SomeID");
1124   entity.set_version(1);
1125   entity.set_ctime(1000);
1126   entity.set_mtime(1001);
1127   entity.set_name("encrypted");
1128   entity.set_deleted(false);
1129
1130   // Encrypt it.
1131   entity.mutable_specifics()->CopyFrom(GenerateSpecifics("tag1", "value1"));
1132   EncryptUpdate(GetNthKeyParams(1), entity.mutable_specifics());
1133
1134   // Replace a few bytes to corrupt it.
1135   entity.mutable_specifics()->mutable_encrypted()->mutable_blob()->replace(
1136       0, 4, "xyz!");
1137
1138   SyncEntityList entity_list;
1139   entity_list.push_back(&entity);
1140
1141   // If a corrupt update could trigger a crash, this is where it would happen.
1142   DeliverRawUpdates(entity_list);
1143
1144   EXPECT_FALSE(HasUpdateResponseOnModelThread("tag1"));
1145
1146   // Deliver a non-corrupt update to see if the everything still works.
1147   SetUpdateEncryptionFilter(1);
1148   TriggerUpdateFromServer(10, "tag1", "value1");
1149   EXPECT_TRUE(HasUpdateResponseOnModelThread("tag1"));
1150 }
1151
1152 }  // namespace syncer