| // 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_proxy_impl.h" |
| |
| #include "sync/engine/model_type_sync_worker.h" |
| #include "sync/internal_api/public/base/model_type.h" |
| #include "sync/internal_api/public/non_blocking_sync_common.h" |
| #include "sync/internal_api/public/sync_context_proxy.h" |
| #include "sync/protocol/sync.pb.h" |
| #include "sync/syncable/syncable_util.h" |
| #include "sync/test/engine/injectable_sync_context_proxy.h" |
| #include "sync/test/engine/mock_model_type_sync_worker.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace syncer_v2 { |
| |
| static const syncer::ModelType kModelType = syncer::PREFERENCES; |
| |
| // Tests the sync engine parts of ModelTypeSyncProxyImpl. |
| // |
| // The ModelTypeSyncProxyImpl contains a non-trivial amount of code dedicated |
| // to turning the sync engine on and off again. That code is fairly well |
| // tested in the NonBlockingDataTypeController unit tests and it doesn't need |
| // to be re-tested here. |
| // |
| // These tests skip past initialization and focus on steady state sync engine |
| // behvior. This is where we test how the type sync proxy responds to the |
| // model's requests to make changes to its data, the messages incoming from the |
| // sync server, and what happens when the two conflict. |
| // |
| // Inputs: |
| // - Initial state from permanent storage. (TODO) |
| // - Create, update or delete requests from the model. |
| // - Update responses and commit responses from the server. |
| // |
| // Outputs: |
| // - Writes to permanent storage. (TODO) |
| // - Callbacks into the model. (TODO) |
| // - Requests to the sync thread. Tested with MockModelTypeSyncWorker. |
| class ModelTypeSyncProxyImplTest : public ::testing::Test { |
| public: |
| ModelTypeSyncProxyImplTest(); |
| ~ModelTypeSyncProxyImplTest() override; |
| |
| // Initialize with no local state. The type sync proxy will be unable to |
| // commit until it receives notification that initial sync has completed. |
| void FirstTimeInitialize(); |
| |
| // Initialize to a "ready-to-commit" state. |
| void InitializeToReadyState(); |
| |
| // Disconnect the ModelTypeSyncWorker from our ModelTypeSyncProxyImpl. |
| void Disconnect(); |
| |
| // Disable sync for this ModelTypeSyncProxyImpl. Should cause sync state to |
| // be discarded. |
| void Disable(); |
| |
| // Re-enable sync after Disconnect() or Disable(). |
| void ReEnable(); |
| |
| // Local data modification. Emulates signals from the model thread. |
| void WriteItem(const std::string& tag, const std::string& value); |
| void DeleteItem(const std::string& tag); |
| |
| // Emulates an "initial sync done" message from the |
| // ModelTypeSyncWorker. |
| void OnInitialSyncDone(); |
| |
| // Emulate updates from the server. |
| // This harness has some functionality to help emulate server behavior. |
| // See the definitions of these methods for more information. |
| void UpdateFromServer(int64 version_offset, |
| const std::string& tag, |
| const std::string& value); |
| void TombstoneFromServer(int64 version_offset, const std::string& tag); |
| |
| // Emulate the receipt of pending updates from the server. |
| // Pending updates are usually caused by a temporary decryption failure. |
| void PendingUpdateFromServer(int64 version_offset, |
| const std::string& tag, |
| const std::string& value, |
| const std::string& key_name); |
| |
| // Returns true if the proxy has an pending update with specified tag. |
| bool HasPendingUpdate(const std::string& tag) const; |
| |
| // Returns the pending update with the specified tag. |
| UpdateResponseData GetPendingUpdate(const std::string& tag) const; |
| |
| // Returns the number of pending updates. |
| size_t GetNumPendingUpdates() const; |
| |
| // Read emitted commit requests as batches. |
| size_t GetNumCommitRequestLists(); |
| CommitRequestDataList GetNthCommitRequestList(size_t n); |
| |
| // Read emitted commit requests by tag, most recent only. |
| bool HasCommitRequestForTag(const std::string& tag); |
| CommitRequestData GetLatestCommitRequestForTag(const std::string& tag); |
| |
| // Sends the type sync proxy a successful commit response. |
| void SuccessfulCommitResponse(const CommitRequestData& request_data); |
| |
| // Sends the type sync proxy an updated DataTypeState to let it know that |
| // the desired encryption key has changed. |
| void UpdateDesiredEncryptionKey(const std::string& key_name); |
| |
| // Sets the key_name that the mock ModelTypeSyncWorker will claim is in use |
| // when receiving items. |
| void SetServerEncryptionKey(const std::string& key_name); |
| |
| private: |
| static std::string GenerateTagHash(const std::string& tag); |
| static sync_pb::EntitySpecifics GenerateSpecifics(const std::string& tag, |
| const std::string& value); |
| static sync_pb::EntitySpecifics GenerateEncryptedSpecifics( |
| const std::string& tag, |
| const std::string& value, |
| const std::string& key_name); |
| |
| int64 GetServerVersion(const std::string& tag); |
| void SetServerVersion(const std::string& tag, int64 version); |
| |
| MockModelTypeSyncWorker* mock_worker_; |
| scoped_ptr<InjectableSyncContextProxy> injectable_sync_context_proxy_; |
| scoped_ptr<ModelTypeSyncProxyImpl> type_sync_proxy_; |
| |
| DataTypeState data_type_state_; |
| }; |
| |
| ModelTypeSyncProxyImplTest::ModelTypeSyncProxyImplTest() |
| : mock_worker_(new MockModelTypeSyncWorker()), |
| injectable_sync_context_proxy_( |
| new InjectableSyncContextProxy(mock_worker_)), |
| type_sync_proxy_(new ModelTypeSyncProxyImpl(kModelType)) { |
| } |
| |
| ModelTypeSyncProxyImplTest::~ModelTypeSyncProxyImplTest() { |
| } |
| |
| void ModelTypeSyncProxyImplTest::FirstTimeInitialize() { |
| type_sync_proxy_->Enable(injectable_sync_context_proxy_->Clone()); |
| } |
| |
| void ModelTypeSyncProxyImplTest::InitializeToReadyState() { |
| // TODO(rlarocque): This should be updated to inject on-disk state. |
| // At the time this code was written, there was no support for on-disk |
| // state so this was the only way to inject a data_type_state into |
| // the |type_sync_proxy_|. |
| FirstTimeInitialize(); |
| OnInitialSyncDone(); |
| } |
| |
| void ModelTypeSyncProxyImplTest::Disconnect() { |
| type_sync_proxy_->Disconnect(); |
| injectable_sync_context_proxy_.reset(); |
| mock_worker_ = NULL; |
| } |
| |
| void ModelTypeSyncProxyImplTest::Disable() { |
| type_sync_proxy_->Disable(); |
| injectable_sync_context_proxy_.reset(); |
| mock_worker_ = NULL; |
| } |
| |
| void ModelTypeSyncProxyImplTest::ReEnable() { |
| DCHECK(!type_sync_proxy_->IsConnected()); |
| |
| // Prepare a new MockModelTypeSyncWorker instance, just as we would |
| // if this happened in the real world. |
| mock_worker_ = new MockModelTypeSyncWorker(); |
| injectable_sync_context_proxy_.reset( |
| new InjectableSyncContextProxy(mock_worker_)); |
| |
| // Re-enable sync with the new ModelTypeSyncWorker. |
| type_sync_proxy_->Enable(injectable_sync_context_proxy_->Clone()); |
| } |
| |
| void ModelTypeSyncProxyImplTest::WriteItem(const std::string& tag, |
| const std::string& value) { |
| const std::string tag_hash = GenerateTagHash(tag); |
| type_sync_proxy_->Put(tag, GenerateSpecifics(tag, value)); |
| } |
| |
| void ModelTypeSyncProxyImplTest::DeleteItem(const std::string& tag) { |
| type_sync_proxy_->Delete(tag); |
| } |
| |
| void ModelTypeSyncProxyImplTest::OnInitialSyncDone() { |
| data_type_state_.initial_sync_done = true; |
| UpdateResponseDataList empty_update_list; |
| |
| type_sync_proxy_->OnUpdateReceived( |
| data_type_state_, empty_update_list, empty_update_list); |
| } |
| |
| void ModelTypeSyncProxyImplTest::UpdateFromServer(int64 version_offset, |
| const std::string& tag, |
| const std::string& value) { |
| const std::string tag_hash = GenerateTagHash(tag); |
| UpdateResponseData data = mock_worker_->UpdateFromServer( |
| version_offset, tag_hash, GenerateSpecifics(tag, value)); |
| |
| UpdateResponseDataList list; |
| list.push_back(data); |
| type_sync_proxy_->OnUpdateReceived(data_type_state_, list, |
| UpdateResponseDataList()); |
| } |
| |
| void ModelTypeSyncProxyImplTest::PendingUpdateFromServer( |
| int64 version_offset, |
| const std::string& tag, |
| const std::string& value, |
| const std::string& key_name) { |
| const std::string tag_hash = GenerateTagHash(tag); |
| UpdateResponseData data = mock_worker_->UpdateFromServer( |
| version_offset, tag_hash, |
| GenerateEncryptedSpecifics(tag, value, key_name)); |
| |
| UpdateResponseDataList list; |
| list.push_back(data); |
| type_sync_proxy_->OnUpdateReceived(data_type_state_, UpdateResponseDataList(), |
| list); |
| } |
| |
| void ModelTypeSyncProxyImplTest::TombstoneFromServer(int64 version_offset, |
| const std::string& tag) { |
| // Overwrite the existing server version if this is the new highest version. |
| std::string tag_hash = GenerateTagHash(tag); |
| |
| UpdateResponseData data = |
| mock_worker_->TombstoneFromServer(version_offset, tag_hash); |
| |
| UpdateResponseDataList list; |
| list.push_back(data); |
| type_sync_proxy_->OnUpdateReceived(data_type_state_, list, |
| UpdateResponseDataList()); |
| } |
| |
| bool ModelTypeSyncProxyImplTest::HasPendingUpdate( |
| const std::string& tag) const { |
| const std::string client_tag_hash = GenerateTagHash(tag); |
| const UpdateResponseDataList list = type_sync_proxy_->GetPendingUpdates(); |
| for (UpdateResponseDataList::const_iterator it = list.begin(); |
| it != list.end(); ++it) { |
| if (it->client_tag_hash == client_tag_hash) |
| return true; |
| } |
| return false; |
| } |
| |
| UpdateResponseData ModelTypeSyncProxyImplTest::GetPendingUpdate( |
| const std::string& tag) const { |
| DCHECK(HasPendingUpdate(tag)); |
| const std::string client_tag_hash = GenerateTagHash(tag); |
| const UpdateResponseDataList list = type_sync_proxy_->GetPendingUpdates(); |
| for (UpdateResponseDataList::const_iterator it = list.begin(); |
| it != list.end(); ++it) { |
| if (it->client_tag_hash == client_tag_hash) |
| return *it; |
| } |
| NOTREACHED(); |
| return UpdateResponseData(); |
| } |
| |
| size_t ModelTypeSyncProxyImplTest::GetNumPendingUpdates() const { |
| return type_sync_proxy_->GetPendingUpdates().size(); |
| } |
| |
| void ModelTypeSyncProxyImplTest::SuccessfulCommitResponse( |
| const CommitRequestData& request_data) { |
| CommitResponseDataList list; |
| list.push_back(mock_worker_->SuccessfulCommitResponse(request_data)); |
| type_sync_proxy_->OnCommitCompleted(data_type_state_, list); |
| } |
| |
| void ModelTypeSyncProxyImplTest::UpdateDesiredEncryptionKey( |
| const std::string& key_name) { |
| data_type_state_.encryption_key_name = key_name; |
| type_sync_proxy_->OnUpdateReceived(data_type_state_, UpdateResponseDataList(), |
| UpdateResponseDataList()); |
| } |
| |
| void ModelTypeSyncProxyImplTest::SetServerEncryptionKey( |
| const std::string& key_name) { |
| mock_worker_->SetServerEncryptionKey(key_name); |
| } |
| |
| std::string ModelTypeSyncProxyImplTest::GenerateTagHash( |
| const std::string& tag) { |
| return syncer::syncable::GenerateSyncableHash(kModelType, tag); |
| } |
| |
| sync_pb::EntitySpecifics ModelTypeSyncProxyImplTest::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; |
| } |
| |
| // These tests never decrypt anything, so we can get away with faking the |
| // encryption for now. |
| sync_pb::EntitySpecifics ModelTypeSyncProxyImplTest::GenerateEncryptedSpecifics( |
| const std::string& tag, |
| const std::string& value, |
| const std::string& key_name) { |
| sync_pb::EntitySpecifics specifics; |
| syncer::AddDefaultFieldValue(kModelType, &specifics); |
| specifics.mutable_encrypted()->set_key_name(key_name); |
| specifics.mutable_encrypted()->set_blob("BLOB" + key_name); |
| return specifics; |
| } |
| |
| size_t ModelTypeSyncProxyImplTest::GetNumCommitRequestLists() { |
| return mock_worker_->GetNumCommitRequestLists(); |
| } |
| |
| CommitRequestDataList ModelTypeSyncProxyImplTest::GetNthCommitRequestList( |
| size_t n) { |
| return mock_worker_->GetNthCommitRequestList(n); |
| } |
| |
| bool ModelTypeSyncProxyImplTest::HasCommitRequestForTag( |
| const std::string& tag) { |
| const std::string tag_hash = GenerateTagHash(tag); |
| return mock_worker_->HasCommitRequestForTagHash(tag_hash); |
| } |
| |
| CommitRequestData ModelTypeSyncProxyImplTest::GetLatestCommitRequestForTag( |
| const std::string& tag) { |
| const std::string tag_hash = GenerateTagHash(tag); |
| return mock_worker_->GetLatestCommitRequestForTagHash(tag_hash); |
| } |
| |
| // Creates a new item locally. |
| // Thoroughly tests the data generated by a local item creation. |
| TEST_F(ModelTypeSyncProxyImplTest, CreateLocalItem) { |
| InitializeToReadyState(); |
| EXPECT_EQ(0U, GetNumCommitRequestLists()); |
| |
| WriteItem("tag1", "value1"); |
| |
| // Verify the commit request this operation has triggered. |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_data = GetLatestCommitRequestForTag("tag1"); |
| |
| EXPECT_TRUE(tag1_data.id.empty()); |
| EXPECT_EQ(kUncommittedVersion, tag1_data.base_version); |
| EXPECT_FALSE(tag1_data.ctime.is_null()); |
| EXPECT_FALSE(tag1_data.mtime.is_null()); |
| EXPECT_EQ("tag1", tag1_data.non_unique_name); |
| EXPECT_FALSE(tag1_data.deleted); |
| EXPECT_EQ("tag1", tag1_data.specifics.preference().name()); |
| EXPECT_EQ("value1", tag1_data.specifics.preference().value()); |
| } |
| |
| // Creates a new local item then modifies it. |
| // Thoroughly tests data generated by modification of server-unknown item. |
| TEST_F(ModelTypeSyncProxyImplTest, CreateAndModifyLocalItem) { |
| InitializeToReadyState(); |
| EXPECT_EQ(0U, GetNumCommitRequestLists()); |
| |
| WriteItem("tag1", "value1"); |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); |
| |
| WriteItem("tag1", "value2"); |
| EXPECT_EQ(2U, GetNumCommitRequestLists()); |
| |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_v2_data = GetLatestCommitRequestForTag("tag1"); |
| |
| // Test some of the relations between old and new commit requests. |
| EXPECT_EQ(tag1_v1_data.specifics.preference().value(), "value1"); |
| EXPECT_GT(tag1_v2_data.sequence_number, tag1_v1_data.sequence_number); |
| |
| // Perform a thorough examination of the update-generated request. |
| EXPECT_TRUE(tag1_v2_data.id.empty()); |
| EXPECT_EQ(kUncommittedVersion, tag1_v2_data.base_version); |
| EXPECT_FALSE(tag1_v2_data.ctime.is_null()); |
| EXPECT_FALSE(tag1_v2_data.mtime.is_null()); |
| EXPECT_EQ("tag1", tag1_v2_data.non_unique_name); |
| EXPECT_FALSE(tag1_v2_data.deleted); |
| EXPECT_EQ("tag1", tag1_v2_data.specifics.preference().name()); |
| EXPECT_EQ("value2", tag1_v2_data.specifics.preference().value()); |
| } |
| |
| // Deletes an item we've never seen before. |
| // Should have no effect and not crash. |
| TEST_F(ModelTypeSyncProxyImplTest, DeleteUnknown) { |
| InitializeToReadyState(); |
| |
| DeleteItem("tag1"); |
| EXPECT_EQ(0U, GetNumCommitRequestLists()); |
| } |
| |
| // Creates an item locally then deletes it. |
| // |
| // In this test, no commit responses are received, so the deleted item is |
| // server-unknown as far as the model thread is concerned. That behavior |
| // is race-dependent; other tests are used to test other races. |
| TEST_F(ModelTypeSyncProxyImplTest, DeleteServerUnknown) { |
| InitializeToReadyState(); |
| |
| WriteItem("tag1", "value1"); |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); |
| |
| DeleteItem("tag1"); |
| EXPECT_EQ(2U, GetNumCommitRequestLists()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_v2_data = GetLatestCommitRequestForTag("tag1"); |
| |
| EXPECT_GT(tag1_v2_data.sequence_number, tag1_v1_data.sequence_number); |
| |
| EXPECT_TRUE(tag1_v2_data.id.empty()); |
| EXPECT_EQ(kUncommittedVersion, tag1_v2_data.base_version); |
| EXPECT_TRUE(tag1_v2_data.deleted); |
| } |
| |
| // Creates an item locally then deletes it. |
| // |
| // The item is created locally then enqueued for commit. The sync thread |
| // successfully commits it, but, before the commit response is picked up |
| // by the model thread, the item is deleted by the model thread. |
| TEST_F(ModelTypeSyncProxyImplTest, DeleteServerUnknown_RacyCommitResponse) { |
| InitializeToReadyState(); |
| |
| WriteItem("tag1", "value1"); |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); |
| |
| DeleteItem("tag1"); |
| EXPECT_EQ(2U, GetNumCommitRequestLists()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| |
| // This commit happened while the deletion was in progress, but the commit |
| // response didn't arrive on our thread until after the delete was issued to |
| // the sync thread. It will update some metadata, but won't do much else. |
| SuccessfulCommitResponse(tag1_v1_data); |
| |
| // TODO(rlarocque): Verify the state of the item is correct once we get |
| // storage hooked up in these tests. For example, verify the item is still |
| // marked as deleted. |
| } |
| |
| // Creates two different sync items. |
| // Verifies that the second has no effect on the first. |
| TEST_F(ModelTypeSyncProxyImplTest, TwoIndependentItems) { |
| InitializeToReadyState(); |
| EXPECT_EQ(0U, GetNumCommitRequestLists()); |
| |
| WriteItem("tag1", "value1"); |
| |
| // There should be one commit request for this item only. |
| ASSERT_EQ(1U, GetNumCommitRequestLists()); |
| EXPECT_EQ(1U, GetNthCommitRequestList(0).size()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| |
| WriteItem("tag2", "value2"); |
| |
| // The second write should trigger another single-item commit request. |
| ASSERT_EQ(2U, GetNumCommitRequestLists()); |
| EXPECT_EQ(1U, GetNthCommitRequestList(1).size()); |
| ASSERT_TRUE(HasCommitRequestForTag("tag2")); |
| } |
| |
| // Starts the type sync proxy with no local state. |
| // Verify that it waits until initial sync is complete before requesting |
| // commits. |
| TEST_F(ModelTypeSyncProxyImplTest, NoCommitsUntilInitialSyncDone) { |
| FirstTimeInitialize(); |
| |
| WriteItem("tag1", "value1"); |
| EXPECT_EQ(0U, GetNumCommitRequestLists()); |
| |
| OnInitialSyncDone(); |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| EXPECT_TRUE(HasCommitRequestForTag("tag1")); |
| } |
| |
| // Test proper handling of disconnect and reconnect. |
| // |
| // Creates items in various states of commit and verifies they re-attempt to |
| // commit on reconnect. |
| TEST_F(ModelTypeSyncProxyImplTest, Disconnect) { |
| InitializeToReadyState(); |
| |
| // The first item is fully committed. |
| WriteItem("tag1", "value1"); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| SuccessfulCommitResponse(GetLatestCommitRequestForTag("tag1")); |
| |
| // The second item has a commit request in progress. |
| WriteItem("tag2", "value2"); |
| EXPECT_TRUE(HasCommitRequestForTag("tag2")); |
| |
| Disconnect(); |
| |
| // The third item is added after disconnection. |
| WriteItem("tag3", "value3"); |
| |
| ReEnable(); |
| |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| EXPECT_EQ(2U, GetNthCommitRequestList(0).size()); |
| |
| // The first item was already in sync. |
| EXPECT_FALSE(HasCommitRequestForTag("tag1")); |
| |
| // The second item's commit was interrupted and should be retried. |
| EXPECT_TRUE(HasCommitRequestForTag("tag2")); |
| |
| // The third item's commit was not started until the reconnect. |
| EXPECT_TRUE(HasCommitRequestForTag("tag3")); |
| } |
| |
| // Test proper handling of disable and re-enable. |
| // |
| // Creates items in various states of commit and verifies they re-attempt to |
| // commit on re-enable. |
| TEST_F(ModelTypeSyncProxyImplTest, Disable) { |
| InitializeToReadyState(); |
| |
| // The first item is fully committed. |
| WriteItem("tag1", "value1"); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| SuccessfulCommitResponse(GetLatestCommitRequestForTag("tag1")); |
| |
| // The second item has a commit request in progress. |
| WriteItem("tag2", "value2"); |
| EXPECT_TRUE(HasCommitRequestForTag("tag2")); |
| |
| Disable(); |
| |
| // The third item is added after disable. |
| WriteItem("tag3", "value3"); |
| |
| // Now we re-enable. |
| ReEnable(); |
| |
| // There should be nothing to commit right away, since we need to |
| // re-initialize the client state first. |
| EXPECT_EQ(0U, GetNumCommitRequestLists()); |
| |
| // Once we're ready to commit, all three local items should consider |
| // themselves uncommitted and pending for commit. |
| OnInitialSyncDone(); |
| EXPECT_EQ(1U, GetNumCommitRequestLists()); |
| EXPECT_EQ(3U, GetNthCommitRequestList(0).size()); |
| EXPECT_TRUE(HasCommitRequestForTag("tag1")); |
| EXPECT_TRUE(HasCommitRequestForTag("tag2")); |
| EXPECT_TRUE(HasCommitRequestForTag("tag3")); |
| } |
| |
| // Test receipt of pending updates. |
| TEST_F(ModelTypeSyncProxyImplTest, ReceivePendingUpdates) { |
| InitializeToReadyState(); |
| |
| EXPECT_FALSE(HasPendingUpdate("tag1")); |
| EXPECT_EQ(0U, GetNumPendingUpdates()); |
| |
| // Receive a pending update. |
| PendingUpdateFromServer(5, "tag1", "value1", "key1"); |
| EXPECT_EQ(1U, GetNumPendingUpdates()); |
| ASSERT_TRUE(HasPendingUpdate("tag1")); |
| UpdateResponseData data1 = GetPendingUpdate("tag1"); |
| EXPECT_EQ(5, data1.response_version); |
| |
| // Receive an updated version of a pending update. |
| // It should overwrite the existing item. |
| PendingUpdateFromServer(10, "tag1", "value15", "key1"); |
| EXPECT_EQ(1U, GetNumPendingUpdates()); |
| ASSERT_TRUE(HasPendingUpdate("tag1")); |
| UpdateResponseData data2 = GetPendingUpdate("tag1"); |
| EXPECT_EQ(15, data2.response_version); |
| |
| // Receive a stale version of a pending update. |
| // It should have no effect. |
| PendingUpdateFromServer(-3, "tag1", "value12", "key1"); |
| EXPECT_EQ(1U, GetNumPendingUpdates()); |
| ASSERT_TRUE(HasPendingUpdate("tag1")); |
| UpdateResponseData data3 = GetPendingUpdate("tag1"); |
| EXPECT_EQ(15, data3.response_version); |
| } |
| |
| // Test that Disable clears pending update state. |
| TEST_F(ModelTypeSyncProxyImplTest, DisableWithPendingUpdates) { |
| InitializeToReadyState(); |
| |
| PendingUpdateFromServer(5, "tag1", "value1", "key1"); |
| EXPECT_EQ(1U, GetNumPendingUpdates()); |
| ASSERT_TRUE(HasPendingUpdate("tag1")); |
| |
| Disable(); |
| ReEnable(); |
| |
| EXPECT_EQ(0U, GetNumPendingUpdates()); |
| EXPECT_FALSE(HasPendingUpdate("tag1")); |
| } |
| |
| // Test that Disconnect does not clear pending update state. |
| TEST_F(ModelTypeSyncProxyImplTest, DisconnectWithPendingUpdates) { |
| InitializeToReadyState(); |
| |
| PendingUpdateFromServer(5, "tag1", "value1", "key1"); |
| EXPECT_EQ(1U, GetNumPendingUpdates()); |
| ASSERT_TRUE(HasPendingUpdate("tag1")); |
| |
| Disconnect(); |
| ReEnable(); |
| |
| EXPECT_EQ(1U, GetNumPendingUpdates()); |
| EXPECT_TRUE(HasPendingUpdate("tag1")); |
| } |
| |
| // Test re-encrypt everything when desired encryption key changes. |
| TEST_F(ModelTypeSyncProxyImplTest, ReEncryptCommitsWithNewKey) { |
| InitializeToReadyState(); |
| |
| // Commit an item. |
| WriteItem("tag1", "value1"); |
| ASSERT_TRUE(HasCommitRequestForTag("tag1")); |
| const CommitRequestData& tag1_v1_data = GetLatestCommitRequestForTag("tag1"); |
| SuccessfulCommitResponse(tag1_v1_data); |
| |
| // Create another item and don't wait for its commit response. |
| WriteItem("tag2", "value2"); |
| |
| ASSERT_EQ(2U, GetNumCommitRequestLists()); |
| |
| // Receive notice that the account's desired encryption key has changed. |
| UpdateDesiredEncryptionKey("k1"); |
| |
| // That should trigger a new commit request. |
| ASSERT_EQ(3U, GetNumCommitRequestLists()); |
| EXPECT_EQ(2U, GetNthCommitRequestList(2).size()); |
| |
| const CommitRequestData& tag1_enc = GetLatestCommitRequestForTag("tag1"); |
| const CommitRequestData& tag2_enc = GetLatestCommitRequestForTag("tag2"); |
| |
| SuccessfulCommitResponse(tag1_enc); |
| SuccessfulCommitResponse(tag2_enc); |
| |
| // And that should be the end of it. |
| ASSERT_EQ(3U, GetNumCommitRequestLists()); |
| } |
| |
| // Test receipt of updates with new and old keys. |
| TEST_F(ModelTypeSyncProxyImplTest, ReEncryptUpdatesWithNewKey) { |
| InitializeToReadyState(); |
| |
| // Receive an unencrpted update. |
| UpdateFromServer(5, "no_enc", "value1"); |
| |
| ASSERT_EQ(0U, GetNumCommitRequestLists()); |
| |
| // Set desired encryption key to k2 to force updates to some items. |
| UpdateDesiredEncryptionKey("k2"); |
| |
| ASSERT_EQ(1U, GetNumCommitRequestLists()); |
| EXPECT_EQ(1U, GetNthCommitRequestList(0).size()); |
| EXPECT_TRUE(HasCommitRequestForTag("no_enc")); |
| |
| // Receive an update that was encrypted with key k1. |
| SetServerEncryptionKey("k1"); |
| UpdateFromServer(10, "enc_k1", "value1"); |
| |
| // Receipt of updates encrypted with old key also forces a re-encrypt commit. |
| ASSERT_EQ(2U, GetNumCommitRequestLists()); |
| EXPECT_EQ(1U, GetNthCommitRequestList(1).size()); |
| EXPECT_TRUE(HasCommitRequestForTag("enc_k1")); |
| |
| // Receive an update that was encrypted with key k2. |
| SetServerEncryptionKey("k2"); |
| UpdateFromServer(15, "enc_k2", "value1"); |
| |
| // That was the correct key, so no re-encryption is required. |
| EXPECT_EQ(2U, GetNumCommitRequestLists()); |
| EXPECT_FALSE(HasCommitRequestForTag("enc_k2")); |
| } |
| |
| } // namespace syncer |