| Index: sync/engine/apply_control_data_updates_unittest.cc
|
| diff --git a/sync/engine/apply_control_data_updates_unittest.cc b/sync/engine/apply_control_data_updates_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a922cad1ce1dfbf4ca233c18b14e5c4899dcda95
|
| --- /dev/null
|
| +++ b/sync/engine/apply_control_data_updates_unittest.cc
|
| @@ -0,0 +1,333 @@
|
| +// Copyright (c) 2012 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 "base/format_macros.h"
|
| +#include "base/location.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/stringprintf.h"
|
| +#include "sync/engine/apply_control_data_updates.h"
|
| +#include "sync/engine/nigori_util.h"
|
| +#include "sync/engine/syncer.h"
|
| +#include "sync/engine/syncer_util.h"
|
| +#include "sync/protocol/nigori_specifics.pb.h"
|
| +#include "sync/syncable/mutable_entry.h"
|
| +#include "sync/syncable/read_transaction.h"
|
| +#include "sync/syncable/write_transaction.h"
|
| +#include "sync/test/engine/fake_model_worker.h"
|
| +#include "sync/test/engine/syncer_command_test.h"
|
| +#include "sync/test/engine/test_id_factory.h"
|
| +#include "sync/test/test_entry_factory.h"
|
| +#include "sync/util/cryptographer.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using syncable::MutableEntry;
|
| +using syncable::UNITTEST;
|
| +using syncable::WriteTransaction;
|
| +using syncable::ReadTransaction;
|
| +using syncable::Id;
|
| +
|
| +namespace csync {
|
| +
|
| +class ApplyControlUpdatesTest : public SyncerCommandTest {
|
| + public:
|
| + protected:
|
| + ApplyControlUpdatesTest() {}
|
| + virtual ~ApplyControlUpdatesTest() {}
|
| +
|
| + virtual void SetUp() {
|
| + workers()->clear();
|
| + mutable_routing_info()->clear();
|
| + workers()->push_back(
|
| + make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
|
| + workers()->push_back(
|
| + make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD)));
|
| + (*mutable_routing_info())[syncable::BOOKMARKS] = GROUP_UI;
|
| + (*mutable_routing_info())[syncable::PASSWORDS] = GROUP_PASSWORD;
|
| + (*mutable_routing_info())[syncable::NIGORI] = GROUP_CONTROL;
|
| + SyncerCommandTest::SetUp();
|
| + entry_factory_.reset(new TestEntryFactory(directory()));
|
| + }
|
| +
|
| + FakeEncryptor encryptor_;
|
| + TestIdFactory id_factory_;
|
| + scoped_ptr<TestEntryFactory> entry_factory_;
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(ApplyControlUpdatesTest);
|
| +};
|
| +
|
| +TEST_F(ApplyControlUpdatesTest, NigoriUpdate) {
|
| + // Storing the cryptographer separately is bad, but for this test we
|
| + // know it's safe.
|
| + Cryptographer* cryptographer;
|
| + syncable::ModelTypeSet encrypted_types;
|
| + encrypted_types.Put(syncable::PASSWORDS);
|
| + encrypted_types.Put(syncable::NIGORI);
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| + cryptographer = directory()->GetCryptographer(&trans);
|
| + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types));
|
| + }
|
| +
|
| + // Nigori node updates should update the Cryptographer.
|
| + Cryptographer other_cryptographer(&encryptor_);
|
| + KeyParams params = {"localhost", "dummy", "foobar"};
|
| + other_cryptographer.AddKey(params);
|
| +
|
| + sync_pb::EntitySpecifics specifics;
|
| + sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
|
| + other_cryptographer.GetKeys(nigori->mutable_encrypted());
|
| + nigori->set_encrypt_bookmarks(true);
|
| + encrypted_types.Put(syncable::BOOKMARKS);
|
| + entry_factory_->CreateUnappliedNewItem(
|
| + syncable::ModelTypeToRootTag(syncable::NIGORI), specifics, true);
|
| + EXPECT_FALSE(cryptographer->has_pending_keys());
|
| +
|
| + ApplyControlDataUpdates(directory());
|
| +
|
| + EXPECT_FALSE(cryptographer->is_ready());
|
| + EXPECT_TRUE(cryptographer->has_pending_keys());
|
| + EXPECT_TRUE(
|
| + cryptographer->GetEncryptedTypes()
|
| + .Equals(syncable::ModelTypeSet::All()));
|
| +}
|
| +
|
| +TEST_F(ApplyControlUpdatesTest, NigoriUpdateForDisabledTypes) {
|
| + // Storing the cryptographer separately is bad, but for this test we
|
| + // know it's safe.
|
| + Cryptographer* cryptographer;
|
| + syncable::ModelTypeSet encrypted_types;
|
| + encrypted_types.Put(syncable::PASSWORDS);
|
| + encrypted_types.Put(syncable::NIGORI);
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| + cryptographer = directory()->GetCryptographer(&trans);
|
| + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types));
|
| + }
|
| +
|
| + // Nigori node updates should update the Cryptographer.
|
| + Cryptographer other_cryptographer(&encryptor_);
|
| + KeyParams params = {"localhost", "dummy", "foobar"};
|
| + other_cryptographer.AddKey(params);
|
| +
|
| + sync_pb::EntitySpecifics specifics;
|
| + sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
|
| + other_cryptographer.GetKeys(nigori->mutable_encrypted());
|
| + nigori->set_encrypt_sessions(true);
|
| + nigori->set_encrypt_themes(true);
|
| + encrypted_types.Put(syncable::SESSIONS);
|
| + encrypted_types.Put(syncable::THEMES);
|
| + entry_factory_->CreateUnappliedNewItem(
|
| + syncable::ModelTypeToRootTag(syncable::NIGORI), specifics, true);
|
| + EXPECT_FALSE(cryptographer->has_pending_keys());
|
| +
|
| + ApplyControlDataUpdates(directory());
|
| +
|
| + EXPECT_FALSE(cryptographer->is_ready());
|
| + EXPECT_TRUE(cryptographer->has_pending_keys());
|
| + EXPECT_TRUE(
|
| + cryptographer->GetEncryptedTypes()
|
| + .Equals(syncable::ModelTypeSet::All()));
|
| +}
|
| +
|
| +// Create some local unsynced and unencrypted data. Apply a nigori update that
|
| +// turns on encryption for the unsynced data. Ensure we properly encrypt the
|
| +// data as part of the nigori update. Apply another nigori update with no
|
| +// changes. Ensure we ignore already-encrypted unsynced data and that nothing
|
| +// breaks.
|
| +TEST_F(ApplyControlUpdatesTest, EncryptUnsyncedChanges) {
|
| + // Storing the cryptographer separately is bad, but for this test we
|
| + // know it's safe.
|
| + Cryptographer* cryptographer;
|
| + syncable::ModelTypeSet encrypted_types;
|
| + encrypted_types.Put(syncable::PASSWORDS);
|
| + encrypted_types.Put(syncable::NIGORI);
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| + cryptographer = directory()->GetCryptographer(&trans);
|
| + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types));
|
| +
|
| + // With default encrypted_types, this should be true.
|
| + EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| +
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_TRUE(handles.empty());
|
| + }
|
| +
|
| + // Create unsynced bookmarks without encryption.
|
| + // First item is a folder
|
| + Id folder_id = id_factory_.NewLocalId();
|
| + entry_factory_->CreateUnsyncedItem(folder_id, id_factory_.root(), "folder",
|
| + true, syncable::BOOKMARKS, NULL);
|
| + // Next five items are children of the folder
|
| + size_t i;
|
| + size_t batch_s = 5;
|
| + for (i = 0; i < batch_s; ++i) {
|
| + entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
|
| + base::StringPrintf("Item %"PRIuS"", i),
|
| + false, syncable::BOOKMARKS, NULL);
|
| + }
|
| + // Next five items are children of the root.
|
| + for (; i < 2*batch_s; ++i) {
|
| + entry_factory_->CreateUnsyncedItem(
|
| + id_factory_.NewLocalId(), id_factory_.root(),
|
| + base::StringPrintf("Item %"PRIuS"", i), false,
|
| + syncable::BOOKMARKS, NULL);
|
| + }
|
| +
|
| + KeyParams params = {"localhost", "dummy", "foobar"};
|
| + cryptographer->AddKey(params);
|
| + sync_pb::EntitySpecifics specifics;
|
| + sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
|
| + cryptographer->GetKeys(nigori->mutable_encrypted());
|
| + nigori->set_encrypt_bookmarks(true);
|
| + encrypted_types.Put(syncable::BOOKMARKS);
|
| + entry_factory_->CreateUnappliedNewItem(
|
| + syncable::ModelTypeToRootTag(syncable::NIGORI), specifics, true);
|
| + EXPECT_FALSE(cryptographer->has_pending_keys());
|
| + EXPECT_TRUE(cryptographer->is_ready());
|
| +
|
| + {
|
| + // Ensure we have unsynced nodes that aren't properly encrypted.
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| + EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| +
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_EQ(2*batch_s+1, handles.size());
|
| + }
|
| +
|
| + ApplyControlDataUpdates(directory());
|
| +
|
| + EXPECT_FALSE(cryptographer->has_pending_keys());
|
| + EXPECT_TRUE(cryptographer->is_ready());
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| +
|
| + // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes
|
| + // should be encrypted now.
|
| + EXPECT_TRUE(syncable::ModelTypeSet::All().Equals(
|
| + cryptographer->GetEncryptedTypes()));
|
| + EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| +
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_EQ(2*batch_s+1, handles.size());
|
| + }
|
| +
|
| + // Simulate another nigori update that doesn't change anything.
|
| + {
|
| + WriteTransaction trans(FROM_HERE, UNITTEST, directory());
|
| + MutableEntry entry(&trans, syncable::GET_BY_SERVER_TAG,
|
| + syncable::ModelTypeToRootTag(syncable::NIGORI));
|
| + ASSERT_TRUE(entry.good());
|
| + entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision());
|
| + entry.Put(syncable::IS_UNAPPLIED_UPDATE, true);
|
| + }
|
| +
|
| + ApplyControlDataUpdates(directory());
|
| +
|
| + EXPECT_FALSE(cryptographer->has_pending_keys());
|
| + EXPECT_TRUE(cryptographer->is_ready());
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| +
|
| + // All our changes should still be encrypted.
|
| + EXPECT_TRUE(syncable::ModelTypeSet::All().Equals(
|
| + cryptographer->GetEncryptedTypes()));
|
| + EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| +
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_EQ(2*batch_s+1, handles.size());
|
| + }
|
| +}
|
| +
|
| +TEST_F(ApplyControlUpdatesTest, CannotEncryptUnsyncedChanges) {
|
| + // Storing the cryptographer separately is bad, but for this test we
|
| + // know it's safe.
|
| + Cryptographer* cryptographer;
|
| + syncable::ModelTypeSet encrypted_types;
|
| + encrypted_types.Put(syncable::PASSWORDS);
|
| + encrypted_types.Put(syncable::NIGORI);
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| + cryptographer = directory()->GetCryptographer(&trans);
|
| + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types));
|
| +
|
| + // With default encrypted_types, this should be true.
|
| + EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| +
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_TRUE(handles.empty());
|
| + }
|
| +
|
| + // Create unsynced bookmarks without encryption.
|
| + // First item is a folder
|
| + Id folder_id = id_factory_.NewLocalId();
|
| + entry_factory_->CreateUnsyncedItem(
|
| + folder_id, id_factory_.root(), "folder", true,
|
| + syncable::BOOKMARKS, NULL);
|
| + // Next five items are children of the folder
|
| + size_t i;
|
| + size_t batch_s = 5;
|
| + for (i = 0; i < batch_s; ++i) {
|
| + entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id,
|
| + base::StringPrintf("Item %"PRIuS"", i),
|
| + false, syncable::BOOKMARKS, NULL);
|
| + }
|
| + // Next five items are children of the root.
|
| + for (; i < 2*batch_s; ++i) {
|
| + entry_factory_->CreateUnsyncedItem(
|
| + id_factory_.NewLocalId(), id_factory_.root(),
|
| + base::StringPrintf("Item %"PRIuS"", i), false,
|
| + syncable::BOOKMARKS, NULL);
|
| + }
|
| +
|
| + // We encrypt with new keys, triggering the local cryptographer to be unready
|
| + // and unable to decrypt data (once updated).
|
| + Cryptographer other_cryptographer(&encryptor_);
|
| + KeyParams params = {"localhost", "dummy", "foobar"};
|
| + other_cryptographer.AddKey(params);
|
| + sync_pb::EntitySpecifics specifics;
|
| + sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori();
|
| + other_cryptographer.GetKeys(nigori->mutable_encrypted());
|
| + nigori->set_encrypt_bookmarks(true);
|
| + encrypted_types.Put(syncable::BOOKMARKS);
|
| + entry_factory_->CreateUnappliedNewItem(
|
| + syncable::ModelTypeToRootTag(syncable::NIGORI), specifics, true);
|
| + EXPECT_FALSE(cryptographer->has_pending_keys());
|
| +
|
| + {
|
| + // Ensure we have unsynced nodes that aren't properly encrypted.
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| + EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_EQ(2*batch_s+1, handles.size());
|
| + }
|
| +
|
| + ApplyControlDataUpdates(directory());
|
| +
|
| + EXPECT_FALSE(cryptographer->is_ready());
|
| + EXPECT_TRUE(cryptographer->has_pending_keys());
|
| + {
|
| + syncable::ReadTransaction trans(FROM_HERE, directory());
|
| +
|
| + // Since we have pending keys, we would have failed to encrypt, but the
|
| + // cryptographer should be updated.
|
| + EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types));
|
| + EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(
|
| + syncable::ModelTypeSet().All()));
|
| + EXPECT_FALSE(cryptographer->is_ready());
|
| + EXPECT_TRUE(cryptographer->has_pending_keys());
|
| +
|
| + Syncer::UnsyncedMetaHandles handles;
|
| + SyncerUtil::GetUnsyncedEntries(&trans, &handles);
|
| + EXPECT_EQ(2*batch_s+1, handles.size());
|
| + }
|
| +}
|
| +
|
| +} // namespace csync
|
|
|