chromium/components/autofill/core/browser/payments/credit_card_fido_authenticator_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 "components/autofill/core/browser/payments/credit_card_fido_authenticator.h"

#include <stddef.h>

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

#include "base/base64.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/metrics_hashes.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/autocomplete_history_manager.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/metrics/form_events/form_events.h"
#include "components/autofill/core/browser/metrics/payments/better_auth_metrics.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/autofill/core/browser/payments/payments_service_url.h"
#include "components/autofill/core/browser/payments/test_authentication_requester.h"
#include "components/autofill/core/browser/payments/test_credit_card_fido_authenticator.h"
#include "components/autofill/core/browser/payments/test_internal_authenticator.h"
#include "components/autofill/core/browser/payments/test_payments_network_interface.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/autofill/core/browser/test_autofill_clock.h"
#include "components/autofill/core/browser/test_autofill_driver.h"
#include "components/autofill/core/browser/test_payments_data_manager.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/autofill/core/common/autofill_util.h"
#include "components/prefs/pref_service.h"
#include "components/security_state/core/security_state.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/test/test_sync_service.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "components/variations/variations_associated_data.h"
#include "components/variations/variations_params_manager.h"
#include "components/version_info/channel.h"
#include "net/base/url_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"

namespace autofill {
namespace {

constexpr char kTestGUID[] = "00000000-0000-0000-0000-000000000001";
constexpr char kTestNumber[] = "4234567890123456";  // Visa
constexpr char16_t kTestNumber16[] = u"4234567890123456";
constexpr char kTestRelyingPartyId[] = "google.com";
// Base64 encoding of "This is a test challenge".
constexpr char kTestChallenge[] = "VGhpcyBpcyBhIHRlc3QgY2hhbGxlbmdl";
// Base64 encoding of "This is a test Credential ID".
constexpr char kTestCredentialId[] = "VGhpcyBpcyBhIHRlc3QgQ3JlZGVudGlhbCBJRC4=";
// Base64 encoding of "This is a test signature".
constexpr char kTestSignature[] = "VGhpcyBpcyBhIHRlc3Qgc2lnbmF0dXJl";
constexpr char kTestAuthToken[] = "dummy_card_authorization_token";
constexpr std::string_view kEnrollmentOfferedHistogramName =
    "Autofill.BetterAuth.EnrollmentPromptOffered";

std::vector<uint8_t> Base64ToBytes(std::string base64) {
  return base::Base64Decode(base64).value_or(std::vector<uint8_t>());
}

std::string BytesToBase64(const std::vector<uint8_t> bytes) {
  return base::Base64Encode(bytes);
}
}  // namespace

class CreditCardFidoAuthenticatorTest : public testing::Test {
 public:
  void SetUp() override {
    personal_data_manager().SetPrefService(autofill_client_.GetPrefs());

    autofill_driver_.SetAuthenticator(new TestInternalAuthenticator());

    autofill_client_.GetPaymentsAutofillClient()
        ->set_test_payments_network_interface(
            std::make_unique<payments::TestPaymentsNetworkInterface>(
                autofill_client_.GetURLLoaderFactory(),
                autofill_client_.GetIdentityManager(),
                &personal_data_manager()));
    autofill_client_.set_test_strike_database(
        std::make_unique<TestStrikeDatabase>());
    fido_authenticator_ = std::make_unique<CreditCardFidoAuthenticator>(
        &autofill_driver_, &autofill_client_);
  }

  CreditCard CreateServerCard(std::string guid, std::string number) {
    CreditCard masked_server_card = CreditCard();
    test::SetCreditCardInfo(&masked_server_card, "Elvis Presley",
                            number.c_str(), test::NextMonth().c_str(),
                            test::NextYear().c_str(), "1");
    masked_server_card.set_guid(guid);
    masked_server_card.set_record_type(
        CreditCard::RecordType::kMaskedServerCard);

    personal_data_manager().test_payments_data_manager().ClearCreditCards();
    personal_data_manager().test_payments_data_manager().AddServerCreditCard(
        masked_server_card);

    return masked_server_card;
  }

  base::Value::Dict GetTestRequestOptions(std::string challenge,
                                          std::string relying_party_id,
                                          std::string credential_id) {
    base::Value::Dict request_options;

    // Building the following JSON structure--
    // request_options = {
    //   "challenge": challenge,
    //   "timeout_millis": kTestTimeoutSeconds,
    //   "relying_party_id": relying_party_id,
    //   "key_info": [{
    //       "credential_id": credential_id,
    //       "authenticator_transport_support": ["INTERNAL"]
    // }]}
    request_options.Set("challenge", base::Value(challenge));
    request_options.Set("relying_party_id", base::Value(relying_party_id));

    base::Value::Dict key_info;
    key_info.Set("credential_id", base::Value(credential_id));
    key_info.Set("authenticator_transport_support",
                 base::Value(base::Value::Type::LIST));
    key_info.FindList("authenticator_transport_support")->Append("INTERNAL");

    request_options.Set("key_info", base::Value(base::Value::Type::LIST));
    request_options.FindList("key_info")->Append(std::move(key_info));
    return request_options;
  }

  base::Value::Dict GetTestCreationOptions(std::string challenge,
                                           std::string relying_party_id) {
    base::Value::Dict creation_options;
    if (!challenge.empty())
      creation_options.Set("challenge", base::Value(challenge));
    creation_options.Set("relying_party_id", base::Value(relying_party_id));
    return creation_options;
  }

  // Invokes GetRealPan callback.
  void GetRealPan(payments::PaymentsAutofillClient::PaymentsRpcResult result,
                  const std::string& real_pan,
                  bool is_virtual_card = false) {
    DCHECK(fido_authenticator().full_card_request_);
    payments::PaymentsNetworkInterface::UnmaskResponseDetails response;
    response.card_type = is_virtual_card ? payments::PaymentsAutofillClient::
                                               PaymentsRpcCardType::kVirtualCard
                                         : payments::PaymentsAutofillClient::
                                               PaymentsRpcCardType::kServerCard;
    fido_authenticator().full_card_request_->OnDidGetRealPan(
        result, response.with_real_pan(real_pan));
  }

  // Mocks an OptChange response from the PaymentsNetworkInterface.
  void OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult result,
                 bool user_is_opted_in,
                 bool include_creation_options = false,
                 bool include_request_options = false) {
    payments::PaymentsNetworkInterface::OptChangeResponseDetails response;
    response.user_is_opted_in = user_is_opted_in;
    if (include_creation_options) {
      response.fido_creation_options =
          GetTestCreationOptions(kTestChallenge, kTestRelyingPartyId);
    }
    if (include_request_options) {
      response.fido_request_options = GetTestRequestOptions(
          kTestChallenge, kTestRelyingPartyId, kTestCredentialId);
    }
    fido_authenticator().OnDidGetOptChangeResult(result, response);
  }

  void SetUserOptInPreference(bool user_is_opted_in) {
    ::autofill::prefs::SetCreditCardFIDOAuthEnabled(autofill_client_.GetPrefs(),
                                                    user_is_opted_in);
    fido_authenticator().user_is_opted_in_ =
        fido_authenticator().IsUserOptedIn();
  }

 protected:
  CreditCardFidoAuthenticator& fido_authenticator() {
    return *fido_authenticator_;
  }
  TestPersonalDataManager& personal_data_manager() {
    return static_cast<TestPersonalDataManager&>(
        *autofill_client_.GetPersonalDataManager());
  }
  TestAuthenticationRequester& requester() { return requester_; }

 private:
  base::test::TaskEnvironment task_environment_;
  variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
      variations::VariationsIdsProvider::Mode::kUseSignedInState};
  TestAutofillClient autofill_client_;
  TestAutofillDriver autofill_driver_{&autofill_client_};
  TestAuthenticationRequester requester_;
  std::unique_ptr<CreditCardFidoAuthenticator> fido_authenticator_;
};

TEST_F(CreditCardFidoAuthenticatorTest, IsUserOptedIn_False) {
  SetUserOptInPreference(false);
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());
}

TEST_F(CreditCardFidoAuthenticatorTest, IsUserOptedIn_True) {
  SetUserOptInPreference(true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());
}

#if BUILDFLAG(IS_ANDROID)
TEST_F(CreditCardFidoAuthenticatorTest,
       GetUserOptInIntention_IntentToOptIn_Android) {
  // If payments is offering to opt-in, then that means user is not opted in
  // from payments.
  payments::PaymentsNetworkInterface::UnmaskDetails unmask_details;
  unmask_details.offer_fido_opt_in = true;
  // Set the local preference to be enabled, which denotes user manually opted
  // in from settings page, and Payments did not update the status in time.
  SetUserOptInPreference(true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());

  EXPECT_EQ(fido_authenticator().GetUserOptInIntention(unmask_details),
            UserOptInIntention::kIntentToOptIn);
  // On Android, the local pref is not consistent with payments until opt-in
  // succeeds, so it is unnecessary to check that IsUserOptedIn() is true here,
  // since it will not have updated yet.
}
#else
TEST_F(CreditCardFidoAuthenticatorTest,
       GetUserOptInIntention_IntentToOptIn_Desktop) {
  // If payments is offering to opt-in, then that means user is not opted in
  // from payments.
  payments::PaymentsNetworkInterface::UnmaskDetails unmask_details;
  unmask_details.offer_fido_opt_in = true;
  // Set the local preference to be enabled, which denotes user manually opted
  // in from settings page and Payments did not update the status in time, or
  // something updated on the server side which caused Chrome to be out of sync.
  SetUserOptInPreference(true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());

  // We won't return user intent to opt in for Desktop.
  EXPECT_EQ(fido_authenticator().GetUserOptInIntention(unmask_details),
            UserOptInIntention::kUnspecified);
  // We update mismatched local pref for Desktop in order to be consistent with
  // payments.
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());
}
#endif

TEST_F(CreditCardFidoAuthenticatorTest, GetUserOptInIntention_IntentToOptOut) {
  // If payments is requesting a FIDO auth, then that means user is opted in
  // from payments.
  payments::PaymentsNetworkInterface::UnmaskDetails unmask_details;
  unmask_details.unmask_auth_method =
      payments::PaymentsAutofillClient::UnmaskAuthMethod::kFido;
  // Set the local preference to be disabled, which denotes user manually opted
  // out from settings page, and Payments did not update the status in time.
  SetUserOptInPreference(false);
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  EXPECT_EQ(fido_authenticator().GetUserOptInIntention(unmask_details),
            UserOptInIntention::kIntentToOptOut);
  // The local pref is not consistent with payments until opt-out succeeds, so
  // it is unnecessary to check that IsUserOptedIn() is false here, since it
  // will not have updated yet.
}

TEST_F(CreditCardFidoAuthenticatorTest, IsUserVerifiable_False) {
  fido_authenticator().IsUserVerifiable(
      base::BindOnce(&TestAuthenticationRequester::IsUserVerifiableCallback,
                     requester().GetWeakPtr()));
  EXPECT_FALSE(requester().is_user_verifiable().value());
}

TEST_F(CreditCardFidoAuthenticatorTest, ParseRequestOptions) {
  base::Value::Dict request_options_json = GetTestRequestOptions(
      kTestChallenge, kTestRelyingPartyId, kTestCredentialId);

  blink::mojom::PublicKeyCredentialRequestOptionsPtr request_options_ptr =
      fido_authenticator().ParseRequestOptions(std::move(request_options_json));
  EXPECT_EQ(kTestChallenge, BytesToBase64(request_options_ptr->challenge));
  EXPECT_EQ(kTestRelyingPartyId, request_options_ptr->relying_party_id);
  EXPECT_EQ(kTestCredentialId,
            BytesToBase64(request_options_ptr->allow_credentials.front().id));
  EXPECT_FALSE(request_options_ptr->extensions.is_null());
}

TEST_F(CreditCardFidoAuthenticatorTest, ParseAssertionResponse) {
  blink::mojom::GetAssertionAuthenticatorResponsePtr assertion_response_ptr =
      blink::mojom::GetAssertionAuthenticatorResponse::New();
  assertion_response_ptr->info = blink::mojom::CommonCredentialInfo::New();
  assertion_response_ptr->info->raw_id = Base64ToBytes(kTestCredentialId);
  assertion_response_ptr->signature = Base64ToBytes(kTestSignature);

  base::Value::Dict assertion_response_json =
      fido_authenticator().ParseAssertionResponse(
          std::move(assertion_response_ptr));
  EXPECT_EQ(kTestCredentialId,
            *assertion_response_json.FindString("credential_id"));
  EXPECT_EQ(kTestSignature, *assertion_response_json.FindString("signature"));
}

TEST_F(CreditCardFidoAuthenticatorTest, ParseCreationOptions) {
  base::Value::Dict creation_options_json =
      GetTestCreationOptions(kTestChallenge, kTestRelyingPartyId);

  blink::mojom::PublicKeyCredentialCreationOptionsPtr creation_options_ptr =
      fido_authenticator().ParseCreationOptions(
          std::move(creation_options_json));
  EXPECT_EQ(kTestChallenge, BytesToBase64(creation_options_ptr->challenge));
  EXPECT_EQ(kTestRelyingPartyId, creation_options_ptr->relying_party.id);

  // Ensure only platform authenticators are allowed.
  EXPECT_EQ(
      device::AuthenticatorAttachment::kPlatform,
      creation_options_ptr->authenticator_selection->authenticator_attachment);
  EXPECT_EQ(device::UserVerificationRequirement::kRequired,
            creation_options_ptr->authenticator_selection
                ->user_verification_requirement);
}

TEST_F(CreditCardFidoAuthenticatorTest, ParseAttestationResponse) {
  blink::mojom::MakeCredentialAuthenticatorResponsePtr
      attestation_response_ptr =
          blink::mojom::MakeCredentialAuthenticatorResponse::New();
  attestation_response_ptr->info = blink::mojom::CommonCredentialInfo::New();
  attestation_response_ptr->attestation_object = Base64ToBytes(kTestSignature);

  base::Value::Dict attestation_response_json =
      fido_authenticator().ParseAttestationResponse(
          std::move(attestation_response_ptr));
  EXPECT_EQ(kTestSignature, *attestation_response_json.FindStringByDottedPath(
                                "fido_attestation_info.attestation_object"));
}

TEST_F(CreditCardFidoAuthenticatorTest, AuthenticateCard_BadRequestOptions) {
  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);

  fido_authenticator().Authenticate(card, requester().GetWeakPtr(),
                                    base::Value::Dict());
  EXPECT_FALSE((*requester().did_succeed()));
}

TEST_F(CreditCardFidoAuthenticatorTest,
       AuthenticateCard_UserVerificationFailed) {
  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);

  fido_authenticator().Authenticate(
      card, requester().GetWeakPtr(),
      GetTestRequestOptions(kTestChallenge, kTestRelyingPartyId,
                            kTestCredentialId));

  TestCreditCardFidoAuthenticator::GetAssertion(&fido_authenticator(),
                                                /*did_succeed=*/false);
  EXPECT_FALSE((*requester().did_succeed()));
}

TEST_F(CreditCardFidoAuthenticatorTest,
       AuthenticateCard_PaymentsResponseError) {
  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);

  fido_authenticator().Authenticate(
      card, requester().GetWeakPtr(),
      GetTestRequestOptions(kTestChallenge, kTestRelyingPartyId,
                            kTestCredentialId));
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::AUTHENTICATION_FLOW,
            fido_authenticator().current_flow());

  // Mock user verification.
  TestCreditCardFidoAuthenticator::GetAssertion(&fido_authenticator(),
                                                /*did_succeed=*/true);
  GetRealPan(payments::PaymentsAutofillClient::PaymentsRpcResult::kNetworkError,
             "");

  EXPECT_FALSE((*requester().did_succeed()));
}

TEST_F(CreditCardFidoAuthenticatorTest,
       AuthenticateCard_PaymentsResponseVcnRetrievalError) {
  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);

  fido_authenticator().Authenticate(
      card, requester().GetWeakPtr(),
      GetTestRequestOptions(kTestChallenge, kTestRelyingPartyId,
                            kTestCredentialId));
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::AUTHENTICATION_FLOW,
            fido_authenticator().current_flow());

  // Mock user verification.
  TestCreditCardFidoAuthenticator::GetAssertion(&fido_authenticator(),
                                                /*did_succeed=*/true);
  GetRealPan(payments::PaymentsAutofillClient::PaymentsRpcResult::
                 kVcnRetrievalPermanentFailure,
             "", /*is_virtual_card=*/true);

  EXPECT_FALSE((*requester().did_succeed()));
  EXPECT_EQ(
      requester().failure_type(),
      payments::FullCardRequest::VIRTUAL_CARD_RETRIEVAL_PERMANENT_FAILURE);
}

TEST_F(CreditCardFidoAuthenticatorTest, AuthenticateCard_Success) {
  CreditCard card = CreateServerCard(kTestGUID, kTestNumber);

  fido_authenticator().Authenticate(
      card, requester().GetWeakPtr(),
      GetTestRequestOptions(kTestChallenge, kTestRelyingPartyId,
                            kTestCredentialId));
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::AUTHENTICATION_FLOW,
            fido_authenticator().current_flow());

  // Mock user verification and payments response.
  TestCreditCardFidoAuthenticator::GetAssertion(&fido_authenticator(),
                                                /*did_succeed=*/true);
  GetRealPan(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
             kTestNumber);

  EXPECT_TRUE((*requester().did_succeed()));
  EXPECT_EQ(kTestNumber16, requester().number());
}

TEST_F(CreditCardFidoAuthenticatorTest, OptIn_PaymentsResponseError) {
  base::HistogramTester histogram_tester;
  std::string histogram_name =
      "Autofill.BetterAuth.OptInCalled.FromCheckoutFlow";

  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(kTestAuthToken);
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_FETCH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());

  // Mock payments response.
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kNetworkError,
            /*user_is_opted_in=*/false);
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());
  histogram_tester.ExpectUniqueSample(
      histogram_name,
      autofill_metrics::WebauthnOptInParameters::kFetchingChallenge, 1);
}

TEST_F(CreditCardFidoAuthenticatorTest, OptIn_Success) {
  base::HistogramTester histogram_tester;
  std::string histogram_name =
      "Autofill.BetterAuth.OptInCalled.FromCheckoutFlow";

  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(kTestAuthToken);
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_FETCH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());

  // Mock payments response.
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());
  histogram_tester.ExpectUniqueSample(
      histogram_name,
      autofill_metrics::WebauthnOptInParameters::kFetchingChallenge, 1);
}

TEST_F(CreditCardFidoAuthenticatorTest, Register_BadCreationOptions) {
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(
      kTestAuthToken,
      GetTestCreationOptions(/*challenge=*/"", kTestRelyingPartyId));

  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());
}

TEST_F(CreditCardFidoAuthenticatorTest, Register_UserResponseFailure) {
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(
      kTestAuthToken,
      GetTestCreationOptions(kTestChallenge, kTestRelyingPartyId));
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_WITH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());

  // Mock user response and payments response.
  TestCreditCardFidoAuthenticator::MakeCredential(&fido_authenticator(),
                                                  /*did_succeed=*/false);
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());
}

TEST_F(CreditCardFidoAuthenticatorTest, Register_Success) {
  base::HistogramTester histogram_tester;
  std::string histogram_name =
      "Autofill.BetterAuth.OptInCalled.FromCheckoutFlow";

  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(
      kTestAuthToken,
      GetTestCreationOptions(kTestChallenge, kTestRelyingPartyId));
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_WITH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());

  // Mock user response and payments response.
  TestCreditCardFidoAuthenticator::MakeCredential(&fido_authenticator(),
                                                  /*did_succeed=*/true);
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());

  histogram_tester.ExpectUniqueSample(
      histogram_name,
      autofill_metrics::WebauthnOptInParameters::kWithCreationChallenge, 1);
}

TEST_F(CreditCardFidoAuthenticatorTest,
       Register_EnrollAttemptReturnsCreationOptions) {
  base::HistogramTester histogram_tester;
  std::string histogram_name =
      "Autofill.BetterAuth.OptInCalled.FromCheckoutFlow";

  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(kTestAuthToken);
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_FETCH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());

  // Mock payments response with challenge to invoke enrollment flow.
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/false, /*include_creation_options=*/true);
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_WITH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  // Mock user response and second payments response.
  TestCreditCardFidoAuthenticator::MakeCredential(&fido_authenticator(),
                                                  /*did_succeed=*/true);
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());

  histogram_tester.ExpectTotalCount(histogram_name, 2);
  histogram_tester.ExpectBucketCount(
      histogram_name,
      autofill_metrics::WebauthnOptInParameters::kFetchingChallenge, 1);
  histogram_tester.ExpectBucketCount(
      histogram_name,
      autofill_metrics::WebauthnOptInParameters::kWithCreationChallenge, 1);
}

#if !BUILDFLAG(IS_ANDROID)
// This test is not applicable for Android (we won't opt-in with Register).
TEST_F(CreditCardFidoAuthenticatorTest,
       Register_OptInAttemptReturnsRequestOptions) {
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Register(kTestAuthToken);
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_FETCH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());

  // Mock payments response with challenge to invoke opt-in flow.
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/false, /*include_creation_options=*/false,
            /*include_request_options=*/true);
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_IN_WITH_CHALLENGE_FLOW,
            fido_authenticator().current_flow());
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());

  // Mock user response and second payments response.
  TestCreditCardFidoAuthenticator::GetAssertion(&fido_authenticator(),
                                                /*did_succeed=*/true);
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());
}
#endif

TEST_F(CreditCardFidoAuthenticatorTest, Register_NewCardAuthorization) {
  SetUserOptInPreference(true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().Authorize(
      requester().GetWeakPtr(), kTestAuthToken,
      GetTestRequestOptions(kTestChallenge, kTestRelyingPartyId,
                            kTestCredentialId));
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::FOLLOWUP_AFTER_CVC_AUTH_FLOW,
            fido_authenticator().current_flow());

  // Mock user response and second payments response.
  TestCreditCardFidoAuthenticator::GetAssertion(&fido_authenticator(),
                                                /*did_succeed=*/true);
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/true);
  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());
}

// Test that if FIDO enrollment is offered, the enrollment histogram logs to the
// enrollment offered bucket.
TEST_F(CreditCardFidoAuthenticatorTest,
       Register_EnrollmentOfferedHistogramBucketLogs) {
  base::HistogramTester histogram_tester;

  SetUserOptInPreference(true);

  fido_authenticator().Authorize(
      requester().GetWeakPtr(), kTestAuthToken,
      GetTestRequestOptions(kTestChallenge, kTestRelyingPartyId,
                            kTestCredentialId));

  histogram_tester.ExpectUniqueSample(kEnrollmentOfferedHistogramName,
                                      /*sample=*/true,
                                      /*expected_bucket_count=*/1);
}

// Test that if FIDO enrollment is not offered, the enrollment histogram logs
// to the enrollment not offered bucket.
TEST_F(CreditCardFidoAuthenticatorTest,
       Register_EnrollmentNotOfferedHistogramBucketLogs) {
  base::HistogramTester histogram_tester;

  SetUserOptInPreference(true);

  fido_authenticator().Authorize(requester().GetWeakPtr(), kTestAuthToken,
                                 base::Value::Dict());

  histogram_tester.ExpectUniqueSample(kEnrollmentOfferedHistogramName,
                                      /*sample=*/false,
                                      /*expected_bucket_count=*/1);
}

TEST_F(CreditCardFidoAuthenticatorTest, OptOut_Success) {
  SetUserOptInPreference(true);

  EXPECT_TRUE(fido_authenticator().IsUserOptedIn());

  fido_authenticator().OptOut();
  EXPECT_EQ(CreditCardFidoAuthenticator::Flow::OPT_OUT_FLOW,
            fido_authenticator().current_flow());

  // Mock payments response.
  OptChange(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
            /*user_is_opted_in=*/false);
  EXPECT_FALSE(fido_authenticator().IsUserOptedIn());
}

}  // namespace autofill