chromium/chrome/browser/keyboard_accessory/android/password_accessory_controller_impl_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/keyboard_accessory/android/password_accessory_controller_impl.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/android/build_info.h"
#include "base/base64.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/browser/keyboard_accessory/android/accessory_controller.h"
#include "chrome/browser/keyboard_accessory/android/accessory_sheet_enums.h"
#include "chrome/browser/keyboard_accessory/test_utils/android/mock_affiliated_plus_profiles_provider.h"
#include "chrome/browser/keyboard_accessory/test_utils/android/mock_manual_filling_controller.h"
#include "chrome/browser/password_manager/android/password_generation_controller.h"
#include "chrome/browser/password_manager/android/password_generation_controller_impl.h"
#include "chrome/browser/password_manager/password_manager_test_util.h"
#include "chrome/browser/plus_addresses/plus_address_service_factory.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_content_autofill_client.h"
#include "components/autofill/core/common/password_generation_util.h"
#include "components/device_reauth/device_authenticator.h"
#include "components/device_reauth/mock_device_authenticator.h"
#include "components/password_manager/content/browser/content_password_manager_driver_factory.h"
#include "components/password_manager/core/browser/credential_cache.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/core/browser/mock_password_manager.h"
#include "components/password_manager/core/browser/mock_webauthn_credentials_delegate.h"
#include "components/password_manager/core/browser/origin_credential_store.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_generation_frame_helper.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
#include "components/password_manager/core/browser/password_store/test_password_store.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/password_manager/core/browser/stub_password_manager_driver.h"
#include "components/password_manager/core/browser/webauthn_credentials_delegate.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/plus_addresses/fake_plus_address_service.h"
#include "components/plus_addresses/features.h"
#include "components/plus_addresses/plus_address_test_environment.h"
#include "components/plus_addresses/plus_address_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/resources/android/theme_resources.h"
#include "components/security_state/core/security_state.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/webauthn/android/cred_man_support.h"
#include "components/webauthn/android/webauthn_cred_man_delegate.h"
#include "components/webauthn/android/webauthn_cred_man_delegate_factory.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace {
using autofill::AccessoryAction;
using autofill::AccessorySheetData;
using autofill::AccessorySheetField;
using autofill::AccessoryTabType;
using autofill::FooterCommand;
using autofill::UserInfo;
using autofill::mojom::FocusedFieldType;
using base::test::RunOnceCallback;
using device_reauth::MockDeviceAuthenticator;
using password_manager::CreateEntry;
using password_manager::CredentialCache;
using password_manager::MockPasswordManager;
using password_manager::MockPasswordStoreInterface;
using password_manager::OriginCredentialStore;
using password_manager::PasskeyCredential;
using password_manager::PasswordForm;
using password_manager::PasswordGenerationFrameHelper;
using password_manager::PasswordManagerClient;
using password_manager::PasswordManagerDriver;
using password_manager::PasswordManagerInterface;
using password_manager::PasswordStoreInterface;
using password_manager::TestPasswordStore;
using plus_addresses::FakePlusAddressService;
using plus_addresses::PlusAddressSettingService;
using plus_addresses::PlusProfile;
using plus_addresses::test::PlusAddressTestEnvironment;
using testing::_;
using testing::ByMove;
using testing::Eq;
using testing::Mock;
using testing::NiceMock;
using testing::Return;
using testing::ReturnRef;
using testing::SaveArg;
using testing::StrictMock;
using webauthn::CredManSupport;
using webauthn::WebAuthnCredManDelegate;
using FillingSource = ManualFillingController::FillingSource;
using IsFillingSourceAvailable = AccessoryController::IsFillingSourceAvailable;
using IsExactMatch = autofill::UserInfo::IsExactMatch;
using ShouldShowAction = ManualFillingController::ShouldShowAction;

constexpr char kExampleSite[] = "https://example.com";
constexpr char kExampleHttpSite[] = "http://example.com";
constexpr char16_t kExampleHttpSite16[] = u"http://example.com";
constexpr char kExampleSiteMobile[] = "https://m.example.com";
constexpr char kExampleSignonRealm[] = "https://example.com/";
constexpr char16_t kExampleDomain[] = u"example.com";
constexpr char16_t kUsername[] = u"alice";
constexpr char16_t kPassword[] = u"password123";
const std::optional<std::vector<PasskeyCredential>> kNoPasskeys = std::nullopt;

class MockPasswordGenerationController
    : public PasswordGenerationControllerImpl {
 public:
  static void CreateForWebContents(content::WebContents* web_contents);

  explicit MockPasswordGenerationController(content::WebContents* web_contents);

  MOCK_METHOD(void,
              OnGenerationRequested,
              (autofill::password_generation::PasswordGenerationType));
};

// static
void MockPasswordGenerationController::CreateForWebContents(
    content::WebContents* web_contents) {
  ASSERT_FALSE(FromWebContents(web_contents));
  web_contents->SetUserData(
      UserDataKey(),
      base::WrapUnique(new MockPasswordGenerationController(web_contents)));
}

MockPasswordGenerationController::MockPasswordGenerationController(
    content::WebContents* web_contents)
    : PasswordGenerationControllerImpl(web_contents) {}

class MockPasswordGenerationFrameHelper : public PasswordGenerationFrameHelper {
 public:
  MockPasswordGenerationFrameHelper(PasswordManagerClient* client,
                                    PasswordManagerDriver* driver)
      : PasswordGenerationFrameHelper(client, driver) {}
  MOCK_METHOD(bool, IsGenerationEnabled, (bool), (const override));
};

class MockPasswordManagerClient
    : public password_manager::StubPasswordManagerClient {
 public:
  MockPasswordManagerClient(PasswordStoreInterface* account_password_store,
                            PasswordStoreInterface* profile_password_store)
      : account_password_store_(account_password_store),
        profile_password_store_(profile_password_store) {}

  MOCK_METHOD(void, UpdateFormManagers, (), (override));

  MOCK_METHOD(bool,
              IsSavingAndFillingEnabled,
              (const GURL&),
              (const, override));

  MOCK_METHOD(std::unique_ptr<device_reauth::DeviceAuthenticator>,
              GetDeviceAuthenticator,
              (),
              (override));

  MOCK_METHOD(bool,
              IsReauthBeforeFillingRequired,
              (device_reauth::DeviceAuthenticator*),
              (override));

  MOCK_METHOD(password_manager::WebAuthnCredentialsDelegate*,
              GetWebAuthnCredentialsDelegateForDriver,
              (password_manager::PasswordManagerDriver*),
              (override));

  MOCK_METHOD(webauthn::WebAuthnCredManDelegate*,
              GetWebAuthnCredManDelegateForDriver,
              (password_manager::PasswordManagerDriver*),
              (override));

  MOCK_METHOD(const PasswordManagerInterface*,
              GetPasswordManager,
              (),
              (const override));

  password_manager::PasswordStoreInterface* GetAccountPasswordStore()
      const override {
    return account_password_store_;
  }

  password_manager::PasswordStoreInterface* GetProfilePasswordStore()
      const override {
    return profile_password_store_;
  }

 private:
  raw_ptr<PasswordStoreInterface> account_password_store_;
  raw_ptr<PasswordStoreInterface> profile_password_store_;
};

class MockPasswordManagerDriver
    : public password_manager::StubPasswordManagerDriver {
 public:
  MOCK_METHOD(void,
              FillIntoFocusedField,
              (bool, const std::u16string&),
              (override));
  MOCK_METHOD(PasswordGenerationFrameHelper*,
              GetPasswordGenerationHelper,
              (),
              (override));
};

class MockAutofillClient : public autofill::TestContentAutofillClient {
 public:
  using autofill::TestContentAutofillClient::TestContentAutofillClient;
  MOCK_METHOD(void,
              OfferPlusAddressCreation,
              (const url::Origin&, autofill::PlusAddressCallback),
              (override));
};

std::u16string password_for_str(const std::u16string& user) {
  return l10n_util::GetStringFUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_PASSWORD_DESCRIPTION, user);
}

std::u16string passwords_empty_str(const std::u16string& domain) {
  return l10n_util::GetStringFUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_PASSWORD_LIST_EMPTY_MESSAGE, domain);
}

std::u16string passwords_title(const std::u16string& domain) {
  return l10n_util::GetStringFUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_PASSWORD_LIST_TITLE, domain);
}

std::u16string plus_address_title(const std::u16string& domain) {
  return l10n_util::GetStringFUTF16(
      IDS_PLUS_ADDRESS_FALLBACK_MANUAL_FILLING_SHEET_TITLE, domain);
}

std::u16string no_user_str() {
  return l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN);
}

std::u16string show_other_passwords_str() {
  return l10n_util::GetStringUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_SELECT_PASSWORD);
}

std::u16string manage_passwords_str() {
  return l10n_util::GetStringUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_ALL_PASSWORDS_LINK);
}

std::u16string manage_passwords_and_passkeys_str() {
  return l10n_util::GetStringUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_ALL_PASSWORDS_AND_PASSKEYS_LINK);
}

std::u16string generate_password_str() {
  return l10n_util::GetStringUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_GENERATE_PASSWORD_BUTTON_TITLE);
}

std::u16string cross_device_passkeys_str() {
  return l10n_util::GetStringUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_USE_DEVICE_PASSKEY);
}

std::u16string select_passkey_str() {
  return l10n_util::GetStringUTF16(
      IDS_PASSWORD_MANAGER_ACCESSORY_SELECT_PASSKEY);
}

std::u16string generate_plus_address_str() {
  return l10n_util::GetStringUTF16(
      IDS_PLUS_ADDRESS_CREATE_NEW_PLUS_ADDRESSES_LINK_ANDROID);
}

std::u16string select_plus_address_str() {
  return l10n_util::GetStringUTF16(
      IDS_PLUS_ADDRESS_SELECT_PLUS_ADDRESS_LINK_ANDROID);
}

// Creates a AccessorySheetDataBuilder object with a "Manage passwords..."
// footer.
AccessorySheetData::Builder PasswordAccessorySheetDataBuilder(
    const std::u16string& user_info_title,
    const std::u16string plus_address_title = u"") {
  return AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                     user_info_title, plus_address_title)
      .AppendFooterCommand(manage_passwords_str(),
                           autofill::AccessoryAction::MANAGE_PASSWORDS);
}

AccessorySheetData::Builder PasswordAccessorySheetDataBuilderEmptyTitle() {
  return PasswordAccessorySheetDataBuilder(std::u16string());
}

PasswordForm MakeSavedPassword() {
  PasswordForm form;
  form.signon_realm = std::string(kExampleSite);
  form.url = GURL(kExampleSite);
  form.username_value = kUsername;
  form.password_value = kPassword;
  form.username_element = u"";
  form.in_store = PasswordForm::Store::kProfileStore;
  return form;
}

std::unique_ptr<KeyedService> BuildFakePlusAddressService(
    PrefService* pref_service,
    signin::IdentityManager* identity_manager,
    PlusAddressSettingService* setting_service,
    content::BrowserContext* context) {
  return std::make_unique<FakePlusAddressService>(
      pref_service, identity_manager, setting_service);
}

}  // namespace

class PasswordAccessoryControllerTest : public ChromeRenderViewHostTestHarness {
 public:
  PasswordAccessoryControllerTest()
      : ChromeRenderViewHostTestHarness(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    features_.InitWithFeatures(
        {plus_addresses::features::kPlusAddressesEnabled,
         plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled},
        {});
  }

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();

    PlusAddressServiceFactory::GetInstance()->SetTestingFactory(
        GetBrowserContext(),
        base::BindRepeating(&BuildFakePlusAddressService,
                            &plus_environment_.pref_service(),
                            plus_environment_.identity_env().identity_manager(),
                            &plus_environment_.setting_service()));

    NavigateAndCommit(GURL(kExampleSite));
    FocusWebContentsOnMainFrame();

    ASSERT_TRUE(web_contents()->GetFocusedFrame());
    ASSERT_EQ(url::Origin::Create(GURL(kExampleSite)),
              web_contents()->GetFocusedFrame()->GetLastCommittedOrigin());

    MockPasswordGenerationController::CreateForWebContents(web_contents());
    profile()->GetPrefs()->SetInteger(
        password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
        static_cast<int>(
            password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOn));
    mock_pwd_manager_client_ =
        std::make_unique<NiceMock<MockPasswordManagerClient>>(
            CreateInternalAccountPasswordStore(),
            CreateInternalProfilePasswordStore());
    mock_frame_helper_ =
        std::make_unique<NiceMock<MockPasswordGenerationFrameHelper>>(
            mock_pwd_manager_client_.get(), &mock_driver_);
    ON_CALL(mock_driver_, GetPasswordGenerationHelper)
        .WillByDefault(Return(mock_frame_helper_.get()));
    ON_CALL(*mock_pwd_manager_client_, GetPasswordManager)
        .WillByDefault(Return(&mock_password_manager_));
    NavigateAndCommit(GURL(kExampleSite));

    webauthn_credentials_delegate_ = std::make_unique<
        NiceMock<password_manager::MockWebAuthnCredentialsDelegate>>();
    ON_CALL(*webauthn_credentials_delegate(), GetPasskeys)
        .WillByDefault(ReturnRef(kNoPasskeys));
    ON_CALL(*password_client(), GetWebAuthnCredentialsDelegateForDriver)
        .WillByDefault(Return(webauthn_credentials_delegate()));
    ON_CALL(*password_client(), GetWebAuthnCredManDelegateForDriver)
        .WillByDefault(Return(cred_man_delegate()));
    ON_CALL(*webauthn_credentials_delegate(), IsAndroidHybridAvailable)
        .WillByDefault(Return(false));
    ON_CALL(*password_client()->GetPasswordFeatureManager(),
            IsOptedInForAccountStorage)
        .WillByDefault(Return(false));
    ON_CALL(*password_client()->GetPasswordFeatureManager(),
            GetDefaultPasswordStore)
        .WillByDefault(Return(PasswordForm::Store::kProfileStore));
  }

  webauthn::WebAuthnCredManDelegate* cred_man_delegate() {
    return webauthn::WebAuthnCredManDelegateFactory::GetFactory(web_contents())
        ->GetRequestDelegate(web_contents()->GetPrimaryMainFrame());
  }

  void CreateSheetController(
      security_state::SecurityLevel security_level = security_state::SECURE) {
    PasswordAccessoryControllerImpl::CreateForWebContentsForTesting(
        web_contents(), cache(), mock_manual_filling_controller_.AsWeakPtr(),
        mock_pwd_manager_client_.get(),
        base::BindRepeating(&PasswordAccessoryControllerTest::GetBaseDriver,
                            base::Unretained(this)),
        show_migration_warning_callback_.Get());

    controller()->RegisterFillingSourceObserver(filling_source_observer_.Get());
    controller()->SetSecurityLevelForTesting(security_level);
  }

  PasswordAccessoryControllerImpl* controller() {
    return PasswordAccessoryControllerImpl::FromWebContents(web_contents());
  }

  password_manager::CredentialCache* cache() { return &credential_cache_; }

  MockPasswordManagerClient* password_client() {
    return mock_pwd_manager_client_.get();
  }

  MockPasswordManagerDriver* driver() { return &mock_driver_; }

  MockPasswordGenerationFrameHelper& frame_helper() {
    return *mock_frame_helper_;
  }

  MockPasswordManager& password_manager() { return mock_password_manager_; }

  password_manager::MockWebAuthnCredentialsDelegate*
  webauthn_credentials_delegate() {
    return webauthn_credentials_delegate_.get();
  }

  MockAutofillClient& autofill_client() {
    return *autofill_client_injector_[web_contents()];
  }

  FakePlusAddressService& plus_address_service() {
    return *static_cast<FakePlusAddressService*>(
        PlusAddressServiceFactory::GetForBrowserContext(
            web_contents()->GetBrowserContext()));
  }

 protected:
  virtual PasswordStoreInterface* CreateInternalAccountPasswordStore() {
    mock_account_password_store_ =
        base::MakeRefCounted<NiceMock<MockPasswordStoreInterface>>();
    return mock_account_password_store_.get();
  }

  virtual PasswordStoreInterface* CreateInternalProfilePasswordStore() {
    mock_profile_password_store_ =
        base::MakeRefCounted<NiceMock<MockPasswordStoreInterface>>();
    return mock_profile_password_store_.get();
  }

  base::test::ScopedFeatureList features_;
  StrictMock<MockManualFillingController> mock_manual_filling_controller_;
  base::MockCallback<AccessoryController::FillingSourceObserver>
      filling_source_observer_;
  base::MockCallback<
      PasswordAccessoryControllerImpl::ShowMigrationWarningCallback>
      show_migration_warning_callback_;
  scoped_refptr<MockPasswordStoreInterface> mock_account_password_store_;
  scoped_refptr<MockPasswordStoreInterface> mock_profile_password_store_;

 private:
  password_manager::PasswordManagerDriver* GetBaseDriver(
      content::WebContents*) {
    return driver();
  }

  PlusAddressTestEnvironment plus_environment_;
  password_manager::CredentialCache credential_cache_;
  std::unique_ptr<MockPasswordManagerClient> mock_pwd_manager_client_;
  NiceMock<MockPasswordManagerDriver> mock_driver_;
  NiceMock<MockPasswordManager> mock_password_manager_;
  std::unique_ptr<MockPasswordGenerationFrameHelper> mock_frame_helper_;
  std::unique_ptr<password_manager::MockWebAuthnCredentialsDelegate>
      webauthn_credentials_delegate_;
  autofill::TestAutofillClientInjector<NiceMock<MockAutofillClient>>
      autofill_client_injector_;
};

TEST_F(PasswordAccessoryControllerTest, IsNotRecreatedForSameWebContents) {
  CreateSheetController();
  PasswordAccessoryControllerImpl* initial_controller =
      PasswordAccessoryControllerImpl::FromWebContents(web_contents());
  EXPECT_NE(nullptr, initial_controller);
  PasswordAccessoryControllerImpl::CreateForWebContents(web_contents(),
                                                        cache());
  EXPECT_EQ(PasswordAccessoryControllerImpl::FromWebContents(web_contents()),
            initial_controller);
}

TEST_F(PasswordAccessoryControllerTest, TransformsMatchesToSuggestions) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, HintsToEmptyUserNames) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilderEmptyTitle()
          .AddUserInfo(kExampleSite)
          .AppendField(no_user_str(), no_user_str(), false, false)
          .AppendField(u"S3cur3", password_for_str(no_user_str()), true, false)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, SortsAlphabeticalDuringTransform) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {
      CreateEntry("Ben", "S3cur3", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact),
      CreateEntry("Zebra", "M3h", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact),
      CreateEntry("Alf", "PWD", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact),
      CreateEntry("Cat", "M1@u", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Alf", u"Alf", false, true)
                .AppendField(u"PWD", password_for_str(u"Alf"), true, false)
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .AddUserInfo(kExampleSite)
                .AppendField(u"Cat", u"Cat", false, true)
                .AppendField(u"M1@u", password_for_str(u"Cat"), true, false)
                .AddUserInfo(kExampleSite)
                .AppendField(u"Zebra", u"Zebra", false, true)
                .AppendField(u"M3h", password_for_str(u"Zebra"), true, false)
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, RepeatsSuggestionsForSameFrame) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  // Pretend that any input in the same frame was focused.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, ProvidesEmptySuggestionsMessage) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, PasswordFieldChangesSuggestionType) {
  CreateSheetController();
  std::vector<password_manager::PasswordForm> matches = {
      CreateEntry("Ben", "S3cur3", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact),
      CreateEntry("", "p455w0rd", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  // Pretend a username field was focused. This should result in non-interactive
  // suggestion.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"No username", u"No username", false, false)
                .AppendField(u"p455w0rd", password_for_str(u"No username"),
                             true, false)
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .Build());

  // Pretend that we focus a password field now: By triggering a refresh with
  // |is_password_field| set to true, all suggestions other than the empty
  // username should become interactive.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"No username", u"No username", false, false)
                .AppendField(u"p455w0rd", password_for_str(u"No username"),
                             true, true)
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, true)
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, CacheChangesReplacePasswords) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));
  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);
  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .Build());

  std::vector<PasswordForm> changed_matches = {CreateEntry(
      "Alf", "M3lm4k", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      changed_matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));
  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);
  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Alf", u"Alf", false, true)
                .AppendField(u"M3lm4k", password_for_str(u"Alf"), true, false)
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, SetsTitleForPSLMatchedOriginsInV2) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {
      CreateEntry("Ben", "S3cur3", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact),
      CreateEntry("Alf", "R4nd0m", GURL(kExampleSiteMobile),
                  PasswordForm::MatchType::kPSL)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben",
                             /*is_obfuscated=*/false, /*selectable=*/true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"),
                             /*is_obfuscated=*/true, /*selectable=*/false)
                .AddUserInfo(kExampleSiteMobile, IsExactMatch(false))
                .AppendField(u"Alf", u"Alf",
                             /*is_obfuscated=*/false, /*selectable=*/true)
                .AppendField(u"R4nd0m", password_for_str(u"Alf"),
                             /*is_obfuscated=*/true, /*selectable=*/false)
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, UnfillableFieldClearsSuggestions) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  // Pretend a username field was focused. This should result in non-emtpy
  // suggestions.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .Build());

  // Pretend that the focus was lost or moved to an unfillable field. Now, only
  // the empty state message should be sent.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kUnfillableElement);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, NavigatingMainFrameClearsSuggestions) {
  CreateSheetController();
  // Set any, non-empty password list and pretend a username field was focused.
  // This should result in non-emtpy suggestions.
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilderEmptyTitle()
                .AddUserInfo(kExampleSite)
                .AppendField(u"Ben", u"Ben", false, true)
                .AppendField(u"S3cur3", password_for_str(u"Ben"), true, false)
                .Build());

  // Pretend that the focus was lost or moved to an unfillable field.
  NavigateAndCommit(GURL("https://random.other-site.org/"));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kUnfillableElement);

  // Now, only the empty state message should be sent.
  EXPECT_EQ(controller()->GetSheetData(),
            PasswordAccessorySheetDataBuilder(
                passwords_empty_str(u"random.other-site.org"))
                .Build());
}

TEST_F(PasswordAccessoryControllerTest, OnAutomaticGenerationRequested) {
  CreateSheetController();
  MockPasswordGenerationController* mock_pwd_generation_controller =
      static_cast<MockPasswordGenerationController*>(
          PasswordGenerationController::GetIfExisting(web_contents()));
  EXPECT_CALL(
      *mock_pwd_generation_controller,
      OnGenerationRequested(
          autofill::password_generation::PasswordGenerationType::kAutomatic));
  controller()->OnGenerationRequested(
      autofill::password_generation::PasswordGenerationType::kAutomatic);
}

TEST_F(PasswordAccessoryControllerTest, AddsGenerationCommandWhenAvailable) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  ON_CALL(password_manager(), HaveFormManagersReceivedData)
      .WillByDefault(Return(true));
  ON_CALL(frame_helper(), IsGenerationEnabled).WillByDefault(Return(true));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(
              generate_password_str(),
              autofill::AccessoryAction::GENERATE_PASSWORD_MANUAL)
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       NoGenerationCommandIfGenerationIsNotEnabled) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  ON_CALL(password_manager(), HaveFormManagersReceivedData)
      .WillByDefault(Return(false));
  ON_CALL(frame_helper(), IsGenerationEnabled).WillByDefault(Return(true));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, NoGenerationCommandIfNoFormsReceived) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  ON_CALL(password_manager(), HaveFormManagersReceivedData)
      .WillByDefault(Return(true));
  ON_CALL(frame_helper(), IsGenerationEnabled).WillByDefault(Return(false));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, OnManualGenerationRequested) {
  CreateSheetController();
  MockPasswordGenerationController* mock_pwd_generation_controller =
      static_cast<MockPasswordGenerationController*>(
          PasswordGenerationController::GetIfExisting(web_contents()));
  EXPECT_CALL(mock_manual_filling_controller_, Hide());
  EXPECT_CALL(
      *mock_pwd_generation_controller,
      OnGenerationRequested(
          autofill::password_generation::PasswordGenerationType::kManual));
  controller()->OnOptionSelected(AccessoryAction::GENERATE_PASSWORD_MANUAL);
}

TEST_F(PasswordAccessoryControllerTest, AddsSaveToggleIfIsBlocklisted) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(true),
      url::Origin::Create(GURL(kExampleSite)));
  ON_CALL(*password_client(), IsSavingAndFillingEnabled(GURL(kExampleSite)))
      .WillByDefault(Return(true));

  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .SetOptionToggle(
              l10n_util::GetStringUTF16(IDS_PASSWORD_SAVING_STATUS_TOGGLE),
              false, autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       NoSaveToggleIfIsBlocklistedAndSavingDisabled) {
  CreateSheetController();

  // Simulate saving being disabled (e.g. being in incognito or having password
  // saving disabled from settings).
  ON_CALL(*password_client(), IsSavingAndFillingEnabled(GURL(kExampleSite)))
      .WillByDefault(Return(false));

  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(true),
      url::Origin::Create(GURL(kExampleSite)));

  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(false)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, AddsSaveToggleIfWasBlocklisted) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(true),
      url::Origin::Create(GURL(kExampleSite)));
  // Simulate unblocklisting.
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));
  ON_CALL(*password_client(), IsSavingAndFillingEnabled(GURL(kExampleSite)))
      .WillByDefault(Return(true));

  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .SetOptionToggle(
              l10n_util::GetStringUTF16(IDS_PASSWORD_SAVING_STATUS_TOGGLE),
              true, autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, AddsSaveToggleOnAnyFieldIfBlocked) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(true),
      url::Origin::Create(GURL(kExampleSite)));
  ON_CALL(*password_client(), IsSavingAndFillingEnabled(GURL(kExampleSite)))
      .WillByDefault(Return(true));

  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableNonSearchField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .SetOptionToggle(
              l10n_util::GetStringUTF16(IDS_PASSWORD_SAVING_STATUS_TOGGLE),
              false, autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, AppendsPlusAddressSuggestions) {
  CreateSheetController();

  MockAffiliatedPlusProfilesProvider provider;
  EXPECT_CALL(provider, AddObserver(controller()));
  controller()->RegisterPlusProfilesProvider(provider.GetWeakPtr());

  // Provide 1 plus address, which is not used as a username in any credential.
  // It should appear as a standalone suggestion in the password sheet.
  std::vector<PlusProfile> profiles{plus_addresses::test::CreatePlusProfile(
      /*plus_address=*/"example@gmail", /*is_confirmed=*/true)};
  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  EXPECT_CALL(provider, GetAffiliatedPlusProfiles)
      .WillRepeatedly(Return(base::span(profiles)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableNonSearchField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain),
                                        plus_address_title(kExampleDomain))
          .AddPlusAddressInfo("https://foo.com", u"example@gmail")
          .AppendFooterCommand(
              l10n_util::GetStringUTF16(
                  IDS_PLUS_ADDRESS_MANAGE_PLUS_ADDRESSES_LINK_ANDROID),
              AccessoryAction::MANAGE_PLUS_ADDRESS_FROM_PASSWORD_SHEET)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, PlusAddressUsedAsUsername) {
  CreateSheetController();

  std::vector<PasswordForm> matches = {
      CreateEntry("example@gmail", "S3cur3", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  MockAffiliatedPlusProfilesProvider provider;
  EXPECT_CALL(provider, AddObserver(controller()));
  controller()->RegisterPlusProfilesProvider(provider.GetWeakPtr());

  // Provide 1 plus address, which is used as a username in the saved
  // credential. It should not appear as a standalone suggestion in the password
  // sheet.
  std::vector<PlusProfile> profiles{plus_addresses::test::CreatePlusProfile(
      /*plus_address=*/"example@gmail", /*is_confirmed=*/true)};
  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  EXPECT_CALL(provider, GetAffiliatedPlusProfiles)
      .WillRepeatedly(Return(base::make_span(profiles)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableNonSearchField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilderEmptyTitle()
          .AddUserInfo(kExampleSite)
          .AppendField(
              u"example@gmail", u"example@gmail", u"example@gmail", "",
              ResourceMapper::MapToJavaDrawableId(IDR_AUTOFILL_PLUS_ADDRESS),
              false, true)
          .AppendField(u"S3cur3", password_for_str(u"example@gmail"), true,
                       false)
          .AppendFooterCommand(
              l10n_util::GetStringUTF16(
                  IDS_PLUS_ADDRESS_MANAGE_PLUS_ADDRESSES_LINK_ANDROID),
              AccessoryAction::MANAGE_PLUS_ADDRESS_FROM_PASSWORD_SHEET)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest, BothPlusAddressAndCredentialShown) {
  CreateSheetController();

  std::vector<PasswordForm> matches = {
      CreateEntry("foo.bar@gmail", "S3cur3", GURL(kExampleSite),
                  PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  MockAffiliatedPlusProfilesProvider provider;
  EXPECT_CALL(provider, AddObserver(controller()));
  controller()->RegisterPlusProfilesProvider(provider.GetWeakPtr());

  // Provide 1 plus address, which is used as a username in the saved
  // credential. It should not appear as a standalone suggestion in the password
  // sheet.
  std::vector<PlusProfile> profiles{plus_addresses::test::CreatePlusProfile(
      /*plus_address=*/"example@gmail", /*is_confirmed=*/true)};
  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  EXPECT_CALL(provider, GetAffiliatedPlusProfiles)
      .WillRepeatedly(Return(base::make_span(profiles)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableNonSearchField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_title(kExampleDomain),
                                        plus_address_title(kExampleDomain))
          .AddUserInfo(kExampleSite)
          .AddPlusAddressInfo("https://foo.com", u"example@gmail")
          .AppendField(u"foo.bar@gmail", u"foo.bar@gmail",
                       /*is_obfuscated=*/false, /*selectable=*/true)
          .AppendField(u"S3cur3", password_for_str(u"foo.bar@gmail"), true,
                       false)
          .AppendFooterCommand(
              l10n_util::GetStringUTF16(
                  IDS_PLUS_ADDRESS_MANAGE_PLUS_ADDRESSES_LINK_ANDROID),
              AccessoryAction::MANAGE_PLUS_ADDRESS_FROM_PASSWORD_SHEET)
          .Build());
}

// Verify that the action to open plus address creation bottom sheet is appended
// when the corresponding feature flag is enabled.
TEST_F(PasswordAccessoryControllerTest,
       PlusAddressFillingDisabled_NoPlusAddressesActions) {
  CreateSheetController();

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       NoPlusAddressesSaved_NoSelectPlusAddressAction) {
  CreateSheetController();

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  // Although the plus address filling is enabled, the user doesn't have any
  // saved plus addresses. The "Select plus address" should not be displayed.
  plus_address_service().set_is_plus_address_filling_enabled(true);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       PlusAddressFillingEnabled_AppendsSelectPlusAddressAction) {
  CreateSheetController();

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  plus_address_service().add_plus_profile(
      plus_addresses::test::CreatePlusProfile());
  plus_address_service().set_is_plus_address_filling_enabled(true);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .AppendFooterCommand(select_plus_address_str(),
                               autofill::AccessoryAction::
                                   SELECT_PLUS_ADDRESS_FROM_PASSWORD_SHEET)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       NoAffiliatedPlusAddresses_AppendsCreatePlusAddressAction) {
  CreateSheetController();

  MockAffiliatedPlusProfilesProvider provider;
  EXPECT_CALL(provider, AddObserver(controller()));
  controller()->RegisterPlusProfilesProvider(provider.GetWeakPtr());

  EXPECT_CALL(provider, GetAffiliatedPlusProfiles)
      .WillRepeatedly(Return(base::span<const PlusProfile, 0>()));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  // Plus address creation can't be supported while plus address filling is
  // disabled.
  plus_address_service().set_is_plus_address_filling_enabled(true);
  plus_address_service().set_should_offer_plus_address_creation(true);

  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain))
          .AppendFooterCommand(generate_plus_address_str(),
                               autofill::AccessoryAction::
                                   CREATE_PLUS_ADDRESS_FROM_PASSWORD_SHEET)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       HasAffiliatedPlusAddresses_NoCreatePlusAddressAction) {
  CreateSheetController();

  MockAffiliatedPlusProfilesProvider provider;
  EXPECT_CALL(provider, AddObserver);
  controller()->RegisterPlusProfilesProvider(provider.GetWeakPtr());

  std::vector<PlusProfile> profiles{plus_addresses::test::CreatePlusProfile()};
  EXPECT_CALL(provider, GetAffiliatedPlusProfiles)
      .WillRepeatedly(Return(base::span(profiles)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  // Plus address creation can't be supported while plus address filling is
  // disabled.
  plus_address_service().set_is_plus_address_filling_enabled(true);
  plus_address_service().set_should_offer_plus_address_creation(true);

  // Although the plus address creation is supported, the user has an affiliated
  // plus address for the current domain. The "Create plus address" action
  // should not be displayed.
  EXPECT_EQ(
      controller()->GetSheetData(),
      PasswordAccessorySheetDataBuilder(passwords_empty_str(kExampleDomain),
                                        plus_address_title(kExampleDomain))
          .AddPlusAddressInfo("https://foo.com", u"[email protected]")
          .AppendFooterCommand(
              l10n_util::GetStringUTF16(
                  IDS_PLUS_ADDRESS_MANAGE_PLUS_ADDRESSES_LINK_ANDROID),
              AccessoryAction::MANAGE_PLUS_ADDRESS_FROM_PASSWORD_SHEET)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       RecordsAccessoryImpressionsForBlocklisted) {
  CreateSheetController();

  base::HistogramTester histogram_tester;

  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(true),
      url::Origin::Create(GURL(kExampleSite)));
  ON_CALL(*password_client(), IsSavingAndFillingEnabled(GURL(kExampleSite)))
      .WillByDefault(Return(true));

  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  histogram_tester.ExpectUniqueSample(
      "KeyboardAccessory.DisabledSavingAccessoryImpressions", true, 1);
}

TEST_F(PasswordAccessoryControllerTest, NoAccessoryImpressionsIfUnblocklisted) {
  CreateSheetController();
  base::HistogramTester histogram_tester;

  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(true),
      url::Origin::Create(GURL(kExampleSite)));
  // Simulate unblocklisting.
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  ON_CALL(*password_client(), IsSavingAndFillingEnabled(GURL(kExampleSite)))
      .WillByDefault(Return(true));
  EXPECT_CALL(filling_source_observer_,
              Run(controller(), IsFillingSourceAvailable(true)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  histogram_tester.ExpectTotalCount(
      "KeyboardAccessory.DisabledSavingAccessoryImpressions", 0);
}

TEST_F(PasswordAccessoryControllerTest, SavePasswordsToggledUpdatesCache) {
  CreateSheetController();
  url::Origin example_origin = url::Origin::Create(GURL(kExampleSite));
  EXPECT_CALL(*password_client(), UpdateFormManagers);
  controller()->OnToggleChanged(
      autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS, true);
}

TEST_F(PasswordAccessoryControllerTest,
       SavePasswordsEnabledUpdatesAccountStore) {
  ON_CALL(*password_client()->GetPasswordFeatureManager(),
          IsOptedInForAccountStorage)
      .WillByDefault(Return(true));
  ON_CALL(*password_client()->GetPasswordFeatureManager(),
          GetDefaultPasswordStore)
      .WillByDefault(Return(PasswordForm::Store::kAccountStore));
  CreateSheetController();
  password_manager::PasswordFormDigest form_digest(
      PasswordForm::Scheme::kHtml, kExampleSignonRealm, GURL(kExampleSite));
  EXPECT_CALL(*mock_account_password_store_, Unblocklist(form_digest, _));
  controller()->OnToggleChanged(
      autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS, true);
}

TEST_F(PasswordAccessoryControllerTest,
       SavePasswordsEnabledUpdatesProfileStore) {
  CreateSheetController();
  password_manager::PasswordFormDigest form_digest(
      PasswordForm::Scheme::kHtml, kExampleSignonRealm, GURL(kExampleSite));
  EXPECT_CALL(*mock_profile_password_store_, Unblocklist(form_digest, _));
  controller()->OnToggleChanged(
      autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS, true);
}

TEST_F(PasswordAccessoryControllerTest,
       SavePasswordsDisabledUpdatesAccountStore) {
  ON_CALL(*password_client()->GetPasswordFeatureManager(),
          IsOptedInForAccountStorage)
      .WillByDefault(Return(true));
  ON_CALL(*password_client()->GetPasswordFeatureManager(),
          GetDefaultPasswordStore)
      .WillByDefault(Return(PasswordForm::Store::kAccountStore));
  CreateSheetController();
  PasswordForm expected_form;
  expected_form.blocked_by_user = true;
  expected_form.scheme = PasswordForm::Scheme::kHtml;
  expected_form.signon_realm = kExampleSignonRealm;
  expected_form.url = GURL(kExampleSite);
  expected_form.date_created = base::Time::Now();
  EXPECT_CALL(*mock_account_password_store_, AddLogin(Eq(expected_form), _));
  controller()->OnToggleChanged(
      autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS, false);
}

TEST_F(PasswordAccessoryControllerTest,
       SavePasswordsDisabledUpdatesProfileStore) {
  CreateSheetController();
  PasswordForm expected_form;
  expected_form.blocked_by_user = true;
  expected_form.scheme = PasswordForm::Scheme::kHtml;
  expected_form.signon_realm = kExampleSignonRealm;
  expected_form.url = GURL(kExampleSite);
  expected_form.date_created = base::Time::Now();
  EXPECT_CALL(*mock_profile_password_store_, AddLogin(Eq(expected_form), _));
  controller()->OnToggleChanged(
      autofill::AccessoryAction::TOGGLE_SAVE_PASSWORDS, false);
}

TEST_F(PasswordAccessoryControllerTest, FillsUsername) {
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"Ben")
                                           .SetSelectable(true)
                                           .Build();
  EXPECT_CALL(*driver(),
              FillIntoFocusedField(selected_field.is_obfuscated(),
                                   Eq(selected_field.display_text())));
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);
}

TEST_F(PasswordAccessoryControllerTest, FillsPasswordIfNoAuthAvailable) {
  // Auth is required to fill passwords in Android automotive.
  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
    GTEST_SKIP();
  }

  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"S3cur3")
                                           .SetIsObfuscated(true)
                                           .SetSelectable(true)
                                           .Build();

  auto mock_authenticator = std::make_unique<MockDeviceAuthenticator>();

  EXPECT_CALL(*password_client(), IsReauthBeforeFillingRequired)
      .WillOnce(Return(false));
  EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
      .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))));
  EXPECT_CALL(*driver(),
              FillIntoFocusedField(selected_field.is_obfuscated(),
                                   Eq(selected_field.display_text())));
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);
}

TEST_F(PasswordAccessoryControllerTest, FillsPasswordIfAuthSuccessful) {
  features_.Reset();
  features_.InitWithFeatures(
      {plus_addresses::features::kPlusAddressesEnabled,
       plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled,
       password_manager::features::kBiometricTouchToFill},
      {});
  CreateSheetController();

  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"S3cur3")
                                           .SetIsObfuscated(true)
                                           .SetSelectable(true)
                                           .Build();

  auto mock_authenticator = std::make_unique<MockDeviceAuthenticator>();

  ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
      .WillByDefault(Return(true));
  EXPECT_CALL(*mock_authenticator, AuthenticateWithMessage)
      .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/true));

  EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
      .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))))
      .RetiresOnSaturation();

  EXPECT_CALL(*driver(),
              FillIntoFocusedField(selected_field.is_obfuscated(),
                                   Eq(selected_field.display_text())));
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);
}

TEST_F(PasswordAccessoryControllerTest, DoesntFillPasswordIfAuthFails) {
  features_.Reset();
  features_.InitWithFeatures(
      {plus_addresses::features::kPlusAddressesEnabled,
       plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled,
       password_manager::features::kBiometricTouchToFill},
      {});
  CreateSheetController();

  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"S3cur3")
                                           .SetIsObfuscated(true)
                                           .SetSelectable(true)
                                           .Build();

  auto mock_authenticator = std::make_unique<MockDeviceAuthenticator>();

  ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
      .WillByDefault(Return(true));
  EXPECT_CALL(*mock_authenticator, AuthenticateWithMessage)
      .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/false));

  EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
      .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))))
      .RetiresOnSaturation();

  EXPECT_CALL(*driver(),
              FillIntoFocusedField(selected_field.is_obfuscated(),
                                   Eq(selected_field.display_text())))
      .Times(0);
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);
}

TEST_F(PasswordAccessoryControllerTest, CancelsOngoingAuthIfDestroyed) {
  features_.Reset();
  features_.InitWithFeatures(
      {plus_addresses::features::kPlusAddressesEnabled,
       plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled,
       password_manager::features::kBiometricTouchToFill},
      {});
  CreateSheetController();

  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"S3cur3")
                                           .SetIsObfuscated(true)
                                           .SetSelectable(true)
                                           .Build();

  auto mock_authenticator = std::make_unique<MockDeviceAuthenticator>();
  auto* mock_authenticator_ptr = mock_authenticator.get();

  ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
      .WillByDefault(Return(true));
  EXPECT_CALL(*mock_authenticator_ptr, AuthenticateWithMessage);

  EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
      .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))))
      .RetiresOnSaturation();

  EXPECT_CALL(*driver(),
              FillIntoFocusedField(selected_field.is_obfuscated(),
                                   Eq(selected_field.display_text())))
      .Times(0);
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);

  EXPECT_CALL(*mock_authenticator_ptr, Cancel());
}

TEST_F(PasswordAccessoryControllerTest, ShowCredManReentry) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  CreateSheetController();
  cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, base::RepeatingCallback<void(bool)>());

  EXPECT_CALL(mock_manual_filling_controller_,
              OnAccessoryActionAvailabilityChanged(
                  ShouldShowAction(true),
                  autofill::AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY));

  controller()->UpdateCredManReentryUi(
      autofill::mojom::FocusedFieldType::kFillableUsernameField);
}

TEST_F(PasswordAccessoryControllerTest, HideCredManReentryWithoutResult) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  CreateSheetController();
  cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/false, base::RepeatingCallback<void(bool)>());

  EXPECT_CALL(mock_manual_filling_controller_,
              OnAccessoryActionAvailabilityChanged(
                  ShouldShowAction(false),
                  autofill::AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY));

  controller()->UpdateCredManReentryUi(
      autofill::mojom::FocusedFieldType::kFillableUsernameField);
}

TEST_F(PasswordAccessoryControllerTest, HideCredManReentryOnNonSignInField) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  CreateSheetController();
  cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, base::RepeatingCallback<void(bool)>());

  EXPECT_CALL(mock_manual_filling_controller_,
              OnAccessoryActionAvailabilityChanged(
                  ShouldShowAction(false),
                  autofill::AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY));

  controller()->UpdateCredManReentryUi(
      autofill::mojom::FocusedFieldType::kFillableNonSearchField);
}

TEST_F(PasswordAccessoryControllerTest, SuppressCredManReentryWithoutFeature) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::DISABLED);
  CreateSheetController();

  EXPECT_CALL(mock_manual_filling_controller_,
              OnAccessoryActionAvailabilityChanged)
      .Times(0);

  controller()->UpdateCredManReentryUi(
      autofill::mojom::FocusedFieldType::kFillableUsernameField);
}

TEST_F(PasswordAccessoryControllerTest, OnCredManConditionalUiRequested) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  CreateSheetController();
  base::MockCallback<base::RepeatingCallback<void(bool)>> cred_man_callback;
  cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, cred_man_callback.Get());

  EXPECT_CALL(cred_man_callback, Run);

  controller()->OnOptionSelected(
      autofill::AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY);
}

TEST_F(PasswordAccessoryControllerTest, ShowAndSelectCredManReentryOption) {
  base::MockCallback<base::RepeatingCallback<void(bool)>> cred_man_callback;
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  CreateSheetController();
  cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, cred_man_callback.Get());
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);
  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(
              select_passkey_str(),
              autofill::AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY)
          .AppendFooterCommand(manage_passwords_and_passkeys_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());

  EXPECT_CALL(cred_man_callback, Run);
  controller()->OnOptionSelected(
      autofill::AccessoryAction::CREDMAN_CONDITIONAL_UI_REENTRY);
}

// Verify that when WebAuthnCredentialsDelegate::IsAndroidHybridAvailable
// returns true, the hybrid passkey option shows on the sheet, and selecting
// it triggers hybrid passkey sign-in invocation.
TEST_F(PasswordAccessoryControllerTest, ShowAndSelectHybridPasskeyOption) {
  ON_CALL(*webauthn_credentials_delegate(), IsAndroidHybridAvailable)
      .WillByDefault(Return(true));
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);
  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(cross_device_passkeys_str(),
                               autofill::AccessoryAction::CROSS_DEVICE_PASSKEY)
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());

  EXPECT_CALL(*webauthn_credentials_delegate(), ShowAndroidHybridSignIn);

  controller()->OnOptionSelected(
      autofill::AccessoryAction::CROSS_DEVICE_PASSKEY);
}

// Verify that the plus address creation bottom sheet is opened when the
// corresponding action is triggered.
TEST_F(PasswordAccessoryControllerTest,
       TriggersPlusAddressCreationBottomSheet) {
  CreateSheetController();
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  const std::string plus_address = "[email protected]";
  EXPECT_CALL(autofill_client(), OfferPlusAddressCreation)
      .WillOnce([&plus_address](const url::Origin&,
                                autofill::PlusAddressCallback callback) {
        std::move(callback).Run(plus_address);
      });
  EXPECT_CALL(*driver(), FillIntoFocusedField(/*is_password=*/false,
                                              base::UTF8ToUTF16(plus_address)));
  // Manual filling sheet is expected to be hidden when the plus address
  // creation bottom sheet is opened.
  EXPECT_CALL(mock_manual_filling_controller_, Hide());

  controller()->OnOptionSelected(
      autofill::AccessoryAction::CREATE_PLUS_ADDRESS_FROM_PASSWORD_SHEET);
}

// Verify that when WebAuthnCredentialsDelegate::SelectPasskey can be invoked
// with a passkey shown in the fallback sheet.
TEST_F(PasswordAccessoryControllerTest, ShowAndSelectPasskey) {
  const PasskeyCredential kTestPasskey(
      PasskeyCredential::Source::kAndroidPhone,
      PasskeyCredential::RpId("rpid.com"),
      PasskeyCredential::CredentialId({21, 22, 23, 24}),
      PasskeyCredential::UserId({81, 28, 83, 84}),
      PasskeyCredential::Username("[email protected]"),
      PasskeyCredential::DisplayName("someone"));
  const std::optional<std::vector<PasskeyCredential>> kTestPasskeys(
      {kTestPasskey});
  ON_CALL(*webauthn_credentials_delegate(), GetPasskeys)
      .WillByDefault(ReturnRef(kTestPasskeys));
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  /*user_info_title=*/std::u16string(),
                                  /*plus_address_title=*/std::u16string())
          .AddPasskeySection(kTestPasskey.display_name(),
                             kTestPasskey.credential_id())
          .AppendFooterCommand(manage_passwords_and_passkeys_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());

  EXPECT_CALL(
      *webauthn_credentials_delegate(),
      SelectPasskey(Eq(base::Base64Encode(kTestPasskey.credential_id())), _));
  controller()->OnPasskeySelected(kTestPasskey.credential_id());
}

// Verify that when WebAuthnCredentialsDelegate::IsAndroidHybridAvailable
// returns false, the hybrid passkey option is not shown on the sheet.
TEST_F(PasswordAccessoryControllerTest,
       HybridPasskeyOptionNotShownWhenUnavailable) {
  CreateSheetController();
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      {}, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

TEST_F(PasswordAccessoryControllerTest,
       ShowMigrationSheetOnFillingCredentialIfEnabled) {
  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
    auto mock_authenticator = std::make_unique<MockDeviceAuthenticator>();
    ON_CALL(*mock_authenticator, AuthenticateWithMessage)
        .WillByDefault(
            base::test::RunOnceCallbackRepeatedly<1>(/*auth_succeeded=*/true));
    EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
        .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))))
        .RetiresOnSaturation();
  }

  features_.Reset();
  features_.InitWithFeatures(
      {plus_addresses::features::kPlusAddressesEnabled,
       plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled,
       password_manager::features::
           kUnifiedPasswordManagerLocalPasswordsMigrationWarning},
      {});
  CreateSheetController();

  // Set up credentials for filling.
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);
  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"S3cur3")
                                           .SetIsObfuscated(true)
                                           .SetSelectable(true)
                                           .Build();
  EXPECT_CALL(
      show_migration_warning_callback_,
      Run(_, _,
          password_manager::metrics_util::PasswordMigrationWarningTriggers::
              kKeyboardAcessorySheet));
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);
}

TEST_F(PasswordAccessoryControllerTest, DontShowMigrationSheetlIfDisabled) {
  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
    auto mock_authenticator = std::make_unique<MockDeviceAuthenticator>();
    ON_CALL(*mock_authenticator, AuthenticateWithMessage)
        .WillByDefault(
            base::test::RunOnceCallbackRepeatedly<1>(/*auth_succeeded=*/true));
    EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
        .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))))
        .RetiresOnSaturation();
  }

  features_.Reset();
  features_.InitWithFeatures(
      {plus_addresses::features::kPlusAddressesEnabled,
       plus_addresses::features::kPlusAddressAndroidManualFallbackEnabled},
      {password_manager::features::
           kUnifiedPasswordManagerLocalPasswordsMigrationWarning});
  // Set up credentials for filling.
  CreateSheetController();
  std::vector<PasswordForm> matches = {CreateEntry(
      "Ben", "S3cur3", GURL(kExampleSite), PasswordForm::MatchType::kExact)};
  cache()->SaveCredentialsAndBlocklistedForOrigin(
      matches, CredentialCache::IsOriginBlocklisted(false),
      url::Origin::Create(GURL(kExampleSite)));

  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  AccessorySheetField selected_field = AccessorySheetField::Builder()
                                           .SetDisplayText(u"S3cur3")
                                           .SetIsObfuscated(true)
                                           .SetSelectable(true)
                                           .Build();
  EXPECT_CALL(show_migration_warning_callback_, Run).Times(0);
  controller()->OnFillingTriggered(autofill::FieldGlobalId(), selected_field);
}

class PasswordAccessoryControllerWithTestStoreTest
    : public PasswordAccessoryControllerTest,
      public testing::WithParamInterface<bool> {
 public:
  TestPasswordStore& test_account_store() { return *test_account_store_; }
  TestPasswordStore& test_profile_store() { return *test_profile_store_; }

  void SetUp() override {
    PasswordAccessoryControllerTest::SetUp();
    test_account_store_->Init(/*prefs=*/nullptr,
                              /*affiliated_match_helper=*/nullptr);
    test_profile_store_->Init(/*prefs=*/nullptr,
                              /*affiliated_match_helper=*/nullptr);
  }

  void TearDown() override {
    test_account_store_->ShutdownOnUIThread();
    test_profile_store_->ShutdownOnUIThread();
    task_environment()->RunUntilIdle();
    PasswordAccessoryControllerTest::TearDown();
  }

 protected:
  PasswordStoreInterface* CreateInternalAccountPasswordStore() override {
    test_account_store_ = CreateAndUseTestAccountPasswordStore(profile());
    return test_account_store_.get();
  }

  PasswordStoreInterface* CreateInternalProfilePasswordStore() override {
    test_profile_store_ = CreateAndUseTestPasswordStore(profile());
    return test_profile_store_.get();
  }

 private:
  scoped_refptr<TestPasswordStore> test_account_store_;
  scoped_refptr<TestPasswordStore> test_profile_store_;
};

TEST_P(PasswordAccessoryControllerWithTestStoreTest,
       AddsShowOtherPasswordsForPasswordField) {
  if (GetParam()) {
    test_account_store().AddLogin(MakeSavedPassword());
  } else {
    test_profile_store().AddLogin(MakeSavedPassword());
  }
  task_environment()->RunUntilIdle();
  CreateSheetController();

  // Trigger suggestion refresh(es) and store the latest refresh only.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  task_environment()->RunUntilIdle();  // Wait for store to trigger update.

  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(show_other_passwords_str(),
                               autofill::AccessoryAction::USE_OTHER_PASSWORD)
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

TEST_P(PasswordAccessoryControllerWithTestStoreTest,
       AddsShowOtherPasswordsForUsernameField) {
  if (GetParam()) {
    test_account_store().AddLogin(MakeSavedPassword());
  } else {
    test_profile_store().AddLogin(MakeSavedPassword());
  }
  task_environment()->RunUntilIdle();
  CreateSheetController();

  // Trigger suggestion refresh(es) and store the latest refresh only.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillableUsernameField);

  task_environment()->RunUntilIdle();  // Wait for store to trigger update.

  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(show_other_passwords_str(),
                               autofill::AccessoryAction::USE_OTHER_PASSWORD)
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

TEST_P(PasswordAccessoryControllerWithTestStoreTest,
       AddsShowOtherPasswordForOnlyCryptographicSchemeSites) {
  if (GetParam()) {
    test_account_store().AddLogin(MakeSavedPassword());
  } else {
    test_profile_store().AddLogin(MakeSavedPassword());
  }
  task_environment()->RunUntilIdle();
  CreateSheetController();
  // `Setup` method sets the URL to https but http is required for this method.
  NavigateAndCommit(GURL(kExampleHttpSite));
  FocusWebContentsOnMainFrame();

  // Trigger suggestion refresh(es).
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);
  task_environment()->RunUntilIdle();  // Wait for store to trigger update.
  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleHttpSite16),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

TEST_P(PasswordAccessoryControllerWithTestStoreTest,
       HideShowOtherPasswordForLowSecurityLevelSites) {
  if (GetParam()) {
    test_account_store().AddLogin(MakeSavedPassword());
  } else {
    test_profile_store().AddLogin(MakeSavedPassword());
  }
  task_environment()->RunUntilIdle();
  CreateSheetController(security_state::WARNING);

  // Trigger suggestion refresh(es) and store the latest refresh only.
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  task_environment()->RunUntilIdle();  // Wait for store to trigger update.
  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

TEST_P(PasswordAccessoryControllerWithTestStoreTest,
       HidesUseOtherPasswordsIfPasswordStoreIsEmpty) {
  CreateSheetController();

  // Trigger suggestion refresh(es).
  controller()->RefreshSuggestionsForField(
      FocusedFieldType::kFillablePasswordField);

  task_environment()->RunUntilIdle();  // Wait for store to trigger update.
  EXPECT_EQ(
      controller()->GetSheetData(),
      AccessorySheetData::Builder(AccessoryTabType::PASSWORDS,
                                  passwords_empty_str(kExampleDomain),
                                  /*plus_address_title=*/std::u16string())
          .AppendFooterCommand(manage_passwords_str(),
                               autofill::AccessoryAction::MANAGE_PASSWORDS)
          .Build());
}

INSTANTIATE_TEST_SUITE_P(,
                         PasswordAccessoryControllerWithTestStoreTest,
                         ::testing::Bool());