chromium/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate_unittest.cc

// Copyright 2019 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/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.h"

#include <memory>
#include <string_view>

#include "base/base64.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.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 "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/types/pass_key.h"
#include "chrome/browser/password_manager/android/access_loss/mock_password_access_loss_warning_bridge.h"
#include "chrome/browser/password_manager/android/password_manager_launcher_android.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/device_reauth/mock_device_authenticator.h"
#include "components/password_manager/content/browser/mock_keyboard_replacing_surface_visibility_controller.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/core/browser/mock_password_credential_filler.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/passkey_credential.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_manager_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/ukm/test_ukm_recorder.h"
#include "components/webauthn/android/mock_webauthn_cred_man_delegate.h"
#include "components/webauthn/android/webauthn_cred_man_delegate.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/native_widget_types.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace {

using ToShowVirtualKeyboard =
    password_manager::PasswordManagerDriver::ToShowVirtualKeyboard;
using autofill::mojom::SubmissionReadinessState;
using base::test::RunOnceCallback;
using device_reauth::MockDeviceAuthenticator;
using password_manager::MockPasswordCredentialFiller;
using password_manager::PasskeyCredential;
using password_manager::UiCredential;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Return;
using ::testing::WithArg;
using webauthn::MockWebAuthnCredManDelegate;
using webauthn::WebAuthnCredManDelegate;
using IsOriginSecure = TouchToFillView::IsOriginSecure;

constexpr char kExampleCom[] = "https://example.com/";

class MockPasswordManagerClient
    : public password_manager::StubPasswordManagerClient {
 public:
  MOCK_METHOD(void,
              StartSubmissionTrackingAfterTouchToFill,
              (const std::u16string& filled_username),
              (override));
  MOCK_METHOD(void,
              NavigateToManagePasswordsPage,
              (password_manager::ManagePasswordsReferrer),
              (override));
  MOCK_METHOD(password_manager::WebAuthnCredentialsDelegate*,
              GetWebAuthnCredentialsDelegateForDriver,
              (password_manager::PasswordManagerDriver*),
              (override));
  MOCK_METHOD(void, MarkSharedCredentialsAsNotified, (const GURL&), (override));
  MOCK_METHOD(bool,
              IsReauthBeforeFillingRequired,
              (device_reauth::DeviceAuthenticator*),
              (override));
};

struct MockTouchToFillView : TouchToFillView {
  MOCK_METHOD(bool,
              Show,
              (const GURL&,
               IsOriginSecure,
               base::span<const UiCredential>,
               base::span<const PasskeyCredential>,
               int),
              (override));
  MOCK_METHOD(void, OnCredentialSelected, (const UiCredential&));
  MOCK_METHOD(void, OnDismiss, ());
};

struct MakeUiCredentialParams {
  std::string_view username;
  std::string_view password;
  std::string_view origin = kExampleCom;
  password_manager_util::GetLoginMatchType match_type =
      password_manager_util::GetLoginMatchType::kExact;
  base::TimeDelta time_since_last_use;
};

UiCredential MakeUiCredential(MakeUiCredentialParams params) {
  return UiCredential(
      base::UTF8ToUTF16(params.username), base::UTF8ToUTF16(params.password),
      url::Origin::Create(GURL(params.origin)), params.match_type,
      base::Time::Now() - params.time_since_last_use);
}

}  // namespace

class TouchToFillControllerAutofillTest
    : public ChromeRenderViewHostTestHarness {
 protected:
  using UkmBuilder = ukm::builders::TouchToFill_Shown;

  TouchToFillControllerAutofillTest()
      : ChromeRenderViewHostTestHarness(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    password_manager_launcher::
        OverrideManagePasswordWhenPasskeysPresentForTesting(false);

    // By default, disable biometric authentication.
    ON_CALL(client(), IsReauthBeforeFillingRequired)
        .WillByDefault(Return(false));

    scoped_feature_list_.InitAndEnableFeature(
        password_manager::features::kBiometricTouchToFill);
  }

  std::unique_ptr<MockPasswordCredentialFiller> CreateMockFiller() {
    std::unique_ptr<MockPasswordCredentialFiller> filler =
        std::make_unique<MockPasswordCredentialFiller>();
    // the Mock filler will be passed to TouchToFillControllerAutofillDelegate.
    // cache the raw pointer here to interact with the mock after passing.
    weak_filler_ = filler.get();
    ON_CALL(*filler, GetFrameUrl()).WillByDefault(Return(GURL(kExampleCom)));
    return filler;
  }

  MockPasswordManagerClient& client() { return client_; }

  MockTouchToFillView& view() { return *mock_view_; }

  MockPasswordCredentialFiller* last_mock_filler() {
    EXPECT_NE(weak_filler_, nullptr) << "Call CreateMockFiller first!";
    return weak_filler_;
  }

  MockDeviceAuthenticator* authenticator() { return authenticator_.get(); }

  ukm::TestAutoSetUkmRecorder& test_recorder() { return test_recorder_; }

  base::HistogramTester& histogram_tester() { return histogram_tester_; }

  TouchToFillController& touch_to_fill_controller() {
    return *touch_to_fill_controller_;
  }

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

  std::unique_ptr<TouchToFillControllerAutofillDelegate>
  MakeTouchToFillControllerDelegate(
      autofill::mojom::SubmissionReadinessState submission_readiness,
      std::unique_ptr<MockPasswordCredentialFiller> filler,
      const password_manager::PasswordForm* form_to_fill,
      autofill::FieldRendererId focused_field_renderer_id,
      TouchToFillControllerAutofillDelegate::ShowHybridOption
          should_show_hybrid_option) {
    ON_CALL(*filler, GetSubmissionReadinessState())
        .WillByDefault(Return(submission_readiness));
    std::unique_ptr<MockPasswordAccessLossWarningBridge> mock_bridge =
        std::make_unique<MockPasswordAccessLossWarningBridge>();
    mock_access_loss_warning_bridge_ = mock_bridge.get();
    return std::make_unique<TouchToFillControllerAutofillDelegate>(
        base::PassKey<TouchToFillControllerAutofillTest>(), &client_,
        web_contents(), std::move(authenticator_),
        webauthn_credentials_delegate_.AsWeakPtr(), std::move(filler),
        form_to_fill, focused_field_renderer_id, should_show_hybrid_option,
        show_password_migration_warning().Get(), std::move(mock_bridge));
  }

  password_manager::MockWebAuthnCredentialsDelegate&
  webauthn_credentials_delegate() {
    return webauthn_credentials_delegate_;
  }

  password_manager::MockKeyboardReplacingSurfaceVisibilityController&
  visibility_controller() {
    return *visibility_controller_;
  }

  base::test::ScopedFeatureList& scoped_feature_list() {
    return scoped_feature_list_;
  }

  const password_manager::PasswordForm* form_to_fill() {
    return &form_to_fill_;
  }

  MockPasswordAccessLossWarningBridge* mock_access_loss_warning_bridge() {
    return mock_access_loss_warning_bridge_;
  }

  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    auto mock_view = std::make_unique<MockTouchToFillView>();
    mock_view_ = mock_view.get();
    visibility_controller_ = std::make_unique<
        password_manager::MockKeyboardReplacingSurfaceVisibilityController>();
    touch_to_fill_controller_ = std::make_unique<TouchToFillController>(
        profile(), visibility_controller_->AsWeakPtr());
    touch_to_fill_controller().set_view(std::move(mock_view));
    form_to_fill_.password_element_renderer_id = autofill::FieldRendererId(1);
    form_to_fill_.username_element_renderer_id = autofill::FieldRendererId(2);
  }

 private:
  raw_ptr<MockTouchToFillView> mock_view_ = nullptr;
  std::unique_ptr<MockDeviceAuthenticator> authenticator_ =
      std::make_unique<MockDeviceAuthenticator>();
  MockPasswordManagerClient client_;
  password_manager::MockWebAuthnCredentialsDelegate
      webauthn_credentials_delegate_;
  base::HistogramTester histogram_tester_;
  ukm::TestAutoSetUkmRecorder test_recorder_;
  std::unique_ptr<
      password_manager::MockKeyboardReplacingSurfaceVisibilityController>
      visibility_controller_;
  std::unique_ptr<TouchToFillController> touch_to_fill_controller_;
  base::test::ScopedFeatureList scoped_feature_list_;
  base::MockCallback<base::RepeatingCallback<void(
      gfx::NativeWindow,
      Profile*,
      password_manager::metrics_util::PasswordMigrationWarningTriggers)>>
      show_password_migration_warning_;
  raw_ptr<MockPasswordAccessLossWarningBridge> mock_access_loss_warning_bridge_;
  raw_ptr<MockPasswordCredentialFiller> weak_filler_;
  password_manager::PasswordForm form_to_fill_;
};

TEST_F(TouchToFillControllerAutofillTest, Show_Fill_And_Submit) {
  auto filler_to_pass = CreateMockFiller();
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(true));

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kTriggerSubmission));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kTwoFields,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"alice"),
                                      std::u16string(u"p4ssw0rd")));

  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(Eq(u"alice")));

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest, Show_Fill_And_Dont_Submit) {
  auto filler_to_pass = CreateMockFiller();
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(false));

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"alice"),
                                      std::u16string(u"p4ssw0rd")));

  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest,
       ShowFillAndShowPasswordMigrationWarning) {
  scoped_feature_list().Reset();
  scoped_feature_list().InitWithFeatures(
      {password_manager::features::
           kUnifiedPasswordManagerLocalPasswordsMigrationWarning},
      {});
  profile()->GetPrefs()->SetInteger(
      password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
      static_cast<int>(
          password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOff));
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  auto filler_to_pass = CreateMockFiller();

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kTwoFields,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"alice"),
                                      std::u16string(u"p4ssw0rd")));
  EXPECT_CALL(*last_mock_filler(), UpdateTriggerSubmission(false));
  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);
  EXPECT_CALL(show_password_migration_warning(),
              Run(_, _,
                  password_manager::metrics_util::
                      PasswordMigrationWarningTriggers::kTouchToFill));

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest, FillingShowsAccessLossWarning) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  auto filler_to_pass = CreateMockFiller();

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kTwoFields,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);
  EXPECT_CALL(*mock_access_loss_warning_bridge(),
              ShouldShowAccessLossNoticeSheet(profile()->GetPrefs()))
      .WillRepeatedly(testing::Return(true));

  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"alice"),
                                      std::u16string(u"p4ssw0rd")));
  EXPECT_CALL(*last_mock_filler(), UpdateTriggerSubmission(false));
  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);
  EXPECT_CALL(
      *mock_access_loss_warning_bridge(),
      MaybeShowAccessLossNoticeSheet(profile()->GetPrefs(), _, profile()));

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest, Dont_Submit_With_Empty_Username) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "", .password = "p4ssw0rd"}),
      MakeUiCredential({.username = "username", .password = "p4ssw0rd"})};
  auto filler_to_pass = CreateMockFiller();
  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(true));

  // As we don't know which credential will be selected, don't disable
  // submission for now.
  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kTriggerSubmission));
  EXPECT_CALL(*last_mock_filler(), UpdateTriggerSubmission(true));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kTwoFields,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(false));
  // The user picks the credential with an empty username, submission should not
  // be triggered.
  EXPECT_CALL(*last_mock_filler(), UpdateTriggerSubmission(false));
  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u""),
                                      std::u16string(u"p4ssw0rd")));
  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest,
       Single_Credential_With_Empty_Username) {
  auto filler_to_pass = CreateMockFiller();
  UiCredential credentials[] = {
      MakeUiCredential({.username = "", .password = "p4ssw0rd"})};
  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(false));

  // Only one credential with empty username - submission is impossible.
  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  EXPECT_CALL(*last_mock_filler(), UpdateTriggerSubmission(false));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kTwoFields,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u""),
                                      std::u16string(u"p4ssw0rd")));
  EXPECT_CALL(*last_mock_filler(), UpdateTriggerSubmission(false));
  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(_)).Times(0);

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest, Show_And_Fill_No_Auth_Available) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  // Test that we correctly log the absence of an Android credential.
  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"alice"),
                                      std::u16string(u"p4ssw0rd")));
  ON_CALL(client(), IsReauthBeforeFillingRequired).WillByDefault(Return(false));

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);

  auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
  ASSERT_EQ(1u, entries.size());
  test_recorder().ExpectEntryMetric(
      entries[0], UkmBuilder::kUserActionName,
      static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
                               kSelectedCredential));
}

TEST_F(TouchToFillControllerAutofillTest,
       Show_And_Fill_Auth_Available_Success) {
  auto filler_to_pass = CreateMockFiller();
  auto* authenticator_ptr = authenticator();
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(true));

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kTriggerSubmission));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kTwoFields,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  ON_CALL(*last_mock_filler(), ShouldTriggerSubmission())
      .WillByDefault(Return(true));
  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"alice"),
                                      std::u16string(u"p4ssw0rd")));

  ON_CALL(client(), IsReauthBeforeFillingRequired).WillByDefault(Return(true));
  EXPECT_CALL(*authenticator_ptr, AuthenticateWithMessage)
      .WillOnce(RunOnceCallback<1>(true));
  EXPECT_CALL(client(), StartSubmissionTrackingAfterTouchToFill(Eq(u"alice")));

  touch_to_fill_controller().OnCredentialSelected(credentials[0]);
}

TEST_F(TouchToFillControllerAutofillTest,
       Show_And_Fill_Auth_Available_Failure) {
  auto* authenticator_ptr = authenticator();
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(*last_mock_filler(), FillUsernameAndPassword(_, _)).Times(0);

  ON_CALL(client(), IsReauthBeforeFillingRequired).WillByDefault(Return(true));
  EXPECT_CALL(*authenticator_ptr, AuthenticateWithMessage)
      .WillOnce(RunOnceCallback<1>(false));
  touch_to_fill_controller().OnCredentialSelected(credentials[0]);

  histogram_tester().ExpectUniqueSample(
      "PasswordManager.TouchToFill.Outcome",
      TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
          kReauthenticationFailed,
      1);
}

TEST_F(TouchToFillControllerAutofillTest, Show_Empty) {
  EXPECT_CALL(view(), Show).Times(0);
  touch_to_fill_controller().Show(
      {}, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);
}

TEST_F(TouchToFillControllerAutofillTest, Show_Insecure_Origin) {
  auto filler_to_pass = CreateMockFiller();
  EXPECT_CALL(*last_mock_filler(), GetFrameUrl())
      .WillOnce(Return(GURL("http://example.com")));

  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL("http://example.com")),
                           IsOriginSecure(false), ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          std::move(filler_to_pass), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);
}

TEST_F(TouchToFillControllerAutofillTest, Show_And_Fill_Android_Credential) {
  // Test multiple credentials with one of them being an Android credential.
  UiCredential credentials[] = {
      MakeUiCredential({
          .username = "alice",
          .password = "p4ssw0rd",
          .time_since_last_use = base::Minutes(2),
      }),
      MakeUiCredential({
          .username = "bob",
          .password = "s3cr3t",
          .origin = "",
          .match_type = password_manager_util::GetLoginMatchType::kAffiliated,
          .time_since_last_use = base::Minutes(3),
      }),
  };

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  // Test that we correctly log the presence of an Android credential.
  EXPECT_CALL(*last_mock_filler(),
              FillUsernameAndPassword(std::u16string(u"bob"),
                                      std::u16string(u"s3cr3t")));
  ON_CALL(client(), IsReauthBeforeFillingRequired).WillByDefault(Return(false));
  touch_to_fill_controller().OnCredentialSelected(credentials[1]);

  auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
  ASSERT_EQ(1u, entries.size());
  test_recorder().ExpectEntryMetric(
      entries[0], UkmBuilder::kUserActionName,
      static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
                               kSelectedCredential));
}

// Verify that the credentials are ordered by their PSL match bit and last
// time used before being passed to the view.
TEST_F(TouchToFillControllerAutofillTest, Show_Orders_Credentials) {
  auto alice = MakeUiCredential({
      .username = "alice",
      .password = "p4ssw0rd",
      .time_since_last_use = base::Minutes(3),
  });
  auto bob = MakeUiCredential({
      .username = "bob",
      .password = "s3cr3t",
      .match_type = password_manager_util::GetLoginMatchType::kPSL,
      .time_since_last_use = base::Minutes(1),
  });
  auto charlie = MakeUiCredential({
      .username = "charlie",
      .password = "very_s3cr3t",
      .time_since_last_use = base::Minutes(2),
  });
  auto david = MakeUiCredential({
      .username = "david",
      .password = "even_more_s3cr3t",
      .match_type = password_manager_util::GetLoginMatchType::kPSL,
      .time_since_last_use = base::Minutes(4),
  });

  UiCredential credentials[] = {alice, bob, charlie, david};
  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           testing::ElementsAre(charlie, alice, bob, david),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->username_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);
}

TEST_F(TouchToFillControllerAutofillTest, Dismiss) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(client(), MarkSharedCredentialsAsNotified(GURL(kExampleCom)));
  EXPECT_CALL(*last_mock_filler(), Dismiss(ToShowVirtualKeyboard(true)));
  touch_to_fill_controller().OnDismiss();

  auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
  ASSERT_EQ(1u, entries.size());
  test_recorder().ExpectEntryMetric(
      entries[0], UkmBuilder::kUserActionName,
      static_cast<int64_t>(
          TouchToFillControllerAutofillDelegate::UserAction::kDismissed));
  histogram_tester().ExpectUniqueSample("PasswordManager.TouchToFill.Outcome",
                                        TouchToFillControllerAutofillDelegate::
                                            TouchToFillOutcome::kSheetDismissed,
                                        1);
}

TEST_F(TouchToFillControllerAutofillTest, ManagePasswordsSelected) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(client(), MarkSharedCredentialsAsNotified(GURL(kExampleCom)));
  EXPECT_CALL(*last_mock_filler(), Dismiss(ToShowVirtualKeyboard(false)));
  EXPECT_CALL(client(),
              NavigateToManagePasswordsPage(
                  password_manager::ManagePasswordsReferrer::kTouchToFill));

  touch_to_fill_controller().OnManagePasswordsSelected(
      /*passkeys_shown=*/false);

  histogram_tester().ExpectUniqueSample(
      "PasswordManager.TouchToFill.Outcome",
      TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
          kManagePasswordsSelected,
      1);

  auto entries = test_recorder().GetEntriesByName(UkmBuilder::kEntryName);
  ASSERT_EQ(1u, entries.size());
  test_recorder().ExpectEntryMetric(
      entries[0], UkmBuilder::kUserActionName,
      static_cast<int64_t>(TouchToFillControllerAutofillDelegate::UserAction::
                               kSelectedManagePasswords));
}

TEST_F(TouchToFillControllerAutofillTest, DestroyedWhileAuthRunning) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  auto* authenticator_ptr = authenticator();

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  ON_CALL(client(), IsReauthBeforeFillingRequired).WillByDefault(Return(true));
  EXPECT_CALL(*authenticator_ptr, AuthenticateWithMessage);
  touch_to_fill_controller().OnCredentialSelected(credentials[0]);

  EXPECT_CALL(*authenticator_ptr, Cancel());
}

TEST_F(TouchToFillControllerAutofillTest, ShowWebAuthnCredential) {
  PasskeyCredential credential(
      PasskeyCredential::Source::kAndroidPhone,
      PasskeyCredential::RpId("example.com"),
      PasskeyCredential::CredentialId({1, 2, 3, 4}),
      PasskeyCredential::UserId({5, 6, 7, 8}),
      PasskeyCredential::Username("[email protected]"));
  std::vector<PasskeyCredential> credentials({credential});

  EXPECT_CALL(view(),
              Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                   ElementsAreArray(std::vector<UiCredential>()),
                   ElementsAreArray(credentials), TouchToFillView::kNone));
  touch_to_fill_controller().Show(
      {}, credentials,
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(webauthn_credentials_delegate(),
              SelectPasskey(base::Base64Encode(credential.credential_id()), _));
  EXPECT_CALL(*last_mock_filler(), Dismiss(ToShowVirtualKeyboard(false)));
  EXPECT_CALL(*last_mock_filler(), FillUsernameAndPassword(_, _)).Times(0);
  touch_to_fill_controller().OnPasskeyCredentialSelected(credentials[0]);
  histogram_tester().ExpectUniqueSample(
      "PasswordManager.TouchToFill.Outcome",
      TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
          kPasskeyCredentialSelected,
      1);
}

TEST_F(TouchToFillControllerAutofillTest, ShowAndSelectHybrid) {
  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kShouldShowHybridOption));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(true)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(webauthn_credentials_delegate(), ShowAndroidHybridSignIn());
  touch_to_fill_controller().OnHybridSignInSelected();
  histogram_tester().ExpectUniqueSample(
      "PasswordManager.TouchToFill.Outcome",
      TouchToFillControllerAutofillDelegate::TouchToFillOutcome::
          kHybridSignInSelected,
      1);
}

TEST_F(TouchToFillControllerAutofillTest, ShowCredManEntryIfThereArePasskeys) {
  const UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  MockWebAuthnCredManDelegate cred_man_delegate;
  EXPECT_CALL(cred_man_delegate, HasPasskeys())
      .Times(2)
      .WillRepeatedly(Return(WebAuthnCredManDelegate::State::kHasPasskeys));
  EXPECT_CALL(cred_man_delegate, SetRequestCompletionCallback(_));
  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           TouchToFillView::kShouldShowCredManEntry));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      &cred_man_delegate, /*frame_driver=*/nullptr);
}

TEST_F(TouchToFillControllerAutofillTest,
       ShowCredManImmediatelyIfNoGpmPasskeys) {
  MockWebAuthnCredManDelegate cred_man_delegate;
  ON_CALL(cred_man_delegate, HasPasskeys())
      .WillByDefault(Return(WebAuthnCredManDelegate::State::kHasPasskeys));
  EXPECT_CALL(
      cred_man_delegate,
      TriggerCredManUi(WebAuthnCredManDelegate::RequestPasswords(false)));
  EXPECT_CALL(view(), Show(_, _, _, _, _)).Times(0);
  touch_to_fill_controller().Show(
      {}, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      &cred_man_delegate, /*frame_driver=*/nullptr);
}

TEST_F(TouchToFillControllerAutofillTest, NoCredManEntryIfNoPasskeys) {
  const UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  MockWebAuthnCredManDelegate cred_man_delegate;
  EXPECT_CALL(cred_man_delegate, HasPasskeys()).Times(2);
  EXPECT_CALL(cred_man_delegate, SetRequestCompletionCallback(_)).Times(0);
  EXPECT_CALL(view(),
              Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                   ElementsAreArray(credentials),
                   ElementsAreArray(std::vector<PasskeyCredential>()), 0));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      &cred_man_delegate, /*frame_driver=*/nullptr);
}

TEST_F(TouchToFillControllerAutofillTest,
       ShouldNotShowTouchToFillForNewPasswordField) {
  base::test::ScopedFeatureList enabledFeatures(
      password_manager::features::kPasswordSuggestionBottomSheetV2);

  const UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};
  MockWebAuthnCredManDelegate cred_man_delegate;
  password_manager::PasswordForm change_password_form(*form_to_fill());
  change_password_form.new_password_element_renderer_id =
      autofill::FieldRendererId(3);
  bool is_shown = touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          autofill::mojom::SubmissionReadinessState::kNoInformation,
          CreateMockFiller(), &change_password_form,
          change_password_form.new_password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      &cred_man_delegate, /*frame_driver=*/nullptr);
  EXPECT_FALSE(is_shown);
}

class TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest
    : public TouchToFillControllerAutofillTest,
      public testing::WithParamInterface<SubmissionReadinessState> {};

TEST_P(TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest,
       SubmissionReadinessMetrics) {
  SubmissionReadinessState submission_readiness = GetParam();
  base::HistogramTester uma_recorder;

  UiCredential credentials[] = {
      MakeUiCredential({.username = "alice", .password = "p4ssw0rd"})};

  EXPECT_CALL(view(), Show(Eq(GURL(kExampleCom)), IsOriginSecure(true),
                           ElementsAreArray(credentials),
                           ElementsAreArray(std::vector<PasskeyCredential>()),
                           /*flags=*/_));
  touch_to_fill_controller().Show(
      credentials, {},
      MakeTouchToFillControllerDelegate(
          submission_readiness, CreateMockFiller(), form_to_fill(),
          form_to_fill()->password_element_renderer_id,
          TouchToFillControllerAutofillDelegate::ShowHybridOption(false)),
      /*cred_man_delegate=*/nullptr, /*frame_driver=*/nullptr);

  EXPECT_CALL(*last_mock_filler(), Dismiss(ToShowVirtualKeyboard(true)));
  touch_to_fill_controller().OnDismiss();

  uma_recorder.ExpectUniqueSample(
      "PasswordManager.TouchToFill.SubmissionReadiness", submission_readiness,
      1);

  auto entries = test_recorder().GetEntriesByName(
      ukm::builders::TouchToFill_SubmissionReadiness::kEntryName);
  ASSERT_EQ(1u, entries.size());
  test_recorder().ExpectEntryMetric(
      entries[0],
      ukm::builders::TouchToFill_SubmissionReadiness::kSubmissionReadinessName,
      static_cast<int64_t>(submission_readiness));
}

INSTANTIATE_TEST_SUITE_P(
    SubmissionReadinessVariation,
    TouchToFillControllerAutofillTestWithSubmissionReadinessVariationTest,
    testing::Values(SubmissionReadinessState::kNoInformation,
                    SubmissionReadinessState::kError,
                    SubmissionReadinessState::kNoUsernameField,
                    SubmissionReadinessState::kFieldBetweenUsernameAndPassword,
                    SubmissionReadinessState::kFieldAfterPasswordField,
                    SubmissionReadinessState::kEmptyFields,
                    SubmissionReadinessState::kMoreThanTwoFields,
                    SubmissionReadinessState::kTwoFields,
                    SubmissionReadinessState::kNoPasswordField));