blob: 860cc703cce50baeea8b80ffe641f6238009186f [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_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 {
static const ModelType kModelType = 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();
virtual ~ModelTypeSyncProxyImplTest();
// 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 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;
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