blob: 3f38d40aedd71381f34f714dc7f95ff459a42e16 [file] [log] [blame]
// Copyright 2019 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 "components/password_manager/core/browser/sync/password_sync_bridge.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind_test_util.h"
#include "components/password_manager/core/browser/password_store_sync.h"
#include "components/sync/model/data_batch.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/model/mock_model_type_change_processor.h"
#include "components/sync/model_impl/in_memory_metadata_change_list.h"
#include "components/sync/model_impl/sync_metadata_store_change_list.h"
#include "components/sync/test/test_matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace password_manager {
namespace {
using testing::_;
using testing::Eq;
using testing::Invoke;
using testing::NotNull;
using testing::Return;
using testing::UnorderedElementsAre;
constexpr char kSignonRealm1[] = "abc";
constexpr char kSignonRealm2[] = "def";
constexpr char kSignonRealm3[] = "xyz";
// |*arg| must be of type EntityData.
MATCHER_P(EntityDataHasSignonRealm, expected_signon_realm, "") {
return arg->specifics.password()
.client_only_encrypted_data()
.signon_realm() == expected_signon_realm;
}
// |*arg| must be of type PasswordForm.
MATCHER_P(FormHasSignonRealm, expected_signon_realm, "") {
return arg.signon_realm == expected_signon_realm;
}
// |*arg| must be of type PasswordStoreChange.
MATCHER_P(ChangeHasPrimaryKey, expected_primary_key, "") {
return arg.primary_key() == expected_primary_key;
}
// |*arg| must be of type SyncMetadataStoreChangeList.
MATCHER_P(IsSyncMetadataStoreChangeListWithStore, expected_metadata_store, "") {
return static_cast<const syncer::SyncMetadataStoreChangeList*>(arg)
->GetMetadataStoreForTesting() == expected_metadata_store;
}
sync_pb::PasswordSpecifics CreateSpecifics(const std::string& origin,
const std::string& username_element,
const std::string& username_value,
const std::string& password_element,
const std::string& signon_realm) {
sync_pb::EntitySpecifics password_specifics;
sync_pb::PasswordSpecificsData* password_data =
password_specifics.mutable_password()
->mutable_client_only_encrypted_data();
password_data->set_origin(origin);
password_data->set_username_element(username_element);
password_data->set_username_value(username_value);
password_data->set_password_element(password_element);
password_data->set_signon_realm(signon_realm);
return password_specifics.password();
}
sync_pb::PasswordSpecifics CreateSpecificsWithSignonRealm(
const std::string& signon_realm) {
return CreateSpecifics("http://www.origin.com", "username_element",
"username_value", "password_element", signon_realm);
}
autofill::PasswordForm MakePasswordForm(const std::string& signon_realm) {
autofill::PasswordForm form;
form.origin = GURL("http://www.origin.com");
form.username_element = base::UTF8ToUTF16("username_element");
form.username_value = base::UTF8ToUTF16("username_value");
form.password_element = base::UTF8ToUTF16("password_element");
form.signon_realm = signon_realm;
return form;
}
// Creates an EntityData/EntityDataPtr around a copy of the given specifics.
syncer::EntityDataPtr SpecificsToEntity(
const sync_pb::PasswordSpecifics& specifics) {
syncer::EntityData data;
// These tests do not care about the tag hash, but EntityData and friends
// cannot differentiate between the default EntityData object if the hash
// is unset, which causes pass/copy operations to no-op and things start to
// break, so we throw in a junk value and forget about it.
data.client_tag_hash = "junk";
*data.specifics.mutable_password() = specifics;
return data.PassToPtr();
}
// A mini database class the supports Add/Update/Remove functionality. It also
// supports an auto increment primary key that starts from 1. It will be used to
// empower the MockPasswordStoreSync be forwarding all database calls to an
// instance of this class.
class FakeDatabase {
public:
FakeDatabase() = default;
~FakeDatabase() = default;
bool ReadAllLogins(PrimaryKeyToFormMap* map) {
map->clear();
for (const auto& pair : data_) {
map->emplace(pair.first,
std::make_unique<autofill::PasswordForm>(*pair.second));
}
return true;
}
PasswordStoreChangeList AddLogin(const autofill::PasswordForm& form) {
data_[primary_key_] = std::make_unique<autofill::PasswordForm>(form);
return {
PasswordStoreChange(PasswordStoreChange::ADD, form, primary_key_++)};
}
PasswordStoreChangeList AddLoginForPrimaryKey(
int primary_key,
const autofill::PasswordForm& form) {
DCHECK_EQ(0U, data_.count(primary_key));
data_[primary_key] = std::make_unique<autofill::PasswordForm>(form);
return {PasswordStoreChange(PasswordStoreChange::ADD, form, primary_key)};
}
PasswordStoreChangeList UpdateLogin(const autofill::PasswordForm& form) {
int key = GetPrimaryKey(form);
DCHECK_NE(-1, key);
data_[key] = std::make_unique<autofill::PasswordForm>(form);
return {PasswordStoreChange(PasswordStoreChange::UPDATE, form, key)};
}
PasswordStoreChangeList RemoveLogin(int key) {
DCHECK_NE(0U, data_.count(key));
autofill::PasswordForm form = *data_[key];
data_.erase(key);
return {PasswordStoreChange(PasswordStoreChange::REMOVE, form, key)};
}
private:
int GetPrimaryKey(const autofill::PasswordForm& form) const {
for (const auto& pair : data_) {
if (ArePasswordFormUniqueKeyEqual(*pair.second, form)) {
return pair.first;
}
}
return -1;
}
int primary_key_ = 1;
PrimaryKeyToFormMap data_;
DISALLOW_COPY_AND_ASSIGN(FakeDatabase);
};
class MockSyncMetadataStore : public PasswordStoreSync::MetadataStore {
public:
MockSyncMetadataStore() = default;
~MockSyncMetadataStore() = default;
MOCK_METHOD0(GetAllSyncMetadata, std::unique_ptr<syncer::MetadataBatch>());
MOCK_METHOD3(UpdateSyncMetadata,
bool(syncer::ModelType,
const std::string&,
const sync_pb::EntityMetadata&));
MOCK_METHOD2(ClearSyncMetadata, bool(syncer::ModelType, const std::string&));
MOCK_METHOD2(UpdateModelTypeState,
bool(syncer::ModelType, const sync_pb::ModelTypeState&));
MOCK_METHOD1(ClearModelTypeState, bool(syncer::ModelType));
};
class MockPasswordStoreSync : public PasswordStoreSync {
public:
MockPasswordStoreSync() = default;
~MockPasswordStoreSync() = default;
MOCK_METHOD1(FillAutofillableLogins,
bool(std::vector<std::unique_ptr<autofill::PasswordForm>>*));
MOCK_METHOD1(FillBlacklistLogins,
bool(std::vector<std::unique_ptr<autofill::PasswordForm>>*));
MOCK_METHOD1(ReadAllLogins, bool(PrimaryKeyToFormMap*));
MOCK_METHOD1(RemoveLoginByPrimaryKeySync, PasswordStoreChangeList(int));
MOCK_METHOD0(DeleteUndecryptableLogins, DatabaseCleanupResult());
MOCK_METHOD1(AddLoginSync,
PasswordStoreChangeList(const autofill::PasswordForm&));
MOCK_METHOD1(UpdateLoginSync,
PasswordStoreChangeList(const autofill::PasswordForm&));
MOCK_METHOD1(RemoveLoginSync,
PasswordStoreChangeList(const autofill::PasswordForm&));
MOCK_METHOD1(NotifyLoginsChanged, void(const PasswordStoreChangeList&));
MOCK_METHOD0(BeginTransaction, bool());
MOCK_METHOD0(CommitTransaction, bool());
MOCK_METHOD0(GetMetadataStore, PasswordStoreSync::MetadataStore*());
};
} // namespace
class PasswordSyncBridgeTest : public testing::Test {
public:
PasswordSyncBridgeTest() {
ON_CALL(mock_password_store_sync_, GetMetadataStore())
.WillByDefault(testing::Return(&mock_sync_metadata_store_sync_));
ON_CALL(mock_password_store_sync_, ReadAllLogins(_))
.WillByDefault(Invoke(&fake_db_, &FakeDatabase::ReadAllLogins));
ON_CALL(mock_password_store_sync_, AddLoginSync(_))
.WillByDefault(Invoke(&fake_db_, &FakeDatabase::AddLogin));
ON_CALL(mock_password_store_sync_, UpdateLoginSync(_))
.WillByDefault(Invoke(&fake_db_, &FakeDatabase::UpdateLogin));
ON_CALL(mock_password_store_sync_, RemoveLoginByPrimaryKeySync(_))
.WillByDefault(Invoke(&fake_db_, &FakeDatabase::RemoveLogin));
bridge_ = std::make_unique<PasswordSyncBridge>(
mock_processor_.CreateForwardingProcessor(),
&mock_password_store_sync_);
// It's the responsibility of the PasswordStoreSync to inform the bridge
// about changes in the password store. The bridge notifies the
// PasswordStoreSync about the new changes even if they are initiated by the
// bridge itself.
ON_CALL(mock_password_store_sync_, NotifyLoginsChanged(_))
.WillByDefault(
Invoke(bridge(), &PasswordSyncBridge::ActOnPasswordStoreChanges));
ON_CALL(mock_sync_metadata_store_sync_, GetAllSyncMetadata())
.WillByDefault(
[]() { return std::make_unique<syncer::MetadataBatch>(); });
ON_CALL(mock_sync_metadata_store_sync_, UpdateSyncMetadata(_, _, _))
.WillByDefault(testing::Return(true));
ON_CALL(mock_sync_metadata_store_sync_, ClearSyncMetadata(_, _))
.WillByDefault(testing::Return(true));
ON_CALL(mock_sync_metadata_store_sync_, UpdateModelTypeState(_, _))
.WillByDefault(testing::Return(true));
ON_CALL(mock_sync_metadata_store_sync_, ClearModelTypeState(_))
.WillByDefault(testing::Return(true));
}
~PasswordSyncBridgeTest() override {}
base::Optional<sync_pb::PasswordSpecifics> GetDataFromBridge(
const std::string& storage_key) {
std::unique_ptr<syncer::DataBatch> batch;
bridge_->GetData({storage_key},
base::BindLambdaForTesting(
[&](std::unique_ptr<syncer::DataBatch> in_batch) {
batch = std::move(in_batch);
}));
EXPECT_THAT(batch, NotNull());
if (!batch || !batch->HasNext()) {
return base::nullopt;
}
const syncer::KeyAndData& data_pair = batch->Next();
EXPECT_THAT(data_pair.first, Eq(storage_key));
EXPECT_FALSE(batch->HasNext());
return data_pair.second->specifics.password();
}
FakeDatabase* fake_db() { return &fake_db_; }
PasswordSyncBridge* bridge() { return bridge_.get(); }
syncer::MockModelTypeChangeProcessor& mock_processor() {
return mock_processor_;
}
MockSyncMetadataStore* mock_sync_metadata_store_sync() {
return &mock_sync_metadata_store_sync_;
}
MockPasswordStoreSync* mock_password_store_sync() {
return &mock_password_store_sync_;
}
private:
FakeDatabase fake_db_;
testing::NiceMock<syncer::MockModelTypeChangeProcessor> mock_processor_;
testing::NiceMock<MockSyncMetadataStore> mock_sync_metadata_store_sync_;
testing::NiceMock<MockPasswordStoreSync> mock_password_store_sync_;
std::unique_ptr<PasswordSyncBridge> bridge_;
};
TEST_F(PasswordSyncBridgeTest, ShouldComputeClientTagHash) {
syncer::EntityData data;
*data.specifics.mutable_password() =
CreateSpecifics("http://www.origin.com", "username_element",
"username_value", "password_element", "signon_realm");
EXPECT_THAT(
bridge()->GetClientTag(data),
Eq("http%3A//www.origin.com/"
"|username_element|username_value|password_element|signon_realm"));
}
TEST_F(PasswordSyncBridgeTest, ShouldForwardLocalChangesToTheProcessor) {
ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(true));
PasswordStoreChangeList changes;
changes.push_back(PasswordStoreChange(
PasswordStoreChange::ADD, MakePasswordForm(kSignonRealm1), /*id=*/1));
changes.push_back(PasswordStoreChange(
PasswordStoreChange::UPDATE, MakePasswordForm(kSignonRealm2), /*id=*/2));
changes.push_back(PasswordStoreChange(
PasswordStoreChange::REMOVE, MakePasswordForm(kSignonRealm3), /*id=*/3));
PasswordStoreSync::MetadataStore* store =
mock_password_store_sync()->GetMetadataStore();
EXPECT_CALL(mock_processor(),
Put("1", EntityDataHasSignonRealm(kSignonRealm1),
IsSyncMetadataStoreChangeListWithStore(store)));
EXPECT_CALL(mock_processor(),
Put("2", EntityDataHasSignonRealm(kSignonRealm2),
IsSyncMetadataStoreChangeListWithStore(store)));
EXPECT_CALL(mock_processor(),
Delete("3", IsSyncMetadataStoreChangeListWithStore(store)));
bridge()->ActOnPasswordStoreChanges(changes);
}
TEST_F(PasswordSyncBridgeTest,
ShouldNotForwardLocalChangesToTheProcessorIfSyncDisabled) {
ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(false));
PasswordStoreChangeList changes;
changes.push_back(PasswordStoreChange(
PasswordStoreChange::ADD, MakePasswordForm(kSignonRealm1), /*id=*/1));
changes.push_back(PasswordStoreChange(
PasswordStoreChange::UPDATE, MakePasswordForm(kSignonRealm2), /*id=*/2));
changes.push_back(PasswordStoreChange(
PasswordStoreChange::REMOVE, MakePasswordForm(kSignonRealm3), /*id=*/3));
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
bridge()->ActOnPasswordStoreChanges(changes);
}
TEST_F(PasswordSyncBridgeTest, ShouldApplyEmptySyncChangesWithoutError) {
base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(), syncer::EntityChangeList());
EXPECT_FALSE(error);
}
TEST_F(PasswordSyncBridgeTest, ShouldApplyMetadataWithEmptySyncChanges) {
const std::string kStorageKey = "1";
const std::string kServerId = "TestServerId";
sync_pb::EntityMetadata metadata;
metadata.set_server_id(kServerId);
auto metadata_change_list =
std::make_unique<syncer::InMemoryMetadataChangeList>();
metadata_change_list->UpdateMetadata(kStorageKey, metadata);
EXPECT_CALL(*mock_password_store_sync(), NotifyLoginsChanged(_)).Times(0);
EXPECT_CALL(*mock_sync_metadata_store_sync(),
UpdateSyncMetadata(syncer::PASSWORDS, kStorageKey, _));
base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
std::move(metadata_change_list), syncer::EntityChangeList());
EXPECT_FALSE(error);
}
TEST_F(PasswordSyncBridgeTest, ShouldApplyRemoteCreation) {
ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(true));
// Since this remote creation is the first entry in the FakeDatabase, it will
// be assigned a primary key 1.
const std::string kStorageKey = "1";
sync_pb::PasswordSpecifics specifics =
CreateSpecificsWithSignonRealm(kSignonRealm1);
testing::InSequence in_sequence;
EXPECT_CALL(*mock_password_store_sync(), BeginTransaction());
EXPECT_CALL(*mock_password_store_sync(),
AddLoginSync(FormHasSignonRealm(kSignonRealm1)));
EXPECT_CALL(mock_processor(), UpdateStorageKey(_, kStorageKey, _));
EXPECT_CALL(*mock_password_store_sync(), CommitTransaction());
EXPECT_CALL(
*mock_password_store_sync(),
NotifyLoginsChanged(UnorderedElementsAre(ChangeHasPrimaryKey(1))));
// Processor shouldn't be notified about remote changes.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(),
{syncer::EntityChange::CreateAdd(
/*storage_key=*/"", SpecificsToEntity(specifics))});
EXPECT_FALSE(error);
}
TEST_F(PasswordSyncBridgeTest, ShouldApplyRemoteUpdate) {
const int kPrimaryKey = 1000;
const std::string kStorageKey = "1000";
// Add the form to the DB.
fake_db()->AddLoginForPrimaryKey(kPrimaryKey,
MakePasswordForm(kSignonRealm1));
sync_pb::PasswordSpecifics specifics =
CreateSpecificsWithSignonRealm(kSignonRealm1);
testing::InSequence in_sequence;
EXPECT_CALL(*mock_password_store_sync(), BeginTransaction());
EXPECT_CALL(*mock_password_store_sync(),
UpdateLoginSync(FormHasSignonRealm(kSignonRealm1)));
EXPECT_CALL(*mock_password_store_sync(), CommitTransaction());
EXPECT_CALL(*mock_password_store_sync(),
NotifyLoginsChanged(
UnorderedElementsAre(ChangeHasPrimaryKey(kPrimaryKey))));
// Processor shouldn't be notified about remote changes.
EXPECT_CALL(mock_processor(), Put(_, _, _)).Times(0);
EXPECT_CALL(mock_processor(), UpdateStorageKey(_, _, _)).Times(0);
base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(),
{syncer::EntityChange::CreateUpdate(kStorageKey,
SpecificsToEntity(specifics))});
EXPECT_FALSE(error);
}
TEST_F(PasswordSyncBridgeTest, ShouldApplyRemoteDeletion) {
const int kPrimaryKey = 1000;
const std::string kStorageKey = "1000";
// Add the form to the DB.
fake_db()->AddLoginForPrimaryKey(kPrimaryKey,
MakePasswordForm(kSignonRealm1));
testing::InSequence in_sequence;
EXPECT_CALL(*mock_password_store_sync(), BeginTransaction());
EXPECT_CALL(*mock_password_store_sync(),
RemoveLoginByPrimaryKeySync(kPrimaryKey));
EXPECT_CALL(*mock_password_store_sync(), CommitTransaction());
EXPECT_CALL(*mock_password_store_sync(),
NotifyLoginsChanged(
UnorderedElementsAre(ChangeHasPrimaryKey(kPrimaryKey))));
// Processor shouldn't be notified about remote changes.
EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
base::Optional<syncer::ModelError> error = bridge()->ApplySyncChanges(
bridge()->CreateMetadataChangeList(),
{syncer::EntityChange::CreateDelete(kStorageKey)});
EXPECT_FALSE(error);
}
TEST_F(PasswordSyncBridgeTest, ShouldGetDataForStorageKey) {
const int kPrimaryKey1 = 1000;
const int kPrimaryKey2 = 1001;
const std::string kPrimaryKeyStr1 = "1000";
const std::string kPrimaryKeyStr2 = "1001";
autofill::PasswordForm form1 = MakePasswordForm(kSignonRealm1);
autofill::PasswordForm form2 = MakePasswordForm(kSignonRealm2);
fake_db()->AddLoginForPrimaryKey(kPrimaryKey1, form1);
fake_db()->AddLoginForPrimaryKey(kPrimaryKey2, form2);
base::Optional<sync_pb::PasswordSpecifics> optional_specifics =
GetDataFromBridge(/*storage_key=*/kPrimaryKeyStr1);
ASSERT_TRUE(optional_specifics.has_value());
EXPECT_EQ(
kSignonRealm1,
optional_specifics.value().client_only_encrypted_data().signon_realm());
optional_specifics = GetDataFromBridge(/*storage_key=*/kPrimaryKeyStr2);
ASSERT_TRUE(optional_specifics.has_value());
EXPECT_EQ(kSignonRealm2,
optional_specifics->client_only_encrypted_data().signon_realm());
}
TEST_F(PasswordSyncBridgeTest, ShouldNotGetDataForNonExistingStorageKey) {
const std::string kPrimaryKeyStr = "1";
base::Optional<sync_pb::PasswordSpecifics> optional_specifics =
GetDataFromBridge(/*storage_key=*/kPrimaryKeyStr);
EXPECT_FALSE(optional_specifics.has_value());
}
TEST_F(PasswordSyncBridgeTest, ShouldMergeSyncRemoteAndLocalPasswords) {
ON_CALL(mock_processor(), IsTrackingMetadata()).WillByDefault(Return(true));
// Setup the test to have Form 1 and Form 2 stored locally, and Form 2 and
// Form 3 coming as remote changes. We will assign primary keys for Form 1 and
// Form 2. Form 3 will arrive as remote creation, and FakeDatabase will assign
// it primary key 1.
const int kPrimaryKey1 = 1000;
const int kPrimaryKey2 = 1001;
const int kExpectedPrimaryKey3 = 1;
const std::string kPrimaryKeyStr1 = "1000";
const std::string kPrimaryKeyStr2 = "1001";
const std::string kExpectedPrimaryKeyStr3 = "1";
autofill::PasswordForm form1 = MakePasswordForm(kSignonRealm1);
autofill::PasswordForm form2 = MakePasswordForm(kSignonRealm2);
autofill::PasswordForm form3 = MakePasswordForm(kSignonRealm3);
sync_pb::PasswordSpecifics specifics1 =
CreateSpecificsWithSignonRealm(kSignonRealm1);
sync_pb::PasswordSpecifics specifics2 =
CreateSpecificsWithSignonRealm(kSignonRealm2);
sync_pb::PasswordSpecifics specifics3 =
CreateSpecificsWithSignonRealm(kSignonRealm3);
fake_db()->AddLoginForPrimaryKey(kPrimaryKey1, form1);
fake_db()->AddLoginForPrimaryKey(kPrimaryKey2, form2);
// Form 1 will be added to the change processor, Form 2 will be updated in the
// password sync store, and Form 3 will be added to the password store sync.
// Interactions should happen in this order:
// +--> Put(1) ------------------------------------+
// | |
// Begin() --|--> UpdateLoginSync(2) --> UpdateStorageKey(2)-|--> Commit()
// | |
// +--> AddLoginSync (3) --> UpdateStorageKey(3)-+
testing::Sequence s1, s2, s3;
EXPECT_CALL(*mock_password_store_sync(), BeginTransaction())
.InSequence(s1, s2, s3);
EXPECT_CALL(mock_processor(),
Put(kPrimaryKeyStr1, EntityDataHasSignonRealm(kSignonRealm1), _))
.InSequence(s1);
EXPECT_CALL(*mock_password_store_sync(),
UpdateLoginSync(FormHasSignonRealm(kSignonRealm2)))
.InSequence(s2);
EXPECT_CALL(*mock_password_store_sync(),
AddLoginSync(FormHasSignonRealm(kSignonRealm3)))
.InSequence(s3);
EXPECT_CALL(mock_processor(), UpdateStorageKey(_, kPrimaryKeyStr2, _))
.InSequence(s2);
EXPECT_CALL(mock_processor(), UpdateStorageKey(_, kExpectedPrimaryKeyStr3, _))
.InSequence(s3);
EXPECT_CALL(*mock_password_store_sync(), CommitTransaction())
.InSequence(s1, s2, s3);
EXPECT_CALL(*mock_password_store_sync(),
NotifyLoginsChanged(UnorderedElementsAre(
ChangeHasPrimaryKey(kPrimaryKey2),
ChangeHasPrimaryKey(kExpectedPrimaryKey3))))
.InSequence(s1, s2, s3);
// Processor shouldn't be informed about Form 2 or Form 3.
EXPECT_CALL(mock_processor(), Put(kPrimaryKeyStr2, _, _)).Times(0);
EXPECT_CALL(mock_processor(), Put(kExpectedPrimaryKeyStr3, _, _)).Times(0);
base::Optional<syncer::ModelError> error = bridge()->MergeSyncData(
bridge()->CreateMetadataChangeList(),
{syncer::EntityChange::CreateAdd(
/*storage_key=*/"", SpecificsToEntity(specifics2)),
syncer::EntityChange::CreateAdd(
/*storage_key=*/"", SpecificsToEntity(specifics3))});
EXPECT_FALSE(error);
}
TEST_F(PasswordSyncBridgeTest, ShouldGetAllDataForDebuggingWithHiddenPassword) {
const int kPrimaryKey1 = 1000;
const int kPrimaryKey2 = 1001;
autofill::PasswordForm form1 = MakePasswordForm(kSignonRealm1);
autofill::PasswordForm form2 = MakePasswordForm(kSignonRealm2);
fake_db()->AddLoginForPrimaryKey(kPrimaryKey1, form1);
fake_db()->AddLoginForPrimaryKey(kPrimaryKey2, form2);
std::unique_ptr<syncer::DataBatch> batch;
bridge()->GetAllDataForDebugging(base::BindLambdaForTesting(
[&](std::unique_ptr<syncer::DataBatch> in_batch) {
batch = std::move(in_batch);
}));
ASSERT_THAT(batch, NotNull());
EXPECT_TRUE(batch->HasNext());
while (batch->HasNext()) {
const syncer::KeyAndData& data_pair = batch->Next();
EXPECT_EQ("hidden", data_pair.second->specifics.password()
.client_only_encrypted_data()
.password_value());
}
}
TEST_F(PasswordSyncBridgeTest,
ShouldCallModelReadyUponConstructionWithMetadata) {
ON_CALL(*mock_sync_metadata_store_sync(), GetAllSyncMetadata())
.WillByDefault([&]() {
sync_pb::ModelTypeState model_type_state;
model_type_state.set_initial_sync_done(true);
auto metadata_batch = std::make_unique<syncer::MetadataBatch>();
metadata_batch->SetModelTypeState(model_type_state);
metadata_batch->AddMetadata("storage_key", sync_pb::EntityMetadata());
return metadata_batch;
});
EXPECT_CALL(mock_processor(), ModelReadyToSync(MetadataBatchContains(
/*state=*/syncer::HasInitialSyncDone(),
/*entities=*/testing::SizeIs(1))));
PasswordSyncBridge bridge(mock_processor().CreateForwardingProcessor(),
mock_password_store_sync());
}
} // namespace password_manager