blob: ac7b6efe30eaa6bc9fff87f6416d53ad5b2cb4d0 [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 "components/password_manager/core/browser/password_syncable_service.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "components/password_manager/core/browser/mock_password_store.h"
#include "sync/api/sync_change_processor.h"
#include "sync/api/sync_error.h"
#include "sync/api/sync_error_factory_mock.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using syncer::SyncChange;
using syncer::SyncData;
using syncer::SyncDataList;
using syncer::SyncError;
using testing::AnyNumber;
using testing::DoAll;
using testing::ElementsAre;
using testing::IgnoreResult;
using testing::Invoke;
using testing::IsEmpty;
using testing::Matches;
using testing::Return;
using testing::SetArgPointee;
using testing::UnorderedElementsAre;
using testing::_;
namespace password_manager {
// Defined in the implementation file corresponding to this test.
syncer::SyncData SyncDataFromPassword(const autofill::PasswordForm& password);
autofill::PasswordForm PasswordFromSpecifics(
const sync_pb::PasswordSpecificsData& password);
std::string MakePasswordSyncTag(const sync_pb::PasswordSpecificsData& password);
std::string MakePasswordSyncTag(const autofill::PasswordForm& password);
namespace {
// PasswordForm values for tests.
const autofill::PasswordForm::Type kArbitraryType =
autofill::PasswordForm::TYPE_GENERATED;
const char kAvatarUrl[] = "https://fb.com/Avatar";
const char kDisplayName[] = "Agent Smith";
const char kFederationUrl[] = "https://fb.com/federation_url";
const char kPassword[] = "abcdef";
const char kSignonRealm[] = "abc";
const char kSignonRealm2[] = "def";
const char kSignonRealm3[] = "xyz";
const int kTimesUsed = 5;
const char kUsername[] = "godzilla";
typedef std::vector<SyncChange> SyncChangeList;
const sync_pb::PasswordSpecificsData& GetPasswordSpecifics(
const syncer::SyncData& sync_data) {
return sync_data.GetSpecifics().password().client_only_encrypted_data();
}
MATCHER(HasDateSynced, "") {
return !arg.date_synced.is_null() && !arg.date_synced.is_max();
}
MATCHER_P(PasswordIs, form, "") {
sync_pb::PasswordSpecificsData actual_password =
GetPasswordSpecifics(SyncDataFromPassword(arg));
sync_pb::PasswordSpecificsData expected_password =
GetPasswordSpecifics(SyncDataFromPassword(form));
if (expected_password.scheme() == actual_password.scheme() &&
expected_password.signon_realm() == actual_password.signon_realm() &&
expected_password.origin() == actual_password.origin() &&
expected_password.action() == actual_password.action() &&
expected_password.username_element() ==
actual_password.username_element() &&
expected_password.password_element() ==
actual_password.password_element() &&
expected_password.username_value() == actual_password.username_value() &&
expected_password.password_value() == actual_password.password_value() &&
expected_password.ssl_valid() == actual_password.ssl_valid() &&
expected_password.preferred() == actual_password.preferred() &&
expected_password.date_created() == actual_password.date_created() &&
expected_password.blacklisted() == actual_password.blacklisted() &&
expected_password.type() == actual_password.type() &&
expected_password.times_used() == actual_password.times_used() &&
expected_password.display_name() == actual_password.display_name() &&
expected_password.avatar_url() == actual_password.avatar_url() &&
expected_password.federation_url() == actual_password.federation_url())
return true;
*result_listener << "Password protobuf does not match; expected:\n"
<< form << '\n'
<< "actual:" << '\n'
<< arg;
return false;
}
MATCHER_P2(SyncChangeIs, change_type, password, "") {
const SyncData& data = arg.sync_data();
autofill::PasswordForm form =
PasswordFromSpecifics(GetPasswordSpecifics(data));
return (arg.change_type() == change_type &&
syncer::SyncDataLocal(data).GetTag() ==
MakePasswordSyncTag(password) &&
(change_type == SyncChange::ACTION_DELETE ||
Matches(PasswordIs(password))(form)));
}
// The argument is std::vector<autofill::PasswordForm*>*. The caller is
// responsible for the lifetime of all the password forms.
ACTION_P(AppendForm, form) {
arg0->push_back(new autofill::PasswordForm(form));
return true;
}
// Creates a sync data consisting of password specifics. The sign on realm is
// set to |signon_realm|.
SyncData CreateSyncData(const std::string& signon_realm) {
sync_pb::EntitySpecifics password_data;
sync_pb::PasswordSpecificsData* password_specifics =
password_data.mutable_password()->mutable_client_only_encrypted_data();
password_specifics->set_signon_realm(signon_realm);
password_specifics->set_type(autofill::PasswordForm::TYPE_GENERATED);
password_specifics->set_times_used(3);
password_specifics->set_display_name("Mr. X");
password_specifics->set_avatar_url("https://accounts.google.com/Avatar");
password_specifics->set_federation_url("https://google.com/federation");
password_specifics->set_username_value("kingkong");
password_specifics->set_password_value("sicrit");
std::string tag = MakePasswordSyncTag(*password_specifics);
return syncer::SyncData::CreateLocalData(tag, tag, password_data);
}
SyncChange CreateSyncChange(const autofill::PasswordForm& password,
SyncChange::SyncChangeType type) {
SyncData data = SyncDataFromPassword(password);
return SyncChange(FROM_HERE, type, data);
}
// A testable implementation of the |PasswordSyncableService| that mocks
// out all interaction with the password database.
class MockPasswordSyncableService : public PasswordSyncableService {
public:
explicit MockPasswordSyncableService(PasswordStoreSync* password_store)
: PasswordSyncableService(password_store) {}
MOCK_METHOD1(StartSyncFlare, void(syncer::ModelType));
private:
DISALLOW_COPY_AND_ASSIGN(MockPasswordSyncableService);
};
// Mock implementation of SyncChangeProcessor.
class MockSyncChangeProcessor : public syncer::SyncChangeProcessor {
public:
MockSyncChangeProcessor() {}
MOCK_METHOD2(ProcessSyncChanges,
SyncError(const tracked_objects::Location&,
const SyncChangeList& list));
SyncDataList GetAllSyncData(syncer::ModelType type) const override {
NOTREACHED();
return SyncDataList();
}
private:
DISALLOW_COPY_AND_ASSIGN(MockSyncChangeProcessor);
};
// Convenience wrapper around a PasswordSyncableService and PasswordStore
// pair.
class PasswordSyncableServiceWrapper {
public:
PasswordSyncableServiceWrapper() {
password_store_ = new testing::StrictMock<MockPasswordStore>;
service_.reset(
new MockPasswordSyncableService(password_store_->GetSyncInterface()));
ON_CALL(*password_store_, AddLoginImpl(HasDateSynced()))
.WillByDefault(Return(PasswordStoreChangeList()));
ON_CALL(*password_store_, RemoveLoginImpl(_))
.WillByDefault(Return(PasswordStoreChangeList()));
ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced()))
.WillByDefault(Return(PasswordStoreChangeList()));
EXPECT_CALL(*password_store(), NotifyLoginsChanged(_)).Times(AnyNumber());
}
~PasswordSyncableServiceWrapper() { password_store_->Shutdown(); }
MockPasswordStore* password_store() { return password_store_.get(); }
MockPasswordSyncableService* service() { return service_.get(); }
// Returnes the scoped_ptr to |service_| thus NULLing out it.
scoped_ptr<syncer::SyncChangeProcessor> ReleaseSyncableService() {
return service_.Pass();
}
private:
scoped_refptr<MockPasswordStore> password_store_;
scoped_ptr<MockPasswordSyncableService> service_;
DISALLOW_COPY_AND_ASSIGN(PasswordSyncableServiceWrapper);
};
class PasswordSyncableServiceTest : public testing::Test {
public:
PasswordSyncableServiceTest()
: processor_(new testing::StrictMock<MockSyncChangeProcessor>) {
ON_CALL(*processor_, ProcessSyncChanges(_, _))
.WillByDefault(Return(SyncError()));
}
MockPasswordStore* password_store() { return wrapper_.password_store(); }
MockPasswordSyncableService* service() { return wrapper_.service(); }
protected:
scoped_ptr<MockSyncChangeProcessor> processor_;
private:
PasswordSyncableServiceWrapper wrapper_;
};
// Both sync and password db have data that are not present in the other.
TEST_F(PasswordSyncableServiceTest, AdditionsInBoth) {
autofill::PasswordForm form;
form.signon_realm = kSignonRealm;
form.username_value = base::ASCIIToUTF16(kUsername);
form.password_value = base::ASCIIToUTF16(kPassword);
SyncDataList list;
list.push_back(CreateSyncData(kSignonRealm2));
autofill::PasswordForm new_from_sync =
PasswordFromSpecifics(GetPasswordSpecifics(list.back()));
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
SyncChangeIs(SyncChange::ACTION_ADD, form))));
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
list,
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
}
// Sync has data that is not present in the password db.
TEST_F(PasswordSyncableServiceTest, AdditionOnlyInSync) {
SyncDataList list;
list.push_back(CreateSyncData(kSignonRealm));
autofill::PasswordForm new_from_sync =
PasswordFromSpecifics(GetPasswordSpecifics(list.back()));
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(Return(true));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
list,
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
}
// Passwords db has data that is not present in sync.
TEST_F(PasswordSyncableServiceTest, AdditionOnlyInPasswordStore) {
autofill::PasswordForm form;
form.signon_realm = kSignonRealm;
form.times_used = kTimesUsed;
form.type = kArbitraryType;
form.display_name = base::ASCIIToUTF16(kDisplayName);
form.avatar_url = GURL(kAvatarUrl);
form.federation_url = GURL(kFederationUrl);
form.username_value = base::ASCIIToUTF16(kUsername);
form.password_value = base::ASCIIToUTF16(kPassword);
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
SyncChangeIs(SyncChange::ACTION_ADD, form))));
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
SyncDataList(),
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
}
// Both passwords db and sync contain the same data.
TEST_F(PasswordSyncableServiceTest, BothInSync) {
autofill::PasswordForm form;
form.signon_realm = kSignonRealm;
form.times_used = kTimesUsed;
form.type = kArbitraryType;
form.username_value = base::ASCIIToUTF16(kUsername);
form.password_value = base::ASCIIToUTF16(kPassword);
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
service()->MergeDataAndStartSyncing(
syncer::PASSWORDS,
SyncDataList(1, SyncDataFromPassword(form)),
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
}
// Both passwords db and sync have the same data but they need to be merged
// as some fields of the data differ.
TEST_F(PasswordSyncableServiceTest, Merge) {
autofill::PasswordForm form1;
form1.signon_realm = kSignonRealm;
form1.action = GURL("http://pie.com");
form1.date_created = base::Time::Now();
form1.preferred = true;
form1.username_value = base::ASCIIToUTF16(kUsername);
form1.password_value = base::ASCIIToUTF16(kPassword);
autofill::PasswordForm form2(form1);
form2.preferred = false;
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form1));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(form2)));
EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
service()->MergeDataAndStartSyncing(
syncer::PASSWORDS,
SyncDataList(1, SyncDataFromPassword(form2)),
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
}
// Initiate sync due to local DB changes.
TEST_F(PasswordSyncableServiceTest, PasswordStoreChanges) {
// Save the reference to the processor because |processor_| is NULL after
// MergeDataAndStartSyncing().
MockSyncChangeProcessor& weak_processor = *processor_;
EXPECT_CALL(weak_processor, ProcessSyncChanges(_, IsEmpty()));
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(Return(true));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
SyncDataList(),
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
autofill::PasswordForm form1;
form1.signon_realm = kSignonRealm;
autofill::PasswordForm form2;
form2.signon_realm = kSignonRealm2;
autofill::PasswordForm form3;
form3.signon_realm = kSignonRealm3;
SyncChangeList sync_list;
sync_list.push_back(CreateSyncChange(form1, SyncChange::ACTION_ADD));
sync_list.push_back(CreateSyncChange(form2, SyncChange::ACTION_UPDATE));
sync_list.push_back(CreateSyncChange(form3, SyncChange::ACTION_DELETE));
PasswordStoreChangeList list;
list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form1));
list.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form2));
list.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form3));
EXPECT_CALL(weak_processor, ProcessSyncChanges(_, ElementsAre(
SyncChangeIs(SyncChange::ACTION_ADD, form1),
SyncChangeIs(SyncChange::ACTION_UPDATE, form2),
SyncChangeIs(SyncChange::ACTION_DELETE, form3))));
service()->ActOnPasswordStoreChanges(list);
}
// Process all types of changes from sync.
TEST_F(PasswordSyncableServiceTest, ProcessSyncChanges) {
autofill::PasswordForm updated_form;
updated_form.signon_realm = kSignonRealm;
updated_form.action = GURL("http://foo.com");
updated_form.date_created = base::Time::Now();
updated_form.username_value = base::ASCIIToUTF16(kUsername);
updated_form.password_value = base::ASCIIToUTF16(kPassword);
autofill::PasswordForm deleted_form;
deleted_form.signon_realm = kSignonRealm2;
deleted_form.action = GURL("http://bar.com");
deleted_form.blacklisted_by_user = true;
SyncData add_data = CreateSyncData(kSignonRealm3);
autofill::PasswordForm new_from_sync =
PasswordFromSpecifics(GetPasswordSpecifics(add_data));
SyncChangeList list;
list.push_back(
SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, add_data));
list.push_back(
CreateSyncChange(updated_form, syncer::SyncChange::ACTION_UPDATE));
list.push_back(
CreateSyncChange(deleted_form, syncer::SyncChange::ACTION_DELETE));
EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(updated_form)));
EXPECT_CALL(*password_store(), RemoveLoginImpl(PasswordIs(deleted_form)));
service()->ProcessSyncChanges(FROM_HERE, list);
}
// Retrives sync data from the model.
TEST_F(PasswordSyncableServiceTest, GetAllSyncData) {
autofill::PasswordForm form1;
form1.signon_realm = kSignonRealm;
form1.action = GURL("http://foo.com");
form1.times_used = kTimesUsed;
form1.type = kArbitraryType;
form1.display_name = base::ASCIIToUTF16(kDisplayName);
form1.avatar_url = GURL(kAvatarUrl);
form1.federation_url = GURL(kFederationUrl);
form1.username_value = base::ASCIIToUTF16(kUsername);
form1.password_value = base::ASCIIToUTF16(kPassword);
autofill::PasswordForm form2;
form2.signon_realm = kSignonRealm2;
form2.action = GURL("http://bar.com");
form2.blacklisted_by_user = true;
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form1));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
.WillOnce(AppendForm(form2));
SyncDataList actual_list = service()->GetAllSyncData(syncer::PASSWORDS);
std::vector<autofill::PasswordForm> actual_form_list;
for (SyncDataList::iterator it = actual_list.begin(); it != actual_list.end();
++it) {
actual_form_list.push_back(
PasswordFromSpecifics(GetPasswordSpecifics(*it)));
}
EXPECT_THAT(actual_form_list,
UnorderedElementsAre(PasswordIs(form1), PasswordIs(form2)));
}
// Creates 2 PasswordSyncableService instances, merges the content of the first
// one to the second one and back.
TEST_F(PasswordSyncableServiceTest, MergeDataAndPushBack) {
autofill::PasswordForm form1;
form1.signon_realm = kSignonRealm;
form1.action = GURL("http://foo.com");
form1.username_value = base::ASCIIToUTF16(kUsername);
form1.password_value = base::ASCIIToUTF16(kPassword);
PasswordSyncableServiceWrapper other_service_wrapper;
autofill::PasswordForm form2;
form2.signon_realm = kSignonRealm2;
form2.action = GURL("http://bar.com");
form2.username_value = base::ASCIIToUTF16(kUsername);
form2.password_value = base::ASCIIToUTF16(kPassword);
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form1));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*other_service_wrapper.password_store(),
FillAutofillableLogins(_)).WillOnce(AppendForm(form2));
EXPECT_CALL(*other_service_wrapper.password_store(), FillBlacklistLogins(_))
.WillOnce(Return(true));
EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(form2)));
EXPECT_CALL(*other_service_wrapper.password_store(),
AddLoginImpl(PasswordIs(form1)));
syncer::SyncDataList other_service_data =
other_service_wrapper.service()->GetAllSyncData(syncer::PASSWORDS);
service()->MergeDataAndStartSyncing(
syncer::PASSWORDS,
other_service_data,
other_service_wrapper.ReleaseSyncableService(),
scoped_ptr<syncer::SyncErrorFactory>());
}
// Calls ActOnPasswordStoreChanges without SyncChangeProcessor. StartSyncFlare
// should be called.
TEST_F(PasswordSyncableServiceTest, StartSyncFlare) {
autofill::PasswordForm form;
form.signon_realm = kSignonRealm;
form.username_value = base::ASCIIToUTF16(kUsername);
form.password_value = base::ASCIIToUTF16(kPassword);
PasswordStoreChangeList list;
list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
// No flare and no SyncChangeProcessor, the call shouldn't crash.
service()->ActOnPasswordStoreChanges(list);
// Set the flare. It should be called as there is no SyncChangeProcessor.
service()->InjectStartSyncFlare(
base::Bind(&MockPasswordSyncableService::StartSyncFlare,
base::Unretained(service())));
EXPECT_CALL(*service(), StartSyncFlare(syncer::PASSWORDS));
service()->ActOnPasswordStoreChanges(list);
}
// Start syncing with an error. Subsequent password store updates shouldn't be
// propagated to Sync.
TEST_F(PasswordSyncableServiceTest, FailedReadFromPasswordStore) {
scoped_ptr<syncer::SyncErrorFactoryMock> error_factory(
new syncer::SyncErrorFactoryMock);
syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR,
"Failed to get passwords from store.",
syncer::PASSWORDS);
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(Return(false));
EXPECT_CALL(*error_factory, CreateAndUploadError(_, _))
.WillOnce(Return(error));
// ActOnPasswordStoreChanges() below shouldn't generate any changes for Sync.
// |processor_| will be destroyed in MergeDataAndStartSyncing().
EXPECT_CALL(*processor_, ProcessSyncChanges(_, _)).Times(0);
syncer::SyncMergeResult result =
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
syncer::SyncDataList(),
processor_.Pass(),
error_factory.Pass());
EXPECT_TRUE(result.error().IsSet());
autofill::PasswordForm form;
form.signon_realm = kSignonRealm;
PasswordStoreChangeList list;
list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
service()->ActOnPasswordStoreChanges(list);
}
// Start syncing with an error in ProcessSyncChanges. Subsequent password store
// updates shouldn't be propagated to Sync.
TEST_F(PasswordSyncableServiceTest, FailedProcessSyncChanges) {
autofill::PasswordForm form;
form.signon_realm = kSignonRealm;
form.username_value = base::ASCIIToUTF16(kUsername);
form.password_value = base::ASCIIToUTF16(kPassword);
scoped_ptr<syncer::SyncErrorFactoryMock> error_factory(
new syncer::SyncErrorFactoryMock);
syncer::SyncError error(FROM_HERE, syncer::SyncError::DATATYPE_ERROR,
"There is a problem", syncer::PASSWORDS);
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(AppendForm(form));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
// ActOnPasswordStoreChanges() below shouldn't generate any changes for Sync.
// |processor_| will be destroyed in MergeDataAndStartSyncing().
EXPECT_CALL(*processor_, ProcessSyncChanges(_, _))
.Times(1)
.WillOnce(Return(error));
syncer::SyncMergeResult result =
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
syncer::SyncDataList(),
processor_.Pass(),
error_factory.Pass());
EXPECT_TRUE(result.error().IsSet());
form.signon_realm = kSignonRealm2;
PasswordStoreChangeList list;
list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
service()->ActOnPasswordStoreChanges(list);
}
// Tests that empty forms known to the client and/or the server are deleted.
TEST_F(PasswordSyncableServiceTest, MergeEmptyPasswords) {
autofill::PasswordForm old_empty_form;
old_empty_form.signon_realm = kSignonRealm;
old_empty_form.times_used = kTimesUsed;
old_empty_form.type = kArbitraryType;
autofill::PasswordForm db_empty_form = old_empty_form;
db_empty_form.signon_realm = kSignonRealm2;
autofill::PasswordForm sync_empty_form = old_empty_form;
sync_empty_form.signon_realm = kSignonRealm3;
SyncDataList sync_data;
sync_data.push_back(SyncDataFromPassword(old_empty_form));
sync_data.push_back(SyncDataFromPassword(sync_empty_form));
EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
.WillOnce(DoAll(IgnoreResult(AppendForm(old_empty_form)),
AppendForm(db_empty_form)));
EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
EXPECT_CALL(*password_store(), RemoveLoginImpl(PasswordIs(old_empty_form)));
EXPECT_CALL(*password_store(), RemoveLoginImpl(PasswordIs(db_empty_form)));
EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
SyncChangeIs(SyncChange::ACTION_DELETE, old_empty_form),
SyncChangeIs(SyncChange::ACTION_DELETE, sync_empty_form))));
service()->MergeDataAndStartSyncing(syncer::PASSWORDS,
sync_data,
processor_.Pass(),
scoped_ptr<syncer::SyncErrorFactory>());
}
} // namespace
} // namespace password_manager