Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/password_manager/core/browser/password_syncable_service.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/basictypes.h"
     12 #include "base/location.h"
     13 #include "base/memory/ref_counted.h"
     14 #include "base/memory/scoped_ptr.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "components/password_manager/core/browser/mock_password_store.h"
     17 #include "sync/api/sync_change_processor.h"
     18 #include "sync/api/sync_error.h"
     19 #include "sync/api/sync_error_factory_mock.h"
     20 #include "testing/gmock/include/gmock/gmock.h"
     21 #include "testing/gtest/include/gtest/gtest.h"
     22 
     23 using syncer::SyncChange;
     24 using syncer::SyncData;
     25 using syncer::SyncDataList;
     26 using syncer::SyncError;
     27 using testing::AnyNumber;
     28 using testing::ElementsAre;
     29 using testing::Invoke;
     30 using testing::IsEmpty;
     31 using testing::Matches;
     32 using testing::Return;
     33 using testing::SetArgPointee;
     34 using testing::UnorderedElementsAre;
     35 using testing::_;
     36 
     37 namespace password_manager {
     38 
     39 // Defined in the implementation file corresponding to this test.
     40 syncer::SyncData SyncDataFromPassword(const autofill::PasswordForm& password);
     41 autofill::PasswordForm PasswordFromSpecifics(
     42     const sync_pb::PasswordSpecificsData& password);
     43 std::string MakePasswordSyncTag(const sync_pb::PasswordSpecificsData& password);
     44 std::string MakePasswordSyncTag(const autofill::PasswordForm& password);
     45 
     46 namespace {
     47 
     48 // PasswordForm values for tests.
     49 const autofill::PasswordForm::Type kArbitraryType =
     50     autofill::PasswordForm::TYPE_GENERATED;
     51 const char kAvatarUrl[] = "https://fb.com/Avatar";
     52 const char kDisplayName[] = "Agent Smith";
     53 const char kFederationUrl[] = "https://fb.com/federation_url";
     54 const char kSignonRealm[] = "abc";
     55 const char kSignonRealm2[] = "def";
     56 const char kSignonRealm3[] = "xyz";
     57 const int kTimesUsed = 5;
     58 
     59 typedef std::vector<SyncChange> SyncChangeList;
     60 
     61 const sync_pb::PasswordSpecificsData& GetPasswordSpecifics(
     62     const syncer::SyncData& sync_data) {
     63   return sync_data.GetSpecifics().password().client_only_encrypted_data();
     64 }
     65 
     66 MATCHER(HasDateSynced, "") {
     67   return !arg.date_synced.is_null() && !arg.date_synced.is_max();
     68 }
     69 
     70 MATCHER_P(PasswordIs, form, "") {
     71   sync_pb::PasswordSpecificsData actual_password =
     72       GetPasswordSpecifics(SyncDataFromPassword(arg));
     73   sync_pb::PasswordSpecificsData expected_password =
     74       GetPasswordSpecifics(SyncDataFromPassword(form));
     75   if (expected_password.scheme() == actual_password.scheme() &&
     76       expected_password.signon_realm() == actual_password.signon_realm() &&
     77       expected_password.origin() == actual_password.origin() &&
     78       expected_password.action() == actual_password.action() &&
     79       expected_password.username_element() ==
     80           actual_password.username_element() &&
     81       expected_password.password_element() ==
     82           actual_password.password_element() &&
     83       expected_password.username_value() == actual_password.username_value() &&
     84       expected_password.password_value() == actual_password.password_value() &&
     85       expected_password.ssl_valid() == actual_password.ssl_valid() &&
     86       expected_password.preferred() == actual_password.preferred() &&
     87       expected_password.date_created() == actual_password.date_created() &&
     88       expected_password.blacklisted() == actual_password.blacklisted() &&
     89       expected_password.type() == actual_password.type() &&
     90       expected_password.times_used() == actual_password.times_used() &&
     91       expected_password.display_name() == actual_password.display_name() &&
     92       expected_password.avatar_url() == actual_password.avatar_url() &&
     93       expected_password.federation_url() == actual_password.federation_url())
     94     return true;
     95 
     96   *result_listener << "Password protobuf does not match; expected:\n"
     97                    << form << '\n'
     98                    << "actual:" << '\n'
     99                    << arg;
    100   return false;
    101 }
    102 
    103 MATCHER_P2(SyncChangeIs, change_type, password, "") {
    104   const SyncData& data = arg.sync_data();
    105   autofill::PasswordForm form = PasswordFromSpecifics(
    106       GetPasswordSpecifics(data));
    107   return (arg.change_type() == change_type &&
    108           syncer::SyncDataLocal(data).GetTag() ==
    109               MakePasswordSyncTag(password) &&
    110           (change_type == SyncChange::ACTION_DELETE ||
    111            Matches(PasswordIs(password))(form)));
    112 }
    113 
    114 // The argument is std::vector<autofill::PasswordForm*>*. The caller is
    115 // responsible for the lifetime of all the password forms.
    116 ACTION_P(AppendForm, form) {
    117   arg0->push_back(new autofill::PasswordForm(form));
    118   return true;
    119 }
    120 
    121 // Creates a sync data consisting of password specifics. The sign on realm is
    122 // set to |signon_realm|.
    123 SyncData CreateSyncData(const std::string& signon_realm) {
    124   sync_pb::EntitySpecifics password_data;
    125   sync_pb::PasswordSpecificsData* password_specifics =
    126       password_data.mutable_password()->mutable_client_only_encrypted_data();
    127   password_specifics->set_signon_realm(signon_realm);
    128   password_specifics->set_type(autofill::PasswordForm::TYPE_GENERATED);
    129   password_specifics->set_times_used(3);
    130   password_specifics->set_display_name("Mr. X");
    131   password_specifics->set_avatar_url("https://accounts.google.com/Avatar");
    132   password_specifics->set_federation_url("https://google.com/federation");
    133 
    134   std::string tag = MakePasswordSyncTag(*password_specifics);
    135   return syncer::SyncData::CreateLocalData(tag, tag, password_data);
    136 }
    137 
    138 SyncChange CreateSyncChange(const autofill::PasswordForm& password,
    139                             SyncChange::SyncChangeType type) {
    140   SyncData data = SyncDataFromPassword(password);
    141   return SyncChange(FROM_HERE, type, data);
    142 }
    143 
    144 // A testable implementation of the |PasswordSyncableService| that mocks
    145 // out all interaction with the password database.
    146 class MockPasswordSyncableService : public PasswordSyncableService {
    147  public:
    148   explicit MockPasswordSyncableService(PasswordStoreSync* password_store)
    149       : PasswordSyncableService(password_store) {}
    150 
    151   MOCK_METHOD1(StartSyncFlare, void(syncer::ModelType));
    152 
    153  private:
    154   DISALLOW_COPY_AND_ASSIGN(MockPasswordSyncableService);
    155 };
    156 
    157 // Mock implementation of SyncChangeProcessor.
    158 class MockSyncChangeProcessor : public syncer::SyncChangeProcessor {
    159  public:
    160   MockSyncChangeProcessor() {}
    161 
    162   MOCK_METHOD2(ProcessSyncChanges,
    163                SyncError(const tracked_objects::Location&,
    164                          const SyncChangeList& list));
    165   virtual SyncDataList GetAllSyncData(syncer::ModelType type) const OVERRIDE {
    166     NOTREACHED();
    167     return SyncDataList();
    168   }
    169 
    170  private:
    171   DISALLOW_COPY_AND_ASSIGN(MockSyncChangeProcessor);
    172 };
    173 
    174 // Convenience wrapper around a PasswordSyncableService and PasswordStore
    175 // pair.
    176 class PasswordSyncableServiceWrapper {
    177  public:
    178   PasswordSyncableServiceWrapper() {
    179     password_store_ = new testing::StrictMock<MockPasswordStore>;
    180     service_.reset(new MockPasswordSyncableService(
    181         password_store_->GetSyncInterface()));
    182     ON_CALL(*password_store_, AddLoginImpl(HasDateSynced()))
    183         .WillByDefault(Return(PasswordStoreChangeList()));
    184     ON_CALL(*password_store_, RemoveLoginImpl(_))
    185         .WillByDefault(Return(PasswordStoreChangeList()));
    186     ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced()))
    187         .WillByDefault(Return(PasswordStoreChangeList()));
    188     EXPECT_CALL(*password_store(), NotifyLoginsChanged(_)).Times(AnyNumber());
    189   }
    190 
    191   ~PasswordSyncableServiceWrapper() {
    192     password_store_->Shutdown();
    193   }
    194 
    195   MockPasswordStore* password_store() { return password_store_.get(); }
    196 
    197   MockPasswordSyncableService* service() { return service_.get(); }
    198 
    199   // Returnes the scoped_ptr to |service_| thus NULLing out it.
    200   scoped_ptr<syncer::SyncChangeProcessor> ReleaseSyncableService() {
    201     return service_.PassAs<syncer::SyncChangeProcessor>();
    202   }
    203 
    204  private:
    205   scoped_refptr<MockPasswordStore> password_store_;
    206   scoped_ptr<MockPasswordSyncableService> service_;
    207 
    208   DISALLOW_COPY_AND_ASSIGN(PasswordSyncableServiceWrapper);
    209 };
    210 
    211 class PasswordSyncableServiceTest : public testing::Test {
    212  public:
    213   PasswordSyncableServiceTest()
    214       : processor_(new testing::StrictMock<MockSyncChangeProcessor>) {
    215     ON_CALL(*processor_, ProcessSyncChanges(_, _))
    216         .WillByDefault(Return(SyncError()));
    217   }
    218   MockPasswordStore* password_store() { return wrapper_.password_store(); }
    219   MockPasswordSyncableService* service() { return wrapper_.service(); }
    220 
    221  protected:
    222   scoped_ptr<MockSyncChangeProcessor> processor_;
    223 
    224  private:
    225   PasswordSyncableServiceWrapper wrapper_;
    226 };
    227 
    228 
    229 // Both sync and password db have data that are not present in the other.
    230 TEST_F(PasswordSyncableServiceTest, AdditionsInBoth) {
    231   autofill::PasswordForm form;
    232   form.signon_realm = kSignonRealm;
    233 
    234   SyncDataList list;
    235   list.push_back(CreateSyncData(kSignonRealm2));
    236   autofill::PasswordForm new_from_sync = PasswordFromSpecifics(
    237       GetPasswordSpecifics(list.back()));
    238 
    239   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    240       .WillOnce(AppendForm(form));
    241   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    242       .WillOnce(Return(true));
    243   EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
    244   EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
    245       SyncChangeIs(SyncChange::ACTION_ADD, form))));
    246 
    247   service()->MergeDataAndStartSyncing(
    248       syncer::PASSWORDS,
    249       list,
    250       processor_.PassAs<syncer::SyncChangeProcessor>(),
    251       scoped_ptr<syncer::SyncErrorFactory>());
    252 }
    253 
    254 // Sync has data that is not present in the password db.
    255 TEST_F(PasswordSyncableServiceTest, AdditionOnlyInSync) {
    256   SyncDataList list;
    257   list.push_back(CreateSyncData(kSignonRealm));
    258   autofill::PasswordForm new_from_sync = PasswordFromSpecifics(
    259       GetPasswordSpecifics(list.back()));
    260 
    261   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    262       .WillOnce(Return(true));
    263   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    264       .WillOnce(Return(true));
    265   EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
    266   EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
    267 
    268   service()->MergeDataAndStartSyncing(
    269       syncer::PASSWORDS,
    270       list,
    271       processor_.PassAs<syncer::SyncChangeProcessor>(),
    272       scoped_ptr<syncer::SyncErrorFactory>());
    273 }
    274 
    275 // Passwords db has data that is not present in sync.
    276 TEST_F(PasswordSyncableServiceTest, AdditionOnlyInPasswordStore) {
    277   autofill::PasswordForm form;
    278   form.signon_realm = kSignonRealm;
    279   form.times_used = kTimesUsed;
    280   form.type = kArbitraryType;
    281   form.display_name = base::ASCIIToUTF16(kDisplayName);
    282   form.avatar_url = GURL(kAvatarUrl);
    283   form.federation_url = GURL(kFederationUrl);
    284   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    285       .WillOnce(AppendForm(form));
    286   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    287       .WillOnce(Return(true));
    288 
    289   EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
    290       SyncChangeIs(SyncChange::ACTION_ADD, form))));
    291 
    292   service()->MergeDataAndStartSyncing(
    293       syncer::PASSWORDS,
    294       SyncDataList(),
    295       processor_.PassAs<syncer::SyncChangeProcessor>(),
    296       scoped_ptr<syncer::SyncErrorFactory>());
    297 }
    298 
    299 // Both passwords db and sync contain the same data.
    300 TEST_F(PasswordSyncableServiceTest, BothInSync) {
    301   autofill::PasswordForm form;
    302   form.signon_realm = kSignonRealm;
    303   form.times_used = kTimesUsed;
    304   form.type = kArbitraryType;
    305   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    306       .WillOnce(AppendForm(form));
    307   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    308       .WillOnce(Return(true));
    309 
    310   EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
    311 
    312   service()->MergeDataAndStartSyncing(
    313       syncer::PASSWORDS,
    314       SyncDataList(1, SyncDataFromPassword(form)),
    315       processor_.PassAs<syncer::SyncChangeProcessor>(),
    316       scoped_ptr<syncer::SyncErrorFactory>());
    317 }
    318 
    319 // Both passwords db and sync have the same data but they need to be merged
    320 // as some fields of the data differ.
    321 TEST_F(PasswordSyncableServiceTest, Merge) {
    322   autofill::PasswordForm form1;
    323   form1.signon_realm = kSignonRealm;
    324   form1.action = GURL("http://pie.com");
    325   form1.date_created = base::Time::Now();
    326   form1.preferred = true;
    327 
    328   autofill::PasswordForm form2(form1);
    329   form2.preferred = false;
    330   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    331       .WillOnce(AppendForm(form1));
    332   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    333       .WillOnce(Return(true));
    334   EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(form2)));
    335   EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
    336 
    337   service()->MergeDataAndStartSyncing(
    338       syncer::PASSWORDS,
    339       SyncDataList(1, SyncDataFromPassword(form2)),
    340       processor_.PassAs<syncer::SyncChangeProcessor>(),
    341       scoped_ptr<syncer::SyncErrorFactory>());
    342 }
    343 
    344 // Initiate sync due to local DB changes.
    345 TEST_F(PasswordSyncableServiceTest, PasswordStoreChanges) {
    346   // Save the reference to the processor because |processor_| is NULL after
    347   // MergeDataAndStartSyncing().
    348   MockSyncChangeProcessor& weak_processor = *processor_;
    349   EXPECT_CALL(weak_processor, ProcessSyncChanges(_, IsEmpty()));
    350   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    351       .WillOnce(Return(true));
    352   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    353       .WillOnce(Return(true));
    354   service()->MergeDataAndStartSyncing(
    355       syncer::PASSWORDS,
    356       SyncDataList(),
    357       processor_.PassAs<syncer::SyncChangeProcessor>(),
    358       scoped_ptr<syncer::SyncErrorFactory>());
    359 
    360   autofill::PasswordForm form1;
    361   form1.signon_realm = kSignonRealm;
    362   autofill::PasswordForm form2;
    363   form2.signon_realm = kSignonRealm2;
    364   autofill::PasswordForm form3;
    365   form3.signon_realm = kSignonRealm3;
    366 
    367   SyncChangeList sync_list;
    368   sync_list.push_back(CreateSyncChange(form1, SyncChange::ACTION_ADD));
    369   sync_list.push_back(CreateSyncChange(form2, SyncChange::ACTION_UPDATE));
    370   sync_list.push_back(CreateSyncChange(form3, SyncChange::ACTION_DELETE));
    371 
    372   PasswordStoreChangeList list;
    373   list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form1));
    374   list.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form2));
    375   list.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form3));
    376   EXPECT_CALL(weak_processor, ProcessSyncChanges(_, ElementsAre(
    377       SyncChangeIs(SyncChange::ACTION_ADD, form1),
    378       SyncChangeIs(SyncChange::ACTION_UPDATE, form2),
    379       SyncChangeIs(SyncChange::ACTION_DELETE, form3))));
    380   service()->ActOnPasswordStoreChanges(list);
    381 }
    382 
    383 // Process all types of changes from sync.
    384 TEST_F(PasswordSyncableServiceTest, ProcessSyncChanges) {
    385   autofill::PasswordForm updated_form;
    386   updated_form.signon_realm = kSignonRealm;
    387   updated_form.action = GURL("http://foo.com");
    388   updated_form.date_created = base::Time::Now();
    389   autofill::PasswordForm deleted_form;
    390   deleted_form.signon_realm = kSignonRealm2;
    391   deleted_form.action = GURL("http://bar.com");
    392   deleted_form.blacklisted_by_user = true;
    393 
    394   SyncData add_data = CreateSyncData(kSignonRealm3);
    395   autofill::PasswordForm new_from_sync = PasswordFromSpecifics(
    396       GetPasswordSpecifics(add_data));
    397 
    398   SyncChangeList list;
    399   list.push_back(SyncChange(FROM_HERE,
    400                             syncer::SyncChange::ACTION_ADD,
    401                             add_data));
    402   list.push_back(CreateSyncChange(updated_form,
    403                                   syncer::SyncChange::ACTION_UPDATE));
    404   list.push_back(CreateSyncChange(deleted_form,
    405                                   syncer::SyncChange::ACTION_DELETE));
    406   EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
    407   EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(updated_form)));
    408   EXPECT_CALL(*password_store(), RemoveLoginImpl(PasswordIs(deleted_form)));
    409   service()->ProcessSyncChanges(FROM_HERE, list);
    410 }
    411 
    412 // Retrives sync data from the model.
    413 TEST_F(PasswordSyncableServiceTest, GetAllSyncData) {
    414   autofill::PasswordForm form1;
    415   form1.signon_realm = kSignonRealm;
    416   form1.action = GURL("http://foo.com");
    417   form1.times_used = kTimesUsed;
    418   form1.type = kArbitraryType;
    419   form1.display_name = base::ASCIIToUTF16(kDisplayName);
    420   form1.avatar_url = GURL(kAvatarUrl);
    421   form1.federation_url = GURL(kFederationUrl);
    422   autofill::PasswordForm form2;
    423   form2.signon_realm = kSignonRealm2;
    424   form2.action = GURL("http://bar.com");
    425   form2.blacklisted_by_user = true;
    426   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    427       .WillOnce(AppendForm(form1));
    428   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    429       .WillOnce(AppendForm(form2));
    430 
    431   SyncDataList actual_list = service()->GetAllSyncData(syncer::PASSWORDS);
    432   std::vector<autofill::PasswordForm> actual_form_list;
    433   for (SyncDataList::iterator it = actual_list.begin();
    434        it != actual_list.end(); ++it) {
    435     actual_form_list.push_back(
    436         PasswordFromSpecifics(GetPasswordSpecifics(*it)));
    437   }
    438   EXPECT_THAT(actual_form_list, UnorderedElementsAre(PasswordIs(form1),
    439                                                      PasswordIs(form2)));
    440 }
    441 
    442 // Creates 2 PasswordSyncableService instances, merges the content of the first
    443 // one to the second one and back.
    444 TEST_F(PasswordSyncableServiceTest, MergeDataAndPushBack) {
    445   autofill::PasswordForm form1;
    446   form1.signon_realm = kSignonRealm;
    447   form1.action = GURL("http://foo.com");
    448 
    449   PasswordSyncableServiceWrapper other_service_wrapper;
    450   autofill::PasswordForm form2;
    451   form2.signon_realm = kSignonRealm2;
    452   form2.action = GURL("http://bar.com");
    453   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    454       .WillOnce(AppendForm(form1));
    455   EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
    456       .WillOnce(Return(true));
    457   EXPECT_CALL(*other_service_wrapper.password_store(),
    458               FillAutofillableLogins(_)).WillOnce(AppendForm(form2));
    459   EXPECT_CALL(*other_service_wrapper.password_store(),
    460               FillBlacklistLogins(_)).WillOnce(Return(true));
    461 
    462   EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(form2)));
    463   EXPECT_CALL(*other_service_wrapper.password_store(),
    464               AddLoginImpl(PasswordIs(form1)));
    465 
    466   syncer::SyncDataList other_service_data =
    467       other_service_wrapper.service()->GetAllSyncData(syncer::PASSWORDS);
    468   service()->MergeDataAndStartSyncing(
    469       syncer::PASSWORDS,
    470       other_service_data,
    471       other_service_wrapper.ReleaseSyncableService(),
    472       scoped_ptr<syncer::SyncErrorFactory>());
    473 }
    474 
    475 // Calls ActOnPasswordStoreChanges without SyncChangeProcessor. StartSyncFlare
    476 // should be called.
    477 TEST_F(PasswordSyncableServiceTest, StartSyncFlare) {
    478   autofill::PasswordForm form;
    479   form.signon_realm = kSignonRealm;
    480   PasswordStoreChangeList list;
    481   list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
    482 
    483   // No flare and no SyncChangeProcessor, the call shouldn't crash.
    484   service()->ActOnPasswordStoreChanges(list);
    485 
    486   // Set the flare. It should be called as there is no SyncChangeProcessor.
    487   service()->InjectStartSyncFlare(
    488       base::Bind(&MockPasswordSyncableService::StartSyncFlare,
    489                  base::Unretained(service())));
    490   EXPECT_CALL(*service(), StartSyncFlare(syncer::PASSWORDS));
    491   service()->ActOnPasswordStoreChanges(list);
    492 }
    493 
    494 // Start syncing with an error. Subsequent password store updates shouldn't be
    495 // propagated to Sync.
    496 TEST_F(PasswordSyncableServiceTest, FailedReadFromPasswordStore) {
    497   scoped_ptr<syncer::SyncErrorFactoryMock> error_factory(
    498       new syncer::SyncErrorFactoryMock);
    499   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
    500       .WillOnce(Return(false));
    501   EXPECT_CALL(*error_factory, CreateAndUploadError(_, _))
    502       .WillOnce(Return(SyncError()));
    503   // ActOnPasswordStoreChanges() below shouldn't generate any changes for Sync.
    504   // |processor_| will be destroyed in MergeDataAndStartSyncing().
    505   EXPECT_CALL(*processor_, ProcessSyncChanges(_, _)).Times(0);
    506   service()->MergeDataAndStartSyncing(
    507       syncer::PASSWORDS,
    508       syncer::SyncDataList(),
    509       processor_.PassAs<syncer::SyncChangeProcessor>(),
    510       error_factory.PassAs<syncer::SyncErrorFactory>());
    511 
    512   autofill::PasswordForm form;
    513   form.signon_realm = kSignonRealm;
    514   PasswordStoreChangeList list;
    515   list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
    516   service()->ActOnPasswordStoreChanges(list);
    517 }
    518 
    519 }  // namespace
    520 
    521 }  // namespace password_manager
    522