text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "sync/engine/model_type_sync_worker_impl.h"
  
  #include "base/strings/stringprintf.h"
  #include "sync/engine/commit_contribution.h"
  #include "sync/engine/model_type_sync_proxy.h"
  #include "sync/internal_api/public/base/model_type.h"
  #include "sync/internal_api/public/non_blocking_sync_common.h"
  #include "sync/protocol/sync.pb.h"
  #include "sync/sessions/status_controller.h"
  #include "sync/syncable/syncable_util.h"
  #include "sync/test/engine/mock_model_type_sync_proxy.h"
  #include "sync/test/engine/mock_nudge_handler.h"
  #include "sync/test/engine/single_type_mock_server.h"
  #include "sync/test/fake_encryptor.h"
  
  #include "testing/gtest/include/gtest/gtest.h"
  
  static const std::string kTypeParentId = "PrefsRootNodeID";
  static const syncer::ModelType kModelType = syncer::PREFERENCES;
  
  // Special constant value taken from cryptographer.cc.
  const char kNigoriKeyName[] = "nigori-key";
  
  namespace syncer {
  
  // Tests the ModelTypeSyncWorkerImpl.
  //
  // This class passes messages between the model thread and sync server.
  // As such, its code is subject to lots of different race conditions.  This
  // test harness lets us exhaustively test all possible races.  We try to
  // focus on just a few interesting cases.
  //
  // Inputs:
  // - Initial data type state from the model thread.
  // - Commit requests from the model thread.
  // - Update responses from the server.
  // - Commit responses from the server.
  // - The cryptographer, if encryption is enabled.
  //
  // Outputs:
  // - Commit requests to the server.
  // - Commit responses to the model thread.
  // - Update responses to the model thread.
  // - Nudges to the sync scheduler.
  //
  // We use the MockModelTypeSyncProxy to stub out all communication
  // with the model thread.  That interface is synchronous, which makes it
  // much easier to test races.
  //
  // The interface with the server is built around "pulling" data from this
  // class, so we don't have to mock out any of it.  We wrap it with some
  // convenience functions to we can emulate server behavior.
  class ModelTypeSyncWorkerImplTest : public ::testing::Test {
   public:
    ModelTypeSyncWorkerImplTest();
    ~ModelTypeSyncWorkerImplTest() override;
  
    // One of these Initialize functions should be called at the beginning of
    // each test.
  
    // Initializes with no data type state.  We will be unable to perform any
    // significant server action until we receive an update response that
    // contains the type root node for this type.
    void FirstInitialize();
  
    // Initializes with some existing data type state.  Allows us to start
    // committing items right away.
    void NormalInitialize();
  
    // Initialize with some saved pending updates from the model thread.
    void InitializeWithPendingUpdates(
        const UpdateResponseDataList& initial_pending_updates);
  
    // Initialize with a custom initial DataTypeState and pending updates.
    void InitializeWithState(const DataTypeState& state,
                             const UpdateResponseDataList& pending_updates);
  
    // Introduce a new key that the local cryptographer can't decrypt.
    void NewForeignEncryptionKey();
  
    // Update the local cryptographer with all relevant keys.
    void UpdateLocalCryptographer();
  
    // Use the Nth nigori instance to encrypt incoming updates.
    // The default value, zero, indicates no encryption.
    void SetUpdateEncryptionFilter(int n);
  
    // Modifications on the model thread that get sent to the worker under test.
    void CommitRequest(const std::string& tag, const std::string& value);
    void DeleteRequest(const std::string& tag);
  
    // Pretends to receive update messages from the server.
    void TriggerTypeRootUpdateFromServer();
    void TriggerUpdateFromServer(int64 version_offset,
                                 const std::string& tag,
                                 const std::string& value);
    void TriggerTombstoneFromServer(int64 version_offset, const std::string& tag);
  
    // Delivers specified protos as updates.
    //
    // Does not update mock server state.  Should be used as a last resort when
    // writing test cases that require entities that don't fit the normal sync
    // protocol.  Try to use the other, higher level methods if possible.
    void DeliverRawUpdates(const SyncEntityList& update_list);
  
    // By default, this harness behaves as if all tasks posted to the model
    // thread are executed immediately.  However, this is not necessarily true.
    // The model's TaskRunner has a queue, and the tasks we post to it could
    // linger there for a while.  In the meantime, the model thread could
    // continue posting tasks to the worker based on its stale state.
    //
    // If you want to test those race cases, then these functions are for you.
    void SetModelThreadIsSynchronous(bool is_synchronous);
    void PumpModelThread();
  
    // Returns true if the |worker_| is ready to commit something.
    bool WillCommit();
  
    // Pretend to successfully commit all outstanding unsynced items.
    // It is safe to call this only if WillCommit() returns true.
    void DoSuccessfulCommit();
  
    // Read commit messages the worker_ sent to the emulated server.
    size_t GetNumCommitMessagesOnServer() const;
    sync_pb::ClientToServerMessage GetNthCommitMessageOnServer(size_t n) const;
  
    // Read the latest version of sync entities committed to the emulated server.
    bool HasCommitEntityOnServer(const std::string& tag) const;
    sync_pb::SyncEntity GetLatestCommitEntityOnServer(
        const std::string& tag) const;
  
    // Read the latest update messages received on the model thread.
    // Note that if the model thread is in non-blocking mode, this data will not
    // be updated until the response is actually processed by the model thread.
    size_t GetNumModelThreadUpdateResponses() const;
    UpdateResponseDataList GetNthModelThreadUpdateResponse(size_t n) const;
    UpdateResponseDataList GetNthModelThreadPendingUpdates(size_t n) const;
    DataTypeState GetNthModelThreadUpdateState(size_t n) const;
  
    // Reads the latest update response datas on the model thread.
    // Note that if the model thread is in non-blocking mode, this data will not
    // be updated until the response is actually processed by the model thread.
    bool HasUpdateResponseOnModelThread(const std::string& tag) const;
    UpdateResponseData GetUpdateResponseOnModelThread(
        const std::string& tag) const;
  
    // Read the latest commit messages received on the model thread.
    // Note that if the model thread is in non-blocking mode, this data will not
    // be updated until the response is actually processed by the model thread.
    size_t GetNumModelThreadCommitResponses() const;
    CommitResponseDataList GetNthModelThreadCommitResponse(size_t n) const;
    DataTypeState GetNthModelThreadCommitState(size_t n) const;
  
    // Reads the latest commit response datas on the model thread.
    // Note that if the model thread is in non-blocking mode, this data will not
    // be updated until the response is actually processed by the model thread.
    bool HasCommitResponseOnModelThread(const std::string& tag) const;
    CommitResponseData GetCommitResponseOnModelThread(
        const std::string& tag) const;
  
    // Returns the number of commit nudges sent to the mock nudge handler.
    int GetNumCommitNudges() const;
  
    // Returns the number of initial sync nudges sent to the mock nudge handler.
    int GetNumInitialDownloadNudges() const;
  
    // Returns the name of the encryption key in the cryptographer last passed to
    // the ModelTypeSyncWorker.  Returns an empty string if no crypgorapher is
    // in use.  See also: UpdateLocalCryptographer().
    std::string GetLocalCryptographerKeyName() const;
  
    // Helpers for building various messages and structures.
    static std::string GenerateTagHash(const std::string& tag);
    static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag,
                                                      const std::string& value);
  
    // Returns a set of KeyParams for the cryptographer.  Each input 'n' value
    // results in a different set of parameters.
    static KeyParams GetNthKeyParams(int n);
  
    // Returns the name for the given Nigori.
    //
    // Uses some 'white-box' knowledge to mimic the names that a real sync client
    // would generate.  It's probably not necessary to do so, but it can't hurt.
    static std::string GetNigoriName(const Nigori& nigori);
  
    // Modifies the input/output parameter |specifics| by encrypting it with
    // a Nigori intialized with the specified KeyParams.
    static void EncryptUpdate(const KeyParams& params,
                              sync_pb::EntitySpecifics* specifics);
  
   private:
    // An encryptor for our cryptographer.
    FakeEncryptor fake_encryptor_;
  
    // The cryptographer itself.  NULL if we're not encrypting the type.
    scoped_ptr<Cryptographer> cryptographer_;
  
    // The number of the most recent foreign encryption key known to our
    // cryptographer.  Note that not all of these will be decryptable.
    int foreign_encryption_key_index_;
  
    // The number of the encryption key used to encrypt incoming updates.  A zero
    // value implies no encryption.
    int update_encryption_filter_index_;
  
    // The ModelTypeSyncWorkerImpl being tested.
    scoped_ptr<ModelTypeSyncWorkerImpl> worker_;
  
    // Non-owned, possibly NULL pointer.  This object belongs to the
    // ModelTypeSyncWorkerImpl under test.
    MockModelTypeSyncProxy* mock_type_sync_proxy_;
  
    // A mock that emulates enough of the sync server that it can be used
    // a single UpdateHandler and CommitContributor pair.  In this test
    // harness, the |worker_| is both of them.
    SingleTypeMockServer mock_server_;
  
    // A mock to track the number of times the ModelTypeSyncWorker requests to
    // sync.
    MockNudgeHandler mock_nudge_handler_;
  };
  
  ModelTypeSyncWorkerImplTest::ModelTypeSyncWorkerImplTest()
      : foreign_encryption_key_index_(0),
        update_encryption_filter_index_(0),
        mock_type_sync_proxy_(NULL),
        mock_server_(kModelType) {
  }
  
  ModelTypeSyncWorkerImplTest::~ModelTypeSyncWorkerImplTest() {
  }
  
  void ModelTypeSyncWorkerImplTest::FirstInitialize() {
    DataTypeState initial_state;
    initial_state.progress_marker.set_data_type_id(
        GetSpecificsFieldNumberFromModelType(kModelType));
    initial_state.next_client_id = 0;
  
    InitializeWithState(initial_state, UpdateResponseDataList());
  }
  
  void ModelTypeSyncWorkerImplTest::NormalInitialize() {
    InitializeWithPendingUpdates(UpdateResponseDataList());
  }
  
  void ModelTypeSyncWorkerImplTest::InitializeWithPendingUpdates(
      const UpdateResponseDataList& initial_pending_updates) {
    DataTypeState initial_state;
    initial_state.progress_marker.set_data_type_id(
        GetSpecificsFieldNumberFromModelType(kModelType));
    initial_state.progress_marker.set_token("some_saved_progress_token");
  
    initial_state.next_client_id = 10;
    initial_state.type_root_id = kTypeParentId;
    initial_state.initial_sync_done = true;
  
    InitializeWithState(initial_state, initial_pending_updates);
  
    mock_nudge_handler_.ClearCounters();
  }
  
  void ModelTypeSyncWorkerImplTest::InitializeWithState(
      const DataTypeState& state,
      const UpdateResponseDataList& initial_pending_updates) {
    DCHECK(!worker_);
  
    // We don't get to own this object.  The |worker_| keeps a scoped_ptr to it.
    mock_type_sync_proxy_ = new MockModelTypeSyncProxy();
    scoped_ptr<ModelTypeSyncProxy> proxy(mock_type_sync_proxy_);
  
    scoped_ptr<Cryptographer> cryptographer_copy;
    if (cryptographer_) {
      cryptographer_copy.reset(new Cryptographer(*cryptographer_));
    }
  
    worker_.reset(new ModelTypeSyncWorkerImpl(kModelType,
                                              state,
                                              initial_pending_updates,
                                              cryptographer_copy.Pass(),
                                              &mock_nudge_handler_,
                                              proxy.Pass()));
  }
  
  void ModelTypeSyncWorkerImplTest::NewForeignEncryptionKey() {
    if (!cryptographer_) {
      cryptographer_.reset(new Cryptographer(&fake_encryptor_));
    }
  
    foreign_encryption_key_index_++;
  
    sync_pb::NigoriKeyBag bag;
  
    for (int i = 0; i <= foreign_encryption_key_index_; ++i) {
      Nigori nigori;
      KeyParams params = GetNthKeyParams(i);
      nigori.InitByDerivation(params.hostname, params.username, params.password);
  
      sync_pb::NigoriKey* key = bag.add_key();
  
      key->set_name(GetNigoriName(nigori));
      nigori.ExportKeys(key->mutable_user_key(),
                        key->mutable_encryption_key(),
                        key->mutable_mac_key());
    }
  
    // Re-create the last nigori from that loop.
    Nigori last_nigori;
    KeyParams params = GetNthKeyParams(foreign_encryption_key_index_);
    last_nigori.InitByDerivation(
        params.hostname, params.username, params.password);
  
    // Serialize and encrypt the bag with the last nigori.
    std::string serialized_bag;
    bag.SerializeToString(&serialized_bag);
  
    sync_pb::EncryptedData encrypted;
    encrypted.set_key_name(GetNigoriName(last_nigori));
    last_nigori.Encrypt(serialized_bag, encrypted.mutable_blob());
  
    // Update the cryptographer with new pending keys.
    cryptographer_->SetPendingKeys(encrypted);
  
    // Update the worker with the latest cryptographer.
    if (worker_) {
      worker_->UpdateCryptographer(
          make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_)));
    }
  }
  
  void ModelTypeSyncWorkerImplTest::UpdateLocalCryptographer() {
    if (!cryptographer_) {
      cryptographer_.reset(new Cryptographer(&fake_encryptor_));
    }
  
    KeyParams params = GetNthKeyParams(foreign_encryption_key_index_);
    bool success = cryptographer_->DecryptPendingKeys(params);
    DCHECK(success);
  
    // Update the worker with the latest cryptographer.
    if (worker_) {
      worker_->UpdateCryptographer(
          make_scoped_ptr<Cryptographer>(new Cryptographer(*cryptographer_)));
    }
  }
  
  void ModelTypeSyncWorkerImplTest::SetUpdateEncryptionFilter(int n) {
    update_encryption_filter_index_ = n;
  }
  
  void ModelTypeSyncWorkerImplTest::CommitRequest(const std::string& name,
                                                  const std::string& value) {
    const std::string tag_hash = GenerateTagHash(name);
    CommitRequestData data = mock_type_sync_proxy_->CommitRequest(
        tag_hash, GenerateSpecifics(name, value));
    CommitRequestDataList list;
    list.push_back(data);
    worker_->EnqueueForCommit(list);
  }
  
  void ModelTypeSyncWorkerImplTest::DeleteRequest(const std::string& tag) {
    const std::string tag_hash = GenerateTagHash(tag);
    CommitRequestData data = mock_type_sync_proxy_->DeleteRequest(tag_hash);
    CommitRequestDataList list;
    list.push_back(data);
    worker_->EnqueueForCommit(list);
  }
  
  void ModelTypeSyncWorkerImplTest::TriggerTypeRootUpdateFromServer() {
    sync_pb::SyncEntity entity = mock_server_.TypeRootUpdate();
    SyncEntityList entity_list;
    entity_list.push_back(&entity);
  
    sessions::StatusController dummy_status;
  
    worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
                                       mock_server_.GetContext(),
                                       entity_list,
                                       &dummy_status);
    worker_->ApplyUpdates(&dummy_status);
  }
  
  void ModelTypeSyncWorkerImplTest::TriggerUpdateFromServer(
      int64 version_offset,
      const std::string& tag,
      const std::string& value) {
    sync_pb::SyncEntity entity = mock_server_.UpdateFromServer(
        version_offset, GenerateTagHash(tag), GenerateSpecifics(tag, value));
  
    if (update_encryption_filter_index_ != 0) {
      EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_),
                    entity.mutable_specifics());
    }
  
    SyncEntityList entity_list;
    entity_list.push_back(&entity);
  
    sessions::StatusController dummy_status;
  
    worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
                                       mock_server_.GetContext(),
                                       entity_list,
                                       &dummy_status);
    worker_->ApplyUpdates(&dummy_status);
  }
  
  void ModelTypeSyncWorkerImplTest::DeliverRawUpdates(
      const SyncEntityList& list) {
    sessions::StatusController dummy_status;
    worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
                                       mock_server_.GetContext(),
                                       list,
                                       &dummy_status);
    worker_->ApplyUpdates(&dummy_status);
  }
  
  void ModelTypeSyncWorkerImplTest::TriggerTombstoneFromServer(
      int64 version_offset,
      const std::string& tag) {
    sync_pb::SyncEntity entity =
        mock_server_.TombstoneFromServer(version_offset, GenerateTagHash(tag));
  
    if (update_encryption_filter_index_ != 0) {
      EncryptUpdate(GetNthKeyParams(update_encryption_filter_index_),
                    entity.mutable_specifics());
    }
  
    SyncEntityList entity_list;
    entity_list.push_back(&entity);
  
    sessions::StatusController dummy_status;
  
    worker_->ProcessGetUpdatesResponse(mock_server_.GetProgress(),
                                       mock_server_.GetContext(),
                                       entity_list,
                                       &dummy_status);
    worker_->ApplyUpdates(&dummy_status);
  }
  
  void ModelTypeSyncWorkerImplTest::SetModelThreadIsSynchronous(
      bool is_synchronous) {
    mock_type_sync_proxy_->SetSynchronousExecution(is_synchronous);
  }
  
  void ModelTypeSyncWorkerImplTest::PumpModelThread() {
    mock_type_sync_proxy_->RunQueuedTasks();
  }
  
  bool ModelTypeSyncWorkerImplTest::WillCommit() {
    scoped_ptr<CommitContribution> contribution(
        worker_->GetContribution(INT_MAX));
  
    if (contribution) {
      contribution->CleanUp();  // Gracefully abort the commit.
      return true;
    } else {
      return false;
    }
  }
  
  // Conveniently, this is all one big synchronous operation.  The sync thread
  // remains blocked while the commit is in progress, so we don't need to worry
  // about other tasks being run between the time when the commit request is
  // issued and the time when the commit response is received.
  void ModelTypeSyncWorkerImplTest::DoSuccessfulCommit() {
    DCHECK(WillCommit());
    scoped_ptr<CommitContribution> contribution(
        worker_->GetContribution(INT_MAX));
  
    sync_pb::ClientToServerMessage message;
    contribution->AddToCommitMessage(&message);
  
    sync_pb::ClientToServerResponse response =
        mock_server_.DoSuccessfulCommit(message);
  
    sessions::StatusController dummy_status;
    contribution->ProcessCommitResponse(response, &dummy_status);
    contribution->CleanUp();
  }
  
  size_t ModelTypeSyncWorkerImplTest::GetNumCommitMessagesOnServer() const {
    return mock_server_.GetNumCommitMessages();
  }
  
  sync_pb::ClientToServerMessage
  ModelTypeSyncWorkerImplTest::GetNthCommitMessageOnServer(size_t n) const {
    DCHECK_LT(n, GetNumCommitMessagesOnServer());
    return mock_server_.GetNthCommitMessage(n);
  }
  
  bool ModelTypeSyncWorkerImplTest::HasCommitEntityOnServer(
      const std::string& tag) const {
    const std::string tag_hash = GenerateTagHash(tag);
    return mock_server_.HasCommitEntity(tag_hash);
  }
  
  sync_pb::SyncEntity ModelTypeSyncWorkerImplTest::GetLatestCommitEntityOnServer(
      const std::string& tag) const {
    DCHECK(HasCommitEntityOnServer(tag));
    const std::string tag_hash = GenerateTagHash(tag);
    return mock_server_.GetLastCommittedEntity(tag_hash);
  }
  
  size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadUpdateResponses() const {
    return mock_type_sync_proxy_->GetNumUpdateResponses();
  }
  
  UpdateResponseDataList
  ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateResponse(size_t n) const {
    DCHECK_LT(n, GetNumModelThreadUpdateResponses());
    return mock_type_sync_proxy_->GetNthUpdateResponse(n);
  }
  
  UpdateResponseDataList
  ModelTypeSyncWorkerImplTest::GetNthModelThreadPendingUpdates(size_t n) const {
    DCHECK_LT(n, GetNumModelThreadUpdateResponses());
    return mock_type_sync_proxy_->GetNthPendingUpdates(n);
  }
  
  DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadUpdateState(
      size_t n) const {
    DCHECK_LT(n, GetNumModelThreadUpdateResponses());
    return mock_type_sync_proxy_->GetNthTypeStateReceivedInUpdateResponse(n);
  }
  
  bool ModelTypeSyncWorkerImplTest::HasUpdateResponseOnModelThread(
      const std::string& tag) const {
    const std::string tag_hash = GenerateTagHash(tag);
    return mock_type_sync_proxy_->HasUpdateResponse(tag_hash);
  }
  
  UpdateResponseData ModelTypeSyncWorkerImplTest::GetUpdateResponseOnModelThread(
      const std::string& tag) const {
    const std::string tag_hash = GenerateTagHash(tag);
    return mock_type_sync_proxy_->GetUpdateResponse(tag_hash);
  }
  
  size_t ModelTypeSyncWorkerImplTest::GetNumModelThreadCommitResponses() const {
    return mock_type_sync_proxy_->GetNumCommitResponses();
  }
  
  CommitResponseDataList
  ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitResponse(size_t n) const {
    DCHECK_LT(n, GetNumModelThreadCommitResponses());
    return mock_type_sync_proxy_->GetNthCommitResponse(n);
  }
  
  DataTypeState ModelTypeSyncWorkerImplTest::GetNthModelThreadCommitState(
      size_t n) const {
    DCHECK_LT(n, GetNumModelThreadCommitResponses());
    return mock_type_sync_proxy_->GetNthTypeStateReceivedInCommitResponse(n);
  }
  
  bool ModelTypeSyncWorkerImplTest::HasCommitResponseOnModelThread(
      const std::string& tag) const {
    const std::string tag_hash = GenerateTagHash(tag);
    return mock_type_sync_proxy_->HasCommitResponse(tag_hash);
  }
  
  CommitResponseData ModelTypeSyncWorkerImplTest::GetCommitResponseOnModelThread(
      const std::string& tag) const {
    DCHECK(HasCommitResponseOnModelThread(tag));
    const std::string tag_hash = GenerateTagHash(tag);
    return mock_type_sync_proxy_->GetCommitResponse(tag_hash);
  }
  
  int ModelTypeSyncWorkerImplTest::GetNumCommitNudges() const {
    return mock_nudge_handler_.GetNumCommitNudges();
  }
  
  int ModelTypeSyncWorkerImplTest::GetNumInitialDownloadNudges() const {
    return mock_nudge_handler_.GetNumInitialDownloadNudges();
  }
  
  std::string ModelTypeSyncWorkerImplTest::GetLocalCryptographerKeyName() const {
    if (!cryptographer_) {
      return std::string();
    }
  
    return cryptographer_->GetDefaultNigoriKeyName();
  }
  
  // static.
  std::string ModelTypeSyncWorkerImplTest::GenerateTagHash(
      const std::string& tag) {
    const std::string& client_tag_hash =
        syncable::GenerateSyncableHash(kModelType, tag);
    return client_tag_hash;
  }
  
  // static.
  sync_pb::EntitySpecifics ModelTypeSyncWorkerImplTest::GenerateSpecifics(
      const std::string& tag,
      const std::string& value) {
    sync_pb::EntitySpecifics specifics;
    specifics.mutable_preference()->set_name(tag);
    specifics.mutable_preference()->set_value(value);
    return specifics;
  }
  
  // static.
  std::string ModelTypeSyncWorkerImplTest::GetNigoriName(const Nigori& nigori) {
    std::string name;
    if (!nigori.Permute(Nigori::Password, kNigoriKeyName, &name)) {
      NOTREACHED();
      return std::string();
    }
  
    return name;
  }
  
  // static.
  KeyParams ModelTypeSyncWorkerImplTest::GetNthKeyParams(int n) {
    KeyParams params;
    params.hostname = std::string("localhost");
    params.username = std::string("userX");
    params.password = base::StringPrintf("pw%02d", n);
    return params;
  }
  
  // static.
  void ModelTypeSyncWorkerImplTest::EncryptUpdate(
      const KeyParams& params,
      sync_pb::EntitySpecifics* specifics) {
    Nigori nigori;
    nigori.InitByDerivation(params.hostname, params.username, params.password);
  
    sync_pb::EntitySpecifics original_specifics = *specifics;
    std::string plaintext;
    original_specifics.SerializeToString(&plaintext);
  
    std::string encrypted;
    nigori.Encrypt(plaintext, &encrypted);
  
    specifics->Clear();
    AddDefaultFieldValue(kModelType, specifics);
    specifics->mutable_encrypted()->set_key_name(GetNigoriName(nigori));
    specifics->mutable_encrypted()->set_blob(encrypted);
  }
  
  // Requests a commit and verifies the messages sent to the client and server as
  // a result.
  //
  // This test performs sanity checks on most of the fields in these messages.
  // For the most part this is checking that the test code behaves as expected
  // and the |worker_| doesn't mess up its simple task of moving around these
  // values.  It makes sense to have one or two tests that are this thorough, but
  // we shouldn't be this verbose in all tests.
  TEST_F(ModelTypeSyncWorkerImplTest, SimpleCommit) {
    NormalInitialize();
  
    EXPECT_FALSE(WillCommit());
    EXPECT_EQ(0U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(0U, GetNumModelThreadCommitResponses());
  
    CommitRequest("tag1", "value1");
  
    EXPECT_EQ(1, GetNumCommitNudges());
  
    ASSERT_TRUE(WillCommit());
    DoSuccessfulCommit();
  
    const std::string& client_tag_hash = GenerateTagHash("tag1");
  
    // Exhaustively verify the SyncEntity sent in the commit message.
    ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
    ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
    EXPECT_FALSE(entity.id_string().empty());
    EXPECT_EQ(kTypeParentId, entity.parent_id_string());
    EXPECT_EQ(kUncommittedVersion, entity.version());
    EXPECT_NE(0, entity.mtime());
    EXPECT_NE(0, entity.ctime());
    EXPECT_FALSE(entity.name().empty());
    EXPECT_EQ(client_tag_hash, entity.client_defined_unique_tag());
    EXPECT_EQ("tag1", entity.specifics().preference().name());
    EXPECT_FALSE(entity.deleted());
    EXPECT_EQ("value1", entity.specifics().preference().value());
  
    // Exhaustively verify the commit response returned to the model thread.
    ASSERT_EQ(1U, GetNumModelThreadCommitResponses());
    EXPECT_EQ(1U, GetNthModelThreadCommitResponse(0).size());
    ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
    const CommitResponseData& commit_response =
        GetCommitResponseOnModelThread("tag1");
  
    // The ID changes in a commit response to initial commit.
    EXPECT_FALSE(commit_response.id.empty());
    EXPECT_NE(entity.id_string(), commit_response.id);
  
    EXPECT_EQ(client_tag_hash, commit_response.client_tag_hash);
    EXPECT_LT(0, commit_response.response_version);
  }
  
  TEST_F(ModelTypeSyncWorkerImplTest, SimpleDelete) {
    NormalInitialize();
  
    // We can't delete an entity that was never committed.
    // Step 1 is to create and commit a new entity.
    CommitRequest("tag1", "value1");
    EXPECT_EQ(1, GetNumCommitNudges());
    ASSERT_TRUE(WillCommit());
    DoSuccessfulCommit();
  
    ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
    const CommitResponseData& initial_commit_response =
        GetCommitResponseOnModelThread("tag1");
    int64 base_version = initial_commit_response.response_version;
  
    // Now that we have an entity, we can delete it.
    DeleteRequest("tag1");
    ASSERT_TRUE(WillCommit());
    DoSuccessfulCommit();
  
    // Verify the SyncEntity sent in the commit message.
    ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
    ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    const sync_pb::SyncEntity& entity = GetLatestCommitEntityOnServer("tag1");
    EXPECT_FALSE(entity.id_string().empty());
    EXPECT_EQ(GenerateTagHash("tag1"), entity.client_defined_unique_tag());
    EXPECT_EQ(base_version, entity.version());
    EXPECT_TRUE(entity.deleted());
  
    // Deletions should contain enough specifics to identify the type.
    EXPECT_TRUE(entity.has_specifics());
    EXPECT_EQ(kModelType, GetModelTypeFromSpecifics(entity.specifics()));
  
    // Verify the commit response returned to the model thread.
    ASSERT_EQ(2U, GetNumModelThreadCommitResponses());
    EXPECT_EQ(1U, GetNthModelThreadCommitResponse(1).size());
    ASSERT_TRUE(HasCommitResponseOnModelThread("tag1"));
    const CommitResponseData& commit_response =
        GetCommitResponseOnModelThread("tag1");
  
    EXPECT_EQ(entity.id_string(), commit_response.id);
    EXPECT_EQ(entity.client_defined_unique_tag(),
              commit_response.client_tag_hash);
    EXPECT_EQ(entity.version(), commit_response.response_version);
  }
  
  // The server doesn't like it when we try to delete an entity it's never heard
  // of before.  This test helps ensure we avoid that scenario.
  TEST_F(ModelTypeSyncWorkerImplTest, NoDeleteUncommitted) {
    NormalInitialize();
  
    // Request the commit of a new, never-before-seen item.
    CommitRequest("tag1", "value1");
    EXPECT_TRUE(WillCommit());
    EXPECT_EQ(1, GetNumCommitNudges());
  
    // Request a deletion of that item before we've had a chance to commit it.
    DeleteRequest("tag1");
    EXPECT_FALSE(WillCommit());
    EXPECT_EQ(2, GetNumCommitNudges());
  }
  
  // Verifies the sending of an "initial sync done" signal.
  TEST_F(ModelTypeSyncWorkerImplTest, SendInitialSyncDone) {
    FirstInitialize();  // Initialize with no saved sync state.
    EXPECT_EQ(0U, GetNumModelThreadUpdateResponses());
    EXPECT_EQ(1, GetNumInitialDownloadNudges());
  
    // Receive an update response that contains only the type root node.
    TriggerTypeRootUpdateFromServer();
  
    // Two updates:
    // - One triggered by process updates to forward the type root ID.
    // - One triggered by apply updates, which the worker interprets to mean
    //   "initial sync done".  This triggers a model thread update, too.
    EXPECT_EQ(2U, GetNumModelThreadUpdateResponses());
  
    // The type root and initial sync done updates both contain no entities.
    EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(0).size());
    EXPECT_EQ(0U, GetNthModelThreadUpdateResponse(1).size());
  
    const DataTypeState& state = GetNthModelThreadUpdateState(1);
    EXPECT_FALSE(state.progress_marker.token().empty());
    EXPECT_FALSE(state.type_root_id.empty());
    EXPECT_TRUE(state.initial_sync_done);
  }
  
  // Commit two new entities in two separate commit messages.
  TEST_F(ModelTypeSyncWorkerImplTest, TwoNewItemsCommittedSeparately) {
    NormalInitialize();
  
    // Commit the first of two entities.
    CommitRequest("tag1", "value1");
    EXPECT_EQ(1, GetNumCommitNudges());
    ASSERT_TRUE(WillCommit());
    DoSuccessfulCommit();
    ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
    ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    const sync_pb::SyncEntity& tag1_entity =
        GetLatestCommitEntityOnServer("tag1");
  
    // Commit the second of two entities.
    CommitRequest("tag2", "value2");
    EXPECT_EQ(2, GetNumCommitNudges());
    ASSERT_TRUE(WillCommit());
    DoSuccessfulCommit();
    ASSERT_EQ(2U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(1, GetNthCommitMessageOnServer(1).commit().entries_size());
    ASSERT_TRUE(HasCommitEntityOnServer("tag2"));
    const sync_pb::SyncEntity& tag2_entity =
        GetLatestCommitEntityOnServer("tag2");
  
    EXPECT_FALSE(WillCommit());
  
    // The IDs assigned by the |worker_| should be unique.
    EXPECT_NE(tag1_entity.id_string(), tag2_entity.id_string());
  
    // Check that the committed specifics values are sane.
    EXPECT_EQ(tag1_entity.specifics().preference().value(), "value1");
    EXPECT_EQ(tag2_entity.specifics().preference().value(), "value2");
  
    // There should have been two separate commit responses sent to the model
    // thread.  They should be uninteresting, so we don't bother inspecting them.
    EXPECT_EQ(2U, GetNumModelThreadCommitResponses());
  }
  
  // Test normal update receipt code path.
  TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUpdates) {
    NormalInitialize();
  
    const std::string& tag_hash = GenerateTagHash("tag1");
  
    TriggerUpdateFromServer(10, "tag1", "value1");
  
    ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
    UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
    ASSERT_EQ(1U, updates_list.size());
  
    ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
    UpdateResponseData update = GetUpdateResponseOnModelThread("tag1");
  
    EXPECT_FALSE(update.id.empty());
    EXPECT_EQ(tag_hash, update.client_tag_hash);
    EXPECT_LT(0, update.response_version);
    EXPECT_FALSE(update.ctime.is_null());
    EXPECT_FALSE(update.mtime.is_null());
    EXPECT_FALSE(update.non_unique_name.empty());
    EXPECT_FALSE(update.deleted);
    EXPECT_EQ("tag1", update.specifics.preference().name());
    EXPECT_EQ("value1", update.specifics.preference().value());
  }
  
  // Test commit of encrypted updates.
  TEST_F(ModelTypeSyncWorkerImplTest, EncryptedCommit) {
    NormalInitialize();
  
    ASSERT_EQ(0U, GetNumModelThreadUpdateResponses());
  
    NewForeignEncryptionKey();
    UpdateLocalCryptographer();
  
    ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
    EXPECT_EQ(GetLocalCryptographerKeyName(),
              GetNthModelThreadUpdateState(0).encryption_key_name);
  
    // Normal commit request stuff.
    CommitRequest("tag1", "value1");
    DoSuccessfulCommit();
    ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
    ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    const sync_pb::SyncEntity& tag1_entity =
        GetLatestCommitEntityOnServer("tag1");
  
    EXPECT_TRUE(tag1_entity.specifics().has_encrypted());
  
    // The title should be overwritten.
    EXPECT_EQ(tag1_entity.name(), "encrypted");
  
    // The type should be set, but there should be no non-encrypted contents.
    EXPECT_TRUE(tag1_entity.specifics().has_preference());
    EXPECT_FALSE(tag1_entity.specifics().preference().has_name());
    EXPECT_FALSE(tag1_entity.specifics().preference().has_value());
  }
  
  // Test items are not committed when encryption is required but unavailable.
  TEST_F(ModelTypeSyncWorkerImplTest, EncryptionBlocksCommits) {
    NormalInitialize();
  
    CommitRequest("tag1", "value1");
    EXPECT_TRUE(WillCommit());
  
    // We know encryption is in use on this account, but don't have the necessary
    // encryption keys.  The worker should refuse to commit.
    NewForeignEncryptionKey();
    EXPECT_FALSE(WillCommit());
  
    // Once the cryptographer is returned to a normal state, we should be able to
    // commit again.
    EXPECT_EQ(1, GetNumCommitNudges());
    UpdateLocalCryptographer();
    EXPECT_EQ(2, GetNumCommitNudges());
    EXPECT_TRUE(WillCommit());
  
    // Verify the committed entity was properly encrypted.
    DoSuccessfulCommit();
    ASSERT_EQ(1U, GetNumCommitMessagesOnServer());
    EXPECT_EQ(1, GetNthCommitMessageOnServer(0).commit().entries_size());
    ASSERT_TRUE(HasCommitEntityOnServer("tag1"));
    const sync_pb::SyncEntity& tag1_entity =
        GetLatestCommitEntityOnServer("tag1");
    EXPECT_TRUE(tag1_entity.specifics().has_encrypted());
    EXPECT_EQ(tag1_entity.name(), "encrypted");
    EXPECT_TRUE(tag1_entity.specifics().has_preference());
    EXPECT_FALSE(tag1_entity.specifics().preference().has_name());
    EXPECT_FALSE(tag1_entity.specifics().preference().has_value());
  }
  
  // Test the receipt of decryptable entities.
  TEST_F(ModelTypeSyncWorkerImplTest, ReceiveDecryptableEntities) {
    NormalInitialize();
  
    // Create a new Nigori and allow the cryptographer to decrypt it.
    NewForeignEncryptionKey();
    UpdateLocalCryptographer();
  
    // First, receive an unencrypted entry.
    TriggerUpdateFromServer(10, "tag1", "value1");
  
    // Test some basic properties regarding the update.
    ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
    UpdateResponseData update1 = GetUpdateResponseOnModelThread("tag1");
    EXPECT_EQ("tag1", update1.specifics.preference().name());
    EXPECT_EQ("value1", update1.specifics.preference().value());
    EXPECT_TRUE(update1.encryption_key_name.empty());
  
    // Set received updates to be encrypted using the new nigori.
    SetUpdateEncryptionFilter(1);
  
    // This next update will be encrypted.
    TriggerUpdateFromServer(10, "tag2", "value2");
  
    // Test its basic features and the value of encryption_key_name.
    ASSERT_TRUE(HasUpdateResponseOnModelThread("tag2"));
    UpdateResponseData update2 = GetUpdateResponseOnModelThread("tag2");
    EXPECT_EQ("tag2", update2.specifics.preference().name());
    EXPECT_EQ("value2", update2.specifics.preference().value());
    EXPECT_FALSE(update2.encryption_key_name.empty());
  }
  
  // Test initializing a ModelTypeSyncWorker with a cryptographer at startup.
  TEST_F(ModelTypeSyncWorkerImplTest, InitializeWithCryptographer) {
    // Set up some encryption state.
    NewForeignEncryptionKey();
    UpdateLocalCryptographer();
  
    // Then initialize.
    NormalInitialize();
  
    // The worker should tell the model thread about encryption as soon as
    // possible, so that it will have the chance to re-encrypt local data if
    // necessary.
    ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
    EXPECT_EQ(GetLocalCryptographerKeyName(),
              GetNthModelThreadUpdateState(0).encryption_key_name);
  }
  
  // Receive updates that are initially undecryptable, then ensure they get
  // delivered to the model thread when decryption becomes possible.
  TEST_F(ModelTypeSyncWorkerImplTest, ReceiveUndecryptableEntries) {
    NormalInitialize();
  
    // Receive a new foreign encryption key that we can't decrypt.
    NewForeignEncryptionKey();
  
    // Receive an encrypted with that new key, which we can't access.
    SetUpdateEncryptionFilter(1);
    TriggerUpdateFromServer(10, "tag1", "value1");
  
    // At this point, the cryptographer does not have access to the key, so the
    // updates will be undecryptable.  They'll be transfered to the model thread
    // for safe-keeping as pending updates.
    ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
    UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
    EXPECT_EQ(0U, updates_list.size());
    UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0);
    EXPECT_EQ(1U, pending_updates.size());
  
    // The update will be delivered as soon as decryption becomes possible.
    UpdateLocalCryptographer();
    ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
    UpdateResponseData update = GetUpdateResponseOnModelThread("tag1");
    EXPECT_EQ("tag1", update.specifics.preference().name());
    EXPECT_EQ("value1", update.specifics.preference().value());
    EXPECT_FALSE(update.encryption_key_name.empty());
  }
  
  // Ensure that even encrypted updates can cause conflicts.
  TEST_F(ModelTypeSyncWorkerImplTest, EncryptedUpdateOverridesPendingCommit) {
    NormalInitialize();
  
    // Prepeare to commit an item.
    CommitRequest("tag1", "value1");
    EXPECT_TRUE(WillCommit());
  
    // Receive an encrypted update for that item.
    SetUpdateEncryptionFilter(1);
    TriggerUpdateFromServer(10, "tag1", "value1");
  
    // The pending commit state should be cleared.
    EXPECT_FALSE(WillCommit());
  
    // The encrypted update will be delivered to the model thread.
    ASSERT_EQ(1U, GetNumModelThreadUpdateResponses());
    UpdateResponseDataList updates_list = GetNthModelThreadUpdateResponse(0);
    EXPECT_EQ(0U, updates_list.size());
    UpdateResponseDataList pending_updates = GetNthModelThreadPendingUpdates(0);
    EXPECT_EQ(1U, pending_updates.size());
  }
  
  // Test decryption of pending updates saved across a restart.
  TEST_F(ModelTypeSyncWorkerImplTest, RestorePendingEntries) {
    // Create a fake pending update.
    UpdateResponseData update;
  
    update.client_tag_hash = GenerateTagHash("tag1");
    update.id = "SomeID";
    update.response_version = 100;
    update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10);
    update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11);
    update.non_unique_name = "encrypted";
    update.deleted = false;
  
    update.specifics = GenerateSpecifics("tag1", "value1");
    EncryptUpdate(GetNthKeyParams(1), &(update.specifics));
  
    // Inject the update during ModelTypeSyncWorker initialization.
    UpdateResponseDataList saved_pending_updates;
    saved_pending_updates.push_back(update);
    InitializeWithPendingUpdates(saved_pending_updates);
  
    // Update will be undecryptable at first.
    EXPECT_EQ(0U, GetNumModelThreadUpdateResponses());
    ASSERT_FALSE(HasUpdateResponseOnModelThread("tag1"));
  
    // Update the cryptographer so it can decrypt that update.
    NewForeignEncryptionKey();
    UpdateLocalCryptographer();
  
    // Verify the item gets decrypted and sent back to the model thread.
    ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
  }
  
  // Test decryption of pending updates saved across a restart.  This test
  // differs from the previous one in that the restored updates can be decrypted
  // immediately after the ModelTypeSyncWorker is constructed.
  TEST_F(ModelTypeSyncWorkerImplTest, RestoreApplicableEntries) {
    // Update the cryptographer so it can decrypt that update.
    NewForeignEncryptionKey();
    UpdateLocalCryptographer();
  
    // Create a fake pending update.
    UpdateResponseData update;
    update.client_tag_hash = GenerateTagHash("tag1");
    update.id = "SomeID";
    update.response_version = 100;
    update.ctime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(10);
    update.mtime = base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(11);
    update.non_unique_name = "encrypted";
    update.deleted = false;
  
    update.specifics = GenerateSpecifics("tag1", "value1");
    EncryptUpdate(GetNthKeyParams(1), &(update.specifics));
  
    // Inject the update during ModelTypeSyncWorker initialization.
    UpdateResponseDataList saved_pending_updates;
    saved_pending_updates.push_back(update);
    InitializeWithPendingUpdates(saved_pending_updates);
  
    // Verify the item gets decrypted and sent back to the model thread.
    ASSERT_TRUE(HasUpdateResponseOnModelThread("tag1"));
  }
  
  // Test that undecryptable updates provide sufficient reason to not commit.
  //
  // This should be rare in practice.  Usually the cryptographer will be in an
  // unusable state when we receive undecryptable updates, and that alone will be
  // enough to prevent all commits.
  TEST_F(ModelTypeSyncWorkerImplTest, CommitBlockedByPending) {
    NormalInitialize();
  
    // Prepeare to commit an item.
    CommitRequest("tag1", "value1");
    EXPECT_TRUE(WillCommit());
  
    // Receive an encrypted update for that item.
    SetUpdateEncryptionFilter(1);
    TriggerUpdateFromServer(10, "tag1", "value1");
  
    // The pending commit state should be cleared.
    EXPECT_FALSE(WillCommit());
  
    // The pending update will be delivered to the model thread.
    HasUpdateResponseOnModelThread("tag1");
  
    // Pretend the update arrived too late to prevent another commit request.
    CommitRequest("tag1", "value2");
  
    EXPECT_FALSE(WillCommit());
  }
  
  // Verify that corrupted encrypted updates don't cause crashes.
  TEST_F(ModelTypeSyncWorkerImplTest, ReceiveCorruptEncryption) {
    // Initialize the worker with basic encryption state.
    NormalInitialize();
    NewForeignEncryptionKey();
    UpdateLocalCryptographer();
  
    // Manually create an update.
    sync_pb::SyncEntity entity;
    entity.set_client_defined_unique_tag(GenerateTagHash("tag1"));
    entity.set_id_string("SomeID");
    entity.set_version(1);
    entity.set_ctime(1000);
    entity.set_mtime(1001);
    entity.set_name("encrypted");
    entity.set_deleted(false);
  
    // Encrypt it.
    entity.mutable_specifics()->CopyFrom(GenerateSpecifics("tag1", "value1"));
    EncryptUpdate(GetNthKeyParams(1), entity.mutable_specifics());
  
    // Replace a few bytes to corrupt it.
    entity.mutable_specifics()->mutable_encrypted()->mutable_blob()->replace(
        0, 4, "xyz!");
  
    SyncEntityList entity_list;
    entity_list.push_back(&entity);
  
    // If a corrupt update could trigger a crash, this is where it would happen.
    DeliverRawUpdates(entity_list);
  
    EXPECT_FALSE(HasUpdateResponseOnModelThread("tag1"));
  
    // Deliver a non-corrupt update to see if the everything still works.
    SetUpdateEncryptionFilter(1);
    TriggerUpdateFromServer(10, "tag1", "value1");
    EXPECT_TRUE(HasUpdateResponseOnModelThread("tag1"));
  }
  
  }  // namespace syncer
  