// 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 <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "components/password_manager/core/browser/mock_password_store.h"
#include "components/sync/model/sync_change_processor.h"
#include "components/sync/model/sync_error.h"
#include "components/sync/model/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.
constexpr autofill::PasswordForm::Type kArbitraryType =
    autofill::PasswordForm::TYPE_GENERATED;
constexpr char kIconUrl[] = "https://fb.com/Icon";
constexpr char kDisplayName[] = "Agent Smith";
constexpr char kFederationUrl[] = "https://fb.com/";
constexpr char kPassword[] = "abcdef";
constexpr char kSignonRealm[] = "abc";
constexpr char kSignonRealm2[] = "def";
constexpr char kSignonRealm3[] = "xyz";
constexpr int kTimesUsed = 5;
constexpr 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.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(std::make_unique<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/Icon");
  password_specifics->set_federation_url("https://google.com");
  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);
}

// Mock implementation of SyncChangeProcessor.
class MockSyncChangeProcessor : public syncer::SyncChangeProcessor {
 public:
  MockSyncChangeProcessor() {}

  MOCK_METHOD2(ProcessSyncChanges,
               SyncError(const base::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>;
    password_store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr);
    service_.reset(
        new PasswordSyncableService(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_->ShutdownOnUIThread(); }

  MockPasswordStore* password_store() { return password_store_.get(); }

  PasswordSyncableService* service() { return service_.get(); }

  // Returnes the scoped_ptr to |service_| thus NULLing out it.
  std::unique_ptr<syncer::SyncChangeProcessor> ReleaseSyncableService() {
    return std::move(service_);
  }

 private:
  scoped_refptr<MockPasswordStore> password_store_;
  std::unique_ptr<PasswordSyncableService> 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(); }
  PasswordSyncableService* service() { return wrapper_.service(); }

  MOCK_METHOD1(StartSyncFlare, void(syncer::ModelType));

 protected:
  std::unique_ptr<MockSyncChangeProcessor> processor_;

 private:
  // Used by the password store.
  base::test::ScopedTaskEnvironment scoped_task_environment_;
  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, std::move(processor_),
      std::unique_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, std::move(processor_),
      std::unique_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.icon_url = GURL(kIconUrl);
  form.federation_origin = url::Origin::Create(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(), std::move(processor_),
      std::unique_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)),
      std::move(processor_), std::unique_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)),
      std::move(processor_), std::unique_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(), std::move(processor_),
      std::unique_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.icon_url = GURL(kIconUrl);
  form1.federation_origin = url::Origin::Create(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(),
      std::unique_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(
      &PasswordSyncableServiceTest::StartSyncFlare, base::Unretained(this)));
  EXPECT_CALL(*this, 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) {
  std::unique_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(), std::move(processor_),
      std::move(error_factory));
  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);
  std::unique_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(), std::move(processor_),
      std::move(error_factory));
  EXPECT_TRUE(result.error().IsSet());

  form.signon_realm = kSignonRealm2;
  PasswordStoreChangeList list;
  list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
  service()->ActOnPasswordStoreChanges(list);
}

// Serialize and deserialize empty federation_origin and make sure it's an empty
// string.
TEST_F(PasswordSyncableServiceTest, SerializeEmptyFederation) {
  autofill::PasswordForm form;
  EXPECT_TRUE(form.federation_origin.unique());
  syncer::SyncData data = SyncDataFromPassword(form);
  const sync_pb::PasswordSpecificsData& specifics = GetPasswordSpecifics(data);
  EXPECT_TRUE(specifics.has_federation_url());
  EXPECT_EQ(std::string(), specifics.federation_url());

  // Deserialize back.
  form = PasswordFromSpecifics(specifics);
  EXPECT_TRUE(form.federation_origin.unique());

  // Make sure that the Origins uploaded incorrectly are still deserialized
  // correctly.
  // crbug.com/593380.
  sync_pb::PasswordSpecificsData specifics1;
  specifics1.set_federation_url("null");
  form = PasswordFromSpecifics(specifics1);
  EXPECT_TRUE(form.federation_origin.unique());
}

// Serialize empty PasswordForm and make sure the Sync representation is
// matching the expectations
TEST_F(PasswordSyncableServiceTest, SerializeEmptyPasswordForm) {
  autofill::PasswordForm form;
  syncer::SyncData data = SyncDataFromPassword(form);
  const sync_pb::PasswordSpecificsData& specifics = GetPasswordSpecifics(data);
  EXPECT_TRUE(specifics.has_scheme());
  EXPECT_EQ(0, specifics.scheme());
  EXPECT_TRUE(specifics.has_signon_realm());
  EXPECT_EQ("", specifics.signon_realm());
  EXPECT_TRUE(specifics.has_origin());
  EXPECT_EQ("", specifics.origin());
  EXPECT_TRUE(specifics.has_action());
  EXPECT_EQ("", specifics.action());
  EXPECT_TRUE(specifics.has_username_element());
  EXPECT_EQ("", specifics.username_element());
  EXPECT_TRUE(specifics.has_username_value());
  EXPECT_EQ("", specifics.username_value());
  EXPECT_TRUE(specifics.has_password_element());
  EXPECT_EQ("", specifics.password_element());
  EXPECT_TRUE(specifics.has_password_value());
  EXPECT_EQ("", specifics.password_value());
  EXPECT_TRUE(specifics.has_preferred());
  EXPECT_FALSE(specifics.preferred());
  EXPECT_TRUE(specifics.has_date_created());
  EXPECT_EQ(0, specifics.date_created());
  EXPECT_TRUE(specifics.has_blacklisted());
  EXPECT_FALSE(specifics.blacklisted());
  EXPECT_TRUE(specifics.has_type());
  EXPECT_EQ(0, specifics.type());
  EXPECT_TRUE(specifics.has_times_used());
  EXPECT_EQ(0, specifics.times_used());
  EXPECT_TRUE(specifics.has_display_name());
  EXPECT_EQ("", specifics.display_name());
  EXPECT_TRUE(specifics.has_avatar_url());
  EXPECT_EQ("", specifics.avatar_url());
  EXPECT_TRUE(specifics.has_federation_url());
  EXPECT_EQ("", specifics.federation_url());
}

// Serialize a PasswordForm with non-default member values and make sure the
// Sync representation is matching the expectations.
TEST_F(PasswordSyncableServiceTest, SerializeNonEmptyPasswordForm) {
  autofill::PasswordForm form;
  form.scheme = autofill::PasswordForm::SCHEME_USERNAME_ONLY;
  form.signon_realm = "http://google.com/";
  form.origin = GURL("https://google.com/origin");
  form.action = GURL("https://google.com/action");
  form.username_element = base::ASCIIToUTF16("username_element");
  form.username_value = base::ASCIIToUTF16("god@google.com");
  form.password_element = base::ASCIIToUTF16("password_element");
  form.password_value = base::ASCIIToUTF16("!@#$%^&*()");
  form.preferred = true;
  form.date_created = base::Time::FromInternalValue(100);
  form.blacklisted_by_user = true;
  form.type = autofill::PasswordForm::TYPE_LAST;
  form.times_used = 11;
  form.display_name = base::ASCIIToUTF16("Great Peter");
  form.icon_url = GURL("https://google.com/icon");
  form.federation_origin = url::Origin::Create(GURL("https://google.com/"));

  syncer::SyncData data = SyncDataFromPassword(form);
  const sync_pb::PasswordSpecificsData& specifics = GetPasswordSpecifics(data);
  EXPECT_TRUE(specifics.has_scheme());
  EXPECT_EQ(autofill::PasswordForm::SCHEME_USERNAME_ONLY, specifics.scheme());
  EXPECT_TRUE(specifics.has_signon_realm());
  EXPECT_EQ("http://google.com/", specifics.signon_realm());
  EXPECT_TRUE(specifics.has_origin());
  EXPECT_EQ("https://google.com/origin", specifics.origin());
  EXPECT_TRUE(specifics.has_action());
  EXPECT_EQ("https://google.com/action", specifics.action());
  EXPECT_TRUE(specifics.has_username_element());
  EXPECT_EQ("username_element", specifics.username_element());
  EXPECT_TRUE(specifics.has_username_value());
  EXPECT_EQ("god@google.com", specifics.username_value());
  EXPECT_TRUE(specifics.has_password_element());
  EXPECT_EQ("password_element", specifics.password_element());
  EXPECT_TRUE(specifics.has_password_value());
  EXPECT_EQ("!@#$%^&*()", specifics.password_value());
  EXPECT_TRUE(specifics.has_preferred());
  EXPECT_TRUE(specifics.preferred());
  EXPECT_TRUE(specifics.has_date_created());
  EXPECT_EQ(100, specifics.date_created());
  EXPECT_TRUE(specifics.has_blacklisted());
  EXPECT_TRUE(specifics.blacklisted());
  EXPECT_TRUE(specifics.has_type());
  EXPECT_EQ(autofill::PasswordForm::TYPE_LAST, specifics.type());
  EXPECT_TRUE(specifics.has_times_used());
  EXPECT_EQ(11, specifics.times_used());
  EXPECT_TRUE(specifics.has_display_name());
  EXPECT_EQ("Great Peter", specifics.display_name());
  EXPECT_TRUE(specifics.has_avatar_url());
  EXPECT_EQ("https://google.com/icon", specifics.avatar_url());
  EXPECT_TRUE(specifics.has_federation_url());
  EXPECT_EQ("https://google.com", specifics.federation_url());
}

}  // namespace

}  // namespace password_manager
