blob: 8f57d6993c487d7eb74d840b3aebf9489c3088d3 [file] [log] [blame]
// 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_processor_impl.h"
#include "sync/engine/commit_queue.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_commit_queue.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer_v2 {
static const syncer::ModelType kModelType = syncer::PREFERENCES;
// Tests the sync engine parts of ModelTypeProcessorImpl.
//
// The ModelTypeProcessorImpl 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 MockCommitQueue.
class ModelTypeProcessorImplTest : public ::testing::Test {
public:
ModelTypeProcessorImplTest();
~ModelTypeProcessorImplTest() 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 CommitQueue from our ModelTypeProcessorImpl.
void Disconnect();
// Disable sync for this ModelTypeProcessorImpl. 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
// CommitQueue.
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 CommitQueue 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);
MockCommitQueue* mock_queue_;
scoped_ptr<InjectableSyncContextProxy> injectable_sync_context_proxy_;
scoped_ptr<ModelTypeProcessorImpl> type_processor_;
DataTypeState data_type_state_;
};
ModelTypeProcessorImplTest::ModelTypeProcessorImplTest()
: mock_queue_(new MockCommitQueue()),
injectable_sync_context_proxy_(
new InjectableSyncContextProxy(mock_queue_)),
type_processor_(
new ModelTypeProcessorImpl(kModelType,
base::WeakPtr<ModelTypeStore>())) {}
ModelTypeProcessorImplTest::~ModelTypeProcessorImplTest() {
}
void ModelTypeProcessorImplTest::FirstTimeInitialize() {
type_processor_->Enable(injectable_sync_context_proxy_->Clone());
}
void ModelTypeProcessorImplTest::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_processor_|.
FirstTimeInitialize();
OnInitialSyncDone();
}
void ModelTypeProcessorImplTest::Disconnect() {
type_processor_->Disconnect();
injectable_sync_context_proxy_.reset();
mock_queue_ = NULL;
}
void ModelTypeProcessorImplTest::Disable() {
type_processor_->Disable();
injectable_sync_context_proxy_.reset();
mock_queue_ = NULL;
}
void ModelTypeProcessorImplTest::ReEnable() {
DCHECK(!type_processor_->IsConnected());
// Prepare a new MockCommitQueue instance, just as we would
// if this happened in the real world.
mock_queue_ = new MockCommitQueue();
injectable_sync_context_proxy_.reset(
new InjectableSyncContextProxy(mock_queue_));
// Re-enable sync with the new CommitQueue.
type_processor_->Enable(injectable_sync_context_proxy_->Clone());
}
void ModelTypeProcessorImplTest::WriteItem(const std::string& tag,
const std::string& value) {
const std::string tag_hash = GenerateTagHash(tag);
type_processor_->Put(tag, GenerateSpecifics(tag, value));
}
void ModelTypeProcessorImplTest::DeleteItem(const std::string& tag) {
type_processor_->Delete(tag);
}
void ModelTypeProcessorImplTest::OnInitialSyncDone() {
data_type_state_.initial_sync_done = true;
UpdateResponseDataList empty_update_list;
type_processor_->OnUpdateReceived(data_type_state_, empty_update_list,
empty_update_list);
}
void ModelTypeProcessorImplTest::UpdateFromServer(int64 version_offset,
const std::string& tag,
const std::string& value) {
const std::string tag_hash = GenerateTagHash(tag);
UpdateResponseData data = mock_queue_->UpdateFromServer(
version_offset, tag_hash, GenerateSpecifics(tag, value));
UpdateResponseDataList list;
list.push_back(data);
type_processor_->OnUpdateReceived(data_type_state_, list,
UpdateResponseDataList());
}
void ModelTypeProcessorImplTest::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_queue_->UpdateFromServer(
version_offset, tag_hash,
GenerateEncryptedSpecifics(tag, value, key_name));
UpdateResponseDataList list;
list.push_back(data);
type_processor_->OnUpdateReceived(data_type_state_, UpdateResponseDataList(),
list);
}
void ModelTypeProcessorImplTest::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_queue_->TombstoneFromServer(version_offset, tag_hash);
UpdateResponseDataList list;
list.push_back(data);
type_processor_->OnUpdateReceived(data_type_state_, list,
UpdateResponseDataList());
}
bool ModelTypeProcessorImplTest::HasPendingUpdate(
const std::string& tag) const {
const std::string client_tag_hash = GenerateTagHash(tag);
const UpdateResponseDataList list = type_processor_->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 ModelTypeProcessorImplTest::GetPendingUpdate(
const std::string& tag) const {
DCHECK(HasPendingUpdate(tag));
const std::string client_tag_hash = GenerateTagHash(tag);
const UpdateResponseDataList list = type_processor_->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 ModelTypeProcessorImplTest::GetNumPendingUpdates() const {
return type_processor_->GetPendingUpdates().size();
}
void ModelTypeProcessorImplTest::SuccessfulCommitResponse(
const CommitRequestData& request_data) {
CommitResponseDataList list;
list.push_back(mock_queue_->SuccessfulCommitResponse(request_data));
type_processor_->OnCommitCompleted(data_type_state_, list);
}
void ModelTypeProcessorImplTest::UpdateDesiredEncryptionKey(
const std::string& key_name) {
data_type_state_.encryption_key_name = key_name;
type_processor_->OnUpdateReceived(data_type_state_, UpdateResponseDataList(),
UpdateResponseDataList());
}
void ModelTypeProcessorImplTest::SetServerEncryptionKey(
const std::string& key_name) {
mock_queue_->SetServerEncryptionKey(key_name);
}
std::string ModelTypeProcessorImplTest::GenerateTagHash(
const std::string& tag) {
return syncer::syncable::GenerateSyncableHash(kModelType, tag);
}
sync_pb::EntitySpecifics ModelTypeProcessorImplTest::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 ModelTypeProcessorImplTest::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 ModelTypeProcessorImplTest::GetNumCommitRequestLists() {
return mock_queue_->GetNumCommitRequestLists();
}
CommitRequestDataList ModelTypeProcessorImplTest::GetNthCommitRequestList(
size_t n) {
return mock_queue_->GetNthCommitRequestList(n);
}
bool ModelTypeProcessorImplTest::HasCommitRequestForTag(
const std::string& tag) {
const std::string tag_hash = GenerateTagHash(tag);
return mock_queue_->HasCommitRequestForTagHash(tag_hash);
}
CommitRequestData ModelTypeProcessorImplTest::GetLatestCommitRequestForTag(
const std::string& tag) {
const std::string tag_hash = GenerateTagHash(tag);
return mock_queue_->GetLatestCommitRequestForTagHash(tag_hash);
}
// Creates a new item locally.
// Thoroughly tests the data generated by a local item creation.
TEST_F(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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(ModelTypeProcessorImplTest, 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