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.
5 #include "sync/engine/model_type_sync_worker_impl.h"
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"
20 #include "testing/gtest/include/gtest/gtest.h"
22 static const std::string kTypeParentId = "PrefsRootNodeID";
23 static const syncer::ModelType kModelType = syncer::PREFERENCES;
25 // Special constant value taken from cryptographer.cc.
26 const char kNigoriKeyName[] = "nigori-key";
30 // Tests the ModelTypeSyncWorkerImpl.
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.
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.
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.
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.
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 {
59 ModelTypeSyncWorkerImplTest();
60 virtual ~ModelTypeSyncWorkerImplTest();
62 // One of these Initialize functions should be called at the beginning of
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();
70 // Initializes with some existing data type state. Allows us to start
71 // committing items right away.
72 void NormalInitialize();
74 // Initialize with some saved pending updates from the model thread.
75 void InitializeWithPendingUpdates(
76 const UpdateResponseDataList& initial_pending_updates);
78 // Initialize with a custom initial DataTypeState and pending updates.
79 void InitializeWithState(const DataTypeState& state,
80 const UpdateResponseDataList& pending_updates);
82 // Introduce a new key that the local cryptographer can't decrypt.
83 void NewForeignEncryptionKey();
85 // Update the local cryptographer with all relevant keys.
86 void UpdateLocalCryptographer();
88 // Use the Nth nigori instance to encrypt incoming updates.
89 // The default value, zero, indicates no encryption.
90 void SetUpdateEncryptionFilter(int n);
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);
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);
103 // Delivers specified protos as updates.
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);
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.
116 // If you want to test those race cases, then these functions are for you.
117 void SetModelThreadIsSynchronous(bool is_synchronous);
118 void PumpModelThread();
120 // Returns true if the |worker_| is ready to commit something.
123 // Pretend to successfully commit all outstanding unsynced items.
124 // It is safe to call this only if WillCommit() returns true.
125 void DoSuccessfulCommit();
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;
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;
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;
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;
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;
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;
165 // Returns the number of commit nudges sent to the mock nudge handler.
166 int GetNumCommitNudges() const;
168 // Returns the number of initial sync nudges sent to the mock nudge handler.
169 int GetNumInitialDownloadNudges() const;
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;
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);
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);
185 // Returns the name for the given Nigori.
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);
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);
197 // An encryptor for our cryptographer.
198 FakeEncryptor fake_encryptor_;
200 // The cryptographer itself. NULL if we're not encrypting the type.
201 scoped_ptr<Cryptographer> cryptographer_;
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_;
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_;
211 // The ModelTypeSyncWorkerImpl being tested.
212 scoped_ptr<ModelTypeSyncWorkerImpl> worker_;
214 // Non-owned, possibly NULL pointer. This object belongs to the
215 // ModelTypeSyncWorkerImpl under test.
216 MockModelTypeSyncProxy* mock_type_sync_proxy_;
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_;
223 // A mock to track the number of times the ModelTypeSyncWorker requests to
225 MockNudgeHandler mock_nudge_handler_;
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) {
235 ModelTypeSyncWorkerImplTest::~ModelTypeSyncWorkerImplTest() {
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;
244 InitializeWithState(initial_state, UpdateResponseDataList());
247 void ModelTypeSyncWorkerImplTest::NormalInitialize() {
248 InitializeWithPendingUpdates(UpdateResponseDataList());
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");
258 initial_state.next_client_id = 10;
259 initial_state.type_root_id = kTypeParentId;
260 initial_state.initial_sync_done = true;
262 InitializeWithState(initial_state, initial_pending_updates);
264 mock_nudge_handler_.ClearCounters();
267 void ModelTypeSyncWorkerImplTest::InitializeWithState(
268 const DataTypeState& state,
269 const UpdateResponseDataList& initial_pending_updates) {
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_);
276 scoped_ptr<Cryptographer> cryptographer_copy;
277 if (cryptographer_) {
278 cryptographer_copy.reset(new Cryptographer(*cryptographer_));
281 worker_.reset(new ModelTypeSyncWorkerImpl(kModelType,
283 initial_pending_updates,
284 cryptographer_copy.Pass(),
285 &mock_nudge_handler_,
289 void ModelTypeSyncWorkerImplTest::NewForeignEncryptionKey() {
290 if (!cryptographer_) {
291 cryptographer_.reset(new Cryptographer(&fake_encryptor_));
294 foreign_encryption_key_index_++;
296 sync_pb::NigoriKeyBag bag;
298 for (int i = 0; i <= foreign_encryption_key_index_; ++i) {
300 KeyParams params = GetNthKeyParams(i);
301 nigori.InitByDerivation(params.hostname, params.username, params.password);
303 sync_pb::NigoriKey* key = bag.add_key();
305 key->set_name(GetNigoriName(nigori));
306 nigori.ExportKeys(key->mutable_user_key(),
307 key->mutable_encryption_key(),
308 key->mutable_mac_key());
311 // Re-create the last nigori from that loop.
313 KeyParams params = GetNthKeyParams(foreign_encryption_key_index_);
314 last_nigori.InitByDerivation(
315 params.hostname, params.username, params.password);
317 // Serialize and encrypt the bag with the last nigori.
318 std::string serialized_bag;
319 bag.SerializeToString(&serialized_bag);
321 sync_pb::EncryptedData encrypted;
322 encrypted.set_key_name(GetNigoriName(last_nigori));
323 last_nigori.Encrypt(serialized_bag, encrypted.mutable_blob());
325 // Update the cryptographer with new pending keys.
326 cryptographer_->SetPendingKeys(encrypted);
328 // Update the worker with the latest cryptographer.
330 worker_->UpdateCryptographer(
331 make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_)));
335 void ModelTypeSyncWorkerImplTest::UpdateLocalCryptographer() {
336 if (!cryptographer_) {
337 cryptographer_.reset(new Cryptographer(&fake_encryptor_));
340 KeyParams params = GetNthKeyParams(foreign_encryption_key_index_);
341 bool success = cryptographer_->DecryptPendingKeys(params);
344 // Update the worker with the latest cryptographer.
346 worker_->UpdateCryptographer(
347 make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_)));
351 void ModelTypeSyncWorkerImplTest::SetUpdateEncryptionFilter(int n) {
352 update_encryption_filter_index_ = n;
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);
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);
373 void ModelTypeSyncWorkerImplTest::TriggerTypeRootUpdateFromServer() {
374 sync_pb::SyncEntity entity = mock_server_.TypeRootUpdate();
375 SyncEntityList entity_list;
376 entity_list.push_back(&entity);
378 sessions::StatusController dummy_status;
380 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
381 mock_server_.GetContext(),
384 worker_->ApplyUpdates(&dummy_status);
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));
394 if (update_encryption_filter_index_ != 0) {
395 EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_),
396 entity.mutable_specifics());
399 SyncEntityList entity_list;
400 entity_list.push_back(&entity);
402 sessions::StatusController dummy_status;
404 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
405 mock_server_.GetContext(),
408 worker_->ApplyUpdates(&dummy_status);
411 void ModelTypeSyncWorkerImplTest::DeliverRawUpdates(
412 const SyncEntityList& list) {
413 sessions::StatusController dummy_status;
414 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
415 mock_server_.GetContext(),
418 worker_->ApplyUpdates(&dummy_status);
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));
427 if (update_encryption_filter_index_ != 0) {
428 EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_),
429 entity.mutable_specifics());
432 SyncEntityList entity_list;
433 entity_list.push_back(&entity);
435 sessions::StatusController dummy_status;
437 worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
438 mock_server_.GetContext(),
441 worker_->ApplyUpdates(&dummy_status);
444 void ModelTypeSyncWorkerImplTest::SetModelThreadIsSynchronous(
445 bool is_synchronous) {
446 mock_type_sync_proxy_->SetSynchronousExecution(is_synchronous);
449 void ModelTypeSyncWorkerImplTest::PumpModelThread() {
450 mock_type_sync_proxy_->RunQueuedTasks();
453 bool ModelTypeSyncWorkerImplTest::WillCommit() {
454 scoped_ptr<CommitContribution> contribution(
455 worker_->GetContribution(INT_MAX));
458 contribution->CleanUp(); // Gracefully abort the commit.
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));
474 sync_pb::ClientToServerMessage message;
475 contribution->AddToCommitMessage(&message);
477 sync_pb::ClientToServerResponse response =
478 mock_server_.DoSuccessfulCommit(message);
480 sessions::StatusController dummy_status;
481 contribution->ProcessCommitResponse(response, &dummy_status);
482 contribution->CleanUp();
485 size_t ModelTypeSyncWorkerImplTest::GetNumCommitMessagesOnServer() const {
486 return mock_server_.GetNumCommitMessages();
489 sync_pb::ClientToServerMessage
490 ModelTypeSyncWorkerImplTest::GetNthCommitMessageOnServer(size_t n) const {
491 DCHECK_LT(n, GetNumCommitMessagesOnServer());
492 return mock_server_.GetNthCommitMessage(n);
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);
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);
508 size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadUpdateResponses() const {
509 return mock_type_sync_proxy_->GetNumUpdateResponses();
512 UpdateResponseDataList
513 ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateResponse(size_t n) const {
514 DCHECK_LT(n, GetNumModelThreadUpdateResponses());
515 return mock_type_sync_proxy_->GetNthUpdateResponse(n);
518 UpdateResponseDataList
519 ModelTypeSyncWorkerImplTest::GetNthModelThreadPendingUpdates(size_t n) const {
520 DCHECK_LT(n, GetNumModelThreadUpdateResponses());
521 return mock_type_sync_proxy_->GetNthPendingUpdates(n);
524 DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateState(
526 DCHECK_LT(n, GetNumModelThreadUpdateResponses());
527 return mock_type_sync_proxy_->GetNthTypeStateReceivedInUpdateResponse(n);
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);
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);
542 size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadCommitResponses() const {
543 return mock_type_sync_proxy_->GetNumCommitResponses();
546 CommitResponseDataList
547 ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitResponse(size_t n) const {
548 DCHECK_LT(n, GetNumModelThreadCommitResponses());
549 return mock_type_sync_proxy_->GetNthCommitResponse(n);
552 DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitState(
554 DCHECK_LT(n, GetNumModelThreadCommitResponses());
555 return mock_type_sync_proxy_->GetNthTypeStateReceivedInCommitResponse(n);
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);
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);
571 int ModelTypeSyncWorkerImplTest::GetNumCommitNudges() const {
572 return mock_nudge_handler_.GetNumCommitNudges();
575 int ModelTypeSyncWorkerImplTest::GetNumInitialDownloadNudges() const {
576 return mock_nudge_handler_.GetNumInitialDownloadNudges();
579 std::string ModelTypeSyncWorkerImplTest::GetLocalCryptographerKeyName() const {
580 if (!cryptographer_) {
581 return std::string();
584 return cryptographer_->GetDefaultNigoriKeyName();
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;
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);
606 std::string ModelTypeSyncWorkerImplTest::GetNigoriName(const Nigori& nigori) {
608 if (!nigori.Permute(Nigori::Password, kNigoriKeyName, &name)) {
610 return std::string();
617 KeyParams ModelTypeSyncWorkerImplTest::GetNthKeyParams(int n) {
619 params.hostname = std::string("localhost");
620 params.username = std::string("userX");
621 params.password = base::StringPrintf("pw%02d", n);
626 void ModelTypeSyncWorkerImplTest::EncryptUpdate(
627 const KeyParams& params,
628 sync_pb::EntitySpecifics* specifics) {
630 nigori.InitByDerivation(params.hostname, params.username, params.password);
632 sync_pb::EntitySpecifics original_specifics = *specifics;
633 std::string plaintext;
634 original_specifics.SerializeToString(&plaintext);
636 std::string encrypted;
637 nigori.Encrypt(plaintext, &encrypted);
640 AddDefaultFieldValue(kModelType, specifics);
641 specifics->mutable_encrypted()->set_key_name(GetNigoriName(nigori));
642 specifics->mutable_encrypted()->set_blob(encrypted);
645 // Requests a commit and verifies the messages sent to the client and server as
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) {
656 EXPECT_FALSE(WillCommit());
657 EXPECT_EQ(0U, GetNumCommitMessagesOnServer());
658 EXPECT_EQ(0U, GetNumModelThreadCommitResponses());
660 CommitRequest("tag1", "value1");
662 EXPECT_EQ(1, GetNumCommitNudges());
664 ASSERT_TRUE(WillCommit());
665 DoSuccessfulCommit();
667 const std::string& client_tag_hash = GenerateTagHash("tag1");
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());
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");
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);
696 EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash);
697 EXPECT_LT(0, commit_response.response_version);
700 TEST_F(ModelTypeSyncWorkerImplTest, SimpleDelete) {
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();
710 ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
711 const CommitResponseData& initial_commit_response =
712 GetCommitResponseOnModelThread("tag1");
713 int64 base_version = initial_commit_response.response_version;
715 // Now that we have an entity, we can delete it.
716 DeleteRequest("tag1");
717 ASSERT_TRUE(WillCommit());
718 DoSuccessfulCommit();
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());
730 // Deletions should contain enough specifics to identify the type.
731 EXPECT_TRUE(entity.has_specifics());
732 EXPECT_EQ(kModelType, GetModelTypeFromSpecifics(entity.specifics()));
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");
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);
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) {
752 // Request the commit of a new, never-before-seen item.
753 CommitRequest("tag1", "value1");
754 EXPECT_TRUE(WillCommit());
755 EXPECT_EQ(1, GetNumCommitNudges());
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());
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());
769 // Receive an update response that contains only the type root node.
770 TriggerTypeRootUpdateFromServer();
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());
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());
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);
788 // Commit two new entities in two separate commit messages.
789 TEST_F(ModelTypeSyncWorkerImplTest, TwoNewItemsCommittedSeparately) {
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");
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");
814 EXPECT_FALSE(WillCommit());
816 // The IDs assigned by the |worker_| should be unique.
817 EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string());
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");
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());
828 // Test normal update receipt code path.
829 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUpdates) {
832 const std::string& tag_hash = GenerateTagHash("tag1");
834 TriggerUpdateFromServer(10, "tag1", "value1");
836 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
837 UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
838 ASSERT_EQ(1U, updates_list.size());
840 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
841 UpdateResponseData update = GetUpdateResponseOnModelThread("tag1");
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());
854 // Test commit of encrypted updates.
855 TEST_F(ModelTypeSyncWorkerImplTest, EncryptedCommit) {
858 ASSERT_EQ(0U, GetNumModelThreadUpdateResponses());
860 NewForeignEncryptionKey();
861 UpdateLocalCryptographer();
863 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
864 EXPECT_EQ(GetLocalCryptographerKeyName(),
865 GetNthModelThreadUpdateState(0).encryption_key_name);
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");
876 EXPECT_TRUE(tag1_entity.specifics().has_encrypted());
878 // The title should be overwritten.
879 EXPECT_EQ(tag1_entity.name(), "encrypted");
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());
887 // Test items are not committed when encryption is required but unavailable.
888 TEST_F(ModelTypeSyncWorkerImplTest, EncryptionBlocksCommits) {
891 CommitRequest("tag1", "value1");
892 EXPECT_TRUE(WillCommit());
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());
899 // Once the cryptographer is returned to a normal state, we should be able to
901 EXPECT_EQ(1, GetNumCommitNudges());
902 UpdateLocalCryptographer();
903 EXPECT_EQ(2, GetNumCommitNudges());
904 EXPECT_TRUE(WillCommit());
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());
920 // Test the receipt of decryptable entities.
921 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveDecryptableEntities) {
924 // Create a new Nigori and allow the cryptographer to decrypt it.
925 NewForeignEncryptionKey();
926 UpdateLocalCryptographer();
928 // First, receive an unencrypted entry.
929 TriggerUpdateFromServer(10, "tag1", "value1");
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());
938 // Set received updates to be encrypted using the new nigori.
939 SetUpdateEncryptionFilter(1);
941 // This next update will be encrypted.
942 TriggerUpdateFromServer(10, "tag2", "value2");
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());
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();
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
964 ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
965 EXPECT_EQ(GetLocalCryptographerKeyName(),
966 GetNthModelThreadUpdateState(0).encryption_key_name);
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) {
974 // Receive a new foreign encryption key that we can't decrypt.
975 NewForeignEncryptionKey();
977 // Receive an encrypted with that new key, which we can't access.
978 SetUpdateEncryptionFilter(1);
979 TriggerUpdateFromServer(10, "tag1", "value1");
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());
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());
999 // Ensure that even encrypted updates can cause conflicts.
1000 TEST_F(ModelTypeSyncWorkerImplTest, EncryptedUpdateOverridesPendingCommit) {
1003 // Prepeare to commit an item.
1004 CommitRequest("tag1", "value1");
1005 EXPECT_TRUE(WillCommit());
1007 // Receive an encrypted update for that item.
1008 SetUpdateEncryptionFilter(1);
1009 TriggerUpdateFromServer(10, "tag1", "value1");
1011 // The pending commit state should be cleared.
1012 EXPECT_FALSE(WillCommit());
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());
1022 // Test decryption of pending updates saved across a restart.
1023 TEST_F(ModelTypeSyncWorkerImplTest, RestorePendingEntries) {
1024 // Create a fake pending update.
1025 UpdateResponseData update;
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;
1035 update.specifics = GenerateSpecifics("tag1", "value1");
1036 EncryptUpdate(GetNthKeyParams(1), &(update.specifics));
1038 // Inject the update during ModelTypeSyncWorker initialization.
1039 UpdateResponseDataList saved_pending_updates;
1040 saved_pending_updates.push_back(update);
1041 InitializeWithPendingUpdates(saved_pending_updates);
1043 // Update will be undecryptable at first.
1044 EXPECT_EQ(0U, GetNumModelThreadUpdateResponses());
1045 ASSERT_FALSE(HasUpdateResponseOnModelThread("tag1"));
1047 // Update the cryptographer so it can decrypt that update.
1048 NewForeignEncryptionKey();
1049 UpdateLocalCryptographer();
1051 // Verify the item gets decrypted and sent back to the model thread.
1052 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
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();
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;
1073 update.specifics = GenerateSpecifics("tag1", "value1");
1074 EncryptUpdate(GetNthKeyParams(1), &(update.specifics));
1076 // Inject the update during ModelTypeSyncWorker initialization.
1077 UpdateResponseDataList saved_pending_updates;
1078 saved_pending_updates.push_back(update);
1079 InitializeWithPendingUpdates(saved_pending_updates);
1081 // Verify the item gets decrypted and sent back to the model thread.
1082 ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
1085 // Test that undecryptable updates provide sufficient reason to not commit.
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) {
1093 // Prepeare to commit an item.
1094 CommitRequest("tag1", "value1");
1095 EXPECT_TRUE(WillCommit());
1097 // Receive an encrypted update for that item.
1098 SetUpdateEncryptionFilter(1);
1099 TriggerUpdateFromServer(10, "tag1", "value1");
1101 // The pending commit state should be cleared.
1102 EXPECT_FALSE(WillCommit());
1104 // The pending update will be delivered to the model thread.
1105 HasUpdateResponseOnModelThread("tag1");
1107 // Pretend the update arrived too late to prevent another commit request.
1108 CommitRequest("tag1", "value2");
1110 EXPECT_FALSE(WillCommit());
1113 // Verify that corrupted encrypted updates don't cause crashes.
1114 TEST_F(ModelTypeSyncWorkerImplTest, ReceiveCorruptEncryption) {
1115 // Initialize the worker with basic encryption state.
1117 NewForeignEncryptionKey();
1118 UpdateLocalCryptographer();
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);
1131 entity.mutable_specifics()->CopyFrom(GenerateSpecifics("tag1", "value1"));
1132 EncryptUpdate(GetNthKeyParams(1), entity.mutable_specifics());
1134 // Replace a few bytes to corrupt it.
1135 entity.mutable_specifics()->mutable_encrypted()->mutable_blob()->replace(
1138 SyncEntityList entity_list;
1139 entity_list.push_back(&entity);
1141 // If a corrupt update could trigger a crash, this is where it would happen.
1142 DeliverRawUpdates(entity_list);
1144 EXPECT_FALSE(HasUpdateResponseOnModelThread("tag1"));
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"));
1152 } // namespace syncer