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