chromium/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc

// Copyright 2020 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/password_manager/android/save_update_password_message_delegate.h"

#include <jni.h>

#include <algorithm>
#include <memory>

#include "base/android/jni_android.h"
#include "base/functional/callback_forward.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/with_feature_override.h"
#include "chrome/browser/android/android_theme_resources.h"
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/password_manager/android/local_passwords_migration_warning_util.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/browser_ui/device_lock/android/device_lock_bridge.h"
#include "components/messages/android/mock_message_dispatcher_bridge.h"
#include "components/password_manager/core/browser/mock_password_form_manager_for_ui.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_form_metrics_recorder.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/stub_password_manager_client.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/native_widget_types.h"
#include "url/gurl.h"

using base::MockCallback;
using base::RepeatingCallback;
using base::test::FeatureRef;
using base::test::FeatureRefAndParams;
using password_manager::MockPasswordFormManagerForUI;
using password_manager::PasswordForm;
using password_manager::PasswordFormManagerForUI;
using password_manager::PasswordFormMetricsRecorder;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Return;
using ::testing::ReturnRef;

namespace {
constexpr char kDefaultUrl[] = "http://example.com";
constexpr char16_t kUsername[] = u"username";
constexpr char16_t kUsername2[] = u"username2";
constexpr char16_t kPassword[] = u"password";
constexpr char kAccountEmail[] = "[email protected]";
constexpr char16_t kAccountEmail16[] = u"[email protected]";
constexpr char kAccountFullName[] = "First Last";
constexpr char16_t kAccountFullName16[] = u"First Last";
constexpr char kSaveUIDismissalReasonHistogramName[] =
    "PasswordManager.SaveUIDismissalReason";
constexpr char kUpdateUIDismissalReasonHistogramName[] =
    "PasswordManager.UpdateUIDismissalReason";

}  // namespace

namespace {
class TestDeviceLockBridge : public DeviceLockBridge {
 public:
  TestDeviceLockBridge() = default;

  TestDeviceLockBridge(const TestDeviceLockBridge&) = delete;
  TestDeviceLockBridge& operator=(const TestDeviceLockBridge&) = delete;

  bool ShouldShowDeviceLockUi() override { return should_show_device_lock_ui_; }

  bool RequiresDeviceLock() override { return requires_device_lock_; }

  void LaunchDeviceLockUiIfNeededBeforeRunningCallback(
      ui::WindowAndroid* window_android,
      DeviceLockRequirementMetCallback callback) override {
    callback_ = std::move(callback);
    device_lock_ui_shown_count_++;
  }

  void SimulateDeviceLockComplete(bool is_device_lock_set) {
    std::move(callback_).Run(is_device_lock_set);
  }

  void SetShouldShowDeviceLockUi(bool should_show_device_lock_ui) {
    requires_device_lock_ = should_show_device_lock_ui;
    should_show_device_lock_ui_ = should_show_device_lock_ui;
  }

  int device_lock_ui_shown_count() { return device_lock_ui_shown_count_; }

 private:
  bool requires_device_lock_ = false;
  bool should_show_device_lock_ui_ = false;
  int device_lock_ui_shown_count_ = 0;
  DeviceLockRequirementMetCallback callback_;
};

}  // namespace

class MockPasswordEditDialog : public PasswordEditDialog {
 public:
  MOCK_METHOD(void,
              ShowPasswordEditDialog,
              (const std::vector<std::u16string>& usernames,
               const std::u16string& username,
               const std::u16string& password,
               const std::optional<std::string>& account_email),
              (override));
  MOCK_METHOD(void, Dismiss, (), (override));
};

class MockPasswordManagerClient
    : public password_manager::StubPasswordManagerClient {
 public:
  MOCK_METHOD(void,
              ShowPasswordManagerErrorMessage,
              (password_manager::ErrorMessageFlowType,
               password_manager::PasswordStoreBackendErrorType),
              (override));
};

class SaveUpdatePasswordMessageDelegateTest
    : public ChromeRenderViewHostTestHarness {
 public:
  SaveUpdatePasswordMessageDelegateTest();

 protected:
  void SetUp() override;
  void TearDown() override;

  std::unique_ptr<MockPasswordFormManagerForUI> CreateFormManager(
      const GURL& password_form_url,
      const std::vector<PasswordForm>& best_matches);
  void RecordPasswordSaved();
  void SetPendingCredentials(std::u16string username,
                             std::u16string password,
                             bool is_account_store = false);
  static PasswordForm CreatePasswordForm(std::u16string username,
                                         std::u16string password,
                                         bool is_account_store = false);

  void EnqueueMessage(std::unique_ptr<PasswordFormManagerForUI> form_to_save,
                      bool user_signed_in,
                      bool update_password,
                      std::optional<AccountInfo> account_info = {});
  void TriggerActionClick();
  void TriggerActionClick(messages::DismissReason dismiss_reason);
  void TriggerPasswordEditDialog(bool update_password);
  void TriggerNeverSaveMenuItem();

  void ExpectDismissMessageCall();
  void DismissMessage(messages::DismissReason dismiss_reason);
  void DestroyDelegate();

  TestDeviceLockBridge* test_bridge();
  bool is_password_saved();

  messages::MessageWrapper* GetMessageWrapper();
  MockPasswordManagerClient* GetClient();

  // Password edit dialog factory function that is passed to
  // SaveUpdatePasswordMessageDelegate. Passes the dialog prepared by
  // PreparePasswordEditDialog. Captures accept and dismiss callbacks.
  std::unique_ptr<PasswordEditDialog> CreatePasswordEditDialog(
      content::WebContents* web_contents,
      PasswordEditDialogBridgeDelegate* pasword_edit_dialog_bridge_delegate);

  // Creates a mock of PasswordEditDialog that will be passed to
  // SaveUpdatePasswordMessageDelegate through CreatePasswordEditDialog factory.
  // Returns non-owning pointer to the mock for test to configure mock
  // expectations.
  MockPasswordEditDialog* PreparePasswordEditDialog();

  base::MockCallback<RepeatingCallback<
      void(gfx::NativeWindow,
           Profile*,
           password_manager::metrics_util::PasswordMigrationWarningTriggers)>>&
  GetMigrationWarningCallback();

  void TriggerDialogAcceptedCallback(const std::u16string& username,
                                     const std::u16string& password);
  void TriggerDialogDismissedCallback(bool dialog_accepted);

  // TODO(crbug.com/40900579): Remove this helper as it makes tests hard to
  // read.
  std::u16string GetExpectedUPMMessageDescription(
      bool is_update,
      bool is_signed_in,
      const std::u16string& account_email);
  void CommitPasswordFormMetrics();
  void VerifyUkmMetrics(const ukm::TestUkmRecorder& ukm_recorder,
                        PasswordFormMetricsRecorder::BubbleDismissalReason
                            expected_dismissal_reason);
  void EnableUseUPMLocalAndSeparateStores();

  messages::MockMessageDispatcherBridge* message_dispatcher_bridge() {
    return &message_dispatcher_bridge_;
  }

  std::vector<PasswordForm> empty_best_matches() { return {}; }

  std::vector<PasswordForm> two_forms_best_matches() {
    return {CreatePasswordForm(kUsername, kPassword),
            CreatePasswordForm(kUsername2, kPassword)};
  }

  PasswordEditDialogBridgeDelegate* get_password_edit_dialog_bridge_delegate() {
    return delegate_.get();
  }

  void FastForward() { task_environment()->FastForwardBy(base::Seconds(1)); }

 private:
  PasswordForm pending_credentials_;
  GURL password_form_url_;
  scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder_;
  ukm::SourceId ukm_source_id_;
  messages::MockMessageDispatcherBridge message_dispatcher_bridge_;
  std::unique_ptr<MockPasswordEditDialog> mock_password_edit_dialog_;
  base::MockCallback<RepeatingCallback<void(
      gfx::NativeWindow,
      Profile*,
      password_manager::metrics_util::PasswordMigrationWarningTriggers)>>
      mock_password_migration_warning_callback_;
  raw_ptr<TestDeviceLockBridge> test_bridge_;
  std::unique_ptr<SaveUpdatePasswordMessageDelegate> delegate_;
  bool is_password_saved_ = false;
  MockPasswordManagerClient password_manager_client_;
};

SaveUpdatePasswordMessageDelegateTest::SaveUpdatePasswordMessageDelegateTest()
    : ChromeRenderViewHostTestHarness(
          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

void SaveUpdatePasswordMessageDelegateTest::SetUp() {
  ChromeRenderViewHostTestHarness::SetUp();
  autofill::ChromeAutofillClient::CreateForWebContents(web_contents());
  ukm_source_id_ = ukm::UkmRecorder::GetNewSourceID();
  metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>(
      true /*is_main_frame_secure*/, ukm_source_id_, nullptr /*pref_service*/);
  NavigateAndCommit(GURL(kDefaultUrl));

  auto bridge = std::make_unique<TestDeviceLockBridge>();
  test_bridge_ = bridge.get();
  delegate_ = std::make_unique<SaveUpdatePasswordMessageDelegate>(
      base::PassKey<class SaveUpdatePasswordMessageDelegateTest>(),
      base::BindRepeating(
          &SaveUpdatePasswordMessageDelegateTest::CreatePasswordEditDialog,
          base::Unretained(this)),
      mock_password_migration_warning_callback_.Get(), std::move(bridge));

  messages::MessageDispatcherBridge::SetInstanceForTesting(
      &message_dispatcher_bridge_);

  ON_CALL(*(password_manager_client_.GetPasswordFeatureManager()),
          ShouldUpdateGmsCore)
      .WillByDefault(Return(false));
}

void SaveUpdatePasswordMessageDelegateTest::TearDown() {
  messages::MessageDispatcherBridge::SetInstanceForTesting(nullptr);
  ChromeRenderViewHostTestHarness::TearDown();
}

std::unique_ptr<MockPasswordFormManagerForUI>
SaveUpdatePasswordMessageDelegateTest::CreateFormManager(
    const GURL& password_form_url,
    const std::vector<PasswordForm>& best_matches) {
  password_form_url_ = password_form_url;
  auto form_manager =
      std::make_unique<testing::NiceMock<MockPasswordFormManagerForUI>>();
  ON_CALL(*form_manager, GetPendingCredentials())
      .WillByDefault(ReturnRef(pending_credentials_));
  ON_CALL(*form_manager, GetCredentialSource())
      .WillByDefault(Return(password_manager::metrics_util::
                                CredentialSourceType::kPasswordManager));
  ON_CALL(*form_manager, GetURL()).WillByDefault(ReturnRef(password_form_url_));
  ON_CALL(*form_manager, GetBestMatches()).WillByDefault(Return(best_matches));
  ON_CALL(*form_manager, GetFederatedMatches())
      .WillByDefault(Return(base::span<const PasswordForm>()));
  ON_CALL(*form_manager, GetMetricsRecorder())
      .WillByDefault(Return(metrics_recorder_.get()));
  ON_CALL(*form_manager, Save())
      .WillByDefault(testing::Invoke(
          this, &SaveUpdatePasswordMessageDelegateTest::RecordPasswordSaved));
  ON_CALL(*form_manager, GetPasswordStoreForSaving(_))
      .WillByDefault([](const PasswordForm& form) -> PasswordForm::Store {
        return form.IsUsingAccountStore() ? PasswordForm::Store::kAccountStore
                                          : PasswordForm::Store::kProfileStore;
      });
  return form_manager;
}

void SaveUpdatePasswordMessageDelegateTest::RecordPasswordSaved() {
  is_password_saved_ = true;
}

void SaveUpdatePasswordMessageDelegateTest::SetPendingCredentials(
    std::u16string username,
    std::u16string password,
    bool is_account_store) {
  pending_credentials_.username_value = std::move(username);
  pending_credentials_.password_value = std::move(password);
  pending_credentials_.in_store =
      is_account_store ? password_manager::PasswordForm::Store::kAccountStore
                       : password_manager::PasswordForm::Store::kProfileStore;
}

// static
PasswordForm SaveUpdatePasswordMessageDelegateTest::CreatePasswordForm(
    std::u16string username,
    std::u16string password,
    bool is_account_store) {
  PasswordForm password_form;
  password_form.username_value = std::move(username);
  password_form.password_value = std::move(password);
  password_form.match_type = PasswordForm::MatchType::kExact;
  password_form.in_store =
      is_account_store ? password_manager::PasswordForm::Store::kAccountStore
                       : password_manager::PasswordForm::Store::kProfileStore;
  return password_form;
}

void SaveUpdatePasswordMessageDelegateTest::EnqueueMessage(
    std::unique_ptr<PasswordFormManagerForUI> form_to_save,
    bool user_signed_in,
    bool update_password,
    std::optional<AccountInfo> account_info) {
  if (user_signed_in && !account_info) {
    account_info = AccountInfo();
    account_info.value().email = kAccountEmail;
  }
  EXPECT_CALL(message_dispatcher_bridge_, EnqueueMessage);
  delegate_->DisplaySaveUpdatePasswordPromptInternal(
      web_contents(), std::move(form_to_save), account_info, update_password,
      &password_manager_client_);
}

void SaveUpdatePasswordMessageDelegateTest::TriggerActionClick() {
  TriggerActionClick(messages::DismissReason::PRIMARY_ACTION);
}

void SaveUpdatePasswordMessageDelegateTest::TriggerActionClick(
    messages::DismissReason dismiss_reason) {
  GetMessageWrapper()->HandleActionClick(base::android::AttachCurrentThread());
  // Simulate call from Java to dismiss message on primary button click.
  DismissMessage(dismiss_reason);
}

void SaveUpdatePasswordMessageDelegateTest::TriggerPasswordEditDialog(
    bool update_password) {
  if (update_password) {
    GetMessageWrapper()->HandleSecondaryActionClick(
        base::android::AttachCurrentThread());
  } else {
    GetMessageWrapper()->HandleSecondaryMenuItemSelected(
        base::android::AttachCurrentThread(),
        static_cast<int>(SaveUpdatePasswordMessageDelegate::
                             SavePasswordDialogMenuItem::kEditPassword));
  }
  // Simulate call from Java to dismiss message on secondary button click.
  DismissMessage(messages::DismissReason::SECONDARY_ACTION);
}

void SaveUpdatePasswordMessageDelegateTest::TriggerNeverSaveMenuItem() {
  GetMessageWrapper()->HandleSecondaryMenuItemSelected(
      base::android::AttachCurrentThread(),
      static_cast<int>(SaveUpdatePasswordMessageDelegate::
                           SavePasswordDialogMenuItem::kNeverSave));
  // Simulate call from Java to dismiss message on secondary button click.
  DismissMessage(messages::DismissReason::SECONDARY_ACTION);
}

void SaveUpdatePasswordMessageDelegateTest::ExpectDismissMessageCall() {
  EXPECT_CALL(message_dispatcher_bridge_, DismissMessage)
      .WillOnce([](messages::MessageWrapper* message,
                   messages::DismissReason dismiss_reason) {
        message->HandleDismissCallback(base::android::AttachCurrentThread(),
                                       static_cast<int>(dismiss_reason));
      });
}

void SaveUpdatePasswordMessageDelegateTest::DismissMessage(
    messages::DismissReason dismiss_reason) {
  ExpectDismissMessageCall();
  delegate_->DismissSaveUpdatePasswordMessage(dismiss_reason);
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

void SaveUpdatePasswordMessageDelegateTest::DestroyDelegate() {
  delegate_.reset();
}

TestDeviceLockBridge* SaveUpdatePasswordMessageDelegateTest::test_bridge() {
  return test_bridge_;
}

bool SaveUpdatePasswordMessageDelegateTest::is_password_saved() {
  return is_password_saved_;
}

messages::MessageWrapper*
SaveUpdatePasswordMessageDelegateTest::GetMessageWrapper() {
  return delegate_->message_.get();
}

MockPasswordManagerClient* SaveUpdatePasswordMessageDelegateTest::GetClient() {
  return &password_manager_client_;
}

std::unique_ptr<PasswordEditDialog>
SaveUpdatePasswordMessageDelegateTest::CreatePasswordEditDialog(
    content::WebContents* web_contents,
    PasswordEditDialogBridgeDelegate* pasword_edit_dialog_bridge_delegate) {
  return std::move(mock_password_edit_dialog_);
}

MockPasswordEditDialog*
SaveUpdatePasswordMessageDelegateTest::PreparePasswordEditDialog() {
  mock_password_edit_dialog_ = std::make_unique<MockPasswordEditDialog>();
  return mock_password_edit_dialog_.get();
}

base::MockCallback<RepeatingCallback<
    void(gfx::NativeWindow,
         Profile*,
         password_manager::metrics_util::PasswordMigrationWarningTriggers)>>&
SaveUpdatePasswordMessageDelegateTest::GetMigrationWarningCallback() {
  return mock_password_migration_warning_callback_;
}

void SaveUpdatePasswordMessageDelegateTest::TriggerDialogAcceptedCallback(
    const std::u16string& username,
    const std::u16string& password) {
  // std::move(dialog_accepted_callback_).Run(username, password);
  delegate_->HandleSavePasswordFromDialog(username, password);
}

void SaveUpdatePasswordMessageDelegateTest::TriggerDialogDismissedCallback(
    bool dialog_accepted) {
  // std::move(dialog_dismissed_callback_).Run(dialog_accepted);

  delegate_->HandleDialogDismissed(dialog_accepted);
}

std::u16string
SaveUpdatePasswordMessageDelegateTest::GetExpectedUPMMessageDescription(
    bool is_update,
    bool is_signed_in,
    const std::u16string& account_email) {
  if (is_signed_in) {
    return l10n_util::GetStringFUTF16(
        is_update
            ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION
            : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
        account_email);
  }
  return l10n_util::GetStringUTF16(
      is_update
          ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION
          : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION);
}

void SaveUpdatePasswordMessageDelegateTest::CommitPasswordFormMetrics() {
  // PasswordFormMetricsRecorder::dtor commits accumulated metrics.
  metrics_recorder_.reset();
}

void SaveUpdatePasswordMessageDelegateTest::VerifyUkmMetrics(
    const ukm::TestUkmRecorder& ukm_recorder,
    PasswordFormMetricsRecorder::BubbleDismissalReason
        expected_dismissal_reason) {
  const auto& entries =
      ukm_recorder.GetEntriesByName(ukm::builders::PasswordForm::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const ukm::mojom::UkmEntry* entry : entries) {
    EXPECT_EQ(ukm_source_id_, entry->source_id);
    ukm_recorder.ExpectEntryMetric(
        entry, ukm::builders::PasswordForm::kSaving_Prompt_ShownName, 1);
    ukm_recorder.ExpectEntryMetric(
        entry, ukm::builders::PasswordForm::kSaving_Prompt_TriggerName,
        static_cast<int64_t>(PasswordFormMetricsRecorder::BubbleTrigger::
                                 kPasswordManagerSuggestionAutomatic));
    ukm_recorder.ExpectEntryMetric(
        entry, ukm::builders::PasswordForm::kSaving_Prompt_InteractionName,
        static_cast<int64_t>(expected_dismissal_reason));
  }
}

void SaveUpdatePasswordMessageDelegateTest::
    EnableUseUPMLocalAndSeparateStores() {
  profile()->GetPrefs()->SetInteger(
      password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
      static_cast<int>(
          password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOn));
}

// Tests that secondary menu icon is set for the save password message
TEST_F(SaveUpdatePasswordMessageDelegateTest, CogButton_SavePassword) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);

  EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SETTINGS),
            GetMessageWrapper()->GetSecondaryIconResourceId());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that secondary menu icon is set for the update password message
// in case when user has only single credential stored for the web site
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       CogButton_SingleCredUpdatePassword) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/true);

  EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SETTINGS),
            GetMessageWrapper()->GetSecondaryIconResourceId());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that secondary menu icon is not set for the update password message
// in case when user has multiple credentials stored for the web site
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       NoCogButton_MultipleCredUpdatePassword) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), two_forms_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/true);

  EXPECT_EQ(0, GetMessageWrapper()->GetSecondaryIconResourceId());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the previous prompt gets dismissed when the new one is enqueued.
TEST_F(SaveUpdatePasswordMessageDelegateTest, OnlyOnePromptAtATime) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/false);

  ExpectDismissMessageCall();
  auto form_manager2 =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager2), /*user_signed_in=*/true,
                 /*update_password=*/false);
  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that password form is saved and metrics recorded correctly when the
// user clicks "Save" button.
TEST_F(SaveUpdatePasswordMessageDelegateTest, SaveOnActionClick) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick();
  EXPECT_EQ(nullptr, GetMessageWrapper());

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted);
  histogram_tester.ExpectUniqueSample(
      kSaveUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_ACCEPT, 1);
}

// Tests that the local password migration warning will show when the user
// clicks the "Save" button.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerLocalPasswordMigrationWarning_OnSaveClicked) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(
      GetMigrationWarningCallback(),
      Run(_, _,
          password_manager::metrics_util::PasswordMigrationWarningTriggers::
              kPasswordSaveUpdateMessage));
  TriggerActionClick();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the message to update GMSCore will show when the user
// clicks the "Save" button if the GMSCore version is too low to save account
// passwords.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       NudgeToUpdateGmsCore_OnSaveClicked) {
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*GetClient(),
              ShowPasswordManagerErrorMessage(
                  password_manager::ErrorMessageFlowType::kSaveFlow,
                  password_manager::PasswordStoreBackendErrorType::
                      kGMSCoreOutdatedSavingPossible));
  EXPECT_CALL(*(GetClient()->GetPasswordFeatureManager()), ShouldUpdateGmsCore)
      .WillOnce(Return(true));
  TriggerActionClick();

  // Fast forward, since Update message is shown with a delay.
  FastForward();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the message to update GMSCore will not show when the user
// clicks the "Save" button.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       DontNudgeToUpdateGmsCore_OnSaveClicked) {
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*GetClient(),
              ShowPasswordManagerErrorMessage(
                  password_manager::ErrorMessageFlowType::kSaveFlow,
                  password_manager::PasswordStoreBackendErrorType::
                      kGMSCoreOutdatedSavingPossible))
      .Times(0);
  EXPECT_CALL(*(GetClient()->GetPasswordFeatureManager()), ShouldUpdateGmsCore)
      .WillOnce(Return(false));
  TriggerActionClick();

  // Fast forward, since Update message is shown with a delay.
  FastForward();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerLocalPasswordMigrationWarning_OnSavePasswordDialogAccepted) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  MockPasswordFormManagerForUI* form_manager_pointer = form_manager.get();
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*mock_dialog, ShowPasswordEditDialog);
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  TriggerPasswordEditDialog(/*update_password=*/false);

  EXPECT_EQ(nullptr, GetMessageWrapper());
  EXPECT_CALL(*form_manager_pointer, Save());
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);
}

// Tests that the local password migration warning will not show when the user
// dismisses the save password message.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       DontTriggerLocalPasswordMigrationWarning_OnSaveMessageDismissed) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  DismissMessage(messages::DismissReason::GESTURE);
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the local password migration warning will show when the user
// accepts the update password message in case when there is no confirmation
// dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerLocalPasswordMigrationWarning_OnUpdatePasswordWithSingleForm) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  SetPendingCredentials(kUsername, kPassword);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  TriggerActionClick();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the message to update GMSCore will show when the user accepts the
// update password message in case when there is no confirmation
// dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       NudgeToUpdateGmsCore_OnUpdatePasswordWithSingleForm) {
  SetPendingCredentials(kUsername, kPassword);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  EXPECT_CALL(*GetClient(),
              ShowPasswordManagerErrorMessage(
                  password_manager::ErrorMessageFlowType::kSaveFlow,
                  password_manager::PasswordStoreBackendErrorType::
                      kGMSCoreOutdatedSavingPossible));
  EXPECT_CALL(*(GetClient()->GetPasswordFeatureManager()), ShouldUpdateGmsCore)
      .WillOnce(Return(true));
  TriggerActionClick();

  // Fast forward, since Update message is shown with a delay.
  FastForward();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the message to update GMSCore will not show when the user accepts
// the update password message in case when there is no confirmation dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       DontNudgeToUpdateGmsCore_OnUpdatePasswordWithSingleForm) {
  SetPendingCredentials(kUsername, kPassword);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  EXPECT_CALL(*GetClient(),
              ShowPasswordManagerErrorMessage(
                  password_manager::ErrorMessageFlowType::kSaveFlow,
                  password_manager::PasswordStoreBackendErrorType::
                      kGMSCoreOutdatedSavingPossible))
      .Times(0);
  EXPECT_CALL(*(GetClient()->GetPasswordFeatureManager()), ShouldUpdateGmsCore)
      .WillOnce(Return(false));
  TriggerActionClick();

  // Fast forward, since Update message is shown with a delay.
  FastForward();
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the local password migration warning will not show when the user
// dismisses the update password message.
TEST_F(
    SaveUpdatePasswordMessageDelegateTest,
    DontTriggerLocalPasswordMigrationWarning_OnUpdatePasswordMessageDismissed) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  SetPendingCredentials(kUsername, kPassword);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  DismissMessage(messages::DismissReason::GESTURE);
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the local password migration warning will show when the user
// accepts the update password message and the confirmation dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerLocalPasswordMigrationWarning_OnUpdatePasswordDialogAccepted) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), two_forms_best_matches());
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();
  EXPECT_CALL(
      *mock_dialog,
      ShowPasswordEditDialog(
          ElementsAre(std::u16string(kUsername), std::u16string(kUsername2)),
          Eq(kUsername), Eq(kPassword), Eq(kAccountEmail)));
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  TriggerActionClick();
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);
}

// Tests that the message to update GMSCore will show when the user accepts the
// update password message and the confirmation dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       NudgeToUpdateGmsCore_OnUpdatePasswordDialogAccepted) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), two_forms_best_matches());
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();
  EXPECT_CALL(
      *mock_dialog,
      ShowPasswordEditDialog(
          ElementsAre(std::u16string(kUsername), std::u16string(kUsername2)),
          Eq(kUsername), Eq(kPassword), Eq(kAccountEmail)));
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  EXPECT_CALL(*GetClient(),
              ShowPasswordManagerErrorMessage(
                  password_manager::ErrorMessageFlowType::kSaveFlow,
                  password_manager::PasswordStoreBackendErrorType::
                      kGMSCoreOutdatedSavingPossible));
  EXPECT_CALL(*(GetClient()->GetPasswordFeatureManager()), ShouldUpdateGmsCore)
      .WillOnce(Return(true));
  TriggerActionClick();
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);
  // Fast forward, since Update message is shown with a delay.
  FastForward();
}

// Tests that the message to update GMSCore will not show when the user accepts
// the update password message and the confirmation dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       DontNudgeToUpdateGmsCore_OnUpdatePasswordDialogAccepted) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), two_forms_best_matches());
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();
  EXPECT_CALL(
      *mock_dialog,
      ShowPasswordEditDialog(
          ElementsAre(std::u16string(kUsername), std::u16string(kUsername2)),
          Eq(kUsername), Eq(kPassword), Eq(kAccountEmail)));
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  EXPECT_CALL(*GetClient(),
              ShowPasswordManagerErrorMessage(
                  password_manager::ErrorMessageFlowType::kSaveFlow,
                  password_manager::PasswordStoreBackendErrorType::
                      kGMSCoreOutdatedSavingPossible))
      .Times(0);
  EXPECT_CALL(*(GetClient()->GetPasswordFeatureManager()), ShouldUpdateGmsCore)
      .WillOnce(Return(false));
  TriggerActionClick();
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);
  // Fast forward, since Update message is shown with a delay.
  FastForward();
}

// Tests that the local password migration warning will show when the user
// accepts the update password message and cancels the confirmation dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerLocalPasswordMigrationWarning_OnUpdatePasswordDialogCanceled) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), two_forms_best_matches());
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();
  EXPECT_CALL(
      *mock_dialog,
      ShowPasswordEditDialog(
          ElementsAre(std::u16string(kUsername), std::u16string(kUsername2)),
          Eq(kUsername), Eq(kPassword), Eq(kAccountEmail)));
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  TriggerActionClick();
  EXPECT_CALL(GetMigrationWarningCallback(), Run);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/false);
}

// Tests that password form is not saved and metrics recorded correctly when the
// user dismisses the message.
TEST_F(SaveUpdatePasswordMessageDelegateTest, DontSaveOnDismiss) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save()).Times(0);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  DismissMessage(messages::DismissReason::GESTURE);
  EXPECT_EQ(nullptr, GetMessageWrapper());

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined);
  histogram_tester.ExpectUniqueSample(
      kSaveUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_CANCEL, 1);
}

// Tests that password form is not saved and metrics recorded correctly when the
// message is autodismissed.
TEST_F(SaveUpdatePasswordMessageDelegateTest, MetricOnAutodismissTimer) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save()).Times(0);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  DismissMessage(messages::DismissReason::TIMER);
  EXPECT_EQ(nullptr, GetMessageWrapper());

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kIgnored);
  histogram_tester.ExpectUniqueSample(
      kSaveUIDismissalReasonHistogramName,
      password_manager::metrics_util::NO_DIRECT_INTERACTION, 1);
}

// Tests that the local password migration warning will not show when the user
// lets the save message time out.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       DontTriggerLocalPasswordMigrationWarning_OnSaveMessageAutodismissTimer) {
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  DismissMessage(messages::DismissReason::TIMER);
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that the local password migration warning will not show when the user
// lets the update message time out.
TEST_F(
    SaveUpdatePasswordMessageDelegateTest,
    DontTriggerLocalPasswordMigrationWarning_OnUpdateMessageAutodismissTimer) {
  SetPendingCredentials(kUsername, kPassword);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  DismissMessage(messages::DismissReason::TIMER);
  EXPECT_EQ(nullptr, GetMessageWrapper());
}

// Tests that update password message with a single PasswordForm immediately
// saves the form on Update button tap and doesn't display confirmation dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest, UpdatePasswordWithSingleForm) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  SetPendingCredentials(kUsername, kPassword);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick();
  EXPECT_EQ(nullptr, GetMessageWrapper());

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted);
  histogram_tester.ExpectUniqueSample(
      kUpdateUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_ACCEPT, 1);
}

// Verifies that:
// 1. Username confirmation dialog is shown after clicking on 'Continue'
// in the message.
// 2. Saving the password form is executed after clicking on Update button of
// the dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerConfirmUsernameDialog_Accept) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), two_forms_best_matches());
  EXPECT_CALL(*form_manager, Save());
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();
  EXPECT_CALL(*mock_dialog, ShowPasswordEditDialog);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick();
  EXPECT_EQ(nullptr, GetMessageWrapper());
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted);
  histogram_tester.ExpectUniqueSample(
      kUpdateUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_ACCEPT, 1);
}
// Verifies that:
// 1. Save password dialog is shown after clicking on cog menu item
// "Edit password"in the message.
// 2. Saving the password form is executed after clicking on Save button of the
// dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerSaveMessage_CogButton_Accept) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  MockPasswordFormManagerForUI* form_manager_pointer = form_manager.get();
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*mock_dialog, ShowPasswordEditDialog);
  TriggerPasswordEditDialog(/*update_password=*/false);

  EXPECT_EQ(nullptr, GetMessageWrapper());
  EXPECT_CALL(*form_manager_pointer, Save());
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);
  // The real password edit dialog triggers dialog dismissed delegate inside.
  // Here we use the mock that doesn't do this, so the dismiss is called
  // manually here.
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted);
  histogram_tester.ExpectUniqueSample(
      kSaveUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_ACCEPT, 1);
}

// Verifies that the site is blocklisted after clicking on
// "Never for this site" menu option in Save message
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerSaveMessage_CogButton_NeverSave) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  MockPasswordFormManagerForUI* form_manager_pointer = form_manager.get();

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*form_manager_pointer, Blocklist());
  TriggerNeverSaveMenuItem();

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined);
  histogram_tester.ExpectUniqueSample(
      kSaveUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_NEVER, 1);
}

// Verifies that the password migration warning is not shown after selecting
// "Never for this site" menu option in the Save message.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       DontTriggerLocalPasswordMigrationWarning_OnNeverSave) {
  base::test::ScopedFeatureList scoped_feature_state;
  scoped_feature_state.InitAndEnableFeature(
      password_manager::features::
          kUnifiedPasswordManagerLocalPasswordsMigrationWarning);

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  MockPasswordFormManagerForUI* form_manager_pointer = form_manager.get();

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(GetMigrationWarningCallback(), Run).Times(0);
  EXPECT_CALL(*form_manager_pointer, Blocklist());
  TriggerNeverSaveMenuItem();
}

// Verifies that:
// 1. Update password dialog is shown after clicking on cog button (secondary
// action) in the message.
// 2. Updating the password form is executed after clicking on Update button of
// the dialog.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerUpdateMessage_CogButton_Accept) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  MockPasswordFormManagerForUI* form_manager_pointer = form_manager.get();
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*mock_dialog, ShowPasswordEditDialog);
  TriggerPasswordEditDialog(/*update_password=*/true);

  EXPECT_EQ(nullptr, GetMessageWrapper());
  EXPECT_CALL(*form_manager_pointer, Save());
  TriggerDialogAcceptedCallback(/*username=*/kUsername,
                                /*password=*/kPassword);

  // The real password edit dialog triggers dialog dismissed delegate inside.
  // Here we use the mock that doesn't do this, so the dismiss is called
  // manually here.
  TriggerDialogDismissedCallback(/*dialog_accepted=*/true);

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kAccepted);
  histogram_tester.ExpectUniqueSample(
      kUpdateUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_ACCEPT, 1);
}

// Verifies that:
// 1. Save password dialog is shown after clicking on cog menu item
// "Edit password"in the message.
// 2. The dialog is dismissed with negative result after clicking on Cancel
// button.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       TriggerSaveMessage_CogButton_Cancel) {
  base::HistogramTester histogram_tester;
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;

  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  MockPasswordFormManagerForUI* form_manager_pointer = form_manager.get();
  MockPasswordEditDialog* mock_dialog = PreparePasswordEditDialog();
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  EXPECT_CALL(*mock_dialog, ShowPasswordEditDialog);
  TriggerPasswordEditDialog(/*update_password=*/false);
  EXPECT_EQ(nullptr, GetMessageWrapper());
  EXPECT_CALL(*form_manager_pointer, Save()).Times(0);
  TriggerDialogDismissedCallback(/*dialog_accepted=*/false);

  CommitPasswordFormMetrics();
  VerifyUkmMetrics(
      test_ukm_recorder,
      PasswordFormMetricsRecorder::BubbleDismissalReason::kDeclined);
  histogram_tester.ExpectUniqueSample(
      kSaveUIDismissalReasonHistogramName,
      password_manager::metrics_util::CLICKED_CANCEL, 1);
}

// Tests that password is saved if device lock UI is shown and device lock is
// set during a save password flow.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SavePassword_DeviceLockUiShown_DeviceLockSet) {
  // Create a scoped window so that WebContents::GetNativeView::GetWindowAndroid
  // does not return null.
  std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window =
      ui::WindowAndroid::CreateForTesting();
  window.get()->get()->AddChild(web_contents()->GetNativeView());

  test_bridge()->SetShouldShowDeviceLockUi(true);

  // Launch save password UI and click the save button.
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick();

  // Verify that device lock UI is shown but password is not saved yet.
  EXPECT_EQ(1, test_bridge()->device_lock_ui_shown_count());
  EXPECT_EQ(false, is_password_saved());

  // Verify that password is saved after receiving the callback that device lock
  // was set.
  test_bridge()->SimulateDeviceLockComplete(true);
  EXPECT_EQ(true, is_password_saved());
}

// Tests that password is updated if device lock UI is shown and device lock is
// set during an update password flow.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       UpdatePassword_DeviceLockUiShown_DeviceLockSet) {
  // Create a scoped window so that WebContents::GetNativeView::GetWindowAndroid
  // does not return null.
  std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window =
      ui::WindowAndroid::CreateForTesting();
  window.get()->get()->AddChild(web_contents()->GetNativeView());

  test_bridge()->SetShouldShowDeviceLockUi(true);

  // Launch save password UI and click the save button.
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/true);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick();

  // Verify that device lock UI is shown but password is not saved yet.
  EXPECT_EQ(1, test_bridge()->device_lock_ui_shown_count());
  EXPECT_EQ(false, is_password_saved());

  // Verify that password is updated after receiving the callback that device
  // lock was set.
  test_bridge()->SimulateDeviceLockComplete(true);
  EXPECT_EQ(true, is_password_saved());
}

// Tests that password is not saved if device lock UI is shown but device lock
// is not set during a save password flow.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SavePassword_DeviceLockUiShown_DeviceLockNotSet) {
  // Create a scoped window so that WebContents::GetNativeView::GetWindowAndroid
  // does not return null.
  std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window =
      ui::WindowAndroid::CreateForTesting();
  window.get()->get()->AddChild(web_contents()->GetNativeView());

  test_bridge()->SetShouldShowDeviceLockUi(true);

  // Launch save password UI and click the save button.
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save()).Times(0);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick();

  // Verify that device lock UI is shown.
  EXPECT_EQ(1, test_bridge()->device_lock_ui_shown_count());

  // Verify that password is not saved after device lock was not set.
  test_bridge()->SimulateDeviceLockComplete(false);
}

// Tests that password is not saved if device lock UI needs to be shown but is
// not.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SavePassword_DeviceLockUiNotShown) {
  test_bridge()->SetShouldShowDeviceLockUi(true);

  // Launch save password UI and click the save button.
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EXPECT_CALL(*form_manager, Save()).Times(0);
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  EXPECT_NE(nullptr, GetMessageWrapper());
  TriggerActionClick(messages::DismissReason::UNKNOWN);
}

// Tests parameterized with different feature states

// Tests that message properties (title, description, icon, button text) are
// set correctly for save password message.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       MessagePropertyValues_SavePassword) {
  SetPendingCredentials(kUsername, kPassword);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  const bool is_signed_in = false;
  const bool is_update = false;
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update);

  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON),
            GetMessageWrapper()->GetPrimaryButtonText());
  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountEmail16),
            GetMessageWrapper()->GetDescription());
  EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(
                IDR_ANDROID_PASSWORD_MANAGER_LOGO_24DP),
            GetMessageWrapper()->GetIconResourceId());
  EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SETTINGS),
            GetMessageWrapper()->GetSecondaryIconResourceId());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that message properties (title, description, icon, button text) are
// set correctly for update password message.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       MessagePropertyValues_UpdatePassword) {
  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/false);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  const bool is_signed_in = false;
  const bool is_update = true;
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update);

  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_UPDATE_PASSWORD),
            GetMessageWrapper()->GetTitle());

  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountEmail16),
            GetMessageWrapper()->GetDescription());

  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UPDATE_BUTTON),
            GetMessageWrapper()->GetPrimaryButtonText());
  EXPECT_EQ(std::u16string(),
            GetMessageWrapper()->GetSecondaryButtonMenuText());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the description is set correctly when signed-in user saves a
// password.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SignedInDescription_SavePassword) {
  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  const bool is_signed_in = true;
  const bool is_update = false;
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update);

  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountEmail16),
            GetMessageWrapper()->GetDescription());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the description is set correctly when the signed-in user with a
// non-displayable email saves a password.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SignedInDescription_SavePasswordNonDisplayableEmail) {
  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  const bool is_signed_in = true;
  const bool is_update = false;

  std::optional<AccountInfo> account_info;
  account_info = AccountInfo();
  account_info.value().email = kAccountEmail;
  account_info.value().full_name = kAccountFullName;
  AccountCapabilitiesTestMutator mutator(&account_info.value().capabilities);
  mutator.set_can_have_email_address_displayed(false);

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update, account_info);

  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountFullName16),
            GetMessageWrapper()->GetDescription());
  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the description is set correctly when signed-in user updates a
// password.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SignedInDescription_UpdatePassword) {
  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  const bool is_signed_in = true;
  const bool is_update = true;
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update);

  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountEmail16),
            GetMessageWrapper()->GetDescription());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the description is set correctly when the signed in user updated
// the password in the local store.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SignedInDescription_UpdatePasswordInAccountStore) {
  // Enables using split storages (local and account).
  profile()->GetPrefs()->SetInteger(
      password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
      static_cast<int>(
          password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOn));

  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword, true)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  const bool is_signed_in = true;
  const bool is_update = true;
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update);

  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountEmail16),
            GetMessageWrapper()->GetDescription());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the description is set correctly when the signed in user updated
// the password in the local store.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SignedOutDescription_UpdatePasswordInLocalStore) {
  // Enables using split storages (local and account).
  EnableUseUPMLocalAndSeparateStores();

  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/false);
  std::vector<PasswordForm> single_form_best_matches = {
      CreatePasswordForm(kUsername, kPassword, false)};
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), single_form_best_matches);
  const bool is_update = true;
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
                 /*update_password=*/is_update);

  // Should display signed out message for updating the password in the local
  // store (even when the user is signed in).
  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, false, kAccountEmail16),
            GetMessageWrapper()->GetDescription());

  DismissMessage(messages::DismissReason::UNKNOWN);
}

// Tests that the description is set correctly when the signed-in user with a
// non-displayable email updates a password.
TEST_F(SaveUpdatePasswordMessageDelegateTest,
       SignedInDescription_UpdatePasswordNonDisplayableEmail) {
  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  const bool is_signed_in = true;
  const bool is_update = true;

  std::optional<AccountInfo> account_info;
  account_info = AccountInfo();
  account_info.value().email = kAccountEmail;
  account_info.value().full_name = kAccountFullName;
  AccountCapabilitiesTestMutator mutator(&account_info.value().capabilities);
  mutator.set_can_have_email_address_displayed(false);

  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
                 /*update_password=*/is_update, account_info);
  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
                                             kAccountFullName16),
            GetMessageWrapper()->GetDescription());
  DismissMessage(messages::DismissReason::UNKNOWN);
}

TEST_F(SaveUpdatePasswordMessageDelegateTest, RecordsPromptShownWhenEnqueuing) {
  base::HistogramTester histogram_tester;
  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
  auto form_manager =
      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/false,
                 /*update_password=*/false);
  histogram_tester.ExpectUniqueSample(
      "PasswordManager.FormSubmissionsVsSavePrompts",
      password_manager::metrics_util::SaveFlowStep::kSavePromptShown, 1);
  DismissMessage(messages::DismissReason::UNKNOWN);
}