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

#include "content/browser/webid/request.h"

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

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webid/disconnect_request.h"
#include "content/browser/webid/idp_network_request_manager.h"
#include "content/browser/webid/metrics.h"
#include "content/browser/webid/request_page_data.h"
#include "content/browser/webid/request_service.h"
#include "content/browser/webid/test/delegated_idp_network_request_manager.h"
#include "content/browser/webid/test/federated_request_token_callback_helper.h"
#include "content/browser/webid/test/mock_api_permission_delegate.h"
#include "content/browser/webid/test/mock_auto_reauthn_permission_delegate.h"
#include "content/browser/webid/test/mock_identity_registry.h"
#include "content/browser/webid/test/mock_identity_request_dialog_controller.h"
#include "content/browser/webid/test/mock_idp_network_request_manager.h"
#include "content/browser/webid/test/mock_permission_delegate.h"
#include "content/browser/webid/webid_utils.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/webid/federated_embedder_login_request.h"
#include "content/public/browser/webid/identity_credential_source.h"
#include "content/public/browser/webid/identity_request_dialog_controller.h"
#include "content/public/common/content_features.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/fake_local_frame.h"
#include "content/public/test/mock_web_contents_observer.h"
#include "content/public/test/navigation_simulator.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "metrics.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/http/http_status_code.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/webid/login_status_account.h"
#include "third_party/blink/public/common/webid/login_status_options.h"
#include "ui/base/page_transition_types.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content::webid {

using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Return;
using ::testing::StrictMock;
using ApiPermissionStatus =
    FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using DismissReason = IdentityRequestDialogController::DismissReason;
using ErrorDialogType = IdpNetworkRequestManager::FedCmErrorDialogType;
using ErrorUrlType = IdpNetworkRequestManager::FedCmErrorUrlType;
using FedCmEntry = ukm::builders::Blink_FedCm;
using FedCmIdpEntry = ukm::builders::Blink_FedCmIdp;
using IdentityProviderDataPtr = scoped_refptr<IdentityProviderData>;
using IdentityRequestAccountPtr = scoped_refptr<IdentityRequestAccount>;
using LoginState = IdentityRequestAccount::LoginState;
using MediationRequirement = ::password_manager::CredentialMediationRequirement;
using RequestCallbackHelper = FederatedRequestTokenCallbackHelper;
using RpMode = blink::mojom::RpMode;
using SignInMode = IdentityRequestAccount::SignInMode;
using TokenError = IdentityCredentialTokenError;
using TokenResponseType = IdpNetworkRequestManager::FedCmTokenResponseType;
using TokenStatus = RequestIdTokenStatus;
using blink::mojom::FederatedRequest;
using blink::mojom::FederatedRequestResult;
using blink::mojom::FederatedRequestService;
using blink::mojom::RequestTokenStatus;
using blink::mojom::TokenRequestFailurePtr;
using blink::mojom::TokenRequestSuccessPtr;

namespace {

constexpr char kProviderUrlFull[] = "https://idp.example/fedcm.json";
constexpr char kRpUrl[] = "https://rp.example/";
constexpr char kRpOtherUrl[] = "https://rp.example/random/";
constexpr char kIdpUrl[] = "https://idp.example/";
constexpr char kAccountsEndpoint[] = "https://idp.example/accounts";
constexpr char kCrossOriginAccountsEndpoint[] = "https://idp2.example/accounts";
constexpr char kTokenEndpoint[] = "https://idp.example/token";
constexpr char kClientMetadataEndpoint[] =
    "https://idp.example/client_metadata";
constexpr char kMetricsEndpoint[] = "https://idp.example/metrics";
constexpr char kIdpBrandIconUrl[] = "https://idp.example/icon";
constexpr char kIdpLoginUrl[] = "https://idp.example/login_url";
constexpr char kIdpDisconnectUrl[] = "https://idp.example/disconnect";
constexpr char kPrivacyPolicyUrl[] = "https://rp.example/pp";
constexpr char kTermsOfServiceUrl[] = "https://rp.example/tos";
constexpr char kRpBrandIconUrl[] = "https://rp.example/icon";
constexpr char kClientId[] = "client_id_123";
constexpr char kNonce[] = "nonce123";
constexpr char kAccountEmailNicolas[] = "nicolas@email.com";
constexpr char kAccountEmailPeter[] = "peter@email.com";
constexpr char kAccountEmailZach[] = "zach@email.com";
constexpr char kAccountId[] = "1234";
constexpr char kAccountIdNicolas[] = "nico_id";
constexpr char kAccountIdPeter[] = "peter_id";
constexpr char kAccountIdZach[] = "zach_id";
constexpr char kAccountPicture[] = "https://idp.example/profilepic";
constexpr char kAccountPicture404[] = "https://idp.example/404";
constexpr int kAccountPictureSize = 10;
constexpr char kEmail[] = "ken@idp.example";
constexpr char kDomainHint[] = "domain@corp.com";
constexpr char kOtherDomainHint[] = "other_domain@corp.com";

// Values will be added here as token introspection is implemented.
constexpr char kToken[] = "[not a real token]";
constexpr char kEmptyToken[] = "";

constexpr char kFilterNoMatchMessage[] =
    "Accounts were received, but none matched the login hint, domain hint, "
    "and/or account labels provided.";

static const std::vector<std::string> kDomainHintVector = {kDomainHint};
static const std::vector<std::string> kLabelVector = {"label"};
static const std::vector<std::string> kLoginHints = {kAccountId, kEmail};
static const std::vector<std::string> kNicolasHints = {kAccountIdNicolas,
                                                       kAccountEmailNicolas};
static const std::vector<std::string> kPeterHints = {kAccountIdPeter,
                                                     kAccountEmailPeter};
static const std::vector<std::string> kTwoDomainHints = {kDomainHint,
                                                         kOtherDomainHint};
static const std::vector<std::string> kZachHints = {kAccountIdZach,
                                                    kAccountEmailZach};

static std::vector<IdentityRequestAccountPtr> kMultipleAccounts;
static std::vector<IdentityRequestAccountPtr>
    kMultipleAccountsWithHintsAndDomains;
static std::vector<IdentityRequestAccountPtr> kSingleAccount;
static std::vector<IdentityRequestAccountPtr> kSingleAccountWithDomainHint;
static std::vector<IdentityRequestAccountPtr> kSingleAccountWithHint;
static std::vector<IdentityRequestAccountPtr> kTwoAccounts;

static const std::set<std::string> kWellKnown{kProviderUrlFull};

struct IdentityProviderParameters {
  const char* provider;
  const char* client_id;
  const char* nonce;
  const char* login_hint;
  const char* domain_hint;
  std::optional<std::vector<std::string>> fields;
  const char* params_json;
  std::optional<std::string> type;
  bool from_idp_registration_api = false;
};

// Parameters for a call to RequestToken.
struct RequestParameters {
  std::vector<IdentityProviderParameters> identity_providers;
  blink::mojom::RpContext rp_context;
  blink::mojom::RpMode rp_mode;
};

// Expected return values from a call to RequestToken.
//
// DO NOT ADD NEW MEMBERS.
// Having a lot of members in RequestExpectations encourages bad test design.
// Specifically:
// - It encourages making the test harness more magic
// - It makes each test "test everything", making it really hard to determine
//   at a later date what the test was actually testing.

struct RequestExpectations {
  std::optional<RequestTokenStatus> return_status;
  FederatedRequestResult devtools_issue_status;
  std::optional<std::string> standalone_console_message;
  std::optional<std::string> selected_idp_config_url;
  bool is_auto_selected{false};
};

// Mock configuration values for test.
struct MockClientIdConfiguration {
  FetchStatus fetch_status;
  std::string privacy_policy_url;
  std::string terms_of_service_url;
  std::string brand_icon_url;
};

struct MockWellKnown {
  std::set<std::string> provider_urls;
  FetchStatus fetch_status;
};

// Mock information returned from IdpNetworkRequestManager::FetchConfig().
struct MockConfig {
  FetchStatus fetch_status;
  std::string accounts_endpoint;
  std::string token_endpoint;
  std::string client_metadata_endpoint;
  std::string metrics_endpoint;
  std::string idp_login_url;
  std::string disconnect_endpoint;
  std::string vc_issuance_endpoint;
  std::optional<SkColor> brand_background_color;
  std::optional<SkColor> brand_text_color;
  std::string requested_label;
  std::vector<std::string> types;
};

struct MockIdpInfo {
  MockWellKnown well_known;
  MockConfig config;
  MockClientIdConfiguration client_metadata;
  FetchStatus accounts_response;
  std::vector<IdentityRequestAccountPtr> accounts;
};

// Action on accounts dialog taken by TestDialogController. Does not indicate a
// test expectation.
enum class AccountsDialogAction {
  kNone,
  kClose,
  kSelectFirstAccount,
  kAddAccount,
};

// Action on IdP-sign-in-status-mismatch dialog taken by TestDialogController.
// Does not indicate a test expectation.
enum class IdpSigninStatusMismatchDialogAction {
  kNone,
  kClose,
  kClosePopup,
};

// Action on error dialog taken by TestDialogController.
// Does not indicate a test expectation.
enum class ErrorDialogAction {
  kNone,
  kClose,
  kSwipe,
  kGotIt,
  kMoreDetails,
};

// Action on loading dialog taken by TestDialogController.
// Does not indicate a test expectation.
enum class LoadingDialogAction {
  kNone,
  kClose,
};

struct MockConfiguration {
  const char* token;
  base::flat_map<std::string, MockIdpInfo> idp_info;
  FetchStatus token_response;
  bool delay_token_response;
  AccountsDialogAction accounts_dialog_action;
  IdpSigninStatusMismatchDialogAction idp_signin_status_mismatch_dialog_action;
  ErrorDialogAction error_dialog_action;
  LoadingDialogAction loading_dialog_action;
  std::optional<GURL> continue_on;
  MediationRequirement mediation_requirement = MediationRequirement::kOptional;
  std::optional<TokenError> token_error;
  TokenResponseType token_response_type = TokenResponseType::
      kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
  std::optional<ErrorDialogType> error_dialog_type;
  std::optional<ErrorUrlType> error_url_type;
  blink::mojom::RpMode rp_mode{blink::mojom::RpMode::kPassive};
  bool suppressed_by_segmentation_platform{false};
};

static const MockClientIdConfiguration kDefaultClientMetadata{
    {ParseStatus::kSuccess, net::HTTP_OK},
    kPrivacyPolicyUrl,
    kTermsOfServiceUrl,
    kRpBrandIconUrl};

static const IdentityProviderParameters kDefaultIdentityProviderRequestOptions{
    kProviderUrlFull, kClientId, kNonce, /*login_hint=*/"",
    /*domain_hint=*/""};

static const RequestParameters kDefaultRequestParameters{
    std::vector<IdentityProviderParameters>{
        kDefaultIdentityProviderRequestOptions},
    blink::mojom::RpContext::kSignIn, blink::mojom::RpMode::kPassive};

static MockIdpInfo kDefaultIdentityProviderInfo;
static MockIdpInfo kProviderTwoInfo;

static base::flat_map<std::string, MockIdpInfo> kSingleProviderInfo;

constexpr char kProviderTwoUrlFull[] = "https://idp2.example/fedcm.json";

static MockConfiguration kConfigurationValid;
static MockConfiguration kConfigurationMultiIdpValid;

static const RequestExpectations kExpectationSuccess{
    RequestTokenStatus::kSuccess, FederatedRequestResult::kSuccess,
    /*standalone_console_message=*/std::nullopt, kProviderUrlFull};

static const RequestParameters kDefaultMultiIdpRequestParameters{
    std::vector<IdentityProviderParameters>{
        {kProviderUrlFull, kClientId, kNonce, /*login_hint=*/"",
         /*domain_hint=*/""},
        {kProviderTwoUrlFull, kClientId, kNonce, /*login_hint=*/"",
         /*domain_hint=*/""}},
    /*rp_context=*/blink::mojom::RpContext::kSignIn,
    /*rp_mode=*/blink::mojom::RpMode::kPassive};

url::Origin OriginFromString(const std::string& url_string) {
  return url::Origin::Create(GURL(url_string));
}

enum class FetchedEndpoint {
  CONFIG,
  CLIENT_METADATA,
  ACCOUNTS,
  TOKEN,
  WELL_KNOWN,
  PICTURE,
};

class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
 public:
  void SetTestConfig(const MockConfiguration& configuration) {
    config_ = configuration;
  }

  void RunDelayedCallbacks() {
    for (base::OnceClosure& delayed_callback : delayed_callbacks_) {
      std::move(delayed_callback).Run();
    }
    delayed_callbacks_.clear();
  }

  void FetchWellKnown(const GURL& provider,
                      FetchWellKnownCallback callback) override {
    ++num_fetched_[FetchedEndpoint::WELL_KNOWN];

    std::string provider_key = provider.spec();
    IdpNetworkRequestManager::WellKnown well_known;
    std::set<GURL> url_set(
        config_.idp_info[provider_key].well_known.provider_urls.begin(),
        config_.idp_info[provider_key].well_known.provider_urls.end());
    well_known.provider_urls = std::move(url_set);

    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback),
                       config_.idp_info[provider_key].well_known.fetch_status,
                       well_known));
  }

  void FetchConfig(const GURL& provider,
                   int idp_brand_icon_ideal_size,
                   int idp_brand_icon_minimum_size,
                   FetchConfigCallback callback) override {
    ++num_fetched_[FetchedEndpoint::CONFIG];

    std::string provider_key = provider.spec();
    const MockConfig& config = config_.idp_info[provider_key].config;
    IdpNetworkRequestManager::Endpoints endpoints;
    endpoints.token = GURL(config.token_endpoint);
    endpoints.accounts = GURL(config.accounts_endpoint);
    endpoints.client_metadata = GURL(config.client_metadata_endpoint);
    endpoints.metrics = GURL(config.metrics_endpoint);
    endpoints.disconnect = GURL(config.disconnect_endpoint);
    endpoints.issuance = GURL(config.vc_issuance_endpoint);

    IdentityProviderMetadata idp_metadata;
    idp_metadata.config_url = provider;
    idp_metadata.idp_login_url = GURL(config.idp_login_url);
    idp_metadata.brand_background_color = config.brand_background_color;
    idp_metadata.brand_icon_url = GURL(kIdpBrandIconUrl);
    idp_metadata.brand_text_color = config.brand_text_color;
    idp_metadata.requested_label = config.requested_label;
    idp_metadata.types = config.types;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback),
                       config_.idp_info[provider_key].config.fetch_status,
                       endpoints, idp_metadata));
  }

  void FetchClientMetadata(const GURL& endpoint,
                           const std::string& client_id,
                           int rp_brand_icon_ideal_size,
                           int rp_brand_icon_minimum_size,
                           FetchClientMetadataCallback callback) override {
    ++num_fetched_[FetchedEndpoint::CLIENT_METADATA];

    // Find the info of the provider with the same client metadata endpoint.
    MockIdpInfo info;
    for (const auto& idp_info : config_.idp_info) {
      info = idp_info.second;
      if (GURL(info.config.client_metadata_endpoint) == endpoint) {
        break;
      }
    }

    IdpNetworkRequestManager::ClientMetadata client_metadata;
    client_metadata.privacy_policy_url =
        GURL(info.client_metadata.privacy_policy_url);
    client_metadata.terms_of_service_url =
        GURL(info.client_metadata.terms_of_service_url);
    client_metadata.brand_icon_url = GURL(info.client_metadata.brand_icon_url);
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), info.client_metadata.fetch_status,
                       client_metadata));
  }

  bool SendAccountsRequest(const url::Origin& idp_origin,
                           const GURL& accounts_url,
                           AccountsRequestCallback callback) override {
    ++num_fetched_[FetchedEndpoint::ACCOUNTS];

    // Find the info of the provider with the same accounts endpoint.
    MockIdpInfo info;
    for (const auto& idp_info : config_.idp_info) {
      info = idp_info.second;
      if (GURL(info.config.accounts_endpoint) == accounts_url) {
        break;
      }
    }

    IdpNetworkRequestManager::AccountsResponse response;
    response.accounts = accounts_list_.empty() ? info.accounts : accounts_list_;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), info.accounts_response, response));
    return true;
  }

  void SendTokenRequest(
      const GURL& token_url,
      const std::string& account,
      const std::string& url_encoded_post_data,
      bool idp_blidness,
      TokenRequestCallback callback,
      ContinueOnCallback on_continue,
      RedirectToCallback redirect_to,
      RecordErrorMetricsCallback record_error_metrics_callback) override {
    ++num_fetched_[FetchedEndpoint::TOKEN];

    base::OnceCallback bound_record_error_metrics_callback = base::BindOnce(
        std::move(record_error_metrics_callback), config_.token_response_type,
        config_.error_dialog_type, config_.error_url_type);
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(bound_record_error_metrics_callback));

    if (config_.token_error) {
      TokenResult result;
      result.error = config_.token_error;

      base::OnceCallback bound_callback = base::BindOnce(
          std::move(callback), config_.token_response, std::move(result));
      if (config_.delay_token_response) {
        delayed_callbacks_.push_back(std::move(bound_callback));
      } else {
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, std::move(bound_callback));
      }
      return;
    }

    if (config_.continue_on) {
      base::OnceCallback bound_callback =
          base::BindOnce(std::move(on_continue), config_.token_response,
                         config_.continue_on.value());
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(bound_callback));
      return;
    }

    std::string delivered_token =
        config_.token_response.parse_status == ParseStatus::kSuccess
            ? config_.token
            : std::string();
    TokenResult result;
    result.token = base::Value(delivered_token);
    base::OnceCallback bound_callback = base::BindOnce(
        std::move(callback), config_.token_response, std::move(result));
    if (config_.delay_token_response) {
      delayed_callbacks_.push_back(std::move(bound_callback));
    } else {
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, std::move(bound_callback));
    }
  }

  void DownloadAndDecodeImage(const GURL& url,
                              ImageCallback callback) override {
    EXPECT_TRUE(url == GURL(kAccountPicture) ||
                url == GURL(kAccountPicture404) ||
                url == GURL(kRpBrandIconUrl) || url == GURL(kIdpBrandIconUrl))
        << url;
    ++num_fetched_[FetchedEndpoint::PICTURE];
    if (url == GURL(kAccountPicture404)) {
      std::move(callback).Run(gfx::Image());
    } else {
      std::move(callback).Run(gfx::test::CreateImage(kAccountPictureSize));
    }
  }

  std::map<FetchedEndpoint, size_t> num_fetched_;

  // If non-empty, the accounts endpoint will return this accounts list instead
  // of the accounts list in the `config_`.
  std::vector<IdentityRequestAccountPtr> accounts_list_;

 protected:
  MockConfiguration config_{kConfigurationValid};
  std::vector<base::OnceClosure> delayed_callbacks_;
};

// TestIdpNetworkRequestManager subclass which checks the values of the method
// params when executing an endpoint request.
class IdpNetworkRequestManagerParamChecker
    : public TestIdpNetworkRequestManager {
 public:
  void SetExpectations(const std::string& expected_client_id,
                       const std::string& expected_selected_account_id) {
    expected_client_id_ = expected_client_id;
    expected_selected_account_id_ = expected_selected_account_id;
  }

  void SetExpectedTokenPostData(
      const std::string& expected_url_encoded_post_data) {
    expected_url_encoded_post_data_ = expected_url_encoded_post_data;
  }

  void FetchClientMetadata(const GURL& endpoint,
                           const std::string& client_id,
                           int rp_brand_icon_ideal_size,
                           int rp_brand_icon_minimum_size,
                           FetchClientMetadataCallback callback) override {
    if (expected_client_id_) {
      EXPECT_EQ(expected_client_id_, client_id);
    }
    TestIdpNetworkRequestManager::FetchClientMetadata(
        endpoint, client_id, rp_brand_icon_ideal_size,
        rp_brand_icon_minimum_size, std::move(callback));
  }

  bool SendAccountsRequest(const url::Origin& idp_origin,
                           const GURL& accounts_url,
                           AccountsRequestCallback callback) override {
    return TestIdpNetworkRequestManager::SendAccountsRequest(
        idp_origin, accounts_url, std::move(callback));
  }

  void SendTokenRequest(
      const GURL& token_url,
      const std::string& account,
      const std::string& url_encoded_post_data,
      bool idp_blindness,
      TokenRequestCallback callback,
      ContinueOnCallback on_continue,
      RedirectToCallback redirect_to,
      RecordErrorMetricsCallback record_error_metrics_callback) override {
    if (expected_selected_account_id_) {
      EXPECT_EQ(expected_selected_account_id_, account);
    }
    if (expected_url_encoded_post_data_) {
      EXPECT_EQ(expected_url_encoded_post_data_, url_encoded_post_data);
    }
    TestIdpNetworkRequestManager::SendTokenRequest(
        token_url, account, url_encoded_post_data, idp_blindness,
        std::move(callback), std::move(on_continue),
        IdpNetworkRequestManager::RedirectToCallback(),
        std::move(record_error_metrics_callback));
  }

 private:
  std::optional<std::string> expected_client_id_;
  std::optional<std::string> expected_selected_account_id_;
  std::optional<std::string> expected_url_encoded_post_data_;
};

class TestDialogController
    : public NiceMock<MockIdentityRequestDialogController> {
 public:
  struct State {
    // State related to ShowAccountsDialog().
    // The list of all accounts passed to the UI.
    std::vector<IdentityRequestAccountPtr> all_accounts_for_display;
    std::optional<IdentityRequestAccount::SignInMode> sign_in_mode;
    blink::mojom::RpContext rp_context;
    std::vector<IdentityRequestAccountPtr> new_accounts;
    // The last seen background/text color from IdP metadata.
    std::optional<SkColor> brand_background_color;
    std::optional<SkColor> brand_text_color;
    // State related to ShowFailureDialog().
    size_t num_show_idp_signin_status_mismatch_dialog_requests{0u};
    IdentityRequestDialogController::DismissCallback mismatch_dismiss_callback;
    // State related to ShowErrorDialog().
    bool did_show_error_dialog{false};
    std::optional<TokenError> token_error;
    // State related to ShowLoadingDialog().
    bool did_show_loading_dialog{false};
    // List of IDP strings for which a mismatch is shown in a test.
    std::vector<std::string> displayed_mismatch_idps;
  };

  explicit TestDialogController(MockConfiguration config)
      : accounts_dialog_action_(config.accounts_dialog_action),
        idp_signin_status_mismatch_dialog_action_(
            config.idp_signin_status_mismatch_dialog_action),
        error_dialog_action_(config.error_dialog_action),
        loading_dialog_action_(config.loading_dialog_action),
        passive_dialog_volume_(
            config.suppressed_by_segmentation_platform
                ? IdentityRequestDialogController::PassiveDialogVolume::kAmbient
                : IdentityRequestDialogController::PassiveDialogVolume::
                      kDefault) {}

  void UpdateConfiguration(MockConfiguration config) {
    accounts_dialog_action_ = config.accounts_dialog_action;
    idp_signin_status_mismatch_dialog_action_ =
        config.idp_signin_status_mismatch_dialog_action;
    error_dialog_action_ = config.error_dialog_action;
    loading_dialog_action_ = config.loading_dialog_action;
    passive_dialog_volume_ =
        config.suppressed_by_segmentation_platform
            ? IdentityRequestDialogController::PassiveDialogVolume::kAmbient
            : IdentityRequestDialogController::PassiveDialogVolume::kDefault;
  }

  ~TestDialogController() override = default;
  TestDialogController(TestDialogController&) = delete;
  TestDialogController& operator=(TestDialogController&) = delete;

  void SetState(State* state) { state_ = state; }

  void SetIdpSigninStatusMismatchDialogAction(
      IdpSigninStatusMismatchDialogAction action) {
    idp_signin_status_mismatch_dialog_action_ = action;
  }

  void GetPassiveDialogVolume(GetPassiveDialogVolumeCallback cb) override {
    std::move(cb).Run(passive_dialog_volume_);
  }

  bool ShowAccountsDialog(
      RelyingPartyData rp_data,
      const std::vector<IdentityProviderDataPtr>& idp_list,
      const std::vector<IdentityRequestAccountPtr>& accounts,
      const std::vector<IdentityRequestAccountPtr>& filtered_accounts,
      blink::mojom::RpMode rp_mode,
      IdentityRequestDialogController::AccountSelectionCallback on_selected,
      IdentityRequestDialogController::LoginToIdPCallback on_add_account,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::AccountsDisplayedCallback
          accounts_displayed_callback) override {
    if (!state_) {
      return false;
    }
    state_->all_accounts_for_display.clear();

    // Auto reauthn should not call ShowAccountsDialog.
    state_->sign_in_mode = SignInMode::kExplicit;
    state_->rp_context = idp_list[0]->rp_context;

    state_->new_accounts.clear();
    for (const auto& account : accounts) {
      if (account->display_priority ==
          IdentityRequestAccount::DisplayPriority::kNew) {
        state_->new_accounts.push_back(account);
      }
    }

    state_->all_accounts_for_display = accounts;
    for (const auto& idp_data : idp_list) {
      if (idp_data->has_login_status_mismatch) {
        state_->displayed_mismatch_idps.push_back(idp_data->idp_for_display);
      }
      EXPECT_FALSE(idp_data->idp_metadata.brand_decoded_icon.IsEmpty());
      state_->brand_background_color =
          idp_data->idp_metadata.brand_background_color;
      state_->brand_text_color = idp_data->idp_metadata.brand_text_color;
    }

    switch (accounts_dialog_action_) {
      case AccountsDialogAction::kSelectFirstAccount: {
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(
                std::move(on_selected),
                accounts[0]->identity_provider->idp_metadata.config_url,
                accounts[0]->id,
                accounts[0]->idp_claimed_login_state.value_or(
                    accounts[0]->browser_trusted_login_state) ==
                    LoginState::kSignIn));
        break;
      }
      case AccountsDialogAction::kClose:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(dismiss_callback),
                                      DismissReason::kCloseButton));
        break;
      case AccountsDialogAction::kAddAccount:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(on_add_account),
                                      idp_list[0]->idp_metadata.config_url,
                                      idp_list[0]->idp_metadata.idp_login_url));
        // Set `accounts_dialog_action_` such that subsequent calls will select
        // the first account.
        accounts_dialog_action_ = AccountsDialogAction::kSelectFirstAccount;
        break;
      case AccountsDialogAction::kNone:
        break;
    }
    std::move(accounts_displayed_callback).Run();
    return true;
  }

  bool ShowFailureDialog(
      const RelyingPartyData& rp_data,
      const std::string& idp_for_display,
      blink::mojom::RpContext rp_context,
      blink::mojom::RpMode rp_mode,
      const IdentityProviderMetadata& idp_metadata,
      const std::vector<scoped_refptr<IdentityRequestAccount>>&
          filtered_accounts,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::LoginToIdPCallback
          identity_registry_callback) override {
    if (!state_) {
      return false;
    }

    state_->displayed_mismatch_idps.push_back(idp_for_display);
    ++state_->num_show_idp_signin_status_mismatch_dialog_requests;
    switch (idp_signin_status_mismatch_dialog_action_) {
      case IdpSigninStatusMismatchDialogAction::kClose:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(dismiss_callback),
                                      DismissReason::kCloseButton));
        break;
      case IdpSigninStatusMismatchDialogAction::kClosePopup:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(dismiss_callback), DismissReason::kOther));
        break;
      case IdpSigninStatusMismatchDialogAction::kNone:
        state_->mismatch_dismiss_callback = std::move(dismiss_callback);
        break;
    }
    return true;
  }

  bool ShowErrorDialog(
      const RelyingPartyData& rp_data,
      const std::string& idp_for_display,
      blink::mojom::RpContext rp_context,
      blink::mojom::RpMode rp_mode,
      const IdentityProviderMetadata& idp_metadata,
      const std::optional<TokenError>& error,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::MoreDetailsCallback
          more_details_callback) override {
    if (!state_) {
      return false;
    }

    state_->did_show_error_dialog = true;
    state_->token_error = error;
    switch (error_dialog_action_) {
      case ErrorDialogAction::kClose:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(dismiss_callback),
                                      DismissReason::kCloseButton));
        break;
      case ErrorDialogAction::kSwipe:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE,
            base::BindOnce(std::move(dismiss_callback), DismissReason::kSwipe));
        break;
      case ErrorDialogAction::kGotIt:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(dismiss_callback),
                                      DismissReason::kGotItButton));
        break;
      case ErrorDialogAction::kMoreDetails:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(more_details_callback)));
        break;
      case ErrorDialogAction::kNone:
        break;
    }
    return true;
  }

  bool ShowLoadingDialog(const RelyingPartyData& rp_data,
                         const std::string& idp_for_display,
                         blink::mojom::RpContext rp_context,
                         blink::mojom::RpMode rp_mode,
                         IdentityRequestDialogController::DismissCallback
                             dismiss_callback) override {
    if (!state_) {
      return false;
    }

    state_->did_show_loading_dialog = true;
    switch (loading_dialog_action_) {
      case LoadingDialogAction::kClose:
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(dismiss_callback),
                                      DismissReason::kCloseButton));
        break;
      case LoadingDialogAction::kNone:
        break;
    }
    return true;
  }

  bool ShowVerifyingDialog(
      const RelyingPartyData& rp_data,
      const scoped_refptr<IdentityProviderData>& idp_data,
      const scoped_refptr<IdentityRequestAccount>& account,
      IdentityRequestAccount::SignInMode sign_in_mode,
      blink::mojom::RpMode rp_mode,
      IdentityRequestDialogController::AccountsDisplayedCallback
          accounts_displayed_callback) override {
    if (!state_) {
      return false;
    }

    state_->sign_in_mode = sign_in_mode;
    state_->all_accounts_for_display = {account};
    std::move(accounts_displayed_callback).Run();
    return true;
  }

  base::WeakPtr<TestDialogController> AsWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  AccountsDialogAction accounts_dialog_action_{AccountsDialogAction::kNone};

 protected:
  IdpSigninStatusMismatchDialogAction idp_signin_status_mismatch_dialog_action_{
      IdpSigninStatusMismatchDialogAction::kNone};
  ErrorDialogAction error_dialog_action_{ErrorDialogAction::kNone};
  LoadingDialogAction loading_dialog_action_{LoadingDialogAction::kNone};
  IdentityRequestDialogController::PassiveDialogVolume passive_dialog_volume_;

  // Pointer so that the state can be queried after Request
  // destroys TestDialogController.
  raw_ptr<State> state_;
  base::WeakPtrFactory<TestDialogController> weak_ptr_factory_{this};
};

class TestApiPermissionDelegate : public MockApiPermissionDelegate {
 public:
  using PermissionOverride = std::pair<url::Origin, ApiPermissionStatus>;
  PermissionOverride permission_override_ =
      std::make_pair(url::Origin(), ApiPermissionStatus::GRANTED);
  std::optional<std::pair<size_t, PermissionOverride>>
      permission_override_for_nth_;
  std::set<url::Origin> embargoed_origins_;
  size_t api_invocation_counter{0};

  ApiPermissionStatus GetApiPermissionStatus(
      const url::Origin& origin) override {
    ++api_invocation_counter;

    if (embargoed_origins_.count(origin)) {
      return ApiPermissionStatus::BLOCKED_EMBARGO;
    }

    if (permission_override_for_nth_ &&
        permission_override_for_nth_->first == api_invocation_counter) {
      return (origin == permission_override_for_nth_->second.first)
                 ? permission_override_for_nth_->second.second
                 : ApiPermissionStatus::GRANTED;
    }

    return (origin == permission_override_.first)
               ? permission_override_.second
               : ApiPermissionStatus::GRANTED;
  }

  void RecordDismissAndEmbargo(const url::Origin& origin) override {
    embargoed_origins_.insert(origin);
  }

  void RemoveEmbargoAndResetCounts(const url::Origin& origin) override {
    embargoed_origins_.erase(origin);
  }
};

class TestPermissionDelegate : public NiceMock<MockPermissionDelegate> {
 public:
  std::map<url::Origin, std::optional<bool>> idp_signin_statuses_;
  std::map<url::Origin, std::vector<IdentityRequestAccountPtr>>
      idp_account_profiles_;

  TestPermissionDelegate() = default;
  ~TestPermissionDelegate() override = default;

  TestPermissionDelegate(TestPermissionDelegate&) = delete;
  TestPermissionDelegate& operator=(TestPermissionDelegate&) = delete;

  std::optional<bool> GetIdpSigninStatus(
      const url::Origin& idp_origin) override {
    auto it = idp_signin_statuses_.find(idp_origin);
    return (it != idp_signin_statuses_.end()) ? it->second : std::nullopt;
  }

  void SetIdpSigninStatus(
      const url::Origin& idp_origin,
      bool idp_signin_status,
      base::optional_ref<const blink::common::webid::LoginStatusOptions>
          options) override {
    idp_signin_statuses_[idp_origin] = idp_signin_status;

    // Call parent so that EXPECT_CALL() works.
    NiceMock<MockPermissionDelegate>::SetIdpSigninStatus(
        idp_origin, idp_signin_status, std::nullopt);
  }

  void SetIdpAccounts(
      const url::Origin& idp_origin,
      std::vector<IdentityRequestAccountPtr>& request_accounts) {
    idp_account_profiles_[idp_origin] = std::move(request_accounts);
  }
};

class TestAutoReauthnPermissionDelegate
    : public MockAutoReauthnPermissionDelegate {
 public:
  std::set<url::Origin> embargoed_origins_;

  void RecordEmbargoForAutoReauthn(const url::Origin& origin) override {
    embargoed_origins_.insert(origin);
  }
};

class TestIdentityRegistry : public NiceMock<MockIdentityRegistry> {
 public:
  bool notified_{false};

  explicit TestIdentityRegistry(
      WebContents* web_contents,
      base::WeakPtr<IdentityRegistryDelegate> delegate,
      const GURL& idp_config_url)
      : NiceMock<MockIdentityRegistry>(web_contents, delegate, idp_config_url) {
  }

  void NotifyClose(const url::Origin& notifier_origin) override {
    notified_ = true;
  }

  base::WeakPtr<TestIdentityRegistry> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  base::WeakPtrFactory<TestIdentityRegistry> weak_ptr_factory_{this};
};

}  // namespace

class RequestTest : public RenderViewHostImplTestHarness {
 protected:
  explicit RequestTest(std::string_view rp_url = kRpUrl) : rp_url_(rp_url) {
    ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  }
  ~RequestTest() override = default;

  void InitConstants() {
    kSingleAccount = {base::MakeRefCounted<IdentityRequestAccount>(
        kAccountId,                  // id
        kEmail,                      // display_identifier
        "Ken R. Example",            // display_name
        kEmail,                      // email
        "Ken R. Example",            // name
        "Ken",                       // given_name
        GURL(),                      // picture
        "(650) 312-3223",            // phone number
        "kenr",                      // username
        std::vector<std::string>(),  // potentially_approved_site_hashes
        std::vector<std::string>(),  // login_hints
        std::vector<std::string>(),  // domain_hints
        std::vector<std::string>()   // labels
        )};
    kDefaultIdentityProviderInfo = {
        {kWellKnown, {ParseStatus::kSuccess, net::HTTP_OK}},
        {{ParseStatus::kSuccess, net::HTTP_OK},
         kAccountsEndpoint,
         kTokenEndpoint,
         kClientMetadataEndpoint,
         kMetricsEndpoint,
         kIdpLoginUrl,
         kIdpDisconnectUrl,
         /*vc_issuance_endpoint=*/"",
         /*brand_background_color=*/std::nullopt,
         /*brand_text_color=*/std::nullopt},
        kDefaultClientMetadata,
        {ParseStatus::kSuccess, net::HTTP_OK},
        kSingleAccount,
    };
    kSingleProviderInfo = {{kProviderUrlFull, kDefaultIdentityProviderInfo}};
    kSingleAccountWithHint = {base::MakeRefCounted<IdentityRequestAccount>(
        kAccountId,                  // id
        kEmail,                      // display_identifier
        "Ken R. Example",            // display_name
        kEmail,                      // email
        "Ken R. Example",            // name
        "Ken",                       // given_name
        GURL(),                      // picture
        "(650) 312-3223",            // phone number
        "kenr",                      // username
        std::vector<std::string>(),  // potentially_approved_site_hashes
        kLoginHints,                 // login_hints
        std::vector<std::string>(),  // domain_hints
        std::vector<std::string>()   // labels
        )};
    kSingleAccountWithDomainHint = {
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountId,                  // id
            kEmail,                      // display_identifier
            "Ken R. Example",            // display_name
            kEmail,                      // email
            "Ken R. Example",            // name
            "Ken",                       // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "kenr",                      // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            std::vector<std::string>(),  // login_hints
            kDomainHintVector,           // domain_hints
            std::vector<std::string>()   // labels
            )};
    kTwoAccounts = {
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdNicolas,           // id
            kAccountEmailNicolas,        // display_identifier
            "Nicolas P",                 // display_name
            kAccountEmailNicolas,        // email
            "Nicolas P",                 // name
            "Nicolas",                   // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "npm",                       // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            std::vector<std::string>(),  // login_hints
            std::vector<std::string>(),  // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignUp          // login_state
            ),
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdZach,              // id
            "zach@email.com",            // display_identifier
            "Zachary T",                 // display_name
            "zach@email.com",            // email
            "Zachary T",                 // name
            "Zach",                      // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "tanz",                      // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            std::vector<std::string>(),  // login_hints
            std::vector<std::string>(),  // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignUp          // login_state
            )};
    kMultipleAccounts = {
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdNicolas,           // id
            kAccountEmailNicolas,        // display_identifier
            "Nicolas P",                 // display_name
            kAccountEmailNicolas,        // email
            "Nicolas P",                 // name
            "Nicolas",                   // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "npm",                       // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            std::vector<std::string>(),  // login_hints
            std::vector<std::string>(),  // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignUp          // login_state
            ),
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdPeter,             // id
            kAccountEmailPeter,          // display_identifier
            "Peter K",                   // display_name
            kAccountEmailPeter,          // email
            "Peter K",                   // name
            "Peter",                     // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "peter",                     // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            std::vector<std::string>(),  // login_hints
            std::vector<std::string>(),  // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignIn          // login_state
            ),
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdZach,              // id
            "zach@email.com",            // display_identifier
            "Zachary T",                 // display_name
            "zach@email.com",            // email
            "Zachary T",                 // name
            "Zach",                      // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "zacht",                     // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            std::vector<std::string>(),  // login_hints
            std::vector<std::string>(),  // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignUp          // login_state
            )};
    kMultipleAccountsWithHintsAndDomains = {
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdNicolas,           // id
            kAccountEmailNicolas,        // display_identifier
            "Nicolas P",                 // display_name
            kAccountEmailNicolas,        // email
            "Nicolas P",                 // name
            "Nicolas",                   // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "npm",                       // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            kNicolasHints,               // login_hints
            kDomainHintVector,           // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignUp          // login_state
            ),
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdPeter,             // id
            kAccountEmailPeter,          // display_identifier
            "Peter K",                   // display_name
            kAccountEmailPeter,          // email
            "Peter K",                   // name
            "Peter",                     // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "peterk",                    // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            kPeterHints,                 // login_hints
            std::vector<std::string>(),  // domain_hints
            kLabelVector,                // labels
            LoginState::kSignIn          // login_state
            ),
        base::MakeRefCounted<IdentityRequestAccount>(
            kAccountIdZach,              // id
            kAccountEmailZach,           // display_identifier
            "Zachary T",                 // display_name
            kAccountEmailZach,           // email
            "Zachary T",                 // name
            "Zach",                      // given_name
            GURL(),                      // picture
            "(650) 312-3223",            // phone number
            "zacht",                     // username
            std::vector<std::string>(),  // potentially_approved_site_hashes
            kZachHints,                  // login_hints
            kTwoDomainHints,             // domain_hints
            std::vector<std::string>(),  // labels
            LoginState::kSignUp          // login_state
            )};
    kProviderTwoInfo = {{{kProviderTwoUrlFull}},
                        {{ParseStatus::kSuccess, net::HTTP_OK},
                         "https://idp2.example/accounts",
                         "https://idp2.example/token",
                         "https://idp2.example/client_metadata",
                         "https://idp2.example/metrics",
                         "https://idp2.example/login_url",
                         "https://idp2.example/disconnect",
                         /*vc_issuance_endpoint=*/"",
                         /*brand_background_color=*/std::nullopt,
                         /*brand_text_color=*/std::nullopt},
                        kDefaultClientMetadata,
                        {ParseStatus::kSuccess, net::HTTP_OK},
                        kMultipleAccounts};
    kConfigurationValid = {kToken,
                           kSingleProviderInfo,
                           {ParseStatus::kSuccess, net::HTTP_OK},
                           /*delay_token_response=*/false,
                           AccountsDialogAction::kSelectFirstAccount,
                           IdpSigninStatusMismatchDialogAction::kNone,
                           ErrorDialogAction::kNone,
                           LoadingDialogAction::kNone};
    kConfigurationMultiIdpValid = {
        kToken,
        {{kProviderUrlFull, kDefaultIdentityProviderInfo},
         {kProviderTwoUrlFull, kProviderTwoInfo}},
        {ParseStatus::kSuccess, net::HTTP_OK},
        false /* delay_token_response */,
        AccountsDialogAction::kSelectFirstAccount,
        IdpSigninStatusMismatchDialogAction::kNone,
        ErrorDialogAction::kClose,
        LoadingDialogAction::kNone};
  }

  // Navigate with identity-credentials-get PP denied and bind a new
  // Mojo remote to the resulting RFH.
  mojo::Remote<FederatedRequestService>
  NavigateAndBindWithDeniedIdentityCredentialsGetPolicy() {
    // Setting `feature` with no `allowed_origins` means disabled for all.
    network::ParsedPermissionsPolicy policy(1);
    policy[0].feature =
        network::mojom::PermissionsPolicyFeature::kIdentityCredentialsGet;

    auto simulator = NavigationSimulator::CreateRendererInitiated(
        GURL(rp_url_), web_contents()->GetPrimaryMainFrame());
    simulator->SetPermissionsPolicyHeader(std::move(policy));
    simulator->Commit();

    mojo::Remote<FederatedRequestService> remote;
    RequestService* service = RequestService::GetOrCreateForCurrentDocument(
        static_cast<TestRenderFrameHost*>(
            simulator->GetFinalRenderFrameHost()));
    service->SetDelegatesForTesting(
        test_api_permission_delegate_.get(),
        test_auto_reauthn_permission_delegate_.get(),
        test_permission_delegate_.get(), test_identity_registry_.get());
    service->BindFederatedRequestService(remote.BindNewPipeAndPassReceiver());
    return remote;
  }

  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();
    // Initialize the accounts and account-dependent constants on SetUp() to
    // ensure they are initialized correctly in every test.
    InitConstants();

    test_api_permission_delegate_ =
        std::make_unique<TestApiPermissionDelegate>();
    test_permission_delegate_ = std::make_unique<TestPermissionDelegate>();
    test_auto_reauthn_permission_delegate_ =
        std::make_unique<TestAutoReauthnPermissionDelegate>();
    auto test_identity_registry = std::make_unique<TestIdentityRegistry>(
        web_contents(), /*delegate=*/nullptr, GURL(kIdpUrl));
    test_identity_registry_ = test_identity_registry->GetWeakPtr();
    web_contents()->SetUserData(IdentityRegistry::UserDataKey(),
                                std::move(test_identity_registry));
    request_helper_ = std::make_unique<RequestCallbackHelper>();

    static_cast<TestWebContents*>(web_contents())
        ->NavigateAndCommit(GURL(rp_url_), ui::PAGE_TRANSITION_LINK);

    RequestService* service =
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
    service->SetDelegatesForTesting(
        test_api_permission_delegate_.get(),
        test_auto_reauthn_permission_delegate_.get(),
        test_permission_delegate_.get(), test_identity_registry_.get());
    service->BindFederatedRequestService(
        service_remote_.BindNewPipeAndPassReceiver());
    request_ = service->GetOrCreateActiveRequest()->GetWeakPtr();

    std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
        std::make_unique<TestIdpNetworkRequestManager>();
    SetNetworkRequestManager(std::move(network_request_manager));
  }

  void TearDown() override {
    ResetAndDeleteRequest();
    test_identity_registry_ = nullptr;
    RenderViewHostImplTestHarness::TearDown();
  }

  void ResetAndDeleteRequest() {
    RequestService* service =
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
    if (service && service->GetActiveRequestForTesting()) {
      service->DestroyActiveRequestForTesting();
    }
    ASSERT_FALSE(request_);
  }

  void SetNetworkRequestManager(
      std::unique_ptr<TestIdpNetworkRequestManager> manager) {
    test_network_request_manager_ = std::move(manager);
    RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
        ->SetNetworkManagerForTests(
            std::make_unique<DelegatedIdpNetworkRequestManager>(
                test_network_request_manager_.get()));
  }

  // Sets the TestDialogController to be used for the next call of
  // RunTest().
  void SetDialogController(
      std::unique_ptr<TestDialogController> dialog_controller) {
    custom_dialog_controller_ = std::move(dialog_controller);
  }

  void CompleteTokenRequest(
      RequestService* service,
      Request* request,
      blink::mojom::FederatedRequestService::StartTokenRequestCallback callback,
      blink::mojom::RequestTokenStatus status,
      const std::optional<GURL>& selected_idp_config_url,
      std::optional<base::Value> token,
      blink::mojom::TokenErrorPtr error,
      bool is_auto_selected) {
    auto wrapped_callback = base::BindOnce(
        &RequestService::InvokeTokenRequestCallback, std::move(callback));

    service->OnTokenRequestCompleteInternal(
        request, std::move(wrapped_callback), status, selected_idp_config_url,
        std::move(token), std::move(error), is_auto_selected);
  }

  void RunTest(const RequestParameters& request_parameters,
               const RequestExpectations& expectations,
               const MockConfiguration& configuration,
               RequestCallbackHelper* concurrent_request_helper = nullptr,
               mojo::Remote<FederatedRequest>* request_remote = nullptr) {
    RequestCallbackHelper* request_helper =
        concurrent_request_helper ?: request_helper_.get();

    RunDontWaitForCallback(request_parameters, configuration, request_helper,
                           request_remote);
    WaitForCurrentRequest(/*should_fast_forward=*/true, request_helper,
                          request_remote);
    CheckExpectations(configuration, expectations, request_helper);
  }

  void RunDontWaitForCallback(
      const RequestParameters& request_parameters,
      const MockConfiguration& configuration,
      RequestCallbackHelper* request_helper = nullptr,
      mojo::Remote<FederatedRequest>* request_remote = nullptr) {
    if (custom_dialog_controller_) {
      active_mock_dialog_controller_ = custom_dialog_controller_->AsWeakPtr();
      active_mock_dialog_controller_->SetState(&dialog_controller_state_);
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
          ->SetDialogControllerForTests(std::move(custom_dialog_controller_));
    } else if (!active_mock_dialog_controller_) {
      auto mock = std::make_unique<TestDialogController>(configuration);
      active_mock_dialog_controller_ = mock->AsWeakPtr();
      active_mock_dialog_controller_->SetState(&dialog_controller_state_);
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
          ->SetDialogControllerForTests(std::move(mock));
    } else {
      active_mock_dialog_controller_->UpdateConfiguration(configuration);
    }
    dialog_controller_state_ = TestDialogController::State();

    SetConfig(configuration);

    // If multiple IdPs are received, add them to a single get call. Unittests
    // for multiple get calls can be added later as needed.
    std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> idp_ptrs;
    for (const auto& identity_provider :
         request_parameters.identity_providers) {
      blink::mojom::IdentityProviderRequestOptionsPtr options =
          blink::mojom::IdentityProviderRequestOptions::New();
      options->config = blink::mojom::IdentityProviderConfig::New();
      options->config->config_url = GURL(identity_provider.provider);
      options->config->client_id = identity_provider.client_id;
      options->config->from_idp_registration_api =
          identity_provider.from_idp_registration_api;
      options->nonce = identity_provider.nonce;
      options->login_hint = identity_provider.login_hint;
      options->domain_hint = identity_provider.domain_hint;
      options->fields = std::move(identity_provider.fields);
      if (identity_provider.params_json) {
        options->params_json = identity_provider.params_json;
      }
      if (identity_provider.type) {
        options->config->type = *identity_provider.type;
      }
      idp_ptrs.push_back(std::move(options));
    }
    blink::mojom::IdentityProviderGetParametersPtr get_params =
        blink::mojom::IdentityProviderGetParameters::New(
            std::move(idp_ptrs), request_parameters.rp_context,
            request_parameters.rp_mode);
    std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
    idp_get_params.push_back(std::move(get_params));

    request_helper = request_helper ?: request_helper_.get();
    PerformRequest(std::move(idp_get_params),
                   configuration.mediation_requirement, request_helper,
                   request_remote);
  }

  void CheckExpectations(const MockConfiguration& configuration,
                         const RequestExpectations& expectation,
                         RequestCallbackHelper* request_helper = nullptr) {
    request_helper = request_helper ?: request_helper_.get();
    ASSERT_EQ(expectation.return_status, request_helper->status());
    if (expectation.return_status == RequestTokenStatus::kSuccess) {
      EXPECT_EQ(configuration.token, request_helper->token());
    } else {
      EXPECT_TRUE(request_helper->token() == std::nullopt ||
                  request_helper->token() == kEmptyToken);
    }

    if (expectation.return_status == RequestTokenStatus::kSuccess) {
      EXPECT_TRUE(DidFetchWellKnownAndConfig());
      if (!base::FeatureList::IsEnabled(features::kFedCmLightweightMode)) {
        EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
      }
      EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
      // FetchedEndpoint::CLIENT_METADATA is optional.

      EXPECT_EQ(did_show_accounts_dialog(),
                !expectation.is_auto_selected ||
                    configuration.rp_mode != blink::mojom::RpMode::kActive);
    }

    EXPECT_EQ(expectation.is_auto_selected, request_helper->is_auto_selected());

    EXPECT_EQ(expectation.selected_idp_config_url,
              request_helper->selected_idp_config_url());

    if (expectation.devtools_issue_status != FederatedRequestResult::kSuccess) {
      int issue_count = main_test_rfh()->GetFederatedRequestIssueCount(
          expectation.devtools_issue_status);
      EXPECT_LE(1, issue_count);
    } else {
      int issue_count =
          main_test_rfh()->GetFederatedRequestIssueCount(std::nullopt);
      if (!expectation.standalone_console_message) {
        EXPECT_EQ(0, issue_count);
      } else {
        EXPECT_GE(1, issue_count);
      }
    }
    CheckConsoleMessages(expectation.devtools_issue_status,
                         expectation.standalone_console_message);
  }

  void SetConfig(const MockConfiguration& config) {
    test_network_request_manager_->SetTestConfig(config);
  }

  std::vector<std::string> GetFilteredConsoleMessages() {
    std::vector<std::string> messages =
        RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();

    // TODO(crbug.com/451219310): Remove when FedCM deprecation warnings
    // removed. Filter out known deprecation warnings
    std::vector<std::string> filtered_messages;
    for (const auto& message : messages) {
      if (message.find("The 'nonce' parameter should be passed within the "
                       "'params' object") == std::string::npos &&
          message.find("The FedCM configuration uses client_metadata") ==
              std::string::npos) {
        filtered_messages.push_back(message);
      }
    }
    return filtered_messages;
  }

  void CheckConsoleMessages(
      FederatedRequestResult devtools_issue_status,
      const std::optional<std::string>& standalone_console_message) {
    std::vector<std::string> messages = GetFilteredConsoleMessages();

    bool did_expect_any_messages = false;
    size_t expected_message_index = messages.size() - 1;
    if (devtools_issue_status != FederatedRequestResult::kSuccess) {
      std::string expected_message =
          GetConsoleErrorMessageFromResult(devtools_issue_status);
      did_expect_any_messages = true;
      ASSERT_GE(expected_message_index, 0u);
      EXPECT_EQ(expected_message, messages[expected_message_index--]);
    }
    if (standalone_console_message) {
      did_expect_any_messages = true;
      ASSERT_EQ(expected_message_index, 0u);
      EXPECT_EQ(*standalone_console_message, messages[0]);
    }

    if (!did_expect_any_messages) {
      EXPECT_EQ(0u, messages.size());
    }
  }

  void PerformRequest(
      std::vector<blink::mojom::IdentityProviderGetParametersPtr>
          idp_get_params,
      MediationRequirement mediation_requirement,
      RequestCallbackHelper* request_helper = nullptr,
      mojo::Remote<FederatedRequest>* request_remote = nullptr) {
    request_helper = request_helper ?: request_helper_.get();
    mojo::Remote<FederatedRequest>& remote =
        request_remote ? *request_remote : request_remote_;
    remote.reset();
    service_remote_->StartTokenRequest(
        std::move(idp_get_params), mediation_requirement,
        remote.BindNewPipeAndPassReceiver(), request_helper->callback());

    // Ensure that the request makes its way to Request.
    service_remote_.FlushForTesting();
    if (remote.is_bound()) {
      remote.set_disconnect_handler(request_helper->quit_closure());
      remote.FlushForTesting();
    }
    base::RunLoop().RunUntilIdle();

    // Re-fetch the active request pointer from the service, as the Mojo call
    // will have created a new Request object if there wasn't one.
    Request* active_request =
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
            ->GetActiveRequestForTesting();
    request_ = active_request ? active_request->GetWeakPtr() : nullptr;
  }

  void WaitForCurrentRequest(
      bool should_fast_forward = true,
      RequestCallbackHelper* request_helper = nullptr,
      mojo::Remote<FederatedRequest>* request_remote = nullptr) {
    request_helper = request_helper ?: request_helper_.get();
    mojo::Remote<FederatedRequest>& remote =
        request_remote ? *request_remote : request_remote_;
    remote.set_disconnect_handler(request_helper->quit_closure());

    // Fast forward clock so that the pending
    // Request::OnRejectRequest() task, if any, gets a
    // chance to run.
    if (should_fast_forward) {
      task_environment()->FastForwardBy(base::Minutes(10));
    }
    request_helper->WaitForCallback();

    remote.set_disconnect_handler(base::OnceClosure());
  }

  void CloseDialog() {
    request_->OnDialogDismissed(
        IdentityRequestDialogController::DismissReason::kCloseButton);
  }

  void CallRedirectTo(const GURL& idp_config_url,
                      blink::mojom::RedirectParams::Tag method,
                      const GURL& redirect_to,
                      const std::string& request_body) {
    request_->RedirectTo(idp_config_url, method, redirect_to, request_body);
  }

  void CompleteDisconnectRequest() {
    std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
        std::make_unique<TestIdpNetworkRequestManager>();
    std::unique_ptr<Metrics> fedcm_metrics =
        std::make_unique<Metrics>(main_test_rfh()->GetPageUkmSourceId());
    blink::mojom::IdentityCredentialDisconnectOptionsPtr options =
        blink::mojom::IdentityCredentialDisconnectOptions::New();
    options->config = blink::mojom::IdentityProviderConfig::New();
    options->config->config_url = GURL(kProviderUrlFull);
    RequestService* service =
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
    service->disconnect_request_ = DisconnectRequest::Create(
        std::move(network_request_manager), test_permission_delegate_.get(),
        main_test_rfh(), std::move(fedcm_metrics), std::move(options));
    service->disconnect_request_->callback_ = base::DoNothing();
    service->disconnect_request_->Complete(
        blink::mojom::DisconnectStatus::kSuccess, DisconnectStatus::kSuccess);
  }

  base::span<const IdentityRequestAccountPtr> all_accounts_for_display() const {
    return dialog_controller_state_.all_accounts_for_display;
  }

  base::span<const IdentityRequestAccountPtr> new_accounts() const {
    return dialog_controller_state_.new_accounts;
  }

  std::vector<std::string> displayed_mismatch_idps() const {
    return dialog_controller_state_.displayed_mismatch_idps;
  }

  bool did_show_accounts_dialog() const {
    return !all_accounts_for_display().empty();
  }
  bool did_show_idp_signin_status_mismatch_dialog() const {
    return dialog_controller_state_
        .num_show_idp_signin_status_mismatch_dialog_requests;
  }

  std::optional<SkColor> brand_background_color() const {
    return dialog_controller_state_.brand_background_color;
  }

  std::optional<SkColor> brand_text_color() const {
    return dialog_controller_state_.brand_text_color;
  }

  int CountNumLoginStateIsSignin() {
    int num_sign_in_login_state = 0;
    for (const auto& account : all_accounts_for_display()) {
      if (account->idp_claimed_login_state.value_or(
              account->browser_trusted_login_state) == LoginState::kSignIn) {
        ++num_sign_in_login_state;
      }
    }
    return num_sign_in_login_state;
  }

  bool DidFetchAnyEndpoint() {
    for (auto& [endpoint, num] : test_network_request_manager_->num_fetched_) {
      if (num > 0) {
        return true;
      }
    }
    return false;
  }

  // Convenience method as WELL_KNOWN and CONFIG endpoints are fetched in
  // parallel.
  bool DidFetchWellKnownAndConfig() {
    return DidFetch(FetchedEndpoint::WELL_KNOWN) &&
           DidFetch(FetchedEndpoint::CONFIG);
  }

  bool DidFetch(FetchedEndpoint endpoint) { return NumFetched(endpoint) > 0u; }

  size_t NumFetched(FetchedEndpoint endpoint) {
    return test_network_request_manager_->num_fetched_[endpoint];
  }

  ukm::TestAutoSetUkmRecorder* ukm_recorder() { return ukm_recorder_.get(); }

  void ExpectStatusMetrics(
      TokenStatus status,
      MediationRequirement requirement = MediationRequirement::kOptional) {
    histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.RequestIdToken",
                                         status, 1);
    histogram_tester_.ExpectUniqueSample(
        "Blink.FedCm.Status.MediationRequirement", requirement, 1);
    ExpectStatusUKMInternal(status, requirement, FedCmEntry::kEntryName);
    ExpectStatusUKMInternal(status, requirement, FedCmIdpEntry::kEntryName);
  }

  void ExpectStatusUKMInternal(TokenStatus status,
                               MediationRequirement requirement,
                               const char* entry_name) {
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);

    ASSERT_FALSE(entries.empty())
        << "No " << entry_name << " entry was recorded";

    // There are multiple types of metrics under the same FedCM UKM. We need to
    // make sure that the metric only includes the expected one.
    bool metric_found = false;
    for (const ukm::mojom::UkmEntry* const entry : entries) {
      const int64_t* metric =
          ukm_recorder()->GetEntryMetric(entry, "Status.RequestIdToken");
      if (!metric) {
        continue;
      }
      EXPECT_FALSE(metric_found)
          << "Found more than one entry with Status.RequestIdToken in "
          << entry_name;
      metric_found = true;
      EXPECT_EQ(static_cast<int>(status), *metric)
          << "Unexpected status recorded in " << entry_name;
    }
    EXPECT_TRUE(metric_found)
        << "No Status.RequestIdToken entry was found in " << entry_name;
  }

  void ExpectUKMPresence(const std::string& metric_name) {
    ExpectUKMPresenceInternal(metric_name, FedCmEntry::kEntryName);
    ExpectUKMPresenceInternal(metric_name, FedCmIdpEntry::kEntryName);
  }

  void ExpectUKMPresenceInternal(const std::string& metric_name,
                                 const char* entry_name) {
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);

    ASSERT_FALSE(entries.empty())
        << "No " << entry_name << " entry was recorded";

    for (const ukm::mojom::UkmEntry* const entry : entries) {
      if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
        SUCCEED();
        return;
      }
    }
    FAIL() << "Expected UKM " << metric_name << " was not recorded in "
           << entry_name;
  }

  void ExpectNoUKMPresence(const std::string& metric_name) {
    ExpectNoUKMPresenceInternal(metric_name, FedCmEntry::kEntryName);
    ExpectNoUKMPresenceInternal(metric_name, FedCmIdpEntry::kEntryName);
  }

  void ExpectNoUKMPresenceInternal(const std::string& metric_name,
                                   const char* entry_name) {
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);

    ASSERT_FALSE(entries.empty())
        << "No " << entry_name << " entry was recorded";

    for (const ukm::mojom::UkmEntry* const entry : entries) {
      if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
        FAIL() << "Unexpectedly found " << metric_name << " UKM in "
               << entry_name;
      }
    }
    SUCCEED();
  }

  void ExpectUKMCount(const std::string& metric_name,
                      const char* entry_name,
                      int expected_count) {
    int count = 0;
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);
    for (const ukm::mojom::UkmEntry* const entry : entries) {
      if (ukm_recorder()->GetEntryMetric(entry, metric_name)) {
        ++count;
      }
    }
    EXPECT_EQ(count, expected_count) << "Did not find the expected count for "
                                     << metric_name << " in " << entry_name;
  }

  void ExpectUkmValue(const std::string& metric_name,
                      int expected_value,
                      int num_idps = 1) {
    ExpectUkmValueInEntry(metric_name, FedCmEntry::kEntryName, expected_value);
    ExpectUkmValueInEntry(metric_name, FedCmIdpEntry::kEntryName,
                          expected_value, num_idps);
  }

  void ExpectUkmValueInEntry(const std::string& metric_name,
                             const char* entry_name,
                             int expected_value,
                             int expected_count = 1,
                             bool other_values_allowed = false) {
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);
    int count = 0;
    for (const ukm::mojom::UkmEntry* const entry : entries) {
      const int64_t* value = ukm_recorder()->GetEntryMetric(entry, metric_name);
      if (!value) {
        continue;
      }
      ++count;
      if (!other_values_allowed) {
        EXPECT_EQ(*value, expected_value);
      }
    }
    EXPECT_EQ(count, expected_count);
    EXPECT_GT(count, 0) << "Did not find " << metric_name << " in "
                        << entry_name;
  }

  void ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus status) {
    ExpectSignInStateMatchStatusUKMInternal(status, FedCmEntry::kEntryName);
    ExpectSignInStateMatchStatusUKMInternal(status, FedCmIdpEntry::kEntryName);
  }

  void ExpectSignInStateMatchStatusUKMInternal(SignInStateMatchStatus status,
                                               const char* entry_name) {
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);

    ASSERT_FALSE(entries.empty()) << "No FedCm entry was recorded";

    // There are multiple types of metrics under the same FedCM UKM. We need to
    // make sure that the metric only includes the expected one.
    bool metric_found = false;
    for (const ukm::mojom::UkmEntry* const entry : entries) {
      const int64_t* metric =
          ukm_recorder()->GetEntryMetric(entry, "Status.SignInStateMatch");
      if (!metric) {
        continue;
      }
      EXPECT_FALSE(metric_found)
          << "Found more than one Status.SignInStateMatch";
      metric_found = true;
      EXPECT_EQ(static_cast<int>(status), *metric);
    }
    EXPECT_TRUE(metric_found) << "No Status.SignInStateMatch was found";
  }

  void ExpectAutoReauthnMetrics(
      std::optional<Metrics::NumAccounts> expected_returning_accounts,
      bool expected_succeeded,
      bool expected_auto_reauthn_setting_blocked,
      bool expected_auto_reauthn_embargoed,
      bool expected_prevent_silent_access,
      bool expected_auto_reauthn_blocked_by_embedder) {
    // UMA checks
    histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
                                         expected_succeeded, 1);
    if (expected_returning_accounts.has_value()) {
      histogram_tester_.ExpectUniqueSample(
          "Blink.FedCm.AutoReauthn.ReturningAccounts",
          static_cast<int>(*expected_returning_accounts), 1);
    } else {
      histogram_tester_.ExpectTotalCount(
          "Blink.FedCm.AutoReauthn.ReturningAccounts", 0);
    }
    histogram_tester_.ExpectUniqueSample(
        "Blink.FedCm.AutoReauthn.BlockedByEmbedder",
        expected_auto_reauthn_blocked_by_embedder, 1);
    histogram_tester_.ExpectUniqueSample(
        "Blink.FedCm.AutoReauthn.BlockedByContentSettings",
        expected_auto_reauthn_setting_blocked, 1);
    histogram_tester_.ExpectUniqueSample(
        "Blink.FedCm.AutoReauthn.BlockedByEmbargo",
        expected_auto_reauthn_embargoed, 1);
    histogram_tester_.ExpectUniqueSample(
        "Blink.FedCm.AutoReauthn.BlockedByPreventSilentAccess",
        expected_prevent_silent_access, 1);
    if (expected_succeeded) {
      histogram_tester_.ExpectTotalCount(
          "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
      histogram_tester_.ExpectUniqueSample(
          "Blink.FedCm.VerifyingDialogResult",
          VerifyingDialogResult::kSuccessAutoReauthn, 1);
    }

    // UKM checks
    auto entries = ukm_recorder()->GetEntriesByName(FedCmEntry::kEntryName);
    ASSERT_FALSE(entries.empty()) << "No FedCM UKM entry was found!";

    bool metric_found = false;
    for (const ukm::mojom::UkmEntry* entry : entries) {
      const int64_t* metric =
          ukm_recorder()->GetEntryMetric(entry, "AutoReauthn.Succeeded");
      if (!metric) {
        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
            entry, "AutoReauthn.ReturningAccounts"));
        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
            entry, "AutoReauthn.BlockedByContentSettings"));
        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
            entry, "AutoReauthn.BlockedByEmbargo"));
        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
            entry, "AutoReauthn.BlockedByPreventSilentAccess"));
        continue;
      }
      EXPECT_FALSE(metric_found) << "Found more than one AutoReauthn entry";
      metric_found = true;
      EXPECT_EQ(expected_succeeded, *metric);

      metric = ukm_recorder()->GetEntryMetric(entry,
                                              "AutoReauthn.ReturningAccounts");
      if (expected_returning_accounts) {
        ASSERT_TRUE(metric) << "AutoReauthn.ReturningAccounts was not found";
        EXPECT_EQ(static_cast<int>(*expected_returning_accounts), *metric);
      } else {
        EXPECT_FALSE(metric)
            << "AutoReauthn.ReturningAccounts should not have been recorded";
      }

      metric = ukm_recorder()->GetEntryMetric(
          entry, "AutoReauthn.BlockedByContentSettings");
      ASSERT_TRUE(metric)
          << "AutoReauthn.BlockedByContentSettings was not found";
      EXPECT_EQ(expected_auto_reauthn_setting_blocked, *metric);

      metric =
          ukm_recorder()->GetEntryMetric(entry, "AutoReauthn.BlockedByEmbargo");
      ASSERT_TRUE(metric) << "AutoReauthn.BlockedByEmbargo was not found";
      EXPECT_EQ(expected_auto_reauthn_embargoed, *metric);

      metric = ukm_recorder()->GetEntryMetric(
          entry, "AutoReauthn.BlockedByPreventSilentAccess");
      ASSERT_TRUE(metric)
          << "AutoReauthn.BlockedByPreventSilentAccess was not found";
      EXPECT_EQ(expected_prevent_silent_access, *metric);
    }
    EXPECT_TRUE(metric_found) << "Did not find AutoReauthn metrics";
    if (expected_succeeded) {
      ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
      ExpectUKMPresence("VerifyingDialogResult");
    }
  }

  void CheckAllFedCmSessionIDs(size_t expected_num_session_ids = 1u,
                               bool check_request_id_token = false) {
    auto CheckUKMSessionID = [&](const auto& ukm_entries) {
      std::set<int> session_ids;
      std::set<int> session_ids_with_request_id_token;
      ASSERT_FALSE(ukm_entries.empty());
      for (const ukm::mojom::UkmEntry* const entry : ukm_entries) {
        const auto* metric =
            ukm_recorder()->GetEntryMetric(entry, "NumRequestsPerDocument");
        if (metric) {
          continue;
        }
        metric = ukm_recorder()->GetEntryMetric(entry, "FedCmSessionID");
        ASSERT_TRUE(metric)
            << "All UKM events except for NumRequestsPerDocument should have "
               "the SessionID metric";
        int session_id = *metric;
        session_ids.insert(session_id);
        metric = ukm_recorder()->GetEntryMetric(entry, "Status.RequestIdToken");
        if (!metric || !check_request_id_token) {
          continue;
        }
        ASSERT_FALSE(session_ids_with_request_id_token.contains(session_id))
            << "A single session ID should only have one RequestIdToken";
        session_ids_with_request_id_token.insert(session_id);
      }
      EXPECT_EQ(session_ids.size(), expected_num_session_ids);
      if (check_request_id_token) {
        EXPECT_EQ(session_ids_with_request_id_token.size(),
                  expected_num_session_ids);
      }
    };
    CheckUKMSessionID(ukm_recorder()->GetEntriesByName(FedCmEntry::kEntryName));
    CheckUKMSessionID(
        ukm_recorder()->GetEntriesByName(FedCmIdpEntry::kEntryName));
  }

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr>
  MaybeAddRegisteredProviders(
      std::vector<blink::mojom::IdentityProviderRequestOptionsPtr>& providers) {
    return request_->MaybeAddRegisteredProviders(providers);
  }

  void ExpectTwoUniqueSessionIDs() {
    auto CheckSessionIDs([&](const char* entry_name) {
      std::vector<int64_t> session_ids =
          ukm_recorder()->GetMetricsEntryValues(entry_name, "FedCmSessionID");
      ASSERT_EQ(2, session_ids.size());
      ASSERT_NE(session_ids[0], session_ids[1]);
    });
    CheckSessionIDs(FedCmEntry::kEntryName);
    CheckSessionIDs(FedCmIdpEntry::kEntryName);
  }

  blink::mojom::IdentityProviderRequestOptionsPtr
  NewNamedIdP(GURL config_url, std::string client_id, bool is_registered) {
    blink::mojom::IdentityProviderRequestOptionsPtr options =
        blink::mojom::IdentityProviderRequestOptions::New();
    blink::mojom::IdentityProviderConfigPtr config =
        blink::mojom::IdentityProviderConfig::New();
    config->config_url = config_url;
    config->client_id = client_id;
    options->config = std::move(config);
    options->config->from_idp_registration_api = is_registered;
    return options;
  }

  blink::mojom::IdentityProviderRequestOptionsPtr NewRegisteredIdP(
      std::string client_id) {
    blink::mojom::IdentityProviderConfigPtr config =
        blink::mojom::IdentityProviderConfig::New();
    config->from_idp_registration_api = true;
    config->client_id = client_id;
    blink::mojom::IdentityProviderRequestOptionsPtr options =
        blink::mojom::IdentityProviderRequestOptions::New();

    options->config = std::move(config);
    return options;
  }

  blink::mojom::IdentityProviderRequestOptionsPtr NewIDPWithFields(
      const std::optional<std::vector<std::string>>& fields) {
    blink::mojom::IdentityProviderRequestOptionsPtr options =
        blink::mojom::IdentityProviderRequestOptions::New();
    blink::mojom::IdentityProviderConfigPtr config =
        blink::mojom::IdentityProviderConfig::New();
    config->config_url = GURL(kProviderUrlFull);
    config->client_id = "";
    options->config = std::move(config);
    options->fields = fields;
    return options;
  }

  void SimulateLoginToIdP(std::string login_url = kIdpLoginUrl) {
    request_->LoginToIdP(/*can_append_hints=*/true, GURL(kIdpUrl),
                         GURL(login_url));
  }

  void ExpectSuccessfulActiveFlow() {
    test_permission_delegate_
        ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;

    auto dialog_controller =
        std::make_unique<TestDialogController>(kConfigurationValid);
    base::WeakPtr<TestDialogController> weak_dialog_controller =
        dialog_controller->AsWeakPtr();
    SetDialogController(std::move(dialog_controller));

    // Expect a modal dialog to be opened to sign-in to the IdP.
    std::unique_ptr<WebContents> modal(CreateTestWebContents());

    base::RunLoop loop;
    EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
        .WillOnce(::testing::WithArg<0>([&modal, &loop](const GURL& url) {
          loop.Quit();
          return modal.get();
        }));

    RequestParameters parameters = kDefaultRequestParameters;
    parameters.rp_mode = blink::mojom::RpMode::kActive;

    static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
        ->SimulateUserActivation();

    RunDontWaitForCallback(parameters, kConfigurationValid);

    loop.Run();

    // When the modal dialog is opened, emulate the user signing-in by
    // updating the internal sign-in status state and notifying the
    // observers.
    test_permission_delegate_
        ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
    request_->OnIdpSigninStatusReceived(OriginFromString(kProviderUrlFull),
                                        true);

    WaitForCurrentRequest(/*should_fast_forward=*/false);
    CheckExpectations(kConfigurationValid, kExpectationSuccess);

    // These metrics are not recorded when a user's LoginStatus is "logged-out"
    // such that they need to sign in to the IdP in the active flow.
    histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                       0);
    histogram_tester_.ExpectTotalCount(
        "Blink.FedCm.Timing.ShowAccountsDialogBreakdown."
        "WellKnownAndConfigFetch",
        0);
    histogram_tester_.ExpectTotalCount(
        "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 0);
    histogram_tester_.ExpectTotalCount(
        "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch",
        0);
  }

 protected:
  std::string rp_url_;

  mojo::Remote<FederatedRequestService> service_remote_;
  mojo::Remote<FederatedRequest> request_remote_;
  base::WeakPtr<Request> request_;

  std::unique_ptr<TestIdpNetworkRequestManager> test_network_request_manager_;

  std::unique_ptr<TestApiPermissionDelegate> test_api_permission_delegate_;
  std::unique_ptr<TestPermissionDelegate> test_permission_delegate_;
  std::unique_ptr<TestAutoReauthnPermissionDelegate>
      test_auto_reauthn_permission_delegate_;
  base::WeakPtr<TestIdentityRegistry> test_identity_registry_ = nullptr;
  std::unique_ptr<RequestCallbackHelper> request_helper_;

  // Enables test to inspect TestDialogController state after
  // Request destroys TestDialogController. Recreated during
  // each run of RunTest().
  TestDialogController::State dialog_controller_state_;

  base::WeakPtr<TestDialogController> active_mock_dialog_controller_;

  base::HistogramTester histogram_tester_;

 private:
  std::unique_ptr<TestDialogController> custom_dialog_controller_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};

// Test successful FedCM request.
TEST_F(RequestTest, SuccessfulRequest) {
  // Use IdpNetworkRequestManagerParamChecker to validate passed-in parameters
  // to IdpNetworkRequestManager methods.
  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectations(kClientId, kAccountId);
  SetNetworkRequestManager(std::move(checker));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  // Check that client metadata is fetched. Using `kExpectationSuccess`
  // expectation does not check that the client metadata was fetched because
  // client metadata is optional.
  EXPECT_TRUE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
}

TEST_F(RequestTest, OnFedCmFederatedLoginSuccess) {
  testing::NiceMock<MockWebContentsObserver> observer(web_contents());

  EXPECT_CALL(observer, OnFedCmFederatedLogin(true)).Times(1);

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

TEST_F(RequestTest, OnFedCmFederatedLoginFailure) {
  testing::NiceMock<MockWebContentsObserver> observer(web_contents());

  EXPECT_CALL(observer, OnFedCmFederatedLogin(false)).Times(1);

  RequestExpectations error_request = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.token_endpoint = "";

  RunTest(kDefaultRequestParameters, error_request, configuration);
}

// Test that the FederatedEmbedderLoginRequest is notified when the FedCM flow
// completes.
TEST_F(RequestTest, NotifiesFederatedEmbedderLoginRequest) {
  GURL idp_url(kProviderUrlFull);
  url::Origin idp_origin = url::Origin::Create(idp_url);
  std::string account_id = "account_id123";

  base::MockCallback<base::OnceCallback<void(FederatedLoginResult)>>
      result_callback;
  // We expect kSuccess because kExpectationSuccess results in kSuccess.
  EXPECT_CALL(result_callback, Run(FederatedLoginResult::kSuccess)).Times(1);

  FederatedEmbedderLoginRequest::Set(web_contents(), idp_origin, account_id,
                                     result_callback.Get());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

// Test successful well-known fetching.
TEST_F(RequestTest, WellKnownSuccess) {
  // Use IdpNetworkRequestManagerParamChecker to validate passed-in parameters
  // to IdpNetworkRequestManager methods.
  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectations(kClientId, kAccountId);
  SetNetworkRequestManager(std::move(checker));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

// Test the provider url is not in the well-known.
TEST_F(RequestTest, WellKnownNotInList) {
  RequestExpectations request_not_in_list = {
      RequestTokenStatus::kError, FederatedRequestResult::kConfigNotInWellKnown,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  const char* idp_config_url =
      kDefaultRequestParameters.identity_providers[0].provider;
  const char* kWellKnownMismatchConfigUrl = "https://mismatch.example";
  EXPECT_NE(std::string(idp_config_url), kWellKnownMismatchConfigUrl);

  MockConfiguration config = kConfigurationValid;
  config.idp_info[idp_config_url].well_known = {
      {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
  RunTest(kDefaultRequestParameters, request_not_in_list, config);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that when the provider url is not in the well-known, it succeeds when it
// is registered.
TEST_F(RequestTest, WellKnownNotInListButRegistered) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmIdPRegistration);

  const char* idp_config_url =
      kDefaultRequestParameters.identity_providers[0].provider;
  const char* kWellKnownMismatchConfigUrl = "https://mismatch.example";
  EXPECT_NE(std::string(idp_config_url), kWellKnownMismatchConfigUrl);

  MockConfiguration config = kConfigurationValid;
  config.idp_info[idp_config_url].well_known = {
      {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
  RequestParameters requestParameters = kDefaultRequestParameters;
  requestParameters.identity_providers[0].provider = "";
  requestParameters.identity_providers[0].from_idp_registration_api = true;

  // Need to simulate there is actually a registered IdP, or the call will fail.
  std::vector<GURL> registry;
  registry.emplace_back(kProviderUrlFull);
  EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
      .WillOnce(Return(registry));

  RunTest(requestParameters, kExpectationSuccess, config);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
}

// Test that the well-known file has too many provider urls.
TEST_F(RequestTest, WellKnownHasTooManyProviderUrls) {
  RequestExpectations expectation = {
      RequestTokenStatus::kError, FederatedRequestResult::kWellKnownTooBig,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration config = kConfigurationValid;
  config.idp_info[kProviderUrlFull].well_known = {
      {kProviderUrlFull, kProviderTwoUrlFull},
      {ParseStatus::kSuccess, net::HTTP_OK}};
  RunTest(kDefaultRequestParameters, expectation, config);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that the well-known enforcement is bypassed.
TEST_F(RequestTest, WellKnownEnforcementBypassed) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmWithoutWellKnownEnforcement);

  MockConfiguration config = kConfigurationValid;
  // The provider is not in the provider_list from the well-known.
  config.idp_info[kProviderUrlFull].well_known = {
      {kProviderTwoUrlFull}, {ParseStatus::kSuccess, net::HTTP_OK}};
  RunTest(kDefaultRequestParameters, kExpectationSuccess, config);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that not having the filename in the well-known fails.
TEST_F(RequestTest, WellKnownHasNoFilename) {
  MockConfiguration config{kConfigurationValid};
  config.idp_info[kProviderUrlFull].well_known.provider_urls =
      std::set<std::string>{GURL(kProviderUrlFull).GetWithoutFilename().spec()};

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kConfigNotInWellKnown,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, config);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that request fails if config is missing token endpoint.
TEST_F(RequestTest, MissingTokenEndpoint) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.token_endpoint = "";
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));

  std::vector<std::string> messages =
      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
  ASSERT_EQ(2U, messages.size());
  EXPECT_EQ(
      "Config file is missing or has an invalid URL for the following:\n"
      "\"id_assertion_endpoint\"\n",
      messages[0]);
  EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}

// Test that request fails if config has a cross-origin vc_issuance_endpoint.
TEST_F(RequestTest, InvalidVcIssuanceEndpoint) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmDelegation);

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.vc_issuance_endpoint =
      "https://cross-origin.idp.example/issuance";
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));

  std::vector<std::string> messages =
      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
  ASSERT_EQ(2U, messages.size());
  EXPECT_EQ(
      "Config file is missing or has an invalid URL for the following:\n"
      "\"vc_issuance_endpoint\"\n",
      messages[0]);
  EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}

// Test that request succeeds if config has a same-origin vc_issuance_endpoint.
TEST_F(RequestTest, ValidVcIssuanceEndpoint) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmDelegation);

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.vc_issuance_endpoint =
      "https://idp.example/issuance";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that request succeeds if config has a cross-origin vc_issuance_endpoint,
// but delegation is disabled.
TEST_F(RequestTest, CrossOriginVcIssuanceEndpointDisabledDelegation) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.vc_issuance_endpoint =
      "https://cross-origin.idp.example/issuance";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that request fails if config is missing accounts endpoint.
TEST_F(RequestTest, MissingAccountsEndpoint) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.accounts_endpoint = "";
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));

  std::vector<std::string> messages =
      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
  ASSERT_EQ(2U, messages.size());
  EXPECT_EQ(
      "Config file is missing or has an invalid URL for the following:\n"
      "\"accounts_endpoint\"\n",
      messages[0]);
  EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}

// Test that request fails if config is missing an IDP login URL.
TEST_F(RequestTest, MissingLoginURL) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.idp_login_url = "";
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());

  std::vector<std::string> messages =
      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
  ASSERT_EQ(2U, messages.size());
  EXPECT_EQ(
      "Config file is missing or has an invalid URL for the following:\n"
      "\"login_url\"\n",
      messages[0]);
  EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}

// Test that client metadata endpoint is not required in config.
TEST_F(RequestTest, MissingClientMetadataEndpoint) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.client_metadata_endpoint = "";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
}

// Test that request fails if the accounts endpoint is in a different origin
// than identity provider.
TEST_F(RequestTest, AccountEndpointDifferentOriginIdp) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.accounts_endpoint =
      kCrossOriginAccountsEndpoint;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

// Test that request fails if IDP login URL is different origin from IDP config
// URL.
TEST_F(RequestTest, LoginUrlDifferentOriginIdp) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.idp_login_url =
      "https://idp2.example/login_url";
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());

  std::vector<std::string> messages =
      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
  ASSERT_EQ(2U, messages.size());
  EXPECT_EQ(
      "Config file is missing or has an invalid URL for the following:\n"
      "\"login_url\"\n",
      messages[0]);
  EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}

// Test that request fails if the idp is not https.
TEST_F(RequestTest, ProviderNotTrustworthy) {
  IdentityProviderParameters identity_provider{
      "http://idp.example/fedcm.json", kClientId, kNonce, /*login_hint=*/"",
      /*domain_hint=*/""};
  RequestParameters request{
      std::vector<IdentityProviderParameters>{identity_provider},
      /*rp_context=*/blink::mojom::RpContext::kSignIn,
      /*rp_mode=*/blink::mojom::RpMode::kPassive};
  MockConfiguration configuration = kConfigurationValid;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdpNotPotentiallyTrustworthy,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(request, expectations, configuration);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  ExpectStatusMetrics(TokenStatus::kIdpNotPotentiallyTrustworthy);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", false, 1);
  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, false);
}

// Test that request fails if accounts endpoint cannot be reached.
TEST_F(RequestTest, AccountEndpointCannotBeReached) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kNoResponseError;
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kAccountsNoResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
}

// Test that request fails if account endpoint response cannot be parsed.
TEST_F(RequestTest, AccountsCannotBeParsed) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.Raw", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
  ExpectNoUKMPresence("AccountsSize.Raw");
  ExpectNoUKMPresence("AccountsSize.ReadyToShow");

  // Only records the following histograms if there are accounts to be shown.
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 0);
  ExpectNoUKMPresence(
      "Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch");
  ExpectNoUKMPresence("Timing.ShowAccountsDialogBreakdown.AccountsFetch");
  ExpectNoUKMPresence("Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch");
  // Records the following histogram as long as the well-known and config file
  // is fetched.
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.WellKnownAndConfigFetch", 1);
}

// Test that privacy policy, terms of service or RP brand icon URLs are not
// required in client metadata.
TEST_F(RequestTest, ClientMetadataNoUrls) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].client_metadata =
      kDefaultClientMetadata;
  configuration.idp_info[kProviderUrlFull].client_metadata.privacy_policy_url =
      "";
  configuration.idp_info[kProviderUrlFull]
      .client_metadata.terms_of_service_url = "";
  configuration.idp_info[kProviderUrlFull].client_metadata.brand_icon_url = "";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}

// Test that privacy policy URL is not required in client metadata.
TEST_F(RequestTest, ClientMetadataNoPrivacyPolicyUrl) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].client_metadata =
      kDefaultClientMetadata;
  configuration.idp_info[kProviderUrlFull].client_metadata.privacy_policy_url =
      "";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}

// Test that terms of service URL is not required in client metadata.
TEST_F(RequestTest, ClientMetadataNoTermsOfServiceUrl) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].client_metadata =
      kDefaultClientMetadata;
  configuration.idp_info[kProviderUrlFull]
      .client_metadata.terms_of_service_url = "";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}

// Test that RP brand icon URL is not required in client metadata.
TEST_F(RequestTest, ClientMetadataNoRpBrandIconUrl) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].client_metadata =
      kDefaultClientMetadata;
  configuration.idp_info[kProviderUrlFull].client_metadata.brand_icon_url = "";
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}

// Test that request fails if all of the endpoints in the config are invalid.
TEST_F(RequestTest, AllInvalidEndpoints) {
  // Both an empty url and cross origin urls are invalid endpoints.
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.accounts_endpoint =
      "https://cross-origin-1.com";
  configuration.idp_info[kProviderUrlFull].config.token_endpoint = "";
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
  std::vector<std::string> messages =
      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
  ASSERT_EQ(2U, messages.size());
  EXPECT_EQ(
      "Config file is missing or has an invalid URL for the following:\n"
      "\"id_assertion_endpoint\"\n"
      "\"accounts_endpoint\"\n",
      messages[0]);
  EXPECT_EQ("Provider's FedCM config file is invalid.", messages[1]);
}

// Tests for browser trusted login states.
TEST_F(RequestTest, BrowserTrustedLoginStateShouldBeSignUpForFirstTimeUser) {
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_EQ(LoginState::kSignUp,
            all_accounts_for_display()[0]->browser_trusted_login_state);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasSigninAccount", false,
                                       1);
}

TEST_F(RequestTest, BrowserTrustedLoginStateShouldBeSignInForReturningUser) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_EQ(LoginState::kSignIn,
            all_accounts_for_display()[0]->browser_trusted_login_state);

  // CLIENT_METADATA only needs to be fetched for obtaining links to display in
  // the disclosure text. The disclosure text is not displayed for returning
  // users, thus fetching the client metadata endpoint should be skipped.
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
  histogram_tester_.ExpectUniqueTimeSample(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch",
      base::TimeDelta(), 1);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasSigninAccount", true, 1);
}

TEST_F(RequestTest, LoginStateSuccessfulSignUpGrantsSharingPermission) {
  EXPECT_CALL(*test_permission_delegate_, GetLastUsedTimestamp(_, _, _, _))
      .WillRepeatedly(Return(std::nullopt));
  EXPECT_CALL(
      *test_permission_delegate_,
      GrantSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                             OriginFromString(kProviderUrlFull), kAccountId))
      .Times(1);
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

TEST_F(RequestTest, LoginStateFailedSignUpNotGrantSharingPermission) {
  EXPECT_CALL(*test_permission_delegate_, GetLastUsedTimestamp(_, _, _, _))
      .WillRepeatedly(Return(std::nullopt));
  EXPECT_CALL(*test_permission_delegate_, GrantSharingPermission(_, _, _, _))
      .Times(0);

  MockConfiguration configuration = kConfigurationValid;
  configuration.token_response.parse_status =
      ParseStatus::kInvalidResponseError;
  configuration.error_dialog_action = ErrorDialogAction::kClose;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  WaitForCurrentRequest(/*should_fast_forward=*/false);
  CheckExpectations(configuration, expectations);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
}

TEST_F(RequestTest, EmbedderInitiatedLoginDoesNotGrantSharingPermission) {
  FederatedEmbedderLoginRequest::Set(web_contents(),
                                     OriginFromString(kProviderUrlFull),
                                     kAccountId, base::DoNothing());

  EXPECT_CALL(*test_permission_delegate_, GetLastUsedTimestamp)
      .WillRepeatedly(Return(std::nullopt));
  EXPECT_CALL(*test_permission_delegate_, GrantSharingPermission).Times(0);

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

// Test that auto re-authn permission is not embargoed upon explicit sign-in.
TEST_F(RequestTest, ExplicitSigninEmbargo) {
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
  EXPECT_TRUE(
      test_auto_reauthn_permission_delegate_->embargoed_origins_.empty());
}

// Test that auto re-authn permission is embargoed upon successful auto
// re-authn.
TEST_F(RequestTest, AutoReauthnEmbargo) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  RequestExpectations expectation = kExpectationSuccess;
  expectation.is_auto_selected = true;
  RunTest(kDefaultRequestParameters, expectation, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
  EXPECT_TRUE(test_auto_reauthn_permission_delegate_->embargoed_origins_.count(
      OriginFromString(kRpUrl)));

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/true,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that sign-in state is enforced when the content embedder blocks the
// sign-in, for example if there is an ongoing task by an actor.
TEST_F(RequestTest, ExplicitSigninBlockedByEmbedder) {
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));
  // Pretend actor task is active.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnDisabledByEmbedder(web_contents()))
      .WillOnce(Return(true));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_FALSE(test_auto_reauthn_permission_delegate_->embargoed_origins_.count(
      OriginFromString(kRpUrl)));
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/true);
}

// Test that auto re-authn with a single account where the account is a
// returning user sets the sign-in mode to auto.
TEST_F(RequestTest, AutoReauthnForSingleReturningUserSingleAccount) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  for (const auto& idp_info : kConfigurationValid.idp_info) {
    ASSERT_EQ(idp_info.second.accounts.size(), 1u);
  }
  RequestExpectations expectation = kExpectationSuccess;
  expectation.is_auto_selected = true;

  RunTest(kDefaultRequestParameters, expectation, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/true,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to auto.
TEST_F(RequestTest, AutoReauthnForSingleReturningUserMultipleAccounts) {
  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdNicolas))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdPeter))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
  RequestExpectations expectation = kExpectationSuccess;
  expectation.is_auto_selected = true;
  RunTest(kDefaultRequestParameters, expectation, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/true,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that auto re-authn with multiple accounts and multiple returning users
// sets the sign-in mode to explicit.
TEST_F(RequestTest, AutoReauthnForMultipleReturningUsersMultipleAccounts) {
  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdNicolas))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdPeter))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  std::vector<IdentityRequestAccountPtr> multiple_accounts = kMultipleAccounts;
  multiple_accounts[0]->idp_claimed_login_state = LoginState::kSignIn;
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = multiple_accounts;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 2);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kMultiple,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that auto re-authn with single non-returning account sets the sign-in
// mode to explicit.
TEST_F(RequestTest, AutoReauthnForZeroReturningUsers) {
  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  for (const auto& idp_info : kConfigurationValid.idp_info) {
    ASSERT_EQ(idp_info.second.accounts.size(), 1u);
  }
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kZero,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to kExplicit if `mediation: required` is specified.
TEST_F(RequestTest,
       AutoReauthnForSingleReturningUserWithoutSettingAutoReauthn) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kRequired;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse,
                      MediationRequirement::kRequired);
}

// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to kExplicit if `RequiresUserMediation` is set
TEST_F(RequestTest,
       AutoReauthnForSingleReturningUserWithRequiresUserMediation) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend that auto re-authn is not disabled in settings.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));

  // Pretend that auto re-authn is not in embargo state.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  // Pretend that re-authn requires user mediation.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
      .WillOnce(Return(true));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/true,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that auto re-authn with multiple accounts and a single returning user
// sets the sign-in mode to kExplicit if "auto sign-in" is disabled.
TEST_F(RequestTest, AutoReauthnForSingleReturningUserWithAutoSigninDisabled) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend that auto re-authn is not in embargo state.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  // Pretend that re-authn does not require user mediation.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
      .WillOnce(Return(false));

  // Pretend that auto re-authn is disabled in settings.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(false));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
}

// Test that if browser has not observed sign-in in the past, the sign-in mode
// is set to explicit regardless the account's login state.
TEST_F(RequestTest, AutoReauthnBrowserNotObservedSigninBefore) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  // Set IDP claims user is signed in.
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull]
      .accounts[0]
      ->idp_claimed_login_state = LoginState::kSignIn;

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
}

// Test that if browser has not observed sign-in in the past, but the IdP has
// third-party cookies access, the sign-in mode is set to auto if IdP claims
// that the user is returning.
TEST_F(RequestTest,
       AutoReauthnBrowserNotObservedSigninButIdpHasThirdPartyCookiesAccess) {
  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  // Pretend the IdP was given third-party cookies access.
  EXPECT_CALL(*test_api_permission_delegate_,
              HasThirdPartyCookiesAccess(_, GURL(kProviderUrlFull),
                                         OriginFromString(kRpUrl)))
      .WillRepeatedly(Return(true));

  // Sharing permission won't be granted with this setup.
  EXPECT_CALL(*test_permission_delegate_,
              GrantSharingPermission(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), std::string(kAccountId)))
      .Times(0);

  // Set IDP claims user is signed in.
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull]
      .accounts[0]
      ->idp_claimed_login_state = LoginState::kSignIn;
  RequestExpectations expectation = kExpectationSuccess;
  expectation.is_auto_selected = true;
  RunTest(kDefaultRequestParameters, expectation, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
}

// Test that auto re-authn for a first time user sets the sign-in mode to
// explicit.
TEST_F(RequestTest, AutoReauthnForFirstTimeUser) {
  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
}

// Test that auto re-authn where the auto re-authn permission is blocked sets
// the sign-in mode to explicit.
TEST_F(RequestTest, AutoReauthnWithBlockedAutoReauthnPermissions) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the auto re-authn permission has been blocked for this account.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(false));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/true,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that auto re-authn where the auto re-authn cooldown is on sets
// the sign-in mode to explicit.
TEST_F(RequestTest, AutoReauthnWithCooldown) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the auto re-authn permission has been granted for this account.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  // Pretend that auto re-authn is embargoed.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(true));

  RequestExpectations expectations = kExpectationSuccess;
  expectations.standalone_console_message =
      "Auto re-authn was previously triggered less than 10 minutes ago. Only "
      "one auto re-authn request can be made every 10 minutes.";
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kOne,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/true,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that no network request is sent if `mediation: silent` is used and user
// has not granted sharing permission in the past.
TEST_F(RequestTest, AutoReauthnMediationSilentFailWithNoSharingPermission) {
  // Pretend the sharing permission has not been granted for any account.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(false));

  // Pretend the auto re-authn is disabled in settings.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/
      "Silent mediation issue: the user has not used FedCM on this "
      "site with this identity provider.",
      /*selected_idp_config_url=*/std::nullopt};
  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  EXPECT_FALSE(DidFetchAnyEndpoint());

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);

  ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
                      MediationRequirement::kSilent);

  ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that no network request is sent if `mediation: silent` is used and auto
// re-authn is in cooldown.
TEST_F(RequestTest, AutoReauthnMediationSilentFailWithEmbargo) {
  // Pretend the sharing permission has been granted for some account.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(true));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/
      "Silent mediation issue: auto re-authn is in quiet period "
      "because "
      "it was recently used on this site.",
      /*selected_idp_config_url=*/std::nullopt};
  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  EXPECT_FALSE(DidFetchAnyEndpoint());

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);
  ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
                      MediationRequirement::kSilent);

  ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/true,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that no network request is sent if `mediation: silent` is used and user
// mediation is required, e.g. `preventSilentAccess` has been invoked
TEST_F(RequestTest, AutoReauthnMediationSilentFailWithRequiresUserMediation) {
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
      .WillOnce(Return(true));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/
      "Silent mediation issue: preventSilentAccess() has been invoked "
      "on the site.",
      /*selected_idp_config_url=*/std::nullopt};
  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  EXPECT_FALSE(DidFetchAnyEndpoint());

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);

  ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
                      MediationRequirement::kSilent);

  ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/true,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test that no network request is sent if `mediation: silent` is used and user
// has disabled "auto sign-in".
TEST_F(RequestTest,
       AutoReauthnMediationSilentFailWithPasswordManagerAutoSigninDisabled) {
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(false));
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
      .WillOnce(Return(false));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/
      "Silent mediation issue: the user has disabled auto re-authn.",
      /*selected_idp_config_url=*/std::nullopt};
  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  EXPECT_FALSE(DidFetchAnyEndpoint());

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);

  ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
                      MediationRequirement::kSilent);

  ExpectAutoReauthnMetrics(/*expected_returning_accounts=*/std::nullopt,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/true,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test `mediation: silent` could fail silently after fetching accounts
TEST_F(RequestTest, AutoReauthnMediationSilentFailWithTwoReturningAccounts) {
  // Pretend the sharing permission has been granted for some account.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));

  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdNicolas))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdPeter))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the sharing permission has not been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillRepeatedly(Return(false));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/
      "Silent mediation issue: the user has used FedCM with multiple "
      "accounts on this site.",
      /*selected_idp_config_url=*/std::nullopt};
  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;
  std::vector<IdentityRequestAccountPtr> multiple_accounts = kMultipleAccounts;
  multiple_accounts[0]->idp_claimed_login_state = LoginState::kSignIn;
  multiple_accounts[1]->idp_claimed_login_state = LoginState::kSignIn;
  configuration.idp_info[kProviderUrlFull].accounts = multiple_accounts;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);

  ExpectStatusMetrics(TokenStatus::kSilentMediationFailure,
                      MediationRequirement::kSilent);

  ExpectAutoReauthnMetrics(Metrics::NumAccounts::kMultiple,
                           /*expected_succeeded=*/false,
                           /*expected_auto_reauthn_setting_blocked=*/false,
                           /*expected_auto_reauthn_embargoed=*/false,
                           /*expected_prevent_silent_access=*/false,
                           /*expected_auto_reauthn_blocked_by_embedder=*/false);
}

// Test `mediation: silent` fails silently after a failed accounts fetch.
TEST_F(RequestTest,
       AutoReauthnMediationSilentFailNotShowMismatchAfterAccountsFetch) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  // Pretend the sharing permission has been granted for exactly one account for
  // the first IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));

  // Ensure auto reauthn is not considered as disabled.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillRepeatedly(Return(false));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/
      GetConsoleErrorMessageFromResult(
          FederatedRequestResult::kAccountsNoResponse),
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;
  // Let the first IDP accounts fetch fail.
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kNoResponseError;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that `mediation: required` sets the sign-in mode to explicit even though
// other auto re-authn conditions are met.
TEST_F(RequestTest, AutoReauthnMediationRequired) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::nullopt));

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull]
      .accounts[0]
      ->idp_claimed_login_state = LoginState::kSignIn;
  configuration.mediation_requirement = MediationRequirement::kRequired;

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);

  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse,
                      MediationRequirement::kRequired);
}

TEST_F(RequestTest, MetricsForSuccessfulSignInCase) {
  // Pretends that the sharing permission has been granted for this account.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(_, _, OriginFromString(kProviderUrlFull),
                                   kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_EQ(LoginState::kSignIn,
            all_accounts_for_display()[0]->browser_trusted_login_state);

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.IsSignInUser", 1, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.AccountFieldsType",
      static_cast<int>(AccountFieldsType::kNameOrEmailAndOtherIdentifier), 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 1, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
                                       1, 1);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("AccountsSize.ReadyToShow", FedCmEntry::kEntryName, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpMode",
                                       static_cast<int>(RpMode::kPassive), 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.VerifyingDialogResult",
      static_cast<int>(VerifyingDialogResult::kSuccessExplicit), 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.FrameType", static_cast<int>(RequesterFrameType::kMainFrame),
      1);

  ExpectUKMPresence("Timing.ShowAccountsDialog");
  ExpectUKMPresenceInternal(
      "Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      FedCmEntry::kEntryName);
  ExpectUKMPresenceInternal("Timing.ShowAccountsDialogBreakdown.AccountsFetch",
                            FedCmEntry::kEntryName);
  ExpectUKMPresenceInternal(
      "Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch",
      FedCmEntry::kEntryName);
  ExpectUKMPresence("Timing.ContinueOnDialog");
  ExpectUKMPresence("Timing.IdTokenResponse");
  ExpectUKMPresence("Timing.TurnaroundTime");
  ExpectNoUKMPresence("Timing.CancelOnDialog");
  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
  ExpectUkmValue("RpMode", static_cast<int>(RpMode::kPassive));
  ExpectUkmValue("VerifyingDialogResult",
                 static_cast<int>(VerifyingDialogResult::kSuccessExplicit));
  ExpectUkmValue("FrameType", static_cast<int>(RequesterFrameType::kMainFrame));

  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}

// Test that request fails if account picker is explicitly dismissed.
TEST_F(RequestTest, MetricsForUIExplicitlyDismissed) {
  base::HistogramTester histogram_tester_;
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  for (const auto& idp_info : kConfigurationValid.idp_info) {
    ASSERT_EQ(idp_info.second.accounts.size(), 1u);
  }
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
  ASSERT_TRUE(did_show_accounts_dialog());
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignUp);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);

  ExpectUKMPresence("Timing.ShowAccountsDialog");
  ExpectUKMPresence("Timing.CancelOnDialog");
  ExpectNoUKMPresence("Timing.ContinueOnDialog");
  ExpectNoUKMPresence("Timing.IdTokenResponse");
  ExpectNoUKMPresence("Timing.TurnaroundTime");
  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");

  ExpectStatusMetrics(TokenStatus::kShouldEmbargo);
}

// Test that request is not completed if user ignores the UI.
TEST_F(RequestTest, UIIsIgnored) {
  base::HistogramTester histogram_tester_;

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;

  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  task_environment()->FastForwardBy(base::Minutes(10));

  EXPECT_FALSE(request_helper_->was_callback_called());

  // The dialog should have been shown. The dialog controller should not be
  // destroyed.
  ASSERT_TRUE(did_show_accounts_dialog());
  EXPECT_TRUE(weak_dialog_controller);

  // Only the time to show the account dialog gets recorded.
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.CancelOnDialog", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Status.RequestIdToken", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
}

TEST_F(RequestTest, MetricsForWebContentsVisible) {
  base::HistogramTester histogram_tester;
  // Sets RenderFrameHost to visible
  test_rvh()->SimulateWasShown();
  ASSERT_EQ(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
            PageVisibilityState::kVisible);

  // Pretends that the sharing permission has been granted for this account.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(_, _, OriginFromString(kProviderUrlFull),
                                   kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_EQ(LoginState::kSignIn,
            all_accounts_for_display()[0]->browser_trusted_login_state);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsVisible", 1, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 1, 1);
}

// Test that request could succeed with auto re-authn even if the web contents
// invisible.
TEST_F(RequestTest, MetricsForWebContentsInvisible) {
  base::HistogramTester histogram_tester;
  test_rvh()->SimulateWasShown();
  ASSERT_EQ(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
            PageVisibilityState::kVisible);

  // Sets the RenderFrameHost to invisible
  test_rvh()->SimulateWasHidden();
  ASSERT_NE(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
            PageVisibilityState::kVisible);

  // Pretends that the sharing permission has been granted for this account.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(_, _, OriginFromString(kProviderUrlFull),
                                   kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_EQ(LoginState::kSignIn,
            all_accounts_for_display()[0]->browser_trusted_login_state);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsVisible", 0, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 1, 1);
}

TEST_F(RequestTest, MetricsForFeatureIsDisabled) {
  test_api_permission_delegate_->permission_override_ =
      std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                     ApiPermissionStatus::BLOCKED_VARIATIONS);

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kDisabledInFlags,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  ExpectStatusMetrics(TokenStatus::kDisabledInFlags);
}

TEST_F(RequestTest,
       MetricsForFeatureIsDisabledNotDoubleCountedWithUnhandledRequest) {
  test_api_permission_delegate_->permission_override_ =
      std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                     ApiPermissionStatus::BLOCKED_VARIATIONS);

  RunDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  // Delete the request before DelayTimer kicks in.
  ResetAndDeleteRequest();

  // If double counted, these samples would not be unique so the following
  // checks will fail.
  ExpectStatusMetrics(TokenStatus::kDisabledInFlags);
}

TEST_F(RequestTest,
       MetricsForFeatureIsDisabledNotDoubleCountedWithAbortedRequest) {
  test_api_permission_delegate_->permission_override_ =
      std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                     ApiPermissionStatus::BLOCKED_VARIATIONS);

  RunDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  // Abort the request before DelayTimer kicks in.
  request_->Abort();

  // If double counted, these samples would not be unique so the following
  // checks will fail.
  ExpectStatusMetrics(TokenStatus::kDisabledInFlags);
}

// Test that sign-in states match if IDP claims that user is signed in and
// browser also observes that user is signed in.
TEST_F(RequestTest, MetricsForSignedInOnBothIdpAndBrowser) {
  // Set browser observes user is signed in.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  // Set IDP claims user is signed in.
  MockConfiguration configuration = kConfigurationValid;
  std::vector<IdentityRequestAccountPtr> all_accounts_for_display =
      std::vector<IdentityRequestAccountPtr>(kSingleAccount.begin(),
                                             kSingleAccount.end());
  all_accounts_for_display[0]->idp_claimed_login_state = LoginState::kSignIn;
  configuration.idp_info[kProviderUrlFull].accounts = all_accounts_for_display;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.SignInStateMatch",
                                       SignInStateMatchStatus::kMatch, 1);
  ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus::kMatch);
}

// Test that sign-in states match if IDP claims that user is not signed in and
// browser also observes that user is not signed in.
TEST_F(RequestTest, MetricsForNotSignedInOnBothIdpAndBrowser) {
  // Set browser observes user is not signed in.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::nullopt));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  // By default, IDP claims user is not signed in.
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.SignInStateMatch",
                                       SignInStateMatchStatus::kMatch, 1);
  ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus::kMatch);
}

// Test that sign-in states mismatch if IDP claims that user is signed in but
// browser observes that user is not signed in.
TEST_F(RequestTest, MetricsForOnlyIdpClaimedSignIn) {
  // Set browser observes user is not signed in.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::nullopt));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  // Set IDP claims user is signed in.
  MockConfiguration configuration = kConfigurationValid;
  std::vector<IdentityRequestAccountPtr> all_accounts_for_display =
      std::vector<IdentityRequestAccountPtr>(kSingleAccount.begin(),
                                             kSingleAccount.end());
  all_accounts_for_display[0]->idp_claimed_login_state = LoginState::kSignIn;
  configuration.idp_info[kProviderUrlFull].accounts = all_accounts_for_display;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.Status.SignInStateMatch",
      SignInStateMatchStatus::kIdpClaimedSignIn, 1);
  ExpectSignInStateMatchStatusUKM(SignInStateMatchStatus::kIdpClaimedSignIn);
}

// Test that sign-in states mismatch if IDP claims that user is not signed in
// but browser observes that user is signed in.
TEST_F(RequestTest, MetricsForOnlyBrowserObservedSignIn) {
  // Set browser observes user is signed in.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.Status.SignInStateMatch",
      SignInStateMatchStatus::kBrowserObservedSignIn, 1);
  ExpectSignInStateMatchStatusUKM(
      SignInStateMatchStatus::kBrowserObservedSignIn);
}

// Test that embargo is requested if the
// IdentityRequestDialogController::ShowAccountsDialog() callback requests it.
TEST_F(RequestTest, RequestEmbargo) {
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;

  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.count(
      main_test_rfh()->GetLastCommittedOrigin()));
}

// Test that the embargo dismiss count is reset when the user grants consent via
// the FedCM dialog.
TEST_F(RequestTest, RemoveEmbargoOnUserConsent) {
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.empty());
}

// Test that token request fails if FEDERATED_IDENTITY_API content setting is
// disabled for the RP origin.
TEST_F(RequestTest, ApiBlockedForOrigin) {
  test_api_permission_delegate_->permission_override_ =
      std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                     ApiPermissionStatus::BLOCKED_SETTINGS);
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kDisabledInSettings,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());
  ExpectStatusMetrics(TokenStatus::kDisabledInSettings);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", false, 1);
  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, false);
}

// Test that token request succeeds if FEDERATED_IDENTITY_API content setting is
// enabled for RP origin but disabled for an unrelated origin.
TEST_F(RequestTest, ApiBlockedForUnrelatedOrigin) {
  const url::Origin kUnrelatedOrigin = OriginFromString("https://rp2.example/");

  test_api_permission_delegate_->permission_override_ =
      std::make_pair(kUnrelatedOrigin, ApiPermissionStatus::BLOCKED_SETTINGS);
  ASSERT_NE(main_test_rfh()->GetLastCommittedOrigin(), kUnrelatedOrigin);
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

class RequestTestCancelConsistency : public RequestTest,
                                     public ::testing::WithParamInterface<int> {
};
INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
                         RequestTestCancelConsistency,
                         ::testing::Values(false, true),
                         ::testing::PrintToStringParamName());

// Test that the RP cannot use CancelTokenRequest() to determine whether
// Option 1: FedCM dialog is shown but user has not interacted with it
// Option 2: FedCM API is disabled via variations
TEST_P(RequestTestCancelConsistency, AccountNotSelected) {
  const bool fedcm_disabled = GetParam();

  if (fedcm_disabled) {
    test_api_permission_delegate_->permission_override_ =
        std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                       ApiPermissionStatus::BLOCKED_VARIATIONS);
  }

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;
  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  request_remote_->Abort();

  WaitForCurrentRequest();
  FederatedRequestResult result = fedcm_disabled
                                      ? FederatedRequestResult::kDisabledInFlags
                                      : FederatedRequestResult::kCanceled;
  RequestExpectations expectations{RequestTokenStatus::kErrorCanceled, result,
                                   /*standalone_console_message=*/std::nullopt,
                                   /*selected_idp_config_url=*/std::nullopt};
  CheckExpectations(configuration, expectations);
  ExpectStatusMetrics(fedcm_disabled ? TokenStatus::kDisabledInFlags
                                     : TokenStatus::kAborted);
}

namespace {

// TestDialogController which disables FedCM API when FedCM account selection
// dialog is shown.
class DisableApiWhenDialogShownDialogController : public TestDialogController {
 public:
  DisableApiWhenDialogShownDialogController(
      MockConfiguration configuration,
      TestApiPermissionDelegate* api_permission_delegate,
      const url::Origin rp_origin_to_disable)
      : TestDialogController(configuration),
        api_permission_delegate_(api_permission_delegate),
        rp_origin_to_disable_(rp_origin_to_disable) {}
  ~DisableApiWhenDialogShownDialogController() override = default;

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

  bool ShowAccountsDialog(
      RelyingPartyData rp_data,
      const std::vector<IdentityProviderDataPtr>& idp_list,
      const std::vector<IdentityRequestAccountPtr>& accounts,
      const std::vector<IdentityRequestAccountPtr>& filtered_accounts,
      blink::mojom::RpMode rp_mode,
      IdentityRequestDialogController::AccountSelectionCallback on_selected,
      IdentityRequestDialogController::LoginToIdPCallback on_add_account,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::AccountsDisplayedCallback
          accounts_displayed_callback) override {
    // Disable FedCM API
    api_permission_delegate_->permission_override_ = std::make_pair(
        rp_origin_to_disable_, ApiPermissionStatus::BLOCKED_SETTINGS);

    // Call parent class method in order to store callback parameters.
    return TestDialogController::ShowAccountsDialog(
        std::move(rp_data), idp_list, accounts, filtered_accounts, rp_mode,
        std::move(on_selected), std::move(on_add_account),
        std::move(dismiss_callback), std::move(accounts_displayed_callback));
  }

 private:
  raw_ptr<TestApiPermissionDelegate> api_permission_delegate_;
  url::Origin rp_origin_to_disable_;
};

}  // namespace

// Test that the request fails if user proceeds with the sign in workflow after
// disabling the API while an existing accounts dialog is shown.
TEST_F(RequestTest, ApiDisabledAfterAccountsDialogShown) {
  base::HistogramTester histogram_tester_;

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kDisabledInSettings,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  url::Origin rp_origin_to_disable = main_test_rfh()->GetLastCommittedOrigin();
  SetDialogController(
      std::make_unique<DisableApiWhenDialogShownDialogController>(
          kConfigurationValid, test_api_permission_delegate_.get(),
          rp_origin_to_disable));

  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));

  ukm_loop.Run();

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.WellKnownAndConfigFetch",
      1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.AccountsFetch", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ShowAccountsDialogBreakdown.ClientMetadataFetch", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);

  ExpectUKMPresence("Timing.ShowAccountsDialog");
  ExpectNoUKMPresence("Timing.ContinueOnDialog");
  ExpectNoUKMPresence("Timing.IdTokenResponse");
  ExpectNoUKMPresence("Timing.TurnaroundTime");
  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");

  ExpectStatusMetrics(TokenStatus::kDisabledInSettings);
}

// Test the disclosure_text_shown value in the token post data for sign-up case.
TEST_F(RequestTest, DisclosureTextShownForFirstTimeUser) {
  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) + "&disclosure_text_shown=true" +
      "&is_auto_selected=false&mode=passive&fields=name,email,picture&"
      "disclosure_shown_for="
      "name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

// Test the disclosure_text_shown value in the token post data for returning
// user case.
TEST_F(RequestTest, DisclosureTextNotShownForReturningUser) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) +
      "&disclosure_text_shown=false&is_auto_"
      "selected=false&mode=passive&fields=name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  MockConfiguration config = kConfigurationValid;
  config.mediation_requirement = MediationRequirement::kRequired;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, config);
}

// Test that the values in the token post data are escaped according to the
// application/x-www-form-urlencoded spec.
TEST_F(RequestTest, TokenEndpointPostDataEscaping) {
  const std::string kAccountIdWithSpace("account id");
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts[0]->id =
      kAccountIdWithSpace;

  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=account+id&disclosure_text_"
      "shown=true&is_auto_selected=false&mode=passive&fields=name,email,"
      "picture&disclosure_"
      "shown_for=name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}

// Test that the is_auto_selected value in the token post
// data for sign-up case.
TEST_F(RequestTest, AutoSelectedFlagForNewUser) {
  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) + "&disclosure_text_shown=true" +
      "&is_auto_selected=false&mode=passive&fields=name,email,picture&"
      "disclosure_shown_for="
      "name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

// Test that the is_auto_selected value in the token post
// data for returning user with `mediation:required`.
TEST_F(RequestTest, AutoSelectedFlagForReturningUserWithMediationRequired) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) +
      "&disclosure_text_shown=false" +
      "&is_auto_selected=false&mode=passive&fields=name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  MockConfiguration config = kConfigurationValid;
  config.mediation_requirement = MediationRequirement::kRequired;

  RunTest(kDefaultRequestParameters, kExpectationSuccess, config);
}

// Test that the is_auto_selected value in the token post
// data for returning user with `mediation:optional`.
TEST_F(RequestTest, AutoSelectedFlagForReturningUserWithMediationOptional) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));

  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) +
      "&disclosure_text_shown=false" +
      "&is_auto_selected=true&mode=passive&fields=name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  MockConfiguration config = kConfigurationValid;
  config.mediation_requirement = MediationRequirement::kOptional;
  RequestExpectations expectation = kExpectationSuccess;
  expectation.is_auto_selected = true;

  RunTest(kDefaultRequestParameters, expectation, config);
}

// Test that the is_auto_selected value in the token post
// data for the quiet period use case.
TEST_F(RequestTest, AutoSelectedFlagIfInQuietPeriod) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));

  // Pretend the auto re-authn is in quiet period.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(true));

  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) +
      "&disclosure_text_shown=false&is_auto_"
      "selected=false&mode=passive&fields=name,email,picture");
  SetNetworkRequestManager(std::move(checker));

  RequestExpectations expectations = kExpectationSuccess;
  expectations.standalone_console_message =
      "Auto re-authn was previously triggered less than 10 minutes ago. Only "
      "one auto re-authn request can be made every 10 minutes.";
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
}

TEST_F(RequestTest, RegisteredIdpInIdAssertion) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmIdPRegistration);
  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  RequestParameters request = kDefaultRequestParameters;
  constexpr char kType[] = "someType";
  request.identity_providers[0].type = kType;
  checker->SetExpectedTokenPostData(
      "client_id=" + std::string(kClientId) + "&nonce=" + std::string(kNonce) +
      "&account_id=" + std::string(kAccountId) + "&disclosure_text_shown=true" +
      "&is_auto_selected=false&mode=passive&fields=name,email,picture&"
      "disclosure_shown_for="
      "name,email,picture"
      "&type=" +
      kType);
  SetNetworkRequestManager(std::move(checker));

  MockConfiguration config = kConfigurationValid;
  config.idp_info[kProviderUrlFull].config.types = {kType};
  RunTest(request, kExpectationSuccess, config);
}

namespace {

// TestIdpNetworkRequestManager subclass which runs the `account_list_task`
// passed-in to the constructor prior to the accounts endpoint returning.
class IdpNetworkRequestManagerClientMetadataTaskRunner
    : public TestIdpNetworkRequestManager {
 public:
  explicit IdpNetworkRequestManagerClientMetadataTaskRunner(
      base::OnceClosure client_metadata_task)
      : client_metadata_task_(std::move(client_metadata_task)) {}

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

  void FetchClientMetadata(const GURL& client_metadata_endpoint_url,
                           const std::string& client_id,
                           int rp_brand_icon_ideal_size,
                           int rp_brand_icon_minimum_size,
                           FetchClientMetadataCallback callback) override {
    // Make copies because running the task might destroy
    // Request and invalidate the references.
    GURL client_metadata_endpoint_url_copy = client_metadata_endpoint_url;
    std::string client_id_copy = client_id;

    if (client_metadata_task_) {
      std::move(client_metadata_task_).Run();
    }
    TestIdpNetworkRequestManager::FetchClientMetadata(
        client_metadata_endpoint_url_copy, client_id_copy,
        rp_brand_icon_ideal_size, rp_brand_icon_minimum_size,
        std::move(callback));
  }

 private:
  base::OnceClosure client_metadata_task_;
};

void NavigateToUrl(WebContents* web_contents, const GURL& url) {
  static_cast<TestWebContents*>(web_contents)
      ->NavigateAndCommit(url, ui::PAGE_TRANSITION_LINK);
}

}  // namespace

// Test that the account chooser is not shown if the page navigates prior to the
// client metadata endpoint request completing and BFCache is enabled.
TEST_F(RequestTest, NavigateDuringClientMetadataFetchBFCacheEnabled) {
  base::test::ScopedFeatureList list;
  list.InitWithFeaturesAndParameters(
      GetBasicBackForwardCacheFeatureForTesting(),
      GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  ASSERT_TRUE(IsBackForwardCacheEnabled());

  SetNetworkRequestManager(
      std::make_unique<IdpNetworkRequestManagerClientMetadataTaskRunner>(
          base::BindOnce(&NavigateToUrl, web_contents(), GURL(kRpOtherUrl))));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      // No console message is received, so pass
      // FederatedRequestResult::kSuccess.
      FederatedRequestResult::kSuccess,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 0, 1);
}

// Test that the account chooser is not shown if the page navigates prior to the
// accounts endpoint request completing and BFCache is disabled.
TEST_F(RequestTest, NavigateDuringClientMetadataFetchBFCacheDisabled) {
  base::test::ScopedFeatureList list;
  list.InitAndDisableFeature(features::kBackForwardCache);
  ASSERT_FALSE(IsBackForwardCacheEnabled());

  SetNetworkRequestManager(
      std::make_unique<IdpNetworkRequestManagerClientMetadataTaskRunner>(
          base::BindOnce(&NavigateToUrl, web_contents(), GURL(kRpOtherUrl))));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      // When the RenderFrameHost changes on navigation, no console message is
      // received, so pass FederatedRequestResult::kSuccess.
      main_rfh()->ShouldChangeRenderFrameHostOnSameSiteNavigation()
          ? FederatedRequestResult::kSuccess
          : FederatedRequestResult::kError,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
}

// Test that the accounts are reordered so that accounts with a LoginState equal
// to kSignIn are listed before accounts with a LoginState equal to kSignUp.
TEST_F(RequestTest, ReorderMultipleAccounts) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);

  // Check the account order using the account ids.
  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
  EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdNicolas);
  EXPECT_EQ(all_accounts_for_display()[2]->id, kAccountIdZach);
}

// Test that first API call with a given IDP is not affected by the
// IdpSigninStatus bit.
TEST_F(RequestTest, IdpSigninStatusTestFirstTimeFetchSuccess) {
  EXPECT_CALL(*test_permission_delegate_,
              SetIdpSigninStatus(OriginFromString(kProviderUrlFull), true, _))
      .Times(1);

  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  checker->SetExpectations(kClientId, kAccountId);
  SetNetworkRequestManager(std::move(checker));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

// Test that first API call with a given IDP will not show a UI in case of
// failure during fetching accounts.
TEST_F(RequestTest, IdpSigninStatusTestFirstTimeFetchNoFailureUi) {
  EXPECT_CALL(*test_permission_delegate_,
              SetIdpSigninStatus(OriginFromString(kProviderUrlFull), false, _))
      .Times(1);
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that a failure UI will be displayed if the accounts fetch is failed but
// the IdpSigninStatus claims that the user is signed in.
TEST_F(RequestTest, IdpSigninStatusTestShowFailureUi) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  configuration.idp_signin_status_mismatch_dialog_action =
      IdpSigninStatusMismatchDialogAction::kClose;
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/
      GetConsoleErrorMessageFromResult(
          FederatedRequestResult::kAccountsInvalidResponse),
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that API calls will fail before sending any network request if
// IdpSigninStatus shows that the user is not signed in with the IDP. No failure
// UI is displayed.
TEST_F(RequestTest, IdpSigninStatusTestApiFailedIfUserNotSignedInWithIdp) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kNotSignedInWithIdp,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

namespace {

// TestIdpNetworkRequestManager which enables specifying the ParseStatus for
// config and accounts endpoint fetch.
class ParseStatusOverrideIdpNetworkRequestManager
    : public TestIdpNetworkRequestManager {
 public:
  ParseStatus config_parse_status_{ParseStatus::kSuccess};
  ParseStatus accounts_parse_status_{ParseStatus::kSuccess};

  ParseStatusOverrideIdpNetworkRequestManager() = default;
  ~ParseStatusOverrideIdpNetworkRequestManager() override = default;

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

  void FetchConfig(const GURL& provider,
                   int idp_brand_icon_ideal_size,
                   int idp_brand_icon_minimum_size,
                   FetchConfigCallback callback) override {
    if (config_parse_status_ != ParseStatus::kSuccess) {
      ++num_fetched_[FetchedEndpoint::CONFIG];

      FetchStatus fetch_status{config_parse_status_, net::HTTP_OK};
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE, base::BindOnce(std::move(callback), fetch_status,
                                    IdpNetworkRequestManager::Endpoints(),
                                    IdentityProviderMetadata()));
      return;
    }
    TestIdpNetworkRequestManager::FetchConfig(
        provider, idp_brand_icon_ideal_size, idp_brand_icon_minimum_size,
        std::move(callback));
  }

  bool SendAccountsRequest(const url::Origin& idp_origin,
                           const GURL& accounts_url,
                           AccountsRequestCallback callback) override {
    if (accounts_parse_status_ != ParseStatus::kSuccess) {
      ++num_fetched_[FetchedEndpoint::ACCOUNTS];

      FetchStatus fetch_status{accounts_parse_status_, net::HTTP_OK};
      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(callback), fetch_status,
                         IdpNetworkRequestManager::AccountsResponse()));
      return true;
    }

    return TestIdpNetworkRequestManager::SendAccountsRequest(
        idp_origin, accounts_url, std::move(callback));
  }
};

}  // namespace

// Test behavior for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) User signs-in
// 3) User selects "Continue" in account chooser dialog.
TEST_F(RequestTest, FailureUiThenSuccessfulSignin) {
  SetNetworkRequestManager(
      std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
  auto* network_manager =
      static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
          test_network_request_manager_.get());

  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);

  // Setup IdP sign-in status mismatch.
  network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;

  RunDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_FALSE(did_show_accounts_dialog());

  // Simulate user signing into IdP by updating the IdP sign-in status and
  // calling the observer.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  network_manager->accounts_parse_status_ = ParseStatus::kSuccess;
  request_->OnIdpSigninStatusReceived(kIdpOrigin, /*idp_signin_status=*/true);

  WaitForCurrentRequest();
  RequestExpectations expectation = kExpectationSuccess;
  expectation.standalone_console_message = GetConsoleErrorMessageFromResult(
      FederatedRequestResult::kAccountsInvalidResponse);
  CheckExpectations(kConfigurationValid, expectation);

  EXPECT_TRUE(did_show_accounts_dialog());

  // After the IdP sign-in status was updated, the endpoints should have been
  // fetched a 2nd time, but well-known is cached so it is only fetched 1 time.
  EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 1u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);

  ExpectUKMPresence("AccountsDialogShown2");
  ExpectUKMPresence("MismatchDialogShown2");
  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}

// Test behavior for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) User switches tabs
// 3) User signs into IdP in different tab
TEST_F(RequestTest, FailureUiThenSuccessfulSigninButHidden) {
  SetNetworkRequestManager(
      std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
  auto* network_manager =
      static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
          test_network_request_manager_.get());

  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);

  // Setup IdP sign-in status mismatch.
  network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;

  RunDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_FALSE(did_show_accounts_dialog());

  // Simulate the user switching to a different tab.
  test_rvh()->SimulateWasHidden();

  // Simulate user signing into IdP by updating the IdP signin status and
  // calling observer.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  network_manager->accounts_parse_status_ = ParseStatus::kSuccess;
  request_->OnIdpSigninStatusReceived(kIdpOrigin, /*idp_signin_status=*/true);

  WaitForCurrentRequest();
  RequestExpectations expectation = kExpectationSuccess;
  expectation.standalone_console_message = GetConsoleErrorMessageFromResult(
      FederatedRequestResult::kAccountsInvalidResponse);
  CheckExpectations(kConfigurationValid, expectation);

  // The FedCM dialog should switch to the account picker. The user should
  // see a new dialog when they switch back to the FedCM tab.
  EXPECT_TRUE(did_show_accounts_dialog());

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);

  ExpectUKMPresence("AccountsDialogShown2");
  ExpectUKMPresence("MismatchDialogShown2");
  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}

// Test behavior for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) In a different tab, user signs into different IdP
TEST_F(RequestTest, FailureUiSigninFromDifferentIdp) {
  SetNetworkRequestManager(
      std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
  auto* network_manager =
      static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
          test_network_request_manager_.get());

  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
  url::Origin kOtherOrigin = OriginFromString("https://idp.other");

  // Setup IdP sign-in status mismatch.
  network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;

  // We want the mismatch dialog to stay open initially, so we use kNone.
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_signin_status_mismatch_dialog_action =
      IdpSigninStatusMismatchDialogAction::kNone;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_FALSE(did_show_accounts_dialog());

  size_t num_well_known_fetches = NumFetched(FetchedEndpoint::WELL_KNOWN);

  // Simulate user signing into different IdP by updating the IdP signin status
  // and calling observer.
  test_permission_delegate_->idp_signin_statuses_[kOtherOrigin] = true;

  // The request must still be alive here.
  ASSERT_TRUE(request_);
  request_->OnIdpSigninStatusReceived(kOtherOrigin,
                                      /*idp_signin_status=*/true);
  // No task should be posted, so we don't need to run the loop here.

  // The request must STILL be alive because different IDP sign-in should not
  // close it.
  ASSERT_TRUE(request_);

  // Now, explicitly close the dialog to finish the test and record metrics.
  ASSERT_TRUE(dialog_controller_state_.mismatch_dismiss_callback);
  std::move(dialog_controller_state_.mismatch_dismiss_callback)
      .Run(DismissReason::kCloseButton);

  // Wait until the request completes (invalidating its weak pointers).
  // TODO(crbug.com/514241802): Actually assert that the Request object is
  // destroyed in RequestService once the cleanup path is implemented.
  ASSERT_TRUE(base::test::RunUntil([&]() { return !request_; }));

  // Now the request should be destroyed.
  EXPECT_FALSE(request_);

  // No fetches should have been triggered.
  EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), num_well_known_fetches);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);

  ExpectNoUKMPresence("AccountsDialogShown2");
  ExpectUKMPresence("MismatchDialogShown2");
  ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectUKMPresence("Timing.MismatchDialogShownDuration");
}

// Test that for the following sequence of events:
// 1) Failure dialog is shown due to IdP sign-in status mismatch
// 2) IdP sign-in status is updated
// 3) Accounts endpoint still returns an empty list
// That ShowFailureDialog() is called a 2nd time after the IdP sign-in status
// update.
TEST_F(RequestTest, FailureUiAccountEndpointKeepsFailing) {
  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);

  MockConfiguration configuration = kConfigurationValid;

  // Setup IdP sign-in status mismatch.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;

  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_FALSE(did_show_accounts_dialog());

  // Update IdP sign-in status. Keep accounts endpoint returning empty list.
  // Close mismatch dialog upon sign-in status change to check that the
  // appropriate metrics are recorded.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  weak_dialog_controller->SetIdpSigninStatusMismatchDialogAction(
      IdpSigninStatusMismatchDialogAction::kClose);
  request_->OnIdpSigninStatusReceived(kIdpOrigin, /*idp_signin_status=*/true);

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(2u, dialog_controller_state_
                    .num_show_idp_signin_status_mismatch_dialog_requests);

  // After the IdP sign-in status was updated, accounts are fetched a 2nd time,
  // but well-known is cached so it is only fetched 1 time.
  EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 1u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);

  ExpectNoUKMPresence("AccountsDialogShown2");
  ExpectUKMPresence("MismatchDialogShown2");
  ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
}

// Test that when IdpSigninStatus API does not have any state for an IDP, that
// the state transitions to sign-in if the accounts endpoint returns a
// non-empty list of accounts.
TEST_F(
    RequestTest,
    IdpSigninStatusMetricsModeUndefinedTransitionsToSignedinWhenHaveAccounts) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = std::nullopt;
  EXPECT_CALL(*test_permission_delegate_,
              SetIdpSigninStatus(OriginFromString(kProviderUrlFull), true, _));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

TEST_F(RequestTest, AllSuccessfulMultiIdpRequestWithoutIdpReorder) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  // Set the account from the first IDP as returning as well, so the first
  // selected account should be that one (second IDP also has returning accounts
  // and no reordering should happen).
  MockConfiguration config = kConfigurationMultiIdpValid;
  config.idp_info[kProviderUrlFull].accounts[0]->idp_claimed_login_state =
      LoginState::kSignIn;
  RunTest(kDefaultMultiIdpRequestParameters, kExpectationSuccess, config);
  EXPECT_EQ(2u, NumFetched(FetchedEndpoint::ACCOUNTS));

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());
  ukm_loop.Run();
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
                                       1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOnDialog", 1);
  ExpectUKMPresence("Timing.ContinueOnDialog");
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 1);
  ExpectUKMPresence("Timing.IdTokenResponse");

  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 1);
  ExpectUKMPresence("Timing.TurnaroundTime");

  ExpectUkmValueInEntry("AccountsRequestSent2", FedCmEntry::kEntryName, 2);
  ExpectUkmValueInEntry("AccountsRequestSent2", FedCmIdpEntry::kEntryName, 1,
                        2);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 2);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  ExpectUKMCount("Timing.AccountsDialogShownDuration", FedCmEntry::kEntryName,
                 1);
  ExpectUKMCount("Timing.AccountsDialogShownDuration",
                 FedCmIdpEntry::kEntryName, 2);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
  ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
  ExpectNoUKMPresence("Timing.MismatchDialogShown2");
  ExpectUKMCount("Timing.ShowAccountsDialog", FedCmEntry::kEntryName, 1);
  ExpectUKMCount("Timing.ShowAccountsDialog", FedCmIdpEntry::kEntryName, 2);
  ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);

  ExpectUkmValue("NumIdpsRequested", 2, 2);
  ExpectUkmValue("NumIdpsMismatch", 0, 2);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                       1);
  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}

// Test successful multi IDP FedCM request.
TEST_F(RequestTest, AllSuccessfulMultiIdpRequestWithIdpReorder) {
  RequestExpectations expectations = kExpectationSuccess;
  // Since the first IDP does not set the login state of the account but the
  // second IDP has one with state set to SignIn, selecting the first account
  // means that the second IDP is the one that is selected.
  expectations.selected_idp_config_url = kProviderTwoUrlFull;
  RunTest(kDefaultMultiIdpRequestParameters, expectations,
          kConfigurationMultiIdpValid);
  EXPECT_EQ(2u, NumFetched(FetchedEndpoint::ACCOUNTS));

  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  ExpectUKMCount("Timing.ShowAccountsDialog", FedCmEntry::kEntryName, 1);
  ExpectUKMCount("Timing.ShowAccountsDialog", FedCmIdpEntry::kEntryName, 2);

  ExpectUkmValueInEntry(
      "Status.RequestIdToken", FedCmEntry::kEntryName,
      static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse));
  ExpectUkmValueInEntry(
      "Status.RequestIdToken", FedCmIdpEntry::kEntryName,
      static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse),
      /*expected_count=*/2,
      /*other_values_allowed=*/true);
  ExpectUkmValueInEntry("Status.RequestIdToken", FedCmIdpEntry::kEntryName,
                        static_cast<int>(TokenStatus::kOtherIdpChosen),
                        /*expected_count=*/2,
                        /*other_values_allowed=*/true);
  ExpectUkmValue("NumIdpsRequested", 2, 2);
  ExpectUkmValue("NumIdpsMismatch", 0, 2);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                       1);
  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}

// Test fetching information for the 1st IdP failing, and succeeding for the
// second.
TEST_F(RequestTest, FirstIdpWellKnownInvalid) {
  // Intentionally fail the 1st provider's request by having an invalid
  // well-known file.
  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.idp_info[kProviderUrlFull].well_known.provider_urls =
      std::set<std::string>{"https://not-in-list.example"};

  RequestExpectations expectations = {
      RequestTokenStatus::kSuccess,
      FederatedRequestResult::kConfigNotInWellKnown,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/kProviderTwoUrlFull};

  RunTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
  EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::CONFIG), 2u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::TOKEN), 1u);

  ExpectUKMCount("AccountsRequestSent2", FedCmEntry::kEntryName, 1);
  ExpectUKMCount("AccountsRequestSent2", FedCmIdpEntry::kEntryName, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                       1);
  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}

// Test fetching information for the 1st IdP succeeding, and failing for the
// second.
TEST_F(RequestTest, SecondIdpWellKnownInvalid) {
  // Intentionally fail the 2nd provider's request by having an invalid
  // well-known file.
  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.idp_info[kProviderTwoUrlFull].well_known.provider_urls =
      std::set<std::string>{"https://not-in-list.example"};

  RequestExpectations expectations = {
      RequestTokenStatus::kSuccess,
      FederatedRequestResult::kConfigNotInWellKnown,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/kProviderUrlFull};

  RunTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
  EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::CONFIG), 2u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::TOKEN), 1u);

  ExpectUKMCount("AccountsRequestSent2", FedCmEntry::kEntryName, 1);
  ExpectUKMCount("AccountsRequestSent2", FedCmIdpEntry::kEntryName, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                       1);
  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}

// Test fetching information for all of the IdPs failing.
TEST_F(RequestTest, AllWellKnownsInvalid) {
  // Intentionally fail the requests for both IdPs by returning an invalid
  // well-known file.
  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.idp_info[kProviderUrlFull].well_known.provider_urls =
      std::set<std::string>{"https://not-in-list.example"};
  configuration.idp_info[kProviderTwoUrlFull].well_known.provider_urls =
      std::set<std::string>{"https://not-in-list.example"};

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kConfigNotInWellKnown,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
  EXPECT_EQ(NumFetched(FetchedEndpoint::WELL_KNOWN), 2u);
  EXPECT_EQ(NumFetched(FetchedEndpoint::CONFIG), 2u);
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));

  ExpectNoUKMPresence("Timing.ShowAccountsDialog");
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsRequestSent", 0);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.IdentityProvidersCount", 2,
                                       1);
  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("IdentityProvidersCount", FedCmEntry::kEntryName, 2);
}

// Test multi IDP FedCM request with duplicate IDPs should throw an error.
TEST_F(RequestTest, DuplicateIdpMultiIdpRequest) {
  RequestParameters request_parameters = kDefaultMultiIdpRequestParameters;
  request_parameters.identity_providers =
      std::vector<IdentityProviderParameters>{
          request_parameters.identity_providers[0],
          request_parameters.identity_providers[0]};

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kError,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(request_parameters, expectations, kConfigurationMultiIdpValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectTotalCount("Blink.FedCm.IdentityProvidersCount", 0);
  ExpectUKMCount("IdentityProvidersCount", FedCmEntry::kEntryName, 0);
}

// Test that API can succeed with multiple IdPs, if one IdP is signed out but
// the other isn't.
TEST_F(RequestTest, MultiIdpWithOneIdpSignedOut) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;

  RequestExpectations expectations = kExpectationSuccess;
  expectations.selected_idp_config_url = kProviderTwoUrlFull;

  RunTest(kDefaultMultiIdpRequestParameters, expectations,
          kConfigurationMultiIdpValid);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that API shows all accounts if the user logs in to the IDP with the
// mismatch UI.
TEST_F(RequestTest, MultiIdpLoginToOneIdp) {
  url::Origin providerOrigin = OriginFromString(kProviderUrlFull);
  test_permission_delegate_->idp_signin_statuses_[providerOrigin] = true;

  MockConfiguration config = kConfigurationMultiIdpValid;
  // Second IDP has invalid accounts response.
  config.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  config.accounts_dialog_action = AccountsDialogAction::kNone;

  RunDontWaitForCallback(kDefaultMultiIdpRequestParameters, config);

  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
  EXPECT_TRUE(did_show_accounts_dialog());
  // The second IDP has 3 accounts, so those should be showing up.
  EXPECT_EQ(all_accounts_for_display().size(), 3u);

  EXPECT_FALSE(request_->HasUserTriedToSignInToIdp(GURL(kProviderUrlFull)));
  EXPECT_FALSE(request_->HasUserTriedToSignInToIdp(GURL(kProviderTwoUrlFull)));
  // First, simulate the user clicking on the sign in to IDP active.
  SimulateLoginToIdP();
  // Then, simulate user signing into IdP by updating the IdP signin status and
  // calling the observer.
  test_permission_delegate_->idp_signin_statuses_[providerOrigin] = true;
  // Second IDP will now correctly return its account.
  config.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kSuccess;
  SetConfig(config);
  request_->OnIdpSigninStatusReceived(providerOrigin,
                                      /*idp_signin_status=*/true);

  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 3u);
  EXPECT_TRUE(did_show_accounts_dialog());
  // The first IDP only has a single account, and the total of accounts is
  // now 4.
  EXPECT_EQ(all_accounts_for_display().size(), 4u);
  EXPECT_EQ(new_accounts().size(), 1u);
  EXPECT_TRUE(request_->HasUserTriedToSignInToIdp(GURL(kProviderUrlFull)));
  EXPECT_FALSE(request_->HasUserTriedToSignInToIdp(GURL(kProviderTwoUrlFull)));
}

// Test that API can succeed with multiple IdPs, if all IDPs have login status
// mismatch.
TEST_F(RequestTest, MultiIdpWithAllIdpsMismatch) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = true;

  // Set the config so that both accounts fetches result in failure.
  MockConfiguration config = kConfigurationMultiIdpValid;
  config.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kEmptyListError;
  config.idp_info[kProviderTwoUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  // Need to change the accounts dialog action since we won't get any accounts.
  config.accounts_dialog_action = AccountsDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(kDefaultMultiIdpRequestParameters, expectations, config);

  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
  EXPECT_TRUE(all_accounts_for_display().empty());
  auto mismatch_idps = displayed_mismatch_idps();
  ASSERT_EQ(mismatch_idps.size(), 2u);
  EXPECT_EQ(mismatch_idps[0], "idp.example");
  EXPECT_EQ(mismatch_idps[1], "idp2.example");
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);

  ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");

  ExpectUKMCount("Timing.MismatchDialogShownDuration", FedCmEntry::kEntryName,
                 1);
  ExpectUKMCount("Timing.MismatchDialogShownDuration",
                 FedCmIdpEntry::kEntryName, 2);

  ExpectUkmValue("Status.RequestIdToken",
                 static_cast<int>(TokenStatus::kShouldEmbargo), 2);
  ExpectUkmValue("NumIdpsRequested", 2, 2);
  ExpectUkmValue("NumIdpsMismatch", 2, 2);
}

TEST_F(RequestTest, MultiIdpWithOneIdpMismatch) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = true;

  // Set the config so that both accounts fetches result in failure.
  MockConfiguration config = kConfigurationMultiIdpValid;
  config.idp_info[kProviderTwoUrlFull].accounts_response.parse_status =
      ParseStatus::kEmptyListError;

  RequestExpectations expectations = kExpectationSuccess;
  expectations.standalone_console_message = GetConsoleErrorMessageFromResult(
      FederatedRequestResult::kAccountsListEmpty);
  RunTest(kDefaultMultiIdpRequestParameters, expectations, config);

  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 2u);
  EXPECT_FALSE(all_accounts_for_display().empty());
  auto mismatch_idps = displayed_mismatch_idps();
  ASSERT_EQ(mismatch_idps.size(), 1u);
  EXPECT_EQ(mismatch_idps[0], "idp2.example");
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  ExpectUKMCount("Timing.AccountsDialogShownDuration", FedCmEntry::kEntryName,
                 1);
  ExpectUKMCount("Timing.AccountsDialogShownDuration",
                 FedCmIdpEntry::kEntryName, 1);

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);
  ExpectUKMCount("Timing.MismatchDialogShownDuration", FedCmEntry::kEntryName,
                 0);
  ExpectUKMCount("Timing.MismatchDialogShownDuration",
                 FedCmIdpEntry::kEntryName, 1);

  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ShowAccountsDialog",
                                     1);
  ExpectUKMCount("Timing.ShowAccountsDialog", FedCmEntry::kEntryName, 1);
  ExpectUKMCount("Timing.ShowAccountsDialog", FedCmIdpEntry::kEntryName, 1);

  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsDialogShown", 1);
  ExpectUKMCount("AccountsDialogShown2", FedCmEntry::kEntryName, 1);
  ExpectUKMCount("AccountsDialogShown2", FedCmIdpEntry::kEntryName, 1);

  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.MismatchDialogShown2",
                                     0);
  ExpectUKMCount("MismatchDialogShown2", FedCmEntry::kEntryName, 0);
  ExpectUKMCount("MismatchDialogShown2", FedCmIdpEntry::kEntryName, 1);

  ExpectUkmValueInEntry(
      "Status.RequestIdToken", FedCmEntry::kEntryName,
      static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse));
  ExpectUkmValueInEntry(
      "Status.RequestIdToken", FedCmIdpEntry::kEntryName,
      static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse),
      /*expected_count=*/2,
      /*other_values_allowed=*/true);
  ExpectUkmValueInEntry("Status.RequestIdToken", FedCmIdpEntry::kEntryName,
                        static_cast<int>(TokenStatus::kOtherIdpChosen),
                        /*expected_count=*/2,
                        /*other_values_allowed=*/true);
  ExpectUkmValue("NumIdpsRequested", 2, 2);
  ExpectUkmValue("NumIdpsMismatch", 1, 2);
}

// Test that API can succeed with multiple IdPs, if silent mediation is used but
// only one IdP has a returning account.
TEST_F(RequestTest, MultiIdpWithSilentMediationAndReturningAccountInSecondIdp) {
  // Pretend the sharing permission has not been granted for any account for the
  // first IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(false));

  // Pretend the sharing permission has been granted for exactly one account for
  // the second IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderTwoUrlFull)))
      .WillOnce(Return(true));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdPeter))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdNicolas))
      .WillRepeatedly(Return(std::nullopt));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::nullopt));

  // Ensure auto reauthn is not considered as disabled.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .Times(3)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .Times(3)
      .WillRepeatedly(Return(false));

  RequestExpectations expectations = kExpectationSuccess;
  expectations.selected_idp_config_url = kProviderTwoUrlFull;
  expectations.is_auto_selected = true;
  // There will be a console message due to using mediation:silent and having an
  // IdP that does not have a returning account.
  expectations.standalone_console_message =
      "Silent mediation issue: the user has not used FedCM on this site with "
      "this identity provider.";

  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;

  RunDontWaitForCallback(kDefaultMultiIdpRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  WaitForCurrentRequest();
  CheckExpectations(configuration, expectations);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that API fails with multiple IdPs, if silent mediation is used and two
// IdPs have a single returning account.
TEST_F(RequestTest, MultiIdpWithSilentMediationAndReturningAccountInTwoIdps) {
  // Pretend the sharing permission has been granted for exactly one account for
  // the first IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Pretend the sharing permission has been granted for exactly one account for
  // the second IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderTwoUrlFull)))
      .WillOnce(Return(true));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdPeter))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdNicolas))
      .WillRepeatedly(Return(std::nullopt));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::nullopt));

  // Ensure auto reauthn is not considered as disabled.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillRepeatedly(Return(false));

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kSilentMediationFailure,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;

  RunDontWaitForCallback(kDefaultMultiIdpRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  // Accounts still need to be fetched since there could have been a single
  // returning account.
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that when there are two IDPs with sharing permissions but the account
// fetch fails for one of them, mediation silent can still succeed.
TEST_F(RequestTest, MultiIdpWithSilentMediationAndOneIdpFetchFailure) {
  // Mark both IDPs as logged in.
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = true;

  // Pretend the sharing permission has been granted for exactly one account for
  // the first IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull)))
      .WillOnce(Return(true));

  // Pretend the sharing permission has been granted for exactly one account for
  // the second IdP.
  EXPECT_CALL(
      *test_permission_delegate_,
      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderTwoUrlFull)))
      .WillOnce(Return(true));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdPeter))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::nullopt));
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderTwoUrlFull), kAccountIdNicolas))
      .WillRepeatedly(Return(std::nullopt));

  // Ensure auto reauthn is not considered as disabled.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .Times(3)
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .Times(3)
      .WillRepeatedly(Return(false));

  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.mediation_requirement = MediationRequirement::kSilent;
  // Let the first IDP accounts fetch fail.
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kNoResponseError;

  RunDontWaitForCallback(kDefaultMultiIdpRequestParameters, configuration);
  EXPECT_FALSE(request_helper_->was_callback_called());

  WaitForCurrentRequest();
  ASSERT_EQ(RequestTokenStatus::kSuccess, request_helper_->status());
  EXPECT_EQ(configuration.token, request_helper_->token());
  EXPECT_TRUE(request_helper_->is_auto_selected());
  EXPECT_EQ(kProviderTwoUrlFull, request_helper_->selected_idp_config_url());

  std::vector<std::string> filtered_messages = GetFilteredConsoleMessages();
  ASSERT_EQ(2u, filtered_messages.size());
  EXPECT_EQ(GetConsoleErrorMessageFromResult(
                FederatedRequestResult::kAccountsNoResponse),
            filtered_messages[0]);
  EXPECT_EQ(
      "Silent mediation was requested, but the conditions to achieve it were "
      "not met.",
      filtered_messages[1]);

  // Accounts still need to be fetched since there could have been a single
  // returning account.
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

TEST_F(RequestTest, MultiIdpLoggedOut) {
  // Mark both IDPs as logged out so the request fails early.
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderTwoUrlFull)] = false;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kNotSignedInWithIdp,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunDontWaitForCallback(kDefaultMultiIdpRequestParameters,
                         kConfigurationMultiIdpValid);
  base::RunLoop().RunUntilIdle();
  // The callback must be delayed.
  EXPECT_FALSE(request_helper_->was_callback_called());
  WaitForCurrentRequest();
  CheckExpectations(kConfigurationMultiIdpValid, expectations);
}

TEST_F(RequestTest, MultiIdpWithError) {
  MockConfiguration configuration = kConfigurationMultiIdpValid;
  ErrorDialogType error_dialog_type =
      ErrorDialogType::kGenericNonEmptyWithoutUrl;
  TokenResponseType token_response_type =
      TokenResponseType::kTokenReceivedAndErrorReceivedAndContinueOnNotReceived;
  configuration.token_response.parse_status =
      ParseStatus::kInvalidResponseError;
  configuration.error_dialog_type = error_dialog_type;
  configuration.token_response_type = token_response_type;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
                                       error_dialog_type, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.Error.ErrorDialogResult",
      ErrorDialogResult::kCloseWithoutMoreDetails, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
                                       token_response_type, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Error.ErrorUrlType", 0);

  ExpectUKMPresence("Error.ErrorDialogType");
  ExpectUKMPresence("Error.ErrorDialogResult");
  ExpectUKMPresence("Error.TokenResponseType");
  ExpectNoUKMPresence("Error.ErrorUrlType");
}

TEST_F(RequestTest, TooManyRequests) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;
  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  // Reset the network request manager so we can check that we fetch no
  // endpoints in the subsequent call.
  configuration.accounts_dialog_action =
      AccountsDialogAction::kSelectFirstAccount;
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  // The next FedCM request should fail since the initial request has not yet
  // been finalized.
  RequestExpectations expectations = {
      RequestTokenStatus::kErrorTooManyRequests,
      FederatedRequestResult::kTooManyRequests,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  auto concurrent_helper = std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> concurrent_remote;
  RunTest(kDefaultRequestParameters, expectations, configuration,
          concurrent_helper.get(), &concurrent_remote);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());

  ukm_loop.Run();

  // Only count the first request, the second request that resulted in
  // RequestTokenStatus::kErrorTooManyRequests should not be counted.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
                                       1);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 0,
                                       1);
  ExpectUkmValue("MultipleRequestsRpMode",
                 static_cast<int>(MultipleRequestsRpMode::kPassiveThenPassive));

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.MultipleRequestsFromDifferentIdPs", 0, 1);
  ExpectUkmValueInEntry("MultipleRequestsFromDifferentIdPs",
                        FedCmEntry::kEntryName, false);

  // Check for RP-keyed UKM presence.
  ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);

  // Metrics for each request should be recorded separately with different
  // session IDs.
  ExpectTwoUniqueSessionIDs();
}

TEST_F(RequestTest, TooManyRequestsDifferentIdP) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;
  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  // Reset the network request manager so we can check that we fetch no
  // endpoints in the subsequent call.
  configuration.accounts_dialog_action =
      AccountsDialogAction::kSelectFirstAccount;
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  // The next FedCM request should fail since the initial request has not yet
  // been finalized.
  RequestExpectations expectations = {
      RequestTokenStatus::kErrorTooManyRequests,
      FederatedRequestResult::kTooManyRequests,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  // Initiates a new API call with a different IdP.
  RequestParameters request{kDefaultRequestParameters};
  request.identity_providers[0].provider = kProviderTwoUrlFull;
  auto concurrent_helper = std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> concurrent_remote;
  RunTest(request, expectations, configuration, concurrent_helper.get(),
          &concurrent_remote);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());

  ukm_loop.Run();

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.MultipleRequestsFromDifferentIdPs", 1, 1);
  ExpectUkmValueInEntry("MultipleRequestsFromDifferentIdPs",
                        FedCmEntry::kEntryName, true);
}

TEST_F(RequestTest, ActiveModeTooManyRequestsWithNewPassiveFlow) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RunDontWaitForCallback(parameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  // Reset the network request manager so we can check that we fetch no
  // endpoints in the subsequent call.
  configuration.accounts_dialog_action =
      AccountsDialogAction::kSelectFirstAccount;
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  // The next FedCM request should fail since a passive flow cannot replace
  // another active flow.
  RequestExpectations expectations = {
      RequestTokenStatus::kErrorTooManyRequests,
      FederatedRequestResult::kTooManyRequests,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  auto concurrent_helper = std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> concurrent_remote;
  RunTest(kDefaultRequestParameters, expectations, configuration,
          concurrent_helper.get(), &concurrent_remote);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());

  ukm_loop.Run();

  // Only count the first request, the second request that resulted in
  // RequestTokenStatus::kErrorTooManyRequests should not be counted.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
                                       1);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 2,
                                       1);
  ExpectUkmValue("MultipleRequestsRpMode",
                 static_cast<int>(MultipleRequestsRpMode::kActiveThenPassive));

  // Check for RP-keyed UKM presence.
  ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
}

TEST_F(RequestTest, ActiveModeTooManyRequestsWithNewActiveFlow) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RunDontWaitForCallback(parameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  // Reset the network request manager so we can check that we fetch no
  // endpoints in the subsequent call.
  configuration.accounts_dialog_action =
      AccountsDialogAction::kSelectFirstAccount;
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  // The next FedCM request should fail since a active flow cannot replace
  // another active flow.
  RequestExpectations expectations = {
      RequestTokenStatus::kErrorTooManyRequests,
      FederatedRequestResult::kTooManyRequests,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  auto concurrent_helper = std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> concurrent_remote;
  RunTest(parameters, expectations, configuration, concurrent_helper.get(),
          &concurrent_remote);
  EXPECT_FALSE(DidFetchAnyEndpoint());

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());

  ukm_loop.Run();

  // Only count the first request, the second request that resulted in
  // RequestTokenStatus::kErrorTooManyRequests should not be counted.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 1,
                                       1);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 3,
                                       1);
  ExpectUkmValue("MultipleRequestsRpMode",
                 static_cast<int>(MultipleRequestsRpMode::kActiveThenActive));

  // Check for RP-keyed UKM presence.
  ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
}

TEST_F(RequestTest, PassiveReplacedByActiveFlow) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RequestExpectations active_flow_expectations = kExpectationSuccess;
  active_flow_expectations.standalone_console_message =
      "The request is replaced by a new one with active mode.";

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  // Create new test helpers so that we can send the second request.
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  std::unique_ptr<RequestCallbackHelper> active_flow_request_helper =
      std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> concurrent_remote;
  RunTest(parameters, active_flow_expectations, kConfigurationValid,
          active_flow_request_helper.get(), &concurrent_remote);

  RequestExpectations passive_flow_expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kReplacedByActiveMode,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  CheckExpectations(configuration, passive_flow_expectations);

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());

  ukm_loop.Run();

  // Count both the replaced passive request and the active request.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 2,
                                       1);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.MultipleRequestsRpMode", 1,
                                       1);
  ExpectUkmValue("MultipleRequestsRpMode",
                 static_cast<std::underlying_type_t<MultipleRequestsRpMode>>(
                     MultipleRequestsRpMode::kPassiveThenActive));

  // Check for RP-keyed UKM presence.
  ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);

  // Metrics for each request should be recorded separately with different
  // session IDs.
  ExpectTwoUniqueSessionIDs();
}

TEST_F(RequestTest, ControllerDestroyedOnReplacedByActiveFlow) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_TRUE(active_mock_dialog_controller_);

  // Store a weak pointer to the first mock controller.
  base::WeakPtr<TestDialogController> old_dialog_controller =
      active_mock_dialog_controller_;
  active_mock_dialog_controller_ = nullptr;

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RequestExpectations active_flow_expectations = kExpectationSuccess;
  active_flow_expectations.standalone_console_message =
      "The request is replaced by a new one with active mode.";

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  // Create new test helpers so that we can send the second request.
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  std::unique_ptr<RequestCallbackHelper> active_flow_helper =
      std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> concurrent_remote;

  // Run the active flow. This will replace the passive flow.
  RunTest(parameters, active_flow_expectations, kConfigurationValid,
          active_flow_helper.get(), &concurrent_remote);

  // The active flow replacement should destroy the old controller.
  EXPECT_FALSE(old_dialog_controller);

  // Check that the passive flow completed with kReplacedByActiveMode.
  RequestExpectations passive_flow_expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kReplacedByActiveMode,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  CheckExpectations(configuration, passive_flow_expectations);
}

// TestIdpNetworkRequestManager subclass which records requests to metrics
// endpoint.
class IdpNetworkRequestMetricsRecorder : public TestIdpNetworkRequestManager {
 public:
  IdpNetworkRequestMetricsRecorder() = default;
  IdpNetworkRequestMetricsRecorder(const IdpNetworkRequestMetricsRecorder&) =
      delete;
  IdpNetworkRequestMetricsRecorder& operator=(
      const IdpNetworkRequestMetricsRecorder&) = delete;

  void SendSuccessfulTokenRequestMetrics(
      const GURL& metrics_endpoint_url,
      base::TimeDelta api_call_to_show_dialog_time,
      base::TimeDelta show_dialog_to_continue_clicked_time,
      base::TimeDelta account_selected_to_token_response_time,
      base::TimeDelta api_call_to_token_response_time) override {
    metrics_endpoints_notified_success_.push_back(metrics_endpoint_url);
  }

  void SendFailedTokenRequestMetrics(
      const GURL& metrics_endpoint_url,
      bool did_show_ui,
      MetricsEndpointErrorCode error_code) override {
    metrics_endpoints_notified_failure_.push_back(metrics_endpoint_url);
  }

  const std::vector<GURL>& get_metrics_endpoints_notified_success() {
    return metrics_endpoints_notified_success_;
  }

  const std::vector<GURL>& get_metrics_endpoints_notified_failure() {
    return metrics_endpoints_notified_failure_;
  }

 private:
  std::vector<GURL> metrics_endpoints_notified_success_;
  std::vector<GURL> metrics_endpoints_notified_failure_;
};

// Test that the metrics endpoint is not notified when the FedCM API is in
// cooldown.
TEST_F(RequestTest, MetricsEndpointDuringCooldown) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);

  test_api_permission_delegate_->RecordDismissAndEmbargo(
      OriginFromString(kRpUrl));
  std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
      std::make_unique<IdpNetworkRequestMetricsRecorder>();
  IdpNetworkRequestMetricsRecorder* metrics_recorder =
      unique_metrics_recorder.get();
  SetNetworkRequestManager(std::move(unique_metrics_recorder));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kDisabledInSettings,
      /*standalone_console_message=*/std::nullopt,
      /* selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_FALSE(DidFetchAnyEndpoint());
  EXPECT_TRUE(
      metrics_recorder->get_metrics_endpoints_notified_success().empty());
  EXPECT_TRUE(
      metrics_recorder->get_metrics_endpoints_notified_failure().empty());
}

// Test that the metrics endpoint is notified as a result of a successful
// multi-IDP Request::RequestToken() call.
TEST_F(RequestTest, MetricsEndpointMultiIdp) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);

  std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
      std::make_unique<IdpNetworkRequestMetricsRecorder>();
  IdpNetworkRequestMetricsRecorder* metrics_recorder =
      unique_metrics_recorder.get();
  SetNetworkRequestManager(std::move(unique_metrics_recorder));

  // Since the first IDP does not set the login state of the account but the
  // second IDP has one with state set to SignIn, selecting the first account
  // means that the second IDP is the one that is selected.
  RequestExpectations expectations = kExpectationSuccess;
  expectations.selected_idp_config_url = kProviderTwoUrlFull;
  RunTest(kDefaultMultiIdpRequestParameters, expectations,
          kConfigurationMultiIdpValid);
  EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_success(),
              ElementsAre("https://idp2.example/metrics"));
  EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_failure(),
              ElementsAre(kMetricsEndpoint));
}

// Test that the metrics endpoint is notified when
// Request::RequestToken() call fails.
TEST_F(RequestTest, MetricsEndpointMultiIdpFail) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);

  std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
      std::make_unique<IdpNetworkRequestMetricsRecorder>();
  IdpNetworkRequestMetricsRecorder* metrics_recorder =
      unique_metrics_recorder.get();
  SetNetworkRequestManager(std::move(unique_metrics_recorder));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /* selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationMultiIdpValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;

  RunTest(kDefaultMultiIdpRequestParameters, expectations, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  EXPECT_TRUE(
      metrics_recorder->get_metrics_endpoints_notified_success().empty());
  EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_failure(),
              ElementsAre(kMetricsEndpoint, "https://idp2.example/metrics"));
}

TEST_F(RequestTest, AccountsSortedWithTimestamps) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
  // First account is kSignUp so the fact that it has a last used timestamp
  // should not affect its relative ordering.
  EXPECT_CALL(*test_permission_delegate_,
              GetLastUsedTimestamp(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdNicolas))
      .WillRepeatedly(Return(std::make_optional<base::Time>(
          base::Time() + base::Microseconds(10))));
  // The second account is marked signed in but has no last used timestamp.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdPeter))
      .WillRepeatedly(Return(std::nullopt));
  // The third account is marked sign (as is the second), but since it has a
  // timestamp it should show first.
  configuration.idp_info[kProviderUrlFull]
      .accounts[2]
      ->idp_claimed_login_state = LoginState::kSignIn;
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountIdZach))
      .WillRepeatedly(Return(std::make_optional<base::Time>(
          base::Time() + base::Microseconds(1))));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  // Account order should be: accounts[2], accounts[1], accounts[0].
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdZach);
  EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdPeter);
  EXPECT_EQ(all_accounts_for_display()[2]->id, kAccountIdNicolas);
}

TEST_F(RequestTest, AccountLabelMultipleAccountsNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.requested_label =
      "invalid_label";
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.AccountLabel.NumMatchingAccounts",
      Metrics::NumAccounts::kZero, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 3);
  ExpectNoUKMPresence("AccountsSize.ReadyToShow");
  ExpectUkmValueInEntry("AccountLabel.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 0);
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, AccountLabelMultipleAccountsOneMatch) {
  RequestParameters parameters = kDefaultRequestParameters;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.requested_label = "label";
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.AccountLabel.NumMatchingAccounts",
      Metrics::NumAccounts::kOne, 1);
  ExpectUkmValueInEntry("AccountLabel.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 1);
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, LoginHintSingleAccountIdMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = kAccountId;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kSingleAccountWithHint;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
                        1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}

TEST_F(RequestTest, LoginHintSingleAccountEmailMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = kEmail;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kSingleAccountWithHint;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->display_identifier, kEmail);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
                        1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}

TEST_F(RequestTest, LoginHintSingleAccountNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = "incorrect_login_hint";
  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kSingleAccountWithHint;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  ExpectNoUKMPresence("HasSigninAccount");

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
      1);
  ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
                        0);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}

TEST_F(RequestTest, LoginHintFirstAccountMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = kAccountIdNicolas;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, kExpectationSuccess, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdNicolas);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
                        1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}

TEST_F(RequestTest, LoginHintLastAccountMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = kAccountIdZach;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdZach);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
                                       1, 1);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 3);
  ExpectUkmValueInEntry("AccountsSize.ReadyToShow", FedCmEntry::kEntryName, 1);
  ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
                        1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
}

TEST_F(RequestTest, LoginHintMultipleAccountsNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = "incorrect_login_hint";
  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LoginHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
      1);
  ExpectUkmValueInEntry("LoginHint.NumMatchingAccounts", FedCmEntry::kEntryName,
                        0);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("DomainHint.NumMatchingAccounts");
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 3);
}

TEST_F(RequestTest, DomainHintSingleAccountMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kSingleAccountWithDomainHint;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, DomainHintSingleAccountStarMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint =
      AccountsFetcher::kWildcardDomainHint;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kSingleAccountWithDomainHint;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, DomainHintSingleAccountStarNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint =
      AccountsFetcher::kWildcardDomainHint;

  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 0);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, DomainHintSingleAccountNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = "incorrect_domain_hint";
  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kSingleAccountWithDomainHint;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 0);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 1, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.AccountsSize.ReadyToShow", 0);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 1);
}

TEST_F(RequestTest, DomainHintNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;
  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(parameters, expectations, kConfigurationValid);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 0);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, DomainHintMultipleAccountsSingleMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kOtherDomainHint;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdZach);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kOne,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 1);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
                                       1, 1);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 3);
  ExpectUkmValueInEntry("AccountsSize.ReadyToShow", FedCmEntry::kEntryName, 1);
}

TEST_F(RequestTest, DomainHintMultipleAccountsMultipleMatches) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 2u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdNicolas);
  EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdZach);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts",
      Metrics::NumAccounts::kMultiple, 1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 2);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.Raw", 3, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsSize.ReadyToShow",
                                       2, 1);
  ExpectUkmValueInEntry("AccountsSize.Raw", FedCmEntry::kEntryName, 3);
  ExpectUkmValueInEntry("AccountsSize.ReadyToShow", FedCmEntry::kEntryName, 2);
}

TEST_F(RequestTest, DomainHintMultipleAccountsStar) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint =
      AccountsFetcher::kWildcardDomainHint;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 2u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdNicolas);
  EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdZach);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts",
      Metrics::NumAccounts::kMultiple, 1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 2);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, DomainHintMultipleAccountsNoMatch) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = "incorrect_domain_hint";
  const RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsListEmpty,
      {kFilterNoMatchMessage},
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts =
      kMultipleAccountsWithHintsAndDomains;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.DomainHint.NumMatchingAccounts", Metrics::NumAccounts::kZero,
      1);
  ExpectUkmValueInEntry("DomainHint.NumMatchingAccounts",
                        FedCmEntry::kEntryName, 0);
  ExpectNoUKMPresence("AccountLabel.NumMatchingAccounts");
  ExpectNoUKMPresence("LoginHint.NumMatchingAccounts");
}

TEST_F(RequestTest, PictureFetch) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts[0]->picture =
      GURL(kAccountPicture);
  // This ensures we don't fetch client metadata, to test a different codepath.
  configuration.idp_info[kProviderUrlFull]
      .accounts[0]
      ->idp_claimed_login_state = LoginState::kSignIn;

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountId);
  EXPECT_FALSE(all_accounts_for_display()[0]->decoded_picture.IsEmpty());
  EXPECT_EQ(kAccountPictureSize,
            all_accounts_for_display()[0]->decoded_picture.Width());
  EXPECT_EQ(kAccountPictureSize,
            all_accounts_for_display()[0]->decoded_picture.Height());
}

TEST_F(RequestTest, PictureFetchMultipleAccounts) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;
  configuration.idp_info[kProviderUrlFull].accounts[0]->picture =
      GURL(kAccountPicture);
  configuration.idp_info[kProviderUrlFull].accounts[1]->picture =
      GURL(kAccountPicture);
  configuration.idp_info[kProviderUrlFull].accounts[2]->picture =
      GURL(kAccountPicture404);

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  EXPECT_FALSE(all_accounts_for_display()[0]->decoded_picture.IsEmpty());
  EXPECT_EQ(kAccountPictureSize,
            all_accounts_for_display()[0]->decoded_picture.Width());
  EXPECT_EQ(kAccountPictureSize,
            all_accounts_for_display()[0]->decoded_picture.Height());
  EXPECT_FALSE(all_accounts_for_display()[1]->decoded_picture.IsEmpty());
  EXPECT_EQ(kAccountPictureSize,
            all_accounts_for_display()[1]->decoded_picture.Width());
  EXPECT_EQ(kAccountPictureSize,
            all_accounts_for_display()[1]->decoded_picture.Height());
  EXPECT_TRUE(all_accounts_for_display()[2]->decoded_picture.IsEmpty());
}

// Test that when FedCmRpContext flag is enabled and rp_context is specified,
// the FedCM request succeeds with the specified rp_context.
TEST_F(RequestTest, RpContextIsSetToNonDefaultValue) {
  RequestParameters request_parameters = kDefaultRequestParameters;
  request_parameters.rp_context = blink::mojom::RpContext::kContinue;
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action =
      AccountsDialogAction::kSelectFirstAccount;
  RunTest(request_parameters, kExpectationSuccess, configuration);

  EXPECT_EQ(dialog_controller_state_.rp_context,
            blink::mojom::RpContext::kContinue);
}

TEST_F(RequestTest, WellKnownInvalidContentType) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull]
      .well_known.fetch_status.parse_status =
      ParseStatus::kInvalidContentTypeError;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kWellKnownInvalidContentType,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, expectations, configuration);
  ukm_loop.Run();

  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  ExpectStatusMetrics(TokenStatus::kWellKnownInvalidContentType);
}

TEST_F(RequestTest, ConfigInvalidContentType) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.fetch_status.parse_status =
      ParseStatus::kInvalidContentTypeError;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kConfigInvalidContentType,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, expectations, configuration);
  ukm_loop.Run();

  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  ExpectStatusMetrics(TokenStatus::kConfigInvalidContentType);
}

TEST_F(RequestTest, ClientMetadataInvalidContentType) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull]
      .client_metadata.fetch_status.parse_status =
      ParseStatus::kInvalidContentTypeError;

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  // The FedCM flow succeeds even if the client metadata fetch fails.
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  ukm_loop.Run();

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}

TEST_F(RequestTest, AccountsInvalidContentType) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidContentTypeError;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kAccountsInvalidContentType,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, expectations, configuration);
  ukm_loop.Run();

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  ExpectStatusMetrics(TokenStatus::kAccountsInvalidContentType);
}

TEST_F(RequestTest, IdTokenInvalidContentType) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.token_response.parse_status =
      ParseStatus::kInvalidContentTypeError;
  configuration.error_dialog_action = ErrorDialogAction::kClose;
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenInvalidContentType,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, expectations, configuration);
  ukm_loop.Run();

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  ExpectStatusMetrics(TokenStatus::kIdTokenInvalidContentType);
}

// Test successful AuthZ request that returns tokens without opening
// pop-up windows.
TEST_F(RequestTest, SuccessfulAuthZRequestNoPopUpWindow) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].fields = {"non_default_field"};

  RunTest(parameters, kExpectationSuccess, kConfigurationValid);

  // When the authorization is delegated and the feature is enabled
  // we don't fetch the client metadata endpoint (which is used to
  // mediate - but not to delegate - the authorization prompt).
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
  // Ensure that metrics were recorded.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpParametersAndScopeState",
                                       RpParameters::kHasNonDefaultScope, 1);
}

// Test successful AuthZ request that request the opening of pop-up
// windows.
TEST_F(RequestTest, SuccessfulAuthZRequestWithPopUpWindow) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmMetricsEndpoint);

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].fields = {"non_default_field"};

  MockConfiguration config = kConfigurationValid;
  // Expect an access token to be produced, rather the typical idtoken.
  config.token = "an-access-token";

  // Set up the network expectations to return a "continue_on" response
  // rather than the typical idtoken response.
  GURL continue_on = GURL(kProviderUrlFull).Resolve("/more-permissions.php");
  config.continue_on = std::move(continue_on);

  std::unique_ptr<IdpNetworkRequestMetricsRecorder> unique_metrics_recorder =
      std::make_unique<IdpNetworkRequestMetricsRecorder>();
  IdpNetworkRequestMetricsRecorder* metrics_recorder =
      unique_metrics_recorder.get();
  SetNetworkRequestManager(std::move(unique_metrics_recorder));

  // Set up the UI dialog controller to show a pop-up window, rather
  // than the typical mediated authorization prompt that generates
  // an idtoken.
  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  // When the pop-up window is opened, resolve it immediately by
  // producing an access token.
  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
        auto params = blink::mojom::ResolveTokenParams::NewToken(
            base::Value("an-access-token"));
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
            ->GetActiveRequestForTesting()
            ->OnResolve(GURL(kProviderUrlFull), std::nullopt,
                        std::move(params));
        return modal.get();
      }));

  RunTest(parameters, kExpectationSuccess, config);
  ExpectStatusMetrics(TokenStatus::kSuccessUsingIdentityProviderResolve);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.ContinueOn.PopupWindowStatus",
      ContinueOnPopupStatus::kPopupOpened, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.ContinueOn.PopupWindowResult",
      ContinueOnPopupResult::kTokenReceived, 1);

  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOn.Response",
                                     1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ContinueOn.TurnaroundTime", 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
  // Authz popup should not be confused with use other account.
  ExpectNoUKMPresence("UseOtherAccountResult");

  EXPECT_THAT(metrics_recorder->get_metrics_endpoints_notified_success(),
              ElementsAre("https://idp.example/metrics"));
  EXPECT_TRUE(
      metrics_recorder->get_metrics_endpoints_notified_failure().empty());

  // When the authorization is delegated and the feature is enabled
  // we don't fetch the client metadata endpoint (which is used to
  // mediate - but not to delegate - the authorization prompt).
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
}

// Test the continuation popup calling close().
TEST_F(RequestTest, ContinuationPopupCallingClose) {
  RequestParameters parameters = kDefaultRequestParameters;

  MockConfiguration config = kConfigurationValid;
  // Expect an access token to be produced, rather the typical idtoken.
  config.token = "an-access-token";

  // Set up the network expectations to return a "continue_on" response
  // rather than the typical idtoken response.
  GURL continue_on = GURL(kProviderUrlFull).Resolve("/more-permissions.php");
  config.continue_on = std::move(continue_on);

  // Set up the UI dialog controller to show a pop-up window, rather
  // than the typical mediated authorization prompt that generates
  // an idtoken.
  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  // When the pop-up window is opened, resolve it immediately by
  // producing an access token.
  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
            ->GetActiveRequestForTesting()
            ->OnClose();
        return modal.get();
      }));

  RequestExpectations error = {RequestTokenStatus::kError,
                               FederatedRequestResult::kError,
                               /*standalone_console_message=*/std::nullopt,
                               /*selected_idp_config_url=*/std::nullopt};

  RunTest(parameters, error, config);
  ExpectStatusMetrics(
      TokenStatus::kContinuationPopupClosedByIdentityProviderClose);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.ContinueOn.PopupWindowStatus",
      ContinueOnPopupStatus::kPopupOpened, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.ContinueOn.PopupWindowResult",
      ContinueOnPopupResult::kClosedByIdentityProviderClose, 1);

  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOn.Response",
                                     0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ContinueOn.TurnaroundTime", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.IdTokenResponse", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.TurnaroundTime", 0);
}

// Test the continuation popup being resolved by a native app token response.
TEST_F(RequestTest, ContinuationPopupNativeAppToken) {
  RequestParameters parameters = kDefaultRequestParameters;

  MockConfiguration config = kConfigurationValid;
  // Expect an access token to be produced, rather the typical idtoken.
  config.token = "a-native-token";

  // Set up the network expectations to return a "continue_on" response
  // rather than the typical idtoken response.
  GURL continue_on = GURL(kProviderUrlFull).Resolve("/more-permissions.php");
  config.continue_on = std::move(continue_on);

  // Set up the UI dialog controller to show a pop-up window, rather
  // than the typical mediated authorization prompt that generates
  // an idtoken.
  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  // When the pop-up window is opened, resolve it by
  // invoking the token_callback with our token.
  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<
                4>([&modal](
                       IdentityRequestDialogController::NativeAppResultCallback
                           native_result_callback) {
        std::move(native_result_callback)
            .Run(IdentityRequestDialogController::NativeAppResult{
                IdentityRequestDialogController::NativeAppResult::Type::kToken,
                "a-native-token"});
        return modal.get();
      }));

  RequestExpectations expectations = {
      RequestTokenStatus::kSuccess, FederatedRequestResult::kSuccess,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/kProviderUrlFull};

  RunTest(parameters, expectations, config);
  ExpectStatusMetrics(TokenStatus::kSuccessUsingIdentityProviderResolve);
}

// Test the login popup being completed by a native app login response.
TEST_F(RequestTest, LoginPopupNativeAppLoginFinished) {
  RequestParameters parameters = kDefaultRequestParameters;

  MockConfiguration config = kConfigurationValid;
  config.accounts_dialog_action = AccountsDialogAction::kAddAccount;

  auto dialog_controller = std::make_unique<TestDialogController>(config);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<4>(
          [&modal](IdentityRequestDialogController::NativeAppResultCallback
                       native_result_callback) {
            std::move(native_result_callback)
                .Run(IdentityRequestDialogController::NativeAppResult{
                    IdentityRequestDialogController::NativeAppResult::Type::
                        kLoginFinished,
                    ""});
            return modal.get();
          }));

  RequestExpectations expectations = {
      RequestTokenStatus::kSuccess, FederatedRequestResult::kSuccess,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/kProviderUrlFull};

  RunTest(parameters, expectations, config);
  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}

// Test the login popup incorrectly returning a native app continuation token.
TEST_F(RequestTest, LoginPopupNativeAppToken) {
  RequestParameters parameters = kDefaultRequestParameters;

  MockConfiguration config = kConfigurationValid;
  config.accounts_dialog_action = AccountsDialogAction::kAddAccount;

  auto dialog_controller = std::make_unique<TestDialogController>(config);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<
                4>([&modal](
                       IdentityRequestDialogController::NativeAppResultCallback
                           native_result_callback) {
        std::move(native_result_callback)
            .Run(IdentityRequestDialogController::NativeAppResult{
                IdentityRequestDialogController::NativeAppResult::Type::kToken,
                "unexpected-token"});
        return modal.get();
      }));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kError,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(parameters, expectations, config);
  ExpectStatusMetrics(TokenStatus::kLoginPopupClosedWithoutSignin);
}

// Test the continuation popup incorrectly returning a native app login finished
// result.
TEST_F(RequestTest, ContinuationPopupNativeAppLoginFinished) {
  RequestParameters parameters = kDefaultRequestParameters;

  MockConfiguration config = kConfigurationValid;
  config.token = "a-native-token";

  GURL continue_on = GURL(kProviderUrlFull).Resolve("/more-permissions.php");
  config.continue_on = std::move(continue_on);

  auto dialog_controller = std::make_unique<TestDialogController>(config);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<4>(
          [&modal](IdentityRequestDialogController::NativeAppResultCallback
                       native_result_callback) {
            std::move(native_result_callback)
                .Run(IdentityRequestDialogController::NativeAppResult{
                    IdentityRequestDialogController::NativeAppResult::Type::
                        kLoginFinished,
                    ""});
            return modal.get();
          }));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kError,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(parameters, expectations, config);
  ExpectStatusMetrics(TokenStatus::kContinuationPopupClosedByUser);
}

// Test successful AuthZ request that request the opening of pop-up
// windows.
TEST_F(RequestTest, FailsLoadingAContinueOnForADifferentOrigin) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].fields = {"non_default_field"};

  MockConfiguration config = kConfigurationValid;

  // Set up the network expectations to return a "continue_on" response
  // rather than the typical idtoken response.
  GURL continue_on =
      GURL("https://another-origin.example").Resolve("/more-permissions.php");
  config.continue_on = std::move(continue_on);

  RequestExpectations error = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenInvalidResponse,
      // TODO(crbug.com/40262526): introduce a more granular error.
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(parameters, error, config);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.ContinueOn.PopupWindowStatus",
      ContinueOnPopupStatus::kUrlNotSameOrigin, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.ContinueOn.PopupWindowResult",
      ContinueOnPopupResult::kTokenReceived, 0);

  // We only record timing on success.
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.ContinueOn.Response",
                                     0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.ContinueOn.TurnaroundTime", 0);
}

// Test metrics for a request with parameters.
TEST_F(RequestTest, RequestWithParameters) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].params_json = "{\"foo\", \"bar\"}";

  RunTest(parameters, kExpectationSuccess, kConfigurationValid);

  // Ensure that metrics were recorded.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpParametersAndScopeState",
                                       RpParameters::kHasParameters, 1);
}

// Test metrics for a request with parameters and scopes.
TEST_F(RequestTest, RequestWithParametersAndScopes) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].fields = {"non_default_field"};
  parameters.identity_providers[0].params_json = "{\"foo\", \"bar\"}";

  RunTest(parameters, kExpectationSuccess, kConfigurationValid);

  // When the authorization is delegated and the feature is enabled
  // we don't fetch the client metadata endpoint (which is used to
  // mediate - but not to delegate - the authorization prompt).
  EXPECT_FALSE(DidFetch(FetchedEndpoint::CLIENT_METADATA));
  // Ensure that metrics were recorded.
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.RpParametersAndScopeState",
      RpParameters::kHasParametersAndNonDefaultScope, 1);
}

// Test successfully signing-in users when they are signed-out on
// active flows.
TEST_F(RequestTest, SignInWhenSignedOutOnActiveModeWithUserActivation) {
  ExpectSuccessfulActiveFlow();
}

// Test active flow failure outside of user activation.
TEST_F(RequestTest, ActiveFlowRequiresUserActivation) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = false;

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RequestExpectations error = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kMissingTransientUserActivation,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  // Active flow request gets rejected without delay.
  RunDontWaitForCallback(parameters, kConfigurationValid);
  CheckExpectations(kConfigurationValid, error);

  EXPECT_FALSE(DidFetchAnyEndpoint());
}

// Test the active flow request fails without delay if IdP config is wrong.
TEST_F(RequestTest, ActiveFlowWellKnownNotInList) {
  RequestExpectations request_not_in_list = {
      RequestTokenStatus::kError, FederatedRequestResult::kConfigNotInWellKnown,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  const char* idp_config_url =
      kDefaultRequestParameters.identity_providers[0].provider;
  const char* kWellKnownMismatchConfigUrl = "https://mismatch.example";
  EXPECT_NE(std::string(idp_config_url), kWellKnownMismatchConfigUrl);

  MockConfiguration config = kConfigurationValid;
  config.idp_info[idp_config_url].well_known = {
      {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  // Active flow request gets rejected without delay.
  RunDontWaitForCallback(parameters, config);
  CheckExpectations(config, request_not_in_list);

  EXPECT_TRUE(DidFetchWellKnownAndConfig());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::ACCOUNTS));
}

TEST_F(RequestTest, ActiveFlowWithUnknownLoginStatus) {
  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;

  // Check that the LoginStatus is "unknown".
  EXPECT_EQ(test_permission_delegate_->idp_signin_statuses_.count(kIdpOrigin),
            0u);

  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  // Check that the pop-up window is displayed.
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(Return(nullptr));

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RunDontWaitForCallback(parameters, configuration);

  // The FedCM call is still pending (showing modal dialog), but we should have
  // already received the accounts invalid response console message.
  CheckConsoleMessages(FederatedRequestResult::kAccountsInvalidResponse,
                       /*standalone_console_message=*/std::nullopt);
}

// Test that active flow can skip the mismatch UI.
TEST_F(RequestTest, ActiveFlowSkipsMismatchUI) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;

  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));
  // Check that the pop-up window is displayed.
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(Return(nullptr));

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;
  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RunDontWaitForCallback(parameters, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());
}

// Test that active flow shows the loading dialog.
TEST_F(RequestTest, ActiveFlowShowsLoadingUI) {
  ExpectSuccessfulActiveFlow();
  EXPECT_TRUE(dialog_controller_state_.did_show_loading_dialog);
}

// Test that active flow with multiple IDPs shows loading dialog and accounts
// dialog.
TEST_F(RequestTest, ActiveFlowMultiIdpShowsLoadingAndAccountsUI) {
  RequestParameters parameters = kDefaultMultiIdpRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestExpectations expectations = kExpectationSuccess;
  expectations.selected_idp_config_url = kProviderTwoUrlFull;
  RunTest(parameters, expectations, kConfigurationMultiIdpValid);

  EXPECT_TRUE(dialog_controller_state_.did_show_loading_dialog);
  EXPECT_TRUE(did_show_accounts_dialog());
}

// Test active flow with multiple IDPs requires user activation.
TEST_F(RequestTest, ActiveFlowMultiIdpRequiresUserActivation) {
  RequestParameters parameters = kDefaultMultiIdpRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RequestExpectations error = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kMissingTransientUserActivation,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunDontWaitForCallback(parameters, kConfigurationMultiIdpValid);
  CheckExpectations(kConfigurationMultiIdpValid, error);

  EXPECT_FALSE(DidFetchAnyEndpoint());
}

// Test dismissing a active flow through the loading UI.
TEST_F(RequestTest, ActiveFlowDismissLoadingUI) {
  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.loading_dialog_action = LoadingDialogAction::kClose;

  RunTest(parameters, expectations, configuration);
  EXPECT_TRUE(dialog_controller_state_.did_show_loading_dialog);
}

TEST_F(RequestTest, CloseModalDialogView) {
  // Test that IdentityRegistry is notified when modal dialog view is closed.
  EXPECT_FALSE(test_identity_registry_->notified_);
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->CloseModalDialogView();
  EXPECT_TRUE(test_identity_registry_->notified_);
}

class RequestNewTabTest : public RequestTest {
 protected:
  void SetUp() override {
    RenderViewHostImplTestHarness::SetUp();
    InitConstants();
    test_api_permission_delegate_ =
        std::make_unique<TestApiPermissionDelegate>();
    test_permission_delegate_ = std::make_unique<TestPermissionDelegate>();
    test_auto_reauthn_permission_delegate_ =
        std::make_unique<TestAutoReauthnPermissionDelegate>();
    auto test_identity_registry = std::make_unique<TestIdentityRegistry>(
        web_contents(), /*delegate=*/nullptr, GURL(kIdpUrl));
    test_identity_registry_ = test_identity_registry->GetWeakPtr();
    web_contents()->SetUserData(IdentityRegistry::UserDataKey(),
                                std::move(test_identity_registry));
    request_helper_ = std::make_unique<RequestCallbackHelper>();

    static_cast<TestWebContents*>(web_contents())
        ->NavigateAndCommit(GURL("chrome://newtab/"), ui::PAGE_TRANSITION_LINK);

    RequestService* service =
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
    service->SetDelegatesForTesting(
        test_api_permission_delegate_.get(),
        test_auto_reauthn_permission_delegate_.get(),
        test_permission_delegate_.get(), test_identity_registry_.get());
    service->BindFederatedRequestService(
        service_remote_.BindNewPipeAndPassReceiver());
    request_ = service->GetOrCreateActiveRequest()->GetWeakPtr();

    std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
        std::make_unique<TestIdpNetworkRequestManager>();
    SetNetworkRequestManager(std::move(network_request_manager));
  }
};

TEST_F(RequestNewTabTest, SuccessfulFlow) {
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
}

class UserInfoCallbackHelper {
 public:
  UserInfoCallbackHelper() = default;
  ~UserInfoCallbackHelper() = default;

  // This can only be called once per lifetime of this object.
  blink::mojom::FederatedRequestService::RequestUserInfoCallback callback() {
    return base::BindOnce(&UserInfoCallbackHelper::Complete,
                          base::Unretained(this));
  }

  // Returns when callback() is called, which can be immediately if it has
  // already been called.
  void WaitForCallback() {
    if (was_called_) {
      return;
    }
    wait_for_callback_loop_.Run();
  }

  void Complete(blink::mojom::RequestUserInfoResultPtr result) {
    CHECK(!was_called_);
    was_called_ = true;
    wait_for_callback_loop_.Quit();
  }

 private:
  bool was_called_{false};
  base::RunLoop wait_for_callback_loop_;
};

TEST_F(RequestTest, RequestUserInfoFailure) {
  blink::mojom::IdentityProviderConfigPtr config =
      blink::mojom::IdentityProviderConfig::New();
  config->config_url = GURL(kIdpUrl);
  UserInfoCallbackHelper callback_helper;
  // This request will fail right away (not from IDP origin).
  service_remote_->RequestUserInfo(
      std::move(config), base::BindOnce(&UserInfoCallbackHelper::Complete,
                                        base::Unretained(&callback_helper)));
  // This is a regression test and it passes if the test does not crash.
  callback_helper.WaitForCallback();
}

// Tests that when an accounts dialog is shown, the appropriate metrics are
// recorded.
TEST_F(RequestTest, AccountsDialogShownMetric) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  ukm_loop.Run();

  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsDialogShown", 1, 1);
  ExpectUKMPresence("AccountsDialogShown2");
  ExpectNoUKMPresence("MismatchDialogShown2");
}

// Tests that when a mismatch dialog is shown, the appropriate metrics are
// recorded.
TEST_F(RequestTest, MismatchDialogShownMetric) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_signin_status_mismatch_dialog_action =
      IdpSigninStatusMismatchDialogAction::kClose;

  // Setup IdP sign-in status mismatch.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/
      GetConsoleErrorMessageFromResult(
          FederatedRequestResult::kAccountsInvalidResponse),
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  ukm_loop.Run();

  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.MismatchDialogShown", 1, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.MismatchDialogType",
      Metrics::MismatchDialogType::kFirstWithoutHints, 1);
  ExpectUKMPresence("MismatchDialogShown2");
  ExpectNoUKMPresence("AccountsDialogShown2");
  ExpectNoUKMPresence("HasSigninAccount");
}

// Tests that a mismatch dialog is shown twice.
TEST_F(RequestTest, DoubleMismatchDialog) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
  MockConfiguration configuration = kConfigurationValid;

  // Setup IdP sign-in status mismatch.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kHttpNotFoundError;

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = "hint";

  RunDontWaitForCallback(parameters, configuration);

  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  request_->OnIdpSigninStatusReceived(kIdpOrigin, true);
  base::RunLoop().RunUntilIdle();

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  ukm_loop.Run();

  // The additional mismatch should be recorded in the metrics.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.MismatchDialogShown", 1, 2);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.MismatchDialogType", 2);
  histogram_tester_.ExpectBucketCount(
      "Blink.FedCm.MismatchDialogType",
      Metrics::MismatchDialogType::kFirstWithHints, 1);
  histogram_tester_.ExpectBucketCount(
      "Blink.FedCm.MismatchDialogType",
      Metrics::MismatchDialogType::kRepeatedWithHints, 1);
}

// Tests that when an accounts request is sent, the appropriate metrics are
// recorded.
TEST_F(RequestTest, AccountsRequestSentMetric) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  ukm_loop.Run();

  EXPECT_EQ(NumFetched(FetchedEndpoint::ACCOUNTS), 1u);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AccountsRequestSent", 1, 1);
  ExpectUKMPresence("AccountsRequestSent2");
}

// Tests that when an accounts dialog is aborted, the appropriate duration
// metrics are recorded.
TEST_F(RequestTest, AbortedAccountsDialogShownDurationMetric) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;
  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  // Abort the request.
  request_->Abort();

  WaitForCurrentRequest();
  RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
                                   FederatedRequestResult::kCanceled,
                                   /*standalone_console_message=*/std::nullopt,
                                   /*selected_idp_config_url=*/std::nullopt};
  CheckExpectations(configuration, expectations);

  ukm_loop.Run();

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 0);

  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectNoUKMPresence("Timing.MismatchDialogShownDuration");
  ExpectStatusMetrics(TokenStatus::kAborted);
}

// Tests that when a mismatch dialog is aborted, the appropriate duration
// metrics are recorded.
TEST_F(RequestTest, AbortedMismatchDialogShownDurationMetric) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  SetNetworkRequestManager(
      std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
  auto* network_manager =
      static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
          test_network_request_manager_.get());

  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);

  // Setup IdP sign-in status mismatch.
  network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;

  RunDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_FALSE(did_show_accounts_dialog());

  // Abort the request.
  request_->Abort();

  RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
                                   FederatedRequestResult::kCanceled,
                                   /*standalone_console_message=*/std::nullopt,
                                   /*selected_idp_config_url=*/std::nullopt};
  WaitForCurrentRequest();
  CheckExpectations(kConfigurationValid, expectations);

  ukm_loop.Run();

  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);

  ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
  ExpectUKMPresence("Timing.MismatchDialogShownDuration");
  ExpectStatusMetrics(TokenStatus::kAborted);
}

// Tests that when requests are made to FedCM in succession, the appropriate
// metrics are recorded upon destruction.
TEST_F(RequestTest, RecordNumRequestsPerDocumentMetric) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  // First request.
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kNone;
  RunDontWaitForCallback(kDefaultRequestParameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  // Abort the first request.
  request_->Abort();

  WaitForCurrentRequest();
  RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
                                   FederatedRequestResult::kCanceled,
                                   /*standalone_console_message=*/std::nullopt,
                                   /*selected_idp_config_url=*/std::nullopt};
  CheckExpectations(configuration, expectations);

  // Reset test classes for second request.
  SetNetworkRequestManager(std::make_unique<TestIdpNetworkRequestManager>());
  request_helper_ = std::make_unique<RequestCallbackHelper>();

  // Second request.
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;
  expectations = {RequestTokenStatus::kError,
                  FederatedRequestResult::kShouldEmbargo,
                  /*standalone_console_message=*/std::nullopt,
                  /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(did_show_idp_signin_status_mismatch_dialog());

  // Check that the appropriate metrics are recorded upon destruction.
  ResetAndDeleteRequest();
  RequestService::DeleteForCurrentDocument(main_test_rfh());

  ukm_loop.Run();

  // Both requests should have been counted.
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.NumRequestsPerDocument", 2,
                                       1);

  // Check for RP-keyed UKM presence.
  ExpectUKMPresenceInternal("NumRequestsPerDocument", FedCmEntry::kEntryName);
}

// Test that an error dialog is shown when the token response is invalid.
TEST_F(RequestTest, InvalidResponseErrorDialogShown) {
  MockConfiguration configuration = kConfigurationValid;
  ErrorDialogType error_dialog_type = ErrorDialogType::kGenericEmptyWithoutUrl;
  TokenResponseType token_response_type = TokenResponseType::
      kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
  configuration.token_response.parse_status =
      ParseStatus::kInvalidResponseError;
  configuration.error_dialog_type = error_dialog_type;
  configuration.token_response_type = token_response_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenInvalidResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
                                       error_dialog_type, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.Error.ErrorDialogResult",
      ErrorDialogResult::kCloseWithoutMoreDetails, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
                                       token_response_type, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Error.ErrorUrlType", 0);

  ExpectUKMPresence("Error.ErrorDialogType");
  ExpectUKMPresence("Error.ErrorDialogResult");
  ExpectUKMPresence("Error.TokenResponseType");
  ExpectNoUKMPresence("Error.ErrorUrlType");
}

// Test that an error dialog is shown when the token response is missing.
TEST_F(RequestTest, NoResponseErrorDialogShown) {
  MockConfiguration configuration = kConfigurationValid;
  ErrorDialogType error_dialog_type = ErrorDialogType::kGenericEmptyWithoutUrl;
  TokenResponseType token_response_type = TokenResponseType::
      kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
  configuration.token_response.parse_status = ParseStatus::kNoResponseError;
  configuration.error_dialog_type = error_dialog_type;
  configuration.token_response_type = token_response_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kIdTokenNoResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
                                       error_dialog_type, 1);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.Error.ErrorDialogResult",
      ErrorDialogResult::kCloseWithoutMoreDetails, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
                                       token_response_type, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.Error.ErrorUrlType", 0);

  ExpectUKMPresence("Error.ErrorDialogType");
  ExpectUKMPresence("Error.ErrorDialogResult");
  ExpectUKMPresence("Error.TokenResponseType");
  ExpectNoUKMPresence("Error.ErrorUrlType");
}

// Test that the error UI has proper url set.
TEST_F(RequestTest, ErrorUrlDisplayedWithProperUrl) {
  MockConfiguration configuration = kConfigurationValid;
  ErrorDialogType error_dialog_type = ErrorDialogType::kGenericEmptyWithUrl;
  TokenResponseType token_response_type = TokenResponseType::
      kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived;
  ErrorUrlType error_url_type = ErrorUrlType::kCrossOriginSameSite;
  configuration.token_error =
      TokenError(/*code=*/"", GURL("https://foo.idp.example/error"));
  configuration.error_dialog_type = error_dialog_type;
  configuration.token_response_type = token_response_type;
  configuration.error_url_type = error_url_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenIdpErrorResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
  EXPECT_EQ(dialog_controller_state_.token_error->url,
            GURL("https://foo.idp.example/error"));

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
                                       error_dialog_type, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogResult",
                                       ErrorDialogResult::kCloseWithMoreDetails,
                                       1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
                                       token_response_type, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorUrlType",
                                       error_url_type, 1);

  ExpectUKMPresence("Error.ErrorDialogType");
  ExpectUKMPresence("Error.ErrorDialogResult");
  ExpectUKMPresence("Error.TokenResponseType");
  ExpectUKMPresenceInternal("Error.ErrorUrlType", FedCmIdpEntry::kEntryName);
}

// Test that permission is embargoed upon closing a mismatch dialog.
TEST_F(RequestTest, IdpSigninStatusCloseMismatchEmbargo) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  configuration.idp_signin_status_mismatch_dialog_action =
      IdpSigninStatusMismatchDialogAction::kClose;
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.count(
      main_test_rfh()->GetLastCommittedOrigin()));
}

// Test that permission is not embargoed upon closing an IDP sign-in flow
// pop-up.
TEST_F(RequestTest, IdpSigninStatusClosePopupEmbargo) {
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;
  configuration.idp_signin_status_mismatch_dialog_action =
      IdpSigninStatusMismatchDialogAction::kClosePopup;
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
  EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.empty());
}

// Test that no registered IdP is added without a registry requested.
TEST_F(RequestTest, MaybeAddRegisteredProvidersEmptyList) {
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
      MaybeAddRegisteredProviders(providers);

  EXPECT_TRUE(result.empty());
}

// Test that no registered IdP with only named providers requested.
TEST_F(RequestTest, MaybeAddRegisteredProvidersNamed) {
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
  providers.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
                                     /*is_registered=*/false));

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
      MaybeAddRegisteredProviders(providers);

  // Expects the vector to be the same.
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
  expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
                                    /*is_registered=*/false));

  EXPECT_EQ(expected, result);
}

// Test that a registered provider is added.
TEST_F(RequestTest, MaybeAddRegisteredProvidersAdded) {
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
  providers.emplace_back(NewRegisteredIdP(kClientId));

  std::vector<GURL> registry;
  registry.emplace_back("https://idp.example");

  EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
      .WillOnce(Return(registry));

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
      MaybeAddRegisteredProviders(providers);

  // Expects that the registered IdP gets replaced by a named IdP.
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
  expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
                                    /*is_registered=*/true));

  EXPECT_EQ(expected, result);
}

// Test that all registered IdPs are expanded.
TEST_F(RequestTest,
       MaybeAddRegisteredProvidersAllRequestsForRegisteredIdPsAreExpanded) {
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
  providers.emplace_back(NewRegisteredIdP(kClientId));
  providers.emplace_back(NewRegisteredIdP(kClientId));

  std::vector<GURL> registry;
  registry.emplace_back("https://idp.example");

  EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
      .WillOnce(Return(registry));

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
      MaybeAddRegisteredProviders(providers);

  // Expects that the registered IdP gets replaced by a named IdP.
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
  expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
                                    /*is_registered=*/true));
  expected.emplace_back(NewNamedIdP(GURL("https://idp.example"), kClientId,
                                    /*is_registered=*/true));

  EXPECT_EQ(expected, result);
}

// Test that the registry can add two idps.
TEST_F(RequestTest, MaybeAddRegisteredProvidersTwoRegisteredIdPs) {
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
  providers.emplace_back(NewRegisteredIdP(kClientId));

  std::vector<GURL> registry;
  registry.emplace_back("https://idp1.example");
  registry.emplace_back("https://idp2.example");

  EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
      .WillOnce(Return(registry));

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
      MaybeAddRegisteredProviders(providers);

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
  expected.emplace_back(NewNamedIdP(GURL("https://idp2.example"), kClientId,
                                    /*is_registered=*/true));
  expected.emplace_back(NewNamedIdP(GURL("https://idp1.example"), kClientId,
                                    /*is_registered=*/true));

  EXPECT_EQ(expected, result);
}

// Test that registered idps are inserted inline.
TEST_F(RequestTest, MaybeAddRegisteredProvidersInsertedInline) {
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> providers;
  providers.emplace_back(NewNamedIdP(GURL("https://idp1.example"), kClientId,
                                     /*is_registered=*/false));
  providers.emplace_back(NewRegisteredIdP(kClientId));
  providers.emplace_back(NewNamedIdP(GURL("https://idp2.example"), kClientId,
                                     /*is_registered=*/false));

  std::vector<GURL> registry;
  registry.emplace_back("https://idp-registered1.example");
  registry.emplace_back("https://idp-registered2.example");

  EXPECT_CALL(*test_permission_delegate_, GetRegisteredIdPs())
      .WillOnce(Return(registry));

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> result =
      MaybeAddRegisteredProviders(providers);

  // Expects that the registered IdP gets replaced by a named IdP.
  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> expected;
  expected.emplace_back(NewNamedIdP(GURL("https://idp1.example"), kClientId,
                                    /*is_registered=*/false));
  expected.emplace_back(NewNamedIdP(GURL("https://idp-registered2.example"),
                                    kClientId, /*is_registered=*/true));
  expected.emplace_back(NewNamedIdP(GURL("https://idp-registered1.example"),
                                    kClientId, /*is_registered=*/true));
  expected.emplace_back(NewNamedIdP(GURL("https://idp2.example"), kClientId,
                                    /*is_registered=*/false));

  EXPECT_EQ(expected, result);
}

// Test that error dialog type metrics are recorded.
TEST_F(RequestTest, ErrorDialogTypeMetrics) {
  MockConfiguration configuration = kConfigurationValid;
  ErrorDialogType error_dialog_type = ErrorDialogType::kInvalidRequestWithUrl;
  configuration.token_error = TokenError(/*code=*/"invalid_request",
                                         GURL("https://foo.idp.example/error"));
  configuration.error_dialog_type = error_dialog_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenIdpErrorResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  base::RunLoop().RunUntilIdle();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogType",
                                       error_dialog_type, 1);

  ExpectUKMPresence("Error.ErrorDialogType");
}

// Test that error dialog result metrics are recorded.
TEST_F(RequestTest, ErrorDialogResultMetrics) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.token_error =
      TokenError(/*code=*/"", GURL("https://foo.idp.example/error"));
  configuration.error_dialog_action = ErrorDialogAction::kGotIt;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenIdpErrorResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  base::RunLoop().RunUntilIdle();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorDialogResult",
                                       ErrorDialogResult::kGotItWithMoreDetails,
                                       1);

  ExpectUKMPresence("Error.ErrorDialogResult");
}

// Test that token response type metrics are recorded.
TEST_F(RequestTest, TokenResponseTypeMetrics) {
  MockConfiguration configuration = kConfigurationValid;
  TokenResponseType token_response_type = TokenResponseType::
      kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived;
  configuration.token_error = TokenError(/*code=*/"invalid_request",
                                         GURL("https://foo.idp.example/error"));
  configuration.token_response_type = token_response_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenIdpErrorResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  base::RunLoop().RunUntilIdle();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.TokenResponseType",
                                       token_response_type, 1);

  ExpectUKMPresence("Error.TokenResponseType");
}

// Test that error url type metrics are recorded.
TEST_F(RequestTest, ErrorUrlTypeMetrics) {
  MockConfiguration configuration = kConfigurationValid;
  ErrorUrlType error_url_type = ErrorUrlType::kCrossOriginSameSite;
  configuration.token_error = TokenError(/*code=*/"invalid_request",
                                         GURL("https://foo.idp.example/error"));
  configuration.error_url_type = error_url_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenIdpErrorResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);

  base::RunLoop().RunUntilIdle();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.Error.ErrorUrlType",
                                       error_url_type, 1);

  ExpectUKMPresenceInternal("Error.ErrorUrlType", FedCmIdpEntry::kEntryName);
}

// Test that cross-site URL fails the request with the appropriate devtools
// issue.
TEST_F(RequestTest, CrossSiteErrorDialogDevtoolsIssue) {
  MockConfiguration configuration = kConfigurationValid;
  ErrorUrlType error_url_type = ErrorUrlType::kCrossSite;
  configuration.token_error = TokenError(
      /*code=*/"invalid_request", GURL("https://cross-site.example/error"));
  configuration.error_url_type = error_url_type;
  configuration.error_dialog_action = ErrorDialogAction::kClose;

  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      FederatedRequestResult::kIdTokenCrossSiteIdpErrorResponse,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_TRUE(dialog_controller_state_.did_show_error_dialog);
}

// Test that the account UI is not displayed if FedCM is disabled after accounts
// fetch.
TEST_F(RequestTest, AccountUiNotDisplayedIfFedCmDisabledAfterAccountsFetch) {
  test_api_permission_delegate_->permission_override_for_nth_ = std::make_pair(
      /*override the nth invocation=*/2,
      std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                     ApiPermissionStatus::BLOCKED_EMBARGO));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kDisabledInSettings,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  EXPECT_FALSE(did_show_accounts_dialog());
}

TEST_F(RequestTest, DomainHintInLoginUrl) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;

  // Need to have sign in status set to signed in to prompt login url
  // computations.
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  GURL login_url;
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
        login_url = url;
        return modal.get();
      }));

  RunDontWaitForCallback(parameters, kConfigurationValid);
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  SimulateLoginToIdP();

  std::string expected_url = kIdpLoginUrl;
  expected_url += "?domain_hint=domain%40corp.com";
  EXPECT_EQ(login_url, expected_url);
}

TEST_F(RequestTest, LoginHintInLoginUrl) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = "hint";

  // Need to have sign in status set to signed in to prompt login url
  // computations.
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  GURL login_url;
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
        login_url = url;
        return modal.get();
      }));

  RunDontWaitForCallback(parameters, kConfigurationValid);
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  SimulateLoginToIdP();

  std::string expected_url = kIdpLoginUrl;
  expected_url += "?login_hint=hint";
  EXPECT_EQ(login_url, expected_url);
}

TEST_F(RequestTest, DomainHintAndLoginHintInLoginUrl) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;
  parameters.identity_providers[0].login_hint = "hint";

  // Need to have sign in status set to signed in to prompt login url
  // computations.
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  GURL login_url;
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
        login_url = url;
        return modal.get();
      }));

  RunDontWaitForCallback(parameters, kConfigurationValid);
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  SimulateLoginToIdP();

  std::string expected_url = kIdpLoginUrl;
  expected_url += "?login_hint=hint&domain_hint=domain%40corp.com";
  EXPECT_EQ(login_url, expected_url);
}

TEST_F(RequestTest, DomainHintAndLoginHintInLoginUrlWithQuery) {
  RequestParameters parameters = kDefaultRequestParameters;
  // Testing that spaces are transformed in the url.
  parameters.identity_providers[0].domain_hint = "domain hint";
  parameters.identity_providers[0].login_hint = "login hint";

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.idp_login_url += "?q=1&t=2";

  // Need to have sign in status set to signed in to prompt login url
  // computations.
  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  GURL login_url;
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
        login_url = url;
        return modal.get();
      }));

  RunDontWaitForCallback(parameters, configuration);
  EXPECT_FALSE(did_show_accounts_dialog());
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  SimulateLoginToIdP(
      configuration.idp_info[kProviderUrlFull].config.idp_login_url);

  std::string expected_url = kIdpLoginUrl;
  expected_url += "?q=1&t=2&login_hint=login%20hint&domain_hint=domain%20hint";
  EXPECT_EQ(login_url, expected_url);
}

TEST_F(RequestTest, DomainHintAddAccount) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
  configuration.idp_info[kProviderUrlFull].accounts =
      kSingleAccountWithDomainHint;

  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  GURL login_url;
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, &login_url](const GURL& url) {
        login_url = url;
        return modal.get();
      }));

  RunDontWaitForCallback(parameters, configuration);
  EXPECT_TRUE(did_show_accounts_dialog());

  // The `login_url` used when invoking AddAccounts should not include hints.
  EXPECT_EQ(login_url, kIdpLoginUrl);
}

// Test that auto re-authn works in active mode.
TEST_F(RequestTest, AutoReauthnInActiveMode) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  for (const auto& idp_info : kConfigurationValid.idp_info) {
    ASSERT_EQ(idp_info.second.accounts.size(), 1u);
  }
  // The following checks work in active mode.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              RequiresUserMediation(url::Origin::Create(GURL(kRpUrl))))
      .WillOnce(Return(false));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RunDontWaitForCallback(parameters, kConfigurationValid);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
}

// Test that IdP claimed SignUp takes precedence over browser observed SignIn.
TEST_F(RequestTest, IdPClaimedSignUpTakesPrecedenceOverBrowserObservedSignIn) {
  // Pretend the sharing permission has been granted for all accounts.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), _))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RunDontWaitForCallback(parameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  // Accounts are reordered to have sign-in users displayed first.
  EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
            LoginState::kSignUp);

  ExpectUkmValue("HasSigninAccount", true);
}

// Test that when an account has an obsolete browser sharing permission (i.e.
// browser has a last_used_timestamp but IdP claimed SignUp via
// approved_clients), the permission is revoked.
TEST_F(RequestTest, RevokeObsoleteSharingPermission) {
  // Pretend the sharing permission has been granted for all accounts.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), _))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  // Expect RevokeSharingPermission to be called for the accounts where
  // approved_clients indicates kSignUp.
  EXPECT_CALL(*test_permission_delegate_,
              RevokeSharingPermission(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdNicolas));
  EXPECT_CALL(*test_permission_delegate_,
              RevokeSharingPermission(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdZach));
  EXPECT_CALL(*test_permission_delegate_,
              RevokeSharingPermission(
                  OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                  OriginFromString(kProviderUrlFull), kAccountIdPeter))
      .Times(0);

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
}

// Test that when an IdP does not provide approved_clients (so
// idp_claimed_login_state is std::nullopt), the sharing permission is NOT
// revoked, even if the browser observed login state is kSignIn.
TEST_F(RequestTest, DoNotRevokeSharingPermissionWithoutApprovedClients) {
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), _))
      .WillRepeatedly(
          Return(std::make_optional<base::Time>(base::Time::Now())));

  EXPECT_CALL(*test_permission_delegate_, RevokeSharingPermission(_, _, _, _))
      .Times(0);

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kSingleAccount;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 1u);
  EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
            std::nullopt);
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
}

// Test that when the browser observed login state is kSignUp, the sharing
// permission is NOT revoked, regardless of whether the IdP claimed login state
// is kSignIn or kSignUp.
TEST_F(RequestTest, DoNotRevokeSharingPermissionWhenBrowserObservedIsSignUp) {
  // Pretend the sharing permission has NOT been granted for any account,
  // so browser_observed_login_state is kSignUp.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), _))
      .WillRepeatedly(Return(std::nullopt));

  EXPECT_CALL(*test_permission_delegate_, RevokeSharingPermission(_, _, _, _))
      .Times(0);

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
            LoginState::kSignUp);
}

// Test that IdP claimed SignIn does not affect browser observed SignUp.
TEST_F(RequestTest, IdPClaimedSignInDoesNotAffectBrowserObservedSignUp) {
  // Pretend the sharing permission has NOT been granted for any account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), _))
      .WillRepeatedly(Return(std::nullopt));

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RunDontWaitForCallback(parameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
            LoginState::kSignIn);
  // This should be kSignUp regardless of IdP's claim.
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
            LoginState::kSignUp);
}

// Test that IdP claimed SignIn can affect browser observed SignUp if they have
// third-party cookies access.
TEST_F(RequestTest, IdPClaimedSignInAffectsBrowserObservedSignUpWith3PCAccess) {
  // Pretend the sharing permission has NOT been granted for any account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), _))
      .WillRepeatedly(Return(std::nullopt));

  // Pretend the IdP was given third-party cookies access.
  EXPECT_CALL(*test_api_permission_delegate_,
              HasThirdPartyCookiesAccess(_, GURL(kProviderUrlFull),
                                         OriginFromString(kRpUrl)))
      .WillRepeatedly(Return(true));

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RunDontWaitForCallback(parameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  EXPECT_EQ(all_accounts_for_display()[0]->idp_claimed_login_state,
            LoginState::kSignIn);
  // This should be kSignIn to match IdP's claim due to it has 3PC access.
  EXPECT_EQ(all_accounts_for_display()[0]->browser_trusted_login_state,
            LoginState::kSignIn);
  EXPECT_EQ(all_accounts_for_display()[1]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[1]->browser_trusted_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->idp_claimed_login_state,
            LoginState::kSignUp);
  EXPECT_EQ(all_accounts_for_display()[2]->browser_trusted_login_state,
            LoginState::kSignUp);
}

// Test active flow is exempted if the FedCM is disabled in  settings.
TEST_F(RequestTest, ActiveFlowNotAffectedBySettings) {
  test_api_permission_delegate_->permission_override_ =
      std::make_pair(main_test_rfh()->GetLastCommittedOrigin(),
                     ApiPermissionStatus::BLOCKED_SETTINGS);
  ExpectSuccessfulActiveFlow();
}

// Test active flow is exempted if the FedCM is embargoed in the passive flow.
TEST_F(RequestTest, ActiveFlowNotAffectedByEmbargo) {
  test_api_permission_delegate_->RecordDismissAndEmbargo(
      OriginFromString(kRpUrl));
  ExpectSuccessfulActiveFlow();
}

// Test dismissing UI in active flow does not trigger embargo.
TEST_F(RequestTest, ActiveFlowNotAffectEmbargo) {
  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  RequestParameters parameters = kDefaultRequestParameters;
  parameters.rp_mode = blink::mojom::RpMode::kActive;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;

  RunTest(parameters, expectations, configuration);

  ukm_loop.Run();
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.RpMode",
                                       static_cast<int>(RpMode::kActive), 1);
  ExpectUkmValue("RpMode", static_cast<int>(RpMode::kActive));

  EXPECT_TRUE(did_show_accounts_dialog());
  EXPECT_FALSE(DidFetch(FetchedEndpoint::TOKEN));
  EXPECT_FALSE(test_api_permission_delegate_->embargoed_origins_.count(
      main_test_rfh()->GetLastCommittedOrigin()));
}

// Test ambient UI is NOT exempt from the cooldown embargo.
// Because the ambient UI is still not entirely complete (e.g. it can't handle
// multiple accounts, mismatch UIs, etc), we respect the cooldown embargo.
TEST_F(RequestTest, AmbientFlowDismissedByEmbargo) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmAmbientUI);

  test_api_permission_delegate_->RecordDismissAndEmbargo(
      OriginFromString(kRpUrl));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kDisabledInSettings,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  ExpectStatusMetrics(TokenStatus::kDisabledEmbargo);
}

// Test dismissing UI in ambient UI does not trigger embargo.
// While the ambient UI respects the embargo, it does not trigger / set it,
// e.g. when the use dismisses it.
TEST_F(RequestTest, AmbientFlowDoesNotCauseAnEmbargo) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmAmbientUI);

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;

  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_FALSE(test_api_permission_delegate_->embargoed_origins_.count(
      main_test_rfh()->GetLastCommittedOrigin()));
}

// Test that non-ambient passive flow still triggers embargo.
TEST_F(RequestTest, NonAmbientPassiveFlowStillAffectsEmbargo) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmAmbientUI);

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kClose;
  // Multiple accounts means it's not ambient capable.
  configuration.idp_info[kProviderUrlFull].accounts = kTwoAccounts;

  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_TRUE(test_api_permission_delegate_->embargoed_origins_.count(
      main_test_rfh()->GetLastCommittedOrigin()));
}

// Tests that when background text is passed but no background color, the
// background text is ignored.
TEST_F(RequestTest, BrandingWithTextColorAndNoBackgroundColor) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.brand_text_color =
      SkColorSetRGB(10, 10, 10);
  RequestExpectations expectations = kExpectationSuccess;
  expectations.standalone_console_message =
      "The FedCM text color is ignored because background color was not "
      "provided";
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_EQ(brand_background_color(), std::nullopt);
  EXPECT_EQ(brand_text_color(), std::nullopt);
}

// Tests that when background text does not contrast enough with the background
// color, the text color is ignored.
TEST_F(RequestTest, BrandingWithInsufficientContrastTextColor) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].config.brand_background_color =
      SkColorSetRGB(0, 0, 0);
  configuration.idp_info[kProviderUrlFull].config.brand_text_color =
      SkColorSetRGB(1, 1, 1);
  RequestExpectations expectations = kExpectationSuccess;
  expectations.standalone_console_message =
      "The FedCM text color is ignored because it does not contrast enough "
      "with the provided background color";
  RunTest(kDefaultRequestParameters, expectations, configuration);

  EXPECT_EQ(brand_background_color(), SkColorSetRGB(0, 0, 0));
  EXPECT_EQ(brand_text_color(), std::nullopt);
}

class FederatedRequestExampleOrgTest : public RequestTest {
 public:
  FederatedRequestExampleOrgTest() : RequestTest("https://rp.example.org/") {}
};

TEST_F(FederatedRequestExampleOrgTest, WellKnownSameSite) {
  static const char kExampleOrgProviderUrl[] =
      "https://idp.example.org/fedcm.json";

  MockIdpInfo idp_info = kDefaultIdentityProviderInfo;
  idp_info.well_known.fetch_status.parse_status =
      ParseStatus::kInvalidContentTypeError;
  idp_info.config.accounts_endpoint = "https://idp.example.org/accounts";
  idp_info.config.token_endpoint = "https://idp.example.org/token";
  idp_info.config.client_metadata_endpoint =
      "https://idp.example.org/client_metadata";
  idp_info.config.metrics_endpoint = "";
  idp_info.config.idp_login_url = "https://idp.example.org/login";
  idp_info.config.disconnect_endpoint = "";
  idp_info.config.vc_issuance_endpoint = "";

  // We make the request from rp.example to idp.example, so it should
  // only succeed despite the well-known failure if the flag is on.
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info.clear();
  configuration.idp_info[kExampleOrgProviderUrl] = idp_info;

  RequestParameters request{kDefaultRequestParameters};
  request.identity_providers[0].provider = kExampleOrgProviderUrl;

  RequestExpectations expectation = kExpectationSuccess;
  expectation.selected_idp_config_url = kExampleOrgProviderUrl;

  RunTest(request, expectation, configuration);
}

class TestDialogControllerWithImmediateDismiss : public TestDialogController {
 public:
  explicit TestDialogControllerWithImmediateDismiss(
      MockConfiguration configuration)
      : TestDialogController(configuration) {}

  ~TestDialogControllerWithImmediateDismiss() override = default;

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

  bool ShowAccountsDialog(
      RelyingPartyData rp_data,
      const std::vector<IdentityProviderDataPtr>& idp_list,
      const std::vector<IdentityRequestAccountPtr>& accounts,
      const std::vector<IdentityRequestAccountPtr>& filtered_accounts,
      blink::mojom::RpMode rp_mode,
      IdentityRequestDialogController::AccountSelectionCallback on_selected,
      IdentityRequestDialogController::LoginToIdPCallback on_add_account,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::AccountsDisplayedCallback
          accounts_displayed_callback) override {
    std::move(dismiss_callback).Run(DismissReason::kOther);
    return false;
  }

  bool ShowFailureDialog(
      const RelyingPartyData& rp_data,
      const std::string& idp_for_display,
      blink::mojom::RpContext rp_context,
      blink::mojom::RpMode rp_mode,
      const IdentityProviderMetadata& idp_metadata,
      const std::vector<scoped_refptr<IdentityRequestAccount>>&
          filtered_accounts,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::LoginToIdPCallback
          identity_registry_callback) override {
    std::move(dismiss_callback).Run(DismissReason::kOther);
    return false;
  }
};

// Crash test for crbug.com/328945371.
TEST_F(RequestTest, ImmediateDismiss) {
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  SetDialogController(
      std::make_unique<TestDialogControllerWithImmediateDismiss>(
          kConfigurationValid));

  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  histogram_tester_.ExpectTotalCount(
      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
}

// Tests that dismissing during ShowFailureDialog() does not crash.
TEST_F(RequestTest, FailureDialogImmediateDismiss) {
  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);

  MockConfiguration configuration = kConfigurationValid;

  // Setup IdP sign-in status mismatch.
  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
  configuration.idp_info[kProviderUrlFull].accounts_response.parse_status =
      ParseStatus::kInvalidResponseError;

  SetDialogController(
      std::make_unique<TestDialogControllerWithImmediateDismiss>(
          configuration));

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
}

TEST_F(RequestTest, UseOtherAccountAccountOrder) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;

  // User has accounts, kAccountIdNicolas and kAccountIdZach, in that order.
  configuration.idp_info[kProviderUrlFull].accounts = kTwoAccounts;

  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
        // The user signs in with account, kAccountIdPeter. User now has
        // accounts, kAccountIdNicolas, kAccountIdPeter and kAccountIdZach,
        // in that order.
        test_network_request_manager_->accounts_list_ = kMultipleAccounts;
        for (const auto& account :
             test_network_request_manager_->accounts_list_) {
          if (account->id == kAccountIdPeter) {
            account->display_priority =
                IdentityRequestAccount::DisplayPriority::kNew;
          }
        }
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
            ->GetActiveRequestForTesting()
            ->OnIdpSigninStatusReceived(OriginFromString(kProviderUrlFull),
                                        true);
        return modal.get();
      }));

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  ASSERT_EQ(new_accounts().size(), 1u);

  // Accounts are reordered to have the most recently signed in account,
  // kAccountIdPeter, displayed first.
  EXPECT_EQ(all_accounts_for_display()[0]->id, kAccountIdPeter);
  EXPECT_EQ(all_accounts_for_display()[1]->id, kAccountIdNicolas);
  EXPECT_EQ(all_accounts_for_display()[2]->id, kAccountIdZach);
  EXPECT_EQ(new_accounts()[0]->id, kAccountIdPeter);

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  WaitForCurrentRequest();
  ukm_loop.Run();

  // The first account is selected, and this is a new account.
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount), 1);
  ExpectUkmValue(
      "UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount));
}

// Tests that when use a different account is used and multiple accounts are
// logged in at once, all the new accounts are part of the new_accounts().
TEST_F(RequestTest, UseOtherAccountMultipleNewAccounts) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
        // The user signs in with kAccountIdNicolas and kAccountIdZach. User now
        // has accounts kAccountId, kAccountIdNicolas, and kAccountIdZach, in
        // that order.
        test_network_request_manager_->accounts_list_ = {
            kSingleAccount[0], kTwoAccounts[0], kTwoAccounts[1]};
        for (const auto& account :
             test_network_request_manager_->accounts_list_) {
          if (account->id == kTwoAccounts[0]->id ||
              account->id == kTwoAccounts[1]->id) {
            account->display_priority =
                IdentityRequestAccount::DisplayPriority::kNew;
          }
        }
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
            ->GetActiveRequestForTesting()
            ->OnIdpSigninStatusReceived(OriginFromString(kProviderUrlFull),
                                        true);
        return modal.get();
      }));

  RunDontWaitForCallback(kDefaultRequestParameters, configuration);

  ASSERT_EQ(all_accounts_for_display().size(), 3u);
  ASSERT_EQ(new_accounts().size(), 2u);

  // Accounts are reordered to have the most recently signed in accounts
  // displayed first.
  EXPECT_EQ(all_accounts_for_display()[0]->id, kTwoAccounts[0]->id);
  EXPECT_EQ(all_accounts_for_display()[1]->id, kTwoAccounts[1]->id);
  EXPECT_EQ(all_accounts_for_display()[2]->id, kSingleAccount[0]->id);
  EXPECT_EQ(new_accounts()[0]->id, kTwoAccounts[0]->id);
  EXPECT_EQ(new_accounts()[1]->id, kTwoAccounts[1]->id);

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  WaitForCurrentRequest();
  ukm_loop.Run();

  // The first account is selected, and this is a new account.
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount), 1);
  ExpectUkmValue(
      "UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserSignsInWithNewAccount));
}

TEST_F(RequestTest, UseOtherAccountNoNewAccount) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>([&modal, this](const GURL& url) {
        // No changes to the accounts being logged in. But set the login status
        // to force the popup to close.
        RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
            ->GetActiveRequestForTesting()
            ->OnIdpSigninStatusReceived(OriginFromString(kProviderUrlFull),
                                        true);
        return modal.get();
      }));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
  ukm_loop.Run();

  // The first account is selected, and this is an existing account since there
  // is no new account.
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserSignsInWithExistingAccount),
      1);
  ExpectUkmValue(
      "UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserSignsInWithExistingAccount));
}

TEST_F(RequestTest, UseOtherAccountThenClose) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.accounts_dialog_action = AccountsDialogAction::kAddAccount;
  auto dialog_controller =
      std::make_unique<TestDialogController>(configuration);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller, ShowModalDialog)
      .WillOnce(::testing::WithArg<0>(
          [&modal, &weak_dialog_controller, this](const GURL& url) {
            // Signs in to new accounts.
            test_network_request_manager_->accounts_list_ = {
                kSingleAccount[0], kTwoAccounts[0], kTwoAccounts[1]};
            RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
                ->GetActiveRequestForTesting()
                ->OnIdpSigninStatusReceived(OriginFromString(kProviderUrlFull),
                                            true);
            // The action is set to close, so the request will be rejected.
            weak_dialog_controller->accounts_dialog_action_ =
                AccountsDialogAction::kClose;
            return modal.get();
          }));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, configuration);
  ukm_loop.Run();

  // The first account is selected, and this is an existing account since there
  // is no new account.
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.UseOtherAccountResult",
      static_cast<int>(UseOtherAccountResult::kUserDoesNotSignIn), 1);
  ExpectUkmValue("UseOtherAccountResult",
                 static_cast<int>(UseOtherAccountResult::kUserDoesNotSignIn));
}

TEST_F(RequestTest, MultipleIdpSigninDueToHint) {
  url::Origin providerOrigin = OriginFromString(kProviderUrlFull);

  // Use an invalid login hint so login to IDP URL is shown.
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].login_hint = "not_a_valid_email";

  MockConfiguration config = kConfigurationValid;
  config.accounts_dialog_action = AccountsDialogAction::kNone;

  RunDontWaitForCallback(kDefaultMultiIdpRequestParameters, config);

  EXPECT_FALSE(request_->HasUserTriedToSignInToIdp(GURL(kProviderUrlFull)));

  for (int i = 0; i < 5; ++i) {
    // First, simulate the user clicking on the sign in to IDP active.
    SimulateLoginToIdP();
    // Then, simulate user signing into IdP by updating the IdP signin status
    // and calling the observer.
    test_permission_delegate_->idp_signin_statuses_[providerOrigin] = true;
    // We do not update the accounts so they would still be filtered out.
    request_->OnIdpSigninStatusReceived(providerOrigin,
                                        /*idp_signin_status=*/true);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(request_->HasUserTriedToSignInToIdp(GURL(kProviderUrlFull)));
  }
}

TEST_F(RequestTest, VerifyingDialogCancelExplicitMetrics) {
  MockConfiguration config = kConfigurationValid;
  config.delay_token_response = true;

  RunDontWaitForCallback(kDefaultRequestParameters, config);
  CloseDialog();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.VerifyingDialogResult",
                                       VerifyingDialogResult::kCancelExplicit,
                                       1);
  ExpectUkmValue("VerifyingDialogResult",
                 static_cast<int>(VerifyingDialogResult::kCancelExplicit));
}

TEST_F(RequestTest, VerifyingDialogCancelAutoReauthnMetrics) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  MockConfiguration config = kConfigurationValid;
  config.delay_token_response = true;

  RunDontWaitForCallback(kDefaultRequestParameters, config);
  CloseDialog();

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.VerifyingDialogResult",
      VerifyingDialogResult::kCancelAutoReauthn, 1);
  ExpectUkmValue("VerifyingDialogResult",
                 static_cast<int>(VerifyingDialogResult::kCancelAutoReauthn));
}

TEST_F(RequestTest, VerifyingDialogDestroyExplicitMetrics) {
  MockConfiguration config = kConfigurationValid;
  config.delay_token_response = true;

  RunDontWaitForCallback(kDefaultRequestParameters, config);
  ResetAndDeleteRequest();

  histogram_tester_.ExpectUniqueSample("Blink.FedCm.VerifyingDialogResult",
                                       VerifyingDialogResult::kDestroyExplicit,
                                       1);
  ExpectUkmValue("VerifyingDialogResult",
                 static_cast<int>(VerifyingDialogResult::kDestroyExplicit));
}

TEST_F(RequestTest, VerifyingDialogDestroyAutoReauthnMetrics) {
  // Pretend the sharing permission has been granted for this account.
  EXPECT_CALL(
      *test_permission_delegate_,
      GetLastUsedTimestamp(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
                           OriginFromString(kProviderUrlFull), kAccountId))
      .WillRepeatedly(Return(std::make_optional<base::Time>()));

  // Pretend the auto re-authn permission has been granted.
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnSettingEnabled())
      .WillOnce(Return(true));
  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
      .WillOnce(Return(false));

  MockConfiguration config = kConfigurationValid;
  config.delay_token_response = true;

  RunDontWaitForCallback(kDefaultRequestParameters, config);
  ResetAndDeleteRequest();

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.VerifyingDialogResult",
      VerifyingDialogResult::kDestroyAutoReauthn, 1);
  ExpectUkmValue("VerifyingDialogResult",
                 static_cast<int>(VerifyingDialogResult::kDestroyAutoReauthn));
}

TEST_F(RequestTest, MetricsForThirdPartyCookiesEnabledInSettings) {
  // Pretend that third party cookies are enabled in settings.
  EXPECT_CALL(*test_api_permission_delegate_,
              AreThirdPartyCookiesEnabledInSettings())
      .WillOnce(Return(true));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ukm_loop.Run();

  ExpectUkmValue("ThirdPartyCookiesStatus",
                 static_cast<int>(ThirdPartyCookiesStatus::kEnabledInSettings));

  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}

TEST_F(RequestTest, MetricsForThirdPartyCookiesDisabledInSettings) {
  // Pretend that third party cookies are disabled in settings.
  EXPECT_CALL(*test_api_permission_delegate_,
              AreThirdPartyCookiesEnabledInSettings())
      .WillOnce(Return(false));

  base::RunLoop ukm_loop;
  ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
                                        ukm_loop.QuitClosure());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ukm_loop.Run();

  ExpectUkmValue(
      "ThirdPartyCookiesStatus",
      static_cast<int>(ThirdPartyCookiesStatus::kDisabledInSettings));

  ExpectStatusMetrics(TokenStatus::kSuccessUsingTokenInHttpResponse);
}

// Tests that we record whether the RP's URL has a path when an accounts dialog
// is shown.
TEST_F(RequestTest, MetricsForRpUrlHasPath) {
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);
  EXPECT_TRUE(did_show_accounts_dialog());

  ExpectUkmValueInEntry("RpUrlHasPath", FedCmEntry::kEntryName, false);
}

// Tests that we record the scroll position when an account is selected.
TEST_F(RequestTest, MetricsForAccountSelectionScrollPosition) {
  // Mock the `GetScrollPosition` call.
  TestRenderFrameHost* test_rfh =
      static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame());
  test_rfh->ResetLocalFrame();
  FakeLocalFrame local_frame;
  local_frame.Init(test_rfh->GetRemoteAssociatedInterfaces());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  ExpectUkmValueInEntry("AccountSelectionScrollPosition",
                        FedCmEntry::kEntryName, 0);
}

TEST_F(RequestTest, CancelReasonMetrics) {
  MockConfiguration config = kConfigurationValid;
  config.accounts_dialog_action = AccountsDialogAction::kClose;
  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kShouldEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, config);

  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.CancelReason",
      IdentityRequestDialogController::DismissReason::kCloseButton, 1);
  ExpectUkmValueInEntry(
      "CancelReason", FedCmEntry::kEntryName,
      static_cast<std::underlying_type_t<
          IdentityRequestDialogController::DismissReason>>(
          IdentityRequestDialogController::DismissReason::kCloseButton));
}

// Tests that the request is successful when the segmentation platform
// recommends the ambient UI volume.
TEST_F(RequestTest, SegmentationPlatformRecommendsAmbientVolume) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.suppressed_by_segmentation_platform = true;
  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
}

TEST_F(RequestTest, MetricsForConsecutiveSuccessfulRequests) {
  // First successful request.
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  // Reset some test helpers so that we can send the second request.
  std::unique_ptr<TestIdpNetworkRequestManager> network_request_manager =
      std::make_unique<TestIdpNetworkRequestManager>();
  SetNetworkRequestManager(std::move(network_request_manager));
  request_helper_ = std::make_unique<RequestCallbackHelper>();

  // Second successful request.
  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  // There should be two samples, one for each successful request.
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.Status.RequestIdToken",
      TokenStatus::kSuccessUsingTokenInHttpResponse, 2);
  auto CheckUkmMetrics([&](const char* entry_name) {
    auto entries = ukm_recorder()->GetEntriesByName(entry_name);
    int num_token_status_ukm = 0;
    for (const ukm::mojom::UkmEntry* const entry : entries) {
      const int64_t* metric =
          ukm_recorder()->GetEntryMetric(entry, "Status.RequestIdToken");
      if (!metric) {
        continue;
      }
      EXPECT_EQ(static_cast<int>(TokenStatus::kSuccessUsingTokenInHttpResponse),
                *metric);
      ++num_token_status_ukm;
    }
    ASSERT_EQ(2, num_token_status_ukm);
  });
  CheckUkmMetrics(FedCmEntry::kEntryName);
  CheckUkmMetrics(FedCmIdpEntry::kEntryName);
}

// Test that completing a disconnect request while there is a pending request
// and then later completing the pending request does not crash.
TEST_F(RequestTest, DisconnectWithPendingRequest) {
  // Start an request.
  RunDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);

  // Complete a disconnect request.
  CompleteDisconnectRequest();

  // Complete the request.
  WaitForCurrentRequest();
  CheckExpectations(kConfigurationValid, kExpectationSuccess);

  // Request and disconnect request metrics should be recorded separately
  // with different session IDs.
  ExpectTwoUniqueSessionIDs();
}

class FakeLocalFrameWithDelayedCallback : public FakeLocalFrame {
 public:
  FakeLocalFrameWithDelayedCallback(RenderFrameHost* rfh) {
    TestRenderFrameHost* test_rfh = static_cast<TestRenderFrameHost*>(rfh);
    test_rfh->ResetLocalFrame();
    Init(test_rfh->GetRemoteAssociatedInterfaces());
  }

  void RunCallback() {
    if (!callback_) {
      return;
    }

    std::move(callback_).Run(gfx::Point(0, 0));
  }

  // FakeLocalFrame:
  void GetScrollPosition(GetScrollPositionCallback callback) override {
    callback_ = std::move(callback);
  }

 private:
  GetScrollPositionCallback callback_;
};

// Tests that we record the scroll position when an account is selected, even
// when `GetScrollPosition` runs its callback after the Request
// object is destroyed.
TEST_F(RequestTest, MetricsForAccountSelectionScrollPositionDelayedCallback) {
  // Mock the `GetScrollPosition` call.
  FakeLocalFrameWithDelayedCallback frame(
      web_contents()->GetPrimaryMainFrame());

  RunTest(kDefaultRequestParameters, kExpectationSuccess, kConfigurationValid);

  frame.RunCallback();
  base::RunLoop().RunUntilIdle();

  ExpectUkmValueInEntry("AccountSelectionScrollPosition",
                        FedCmEntry::kEntryName, 0);
}

TEST_F(RequestTest, RecordsNoncePresence) {
  // TODO(crbug.com/441895082): Remove this test when FedCmNonceInParams is
  // enabled by default. Nonce should be passed via params field, not as a
  // separate field.
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].nonce = "12345";
  RunTest(parameters, kExpectationSuccess, kConfigurationValid);
  ExpectUkmValue("HasNonce", true);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasNonce", true, 1);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasNonceOutsideParamsOnly",
                                       true, 1);
}

// Test that Blink.FedCm.HasNonceOutsideParamsOnly is not recorded when a nonce
// is present in params.
TEST_F(RequestTest, HasNonceOutsideParamsOnly_ParamsWithNonce) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].params_json = "{\"nonce\":\"abc\"}";
  RunTest(parameters, kExpectationSuccess, kConfigurationValid);
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.HasNonce", true, 1);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonceOutsideParamsOnly",
                                     0);
  ExpectUkmValue("HasNonce", true);
  ExpectNoUKMPresence("HasNonceOutsideParamsOnly");
}

// Test that Blink.FedCm.HasNonceOutsideParamsOnly is not recorded when a nonce
// is not present.
TEST_F(RequestTest, HasNonceOutsideParamsOnly_NoNonce) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].nonce = "";
  RunTest(parameters, kExpectationSuccess, kConfigurationValid);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonce", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonceOutsideParamsOnly",
                                     0);
  ExpectNoUKMPresence("HasNonce");
  ExpectNoUKMPresence("HasNonceOutsideParamsOnly");
}

// Test that Blink.FedCm.HasNonceOutsideParamsOnly is not recorded when a nonce
// is only present in params.
TEST_F(RequestTest, HasNonceOutsideParamsOnly_NonceOnlyInParams) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].nonce = "";
  parameters.identity_providers[0].params_json = "{\"nonce\":\"abc\"}";
  RunTest(parameters, kExpectationSuccess, kConfigurationValid);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonce", 0);
  histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonceOutsideParamsOnly",
                                     0);
  ExpectNoUKMPresence("HasNonce");
  ExpectNoUKMPresence("HasNonceOutsideParamsOnly");
}

TEST_F(RequestTest, NonceAbsenceNoRecord) {
  // TODO(crbug.com/441895082): Remove this test when FedCmNonceInParams is
  // enabled by default. Nonce should be passed via params field, not as a
  // separate field.
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].nonce = "";
  RunTest(parameters, kExpectationSuccess, kConfigurationValid);
  ExpectNoUKMPresence("HasNonce");
  histogram_tester_.ExpectTotalCount("Blink.FedCm.HasNonce", 0);
}

// Tests that LifecycleStateFailureReason is recorded when page is non-primary.
TEST_F(RequestTest, NonPrimaryPageMetrics) {
  static_cast<RenderFrameHostImpl*>(web_contents()->GetPrimaryMainFrame())
      ->SetLifecycleState(
          RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache);
  RequestExpectations expectations = {
      RequestTokenStatus::kError,
      // When the RenderFrameHost changes on navigation, no console message is
      // received, so pass FederatedRequestResult::kSuccess.
      FederatedRequestResult::kSuccess,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};
  RunTest(kDefaultRequestParameters, expectations, kConfigurationValid);
  histogram_tester_.ExpectUniqueSample(
      "Blink.FedCm.LifecycleStateFailureReason",
      LifecycleStateFailureReason::kInBackForwardCache, 1);
}

// Test that if there are multiple IdPs, the UI should not be suppressed even if
// configuration.suppressed_by_segmentation_platform is set to true.
TEST_F(RequestTest, SuppressedBySegmentationPlatformButMultipleIdps) {
  // Use IdpNetworkRequestManagerParamChecker to validate passed-in parameters
  // to IdpNetworkRequestManager methods.
  std::unique_ptr<IdpNetworkRequestManagerParamChecker> checker =
      std::make_unique<IdpNetworkRequestManagerParamChecker>();
  SetNetworkRequestManager(std::move(checker));

  RequestExpectations expectations = kExpectationSuccess;
  // Since the first IDP does not set the login state of the account but the
  // second IDP has one with state set to SignIn, selecting the first account
  // means that the second IDP is the one that is selected.
  expectations.selected_idp_config_url = kProviderTwoUrlFull;
  MockConfiguration config = kConfigurationMultiIdpValid;
  config.suppressed_by_segmentation_platform = true;
  RunTest(kDefaultMultiIdpRequestParameters, expectations, config);

  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
  histogram_tester_.ExpectUniqueSample("Blink.FedCm.DidShowUI", true, 1);
  ExpectUkmValueInEntry("DidShowUI", FedCmEntry::kEntryName, true);
}

class TestDialogControllerWithIdentityCredentialSource
    : public TestDialogController {
 public:
  enum class SelectionMode {
    kValid,
    kInvalidAccountId,
    kInvalidOrigin,
  };

  explicit TestDialogControllerWithIdentityCredentialSource(
      MockConfiguration configuration,
      WebContents* web_contents,
      SelectionMode selection_mode = SelectionMode::kValid)
      : TestDialogController(configuration),
        web_contents_(web_contents),
        selection_mode_(selection_mode) {}

  ~TestDialogControllerWithIdentityCredentialSource() override = default;

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

  bool ShowAccountsDialog(
      RelyingPartyData rp_data,
      const std::vector<IdentityProviderDataPtr>& idp_list,
      const std::vector<IdentityRequestAccountPtr>& accounts,
      const std::vector<IdentityRequestAccountPtr>& filtered_accounts,
      blink::mojom::RpMode rp_mode,
      IdentityRequestDialogController::AccountSelectionCallback on_selected,
      IdentityRequestDialogController::LoginToIdPCallback on_add_account,
      IdentityRequestDialogController::DismissCallback dismiss_callback,
      IdentityRequestDialogController::AccountsDisplayedCallback
          accounts_displayed_callback) override {
    state_->all_accounts_for_display = accounts;
    IdentityCredentialSource* source =
        IdentityCredentialSource::FromPage(web_contents_->GetPrimaryPage());
    // Check that we can get the correct account list, immediately.
    source->GetIdentityCredentialSuggestions(
        {GURL(kProviderUrlFull)},
        base::BindOnce(
            [](const std::vector<IdentityRequestAccountPtr>& all_accounts,
               const std::vector<IdentityRequestAccountPtr>& filtered_accounts,
               const std::optional<std::vector<
                   scoped_refptr<IdentityRequestAccount>>>& actual_accounts) {
              ASSERT_TRUE(actual_accounts.has_value());
              std::vector<IdentityRequestAccountPtr> expected_signin_accounts;
              for (const auto& account : all_accounts) {
                if (account->idp_claimed_login_state.value_or(
                        account->browser_trusted_login_state) ==
                    LoginState::kSignIn) {
                  expected_signin_accounts.push_back(account);
                }
              }

              EXPECT_EQ(expected_signin_accounts.size(),
                        actual_accounts->size());
              for (size_t i = 0; i < expected_signin_accounts.size(); ++i) {
                EXPECT_EQ(expected_signin_accounts[i]->id,
                          (*actual_accounts)[i]->id);
              }
              // Check that the actual accounts are not from the filtered list.
              for (const auto& account : *actual_accounts) {
                ASSERT_TRUE(std::find(filtered_accounts.begin(),
                                      filtered_accounts.end(),
                                      account) == filtered_accounts.end());
              }
            },
            accounts, filtered_accounts));
    // Now, check that selecting the account works. Find the first sign-in
    // account.
    std::string signin_account_id;
    for (const auto& account : accounts) {
      if (account->idp_claimed_login_state.value_or(
              account->browser_trusted_login_state) == LoginState::kSignIn) {
        signin_account_id = account->id;
        break;
      }
    }

    url::Origin origin = url::Origin::Create(GURL(kProviderUrlFull));
    if (selection_mode_ == SelectionMode::kInvalidAccountId) {
      signin_account_id = "invalid_account_id";
    } else if (selection_mode_ == SelectionMode::kInvalidOrigin) {
      origin = url::Origin::Create(GURL("https://invalid.example"));
    }

    if (!signin_account_id.empty() &&
        source->SelectAccount(origin, signin_account_id)) {
      // No need to call a callback here; SelectAccount took care of that.
      return false;
    }
    // Something went wrong, dismiss dialog.
    std::move(dismiss_callback).Run(DismissReason::kOther);
    return false;
  }

 private:
  raw_ptr<WebContents> web_contents_;
  SelectionMode selection_mode_;
};

TEST_F(
    RequestTest,
    IdentityCredentialSourceReturnsReturningAccountsAndResolvesFedCmRequest) {
  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  SetDialogController(
      std::make_unique<TestDialogControllerWithIdentityCredentialSource>(
          configuration, web_contents()));

  RunTest(kDefaultRequestParameters, kExpectationSuccess, configuration);

  // Check that GetIdentityCredentialSuggestions did not fetch new accounts.
  EXPECT_EQ(1u, NumFetched(FetchedEndpoint::ACCOUNTS));
  EXPECT_EQ(1u, NumFetched(FetchedEndpoint::TOKEN));
}

TEST_F(RequestTest, IdentityCredentialSourceReturnsFilteredSigninAccounts) {
  MockConfiguration configuration = kConfigurationValid;

  // Use Nicolas (SignUp), Peter (SignIn), and Zach (SignIn).
  configuration.idp_info[kProviderUrlFull].accounts = {
      base::MakeRefCounted<IdentityRequestAccount>(
          kAccountIdNicolas, kAccountEmailNicolas, "Nicolas P",
          kAccountEmailNicolas, "Nicolas P", "Nicolas", GURL(),
          "(650) 312-3223", "npm", std::vector<std::string>(), kNicolasHints,
          std::vector<std::string>(), std::vector<std::string>(),
          LoginState::kSignUp, LoginState::kSignUp),
      base::MakeRefCounted<IdentityRequestAccount>(
          kAccountIdPeter, kAccountEmailPeter, "Peter K", kAccountEmailPeter,
          "Peter K", "Peter", GURL(), "(650) 312-3223", "peter",
          std::vector<std::string>(), kPeterHints, std::vector<std::string>(),
          std::vector<std::string>(), LoginState::kSignIn, LoginState::kSignIn),
      base::MakeRefCounted<IdentityRequestAccount>(
          kAccountIdZach, kAccountEmailZach, "Zachary T", kAccountEmailZach,
          "Zachary T", "Zach", GURL(), "(650) 312-3223", "zacht",
          std::vector<std::string>(), kZachHints, std::vector<std::string>(),
          std::vector<std::string>(), LoginState::kSignIn,
          LoginState::kSignIn)};

  RequestParameters parameters = kDefaultRequestParameters;
  // Only Peter matches this hint.
  parameters.identity_providers[0].login_hint = kAccountIdPeter;

  SetDialogController(
      std::make_unique<TestDialogControllerWithIdentityCredentialSource>(
          configuration, web_contents()));

  RunTest(parameters, kExpectationSuccess, configuration);

  // Suggestions should have returned just Peter since Zach was filtered out of
  // the FedCM dialog by the login hint.
  EXPECT_EQ(1u, NumFetched(FetchedEndpoint::ACCOUNTS));
  EXPECT_EQ(1u, NumFetched(FetchedEndpoint::TOKEN));
}

TEST_F(RequestTest, IdentityCredentialSourceFailsOnInvalidAccountId) {
  RenderFrameHostTester::For(main_rfh())->ClearConsoleMessages();

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  SetDialogController(
      std::make_unique<TestDialogControllerWithIdentityCredentialSource>(
          configuration, web_contents(),
          TestDialogControllerWithIdentityCredentialSource::SelectionMode::
              kInvalidAccountId));

  RunTest(kDefaultRequestParameters, expectations, configuration);
}

TEST_F(RequestTest, IdentityCredentialSourceFailsOnInvalidOrigin) {
  RenderFrameHostTester::For(main_rfh())->ClearConsoleMessages();

  MockConfiguration configuration = kConfigurationValid;
  configuration.idp_info[kProviderUrlFull].accounts = kMultipleAccounts;

  RequestExpectations expectations = {
      RequestTokenStatus::kError, FederatedRequestResult::kUiDismissedNoEmbargo,
      /*standalone_console_message=*/std::nullopt,
      /*selected_idp_config_url=*/std::nullopt};

  SetDialogController(
      std::make_unique<TestDialogControllerWithIdentityCredentialSource>(
          configuration, web_contents(),
          TestDialogControllerWithIdentityCredentialSource::SelectionMode::
              kInvalidOrigin));

  RunTest(kDefaultRequestParameters, expectations, configuration);
}

TEST_F(RequestTest, RequestTokenDeniedByPermissionsPolicy) {
  auto remote = NavigateAndBindWithDeniedIdentityCredentialsGetPolicy();
  mojo::test::BadMessageObserver bad_message_observer;

  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
  get_params->providers.push_back(
      blink::mojom::IdentityProviderRequestOptions::New());
  get_params->providers[0]->config =
      blink::mojom::IdentityProviderConfig::New();
  get_params->providers[0]->config->config_url = GURL(kProviderUrlFull);
  get_params->providers[0]->config->client_id = kClientId;

  std::vector<blink::mojom::IdentityProviderGetParametersPtr> params;
  params.push_back(std::move(get_params));

  mojo::Remote<FederatedRequest> request_remote;
  remote->StartTokenRequest(
      std::move(params),
      password_manager::CredentialMediationRequirement::kOptional,
      request_remote.BindNewPipeAndPassReceiver(), base::DoNothing());

  EXPECT_EQ("identity-credentials-get permissions policy not enabled",
            bad_message_observer.WaitForBadMessage());
}

TEST_F(RequestTest, RequestUserInfoDeniedByPermissionsPolicy) {
  auto remote = NavigateAndBindWithDeniedIdentityCredentialsGetPolicy();
  mojo::test::BadMessageObserver bad_message_observer;

  auto config = blink::mojom::IdentityProviderConfig::New();
  config->config_url = GURL(kProviderUrlFull);
  config->client_id = kClientId;

  remote->RequestUserInfo(std::move(config), base::DoNothing());

  EXPECT_EQ("identity-credentials-get permissions policy not enabled",
            bad_message_observer.WaitForBadMessage());
}

TEST_F(RequestTest, DisconnectDeniedByPermissionsPolicy) {
  auto remote = NavigateAndBindWithDeniedIdentityCredentialsGetPolicy();
  mojo::test::BadMessageObserver bad_message_observer;

  auto options = blink::mojom::IdentityCredentialDisconnectOptions::New();
  options->config = blink::mojom::IdentityProviderConfig::New();
  options->config->config_url = GURL(kProviderUrlFull);
  options->config->client_id = kClientId;
  options->account_hint = "hint";

  remote->Disconnect(std::move(options), base::DoNothing());

  EXPECT_EQ("identity-credentials-get permissions policy not enabled",
            bad_message_observer.WaitForBadMessage());
}

struct NotifyAutofillTestParams {
  bool unknown_idp;
  bool show_modal;
};

class RequestNotifyAutofillParamTest
    : public RequestTest,
      public ::testing::WithParamInterface<NotifyAutofillTestParams> {};

TEST_P(RequestNotifyAutofillParamTest, NotifyAutofillSuggestionAccepted) {
  NotifyAutofillTestParams params = GetParam();
  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);

  dialog_controller->SetState(&dialog_controller_state_);
  RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
      ->SetDialogControllerForTests(std::move(dialog_controller));

  GURL idp = params.unknown_idp ? GURL("https://unknownidp.example/")
                                : GURL(kProviderUrlFull);
  std::string account_id =
      params.unknown_idp ? "account_id" : "unknown_account_id";

  std::optional<bool> callback_result;
  base::RunLoop run_loop;
  Request::OnFederatedTokenReceivedCallback callback = base::BindOnce(
      [](std::optional<bool>* callback_result, base::OnceClosure quit_closure,
         bool accepted) {
        *callback_result = accepted;
        std::move(quit_closure).Run();
      },
      &callback_result, run_loop.QuitClosure());

  // This should return early and not crash.
  request_->NotifyAutofillSuggestionAccepted(idp, account_id, params.show_modal,
                                             std::move(callback));

  run_loop.Run();

  // Verify that the loading dialog was not shown.
  EXPECT_FALSE(dialog_controller_state_.did_show_loading_dialog);
  // Verify that the callback was called with false.
  EXPECT_EQ(callback_result, false);
}

INSTANTIATE_TEST_SUITE_P(
    /*no prefix*/,
    RequestNotifyAutofillParamTest,
    ::testing::Values(NotifyAutofillTestParams{/*unknown_idp=*/true,
                                               /*show_modal=*/true},
                      NotifyAutofillTestParams{/*unknown_idp=*/true,
                                               /*show_modal=*/false},
                      NotifyAutofillTestParams{/*unknown_idp=*/false,
                                               /*show_modal=*/true},
                      NotifyAutofillTestParams{/*unknown_idp=*/false,
                                               /*show_modal=*/false}));

TEST_F(RequestTest, DismissIgnoredDuringRedirectTo) {
  base::HistogramTester histogram_tester;

  MockConfiguration config = kConfigurationValid;
  config.delay_token_response = true;

  RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
      ->SetForceAllowRedirectToForTesting(true);

  // Start the request flow.
  RunDontWaitForCallback(kDefaultRequestParameters, config);

  MockWebContentsObserver observer(web_contents());
  EXPECT_CALL(observer, DidStartNavigation(_))
      .WillOnce([&](NavigationHandle* handle) { CloseDialog(); });

  // Call RedirectTo. This should trigger navigation and thus the observer.
  CallRedirectTo(GURL(kProviderUrlFull),
                 blink::mojom::RedirectParams::Tag::kGet,
                 GURL("https://rp.example/redirect"), "");

  WaitForCurrentRequest(/*should_fast_forward=*/true);

  // Verify that the request completed successfully.
  EXPECT_EQ(request_helper_->status(), RequestTokenStatus::kSuccess);
  // In the success case via RedirectTo, the token is explicitly returned as a
  // null base::Value (type NONE). So token().has_value() is true (it contains
  // the null value), but is_none() is true.
  EXPECT_TRUE(request_helper_->token().has_value());
  EXPECT_TRUE(request_helper_->token()->is_none());

  // Verify that the histogram records success (53) instead of dismiss (5).
  histogram_tester.ExpectUniqueSample(
      "Blink.FedCm.Status.RequestIdToken",
      static_cast<int>(RequestIdTokenStatus::kSuccessUsingRedirectTo), 1);
}

TEST_F(RequestTest, DisconnectViaFederatedRequestService) {
  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  auto options = blink::mojom::IdentityCredentialDisconnectOptions::New();
  options->config = blink::mojom::IdentityProviderConfig::New();
  options->config->config_url = GURL(kProviderUrlFull);
  options->config->client_id = kClientId;
  options->account_hint = "hint";

  base::RunLoop run_loop;
  federated_request_service->Disconnect(
      std::move(options),
      base::BindLambdaForTesting([&](blink::mojom::DisconnectStatus status) {
        // Checking that the callback resolves (even if with an expected error
        // status due to test environment setup) proves that the IPC routing
        // works.
        run_loop.Quit();
      }));
  run_loop.Run();
}

TEST_F(RequestTest, DisconnectFromOpaqueOrigin) {
  base::HistogramTester histogram_tester;
  ResetAndDeleteRequest();

  static_cast<TestWebContents*>(web_contents())
      ->NavigateAndCommit(GURL("data:text/html,hi"), ui::PAGE_TRANSITION_LINK);

  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  auto options = blink::mojom::IdentityCredentialDisconnectOptions::New();
  options->config = blink::mojom::IdentityProviderConfig::New();
  options->config->config_url = GURL(kProviderUrlFull);
  options->config->client_id = kClientId;
  options->account_hint = "hint";

  base::RunLoop run_loop;
  federated_request_service->Disconnect(
      std::move(options),
      base::BindLambdaForTesting([&](blink::mojom::DisconnectStatus status) {
        EXPECT_EQ(blink::mojom::DisconnectStatus::kError, status);
        run_loop.Quit();
      }));
  run_loop.Run();
  histogram_tester.ExpectTotalCount("Blink.FedCm.Status.Disconnect", 0);
}

TEST_F(RequestTest, DisconnectFromFencedFrame) {
  base::HistogramTester histogram_tester;
  ResetAndDeleteRequest();

  RenderFrameHost* fenced_frame =
      RenderFrameHostTester::For(main_test_rfh())->AppendFencedFrame();
  ASSERT_TRUE(fenced_frame);

  GURL fenced_frame_url = GURL("https://fencedframe.com");
  std::unique_ptr<NavigationSimulator> navigation_simulator =
      NavigationSimulator::CreateRendererInitiated(fenced_frame_url,
                                                   fenced_frame);
  navigation_simulator->Commit();
  fenced_frame = navigation_simulator->GetFinalRenderFrameHost();
  ASSERT_TRUE(fenced_frame);

  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(fenced_frame);
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  auto options = blink::mojom::IdentityCredentialDisconnectOptions::New();
  options->config = blink::mojom::IdentityProviderConfig::New();
  options->config->config_url = GURL(kProviderUrlFull);
  options->config->client_id = kClientId;
  options->account_hint = "hint";

  base::RunLoop run_loop;
  federated_request_service->Disconnect(
      std::move(options),
      base::BindLambdaForTesting([&](blink::mojom::DisconnectStatus status) {
        EXPECT_EQ(blink::mojom::DisconnectStatus::kError, status);
        run_loop.Quit();
      }));
  run_loop.Run();
  histogram_tester.ExpectTotalCount("Blink.FedCm.Status.Disconnect", 0);
}

TEST_F(RequestTest, DisconnectFromNonPrimaryPage) {
  base::HistogramTester histogram_tester;
  ResetAndDeleteRequest();

  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  static_cast<RenderFrameHostImpl*>(main_test_rfh())
      ->SetLifecycleState(
          RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache);

  auto options = blink::mojom::IdentityCredentialDisconnectOptions::New();
  options->config = blink::mojom::IdentityProviderConfig::New();
  options->config->config_url = GURL(kProviderUrlFull);
  options->config->client_id = kClientId;
  options->account_hint = "hint";

  base::RunLoop run_loop;
  federated_request_service->Disconnect(
      std::move(options),
      base::BindLambdaForTesting([&](blink::mojom::DisconnectStatus status) {
        EXPECT_EQ(blink::mojom::DisconnectStatus::kError, status);
        run_loop.Quit();
      }));
  run_loop.Run();
  histogram_tester.ExpectTotalCount("Blink.FedCm.Status.Disconnect", 0);
}

TEST_F(RequestTest, ResolveViaFederatedRequestService) {
  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  EXPECT_CALL(*test_identity_registry_, NotifyResolve)
      .WillOnce(::testing::Return(true));

  auto params = blink::mojom::ResolveTokenParams::NewToken(
      base::Value("an-access-token"));

  base::RunLoop run_loop;
  federated_request_service->ResolveTokenRequest(
      "account", std::move(params),
      base::BindLambdaForTesting([&](bool success) {
        EXPECT_TRUE(success);
        run_loop.Quit();
      }));
  run_loop.Run();
}

TEST_F(RequestTest, ResolveViaFederatedRequestServiceEmptyPostRedirectBody) {
  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  // Construct a redirect param with a POST method but an EMPTY request body.
  auto post_params = blink::mojom::RedirectPostParams::New();
  post_params->url = GURL("https://example.com/redirect");
  post_params->request_body = "";  // Empty body (violates validation rule)

  auto params = blink::mojom::ResolveTokenParams::NewRedirectTo(
      blink::mojom::RedirectParams::NewPost(std::move(post_params)));

  // Observe bad Mojo messages.
  mojo::test::BadMessageObserver bad_message_observer;
  federated_request_service->ResolveTokenRequest("account", std::move(params),
                                                 base::DoNothing());

  // Verify that the browser-side implementation correctly detected the
  // violation and reported a bad message.
  EXPECT_EQ("POST redirects must have a body",
            bad_message_observer.WaitForBadMessage());
}

TEST_F(RequestTest, StartTokenRequestViaFederatedRequestService) {
  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  // We must reset the active request first if one was created in SetUp,
  // because StartTokenRequest will reject with kErrorTooManyRequests if there
  // is one.
  ResetAndDeleteRequest();

  auto idp_ptr = blink::mojom::IdentityProviderRequestOptions::New();
  idp_ptr->config = blink::mojom::IdentityProviderConfig::New();
  idp_ptr->config->config_url = GURL(kProviderUrlFull);
  idp_ptr->config->client_id = kClientId;
  idp_ptr->nonce = kNonce;

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> idp_ptrs;
  idp_ptrs.push_back(std::move(idp_ptr));

  auto get_params = blink::mojom::IdentityProviderGetParameters::New(
      std::move(idp_ptrs), blink::mojom::RpContext::kSignIn,
      blink::mojom::RpMode::kPassive);

  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
  idp_get_params.push_back(std::move(get_params));

  mojo::Remote<FederatedRequest> federated_request;

  base::RunLoop run_loop;
  federated_request_service->StartTokenRequest(
      std::move(idp_get_params), MediationRequirement::kSilent,
      federated_request.BindNewPipeAndPassReceiver(),
      base::BindLambdaForTesting(
          [&](base::expected<blink::mojom::TokenRequestSuccessPtr,
                             blink::mojom::TokenRequestFailurePtr> result) {
            // Checking that the callback resolves proves that the IPC routing
            // works.
            EXPECT_FALSE(result.has_value());
            ASSERT_TRUE(result.error());
            EXPECT_EQ(result.error()->status,
                      blink::mojom::RequestTokenStatus::kError);
            run_loop.Quit();
          }));
  run_loop.Run();
}

TEST_F(RequestTest, AbortViaFederatedRequest) {
  mojo::Remote<FederatedRequestService> federated_request_service;
  RequestService* service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());
  service->BindFederatedRequestService(
      federated_request_service.BindNewPipeAndPassReceiver());

  ResetAndDeleteRequest();

  auto idp_ptr = blink::mojom::IdentityProviderRequestOptions::New();
  idp_ptr->config = blink::mojom::IdentityProviderConfig::New();
  idp_ptr->config->config_url = GURL(kProviderUrlFull);
  idp_ptr->config->client_id = kClientId;
  idp_ptr->nonce = kNonce;

  std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> idp_ptrs;
  idp_ptrs.push_back(std::move(idp_ptr));

  auto get_params = blink::mojom::IdentityProviderGetParameters::New(
      std::move(idp_ptrs), blink::mojom::RpContext::kSignIn,
      blink::mojom::RpMode::kPassive);

  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
  idp_get_params.push_back(std::move(get_params));

  mojo::Remote<FederatedRequest> federated_request;

  base::RunLoop run_loop;
  federated_request_service->StartTokenRequest(
      std::move(idp_get_params), MediationRequirement::kSilent,
      federated_request.BindNewPipeAndPassReceiver(),
      base::BindLambdaForTesting(
          [&](base::expected<blink::mojom::TokenRequestSuccessPtr,
                             blink::mojom::TokenRequestFailurePtr> result) {
            // The request should fail because it was aborted explicitly.
            ASSERT_FALSE(result.has_value());
            ASSERT_TRUE(result.error());
            EXPECT_EQ(result.error()->status,
                      blink::mojom::RequestTokenStatus::kErrorCanceled);
            run_loop.Quit();
          }));

  // Abort the request session explicitly via the FederatedRequest pipe!
  federated_request->Abort();
  run_loop.Run();
}

class TestContentBrowserClientForDialogReset : public ContentBrowserClient {
 public:
  TestContentBrowserClientForDialogReset() = default;
  ~TestContentBrowserClientForDialogReset() override = default;

  std::unique_ptr<IdentityRequestDialogController>
  CreateIdentityRequestDialogController(WebContents* web_contents) override {
    create_count_++;
    return std::make_unique<MockIdentityRequestDialogController>();
  }

  int create_count() const { return create_count_; }

 private:
  int create_count_ = 0;
};

TEST_F(RequestTest, DialogControllerResetOnCompletion) {
  TestContentBrowserClientForDialogReset browser_client;
  ContentBrowserClient* original_client =
      SetBrowserClientForTesting(&browser_client);

  RequestService* request_service =
      RequestService::GetOrCreateForCurrentDocument(main_test_rfh());

  request_service->SetDialogControllerForTests(nullptr);

  EXPECT_FALSE(request_service->HasDialogControllerForTesting());

  request_service->GetOrCreateActiveRequest();
  request_service->GetOrCreateDialogController();

  EXPECT_TRUE(request_service->HasDialogControllerForTesting());
  EXPECT_EQ(browser_client.create_count(), 1);

  CompleteTokenRequest(
      request_service, request_service->GetActiveRequestForTesting(),
      base::DoNothing(), blink::mojom::RequestTokenStatus::kError,
      /*selected_idp_config_url=*/std::nullopt,
      /*token=*/std::nullopt,
      /*error=*/nullptr,
      /*is_auto_selected=*/false);
  ASSERT_TRUE(base::test::RunUntil([&]() { return !request_; }));

  request_service->GetOrCreateActiveRequest();
  request_service->GetOrCreateDialogController();

  EXPECT_EQ(browser_client.create_count(), 2);

  SetBrowserClientForTesting(original_client);
}

TEST_F(RequestTest, ActiveRequestReplacesPassiveRequestNoRace) {
  // Start a passive request.
  RequestParameters passive_parameters = kDefaultRequestParameters;
  passive_parameters.rp_mode = blink::mojom::RpMode::kPassive;

  auto passive_request_helper = std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> passive_request_remote;

  RunDontWaitForCallback(passive_parameters, kConfigurationValid,
                         passive_request_helper.get(), &passive_request_remote);

  // Assert pending request exists.
  RequestPageData* page_data = GetPageData(main_test_rfh()->GetPage());
  Request* pending_request = page_data->PendingWebIdentityRequest();
  EXPECT_TRUE(pending_request);

  // Start an active request.
  // This will replace the passive request.
  RequestParameters active_parameters = kDefaultRequestParameters;
  active_parameters.rp_mode = blink::mojom::RpMode::kActive;

  auto active_request_helper = std::make_unique<RequestCallbackHelper>();
  mojo::Remote<FederatedRequest> active_request_remote;

  static_cast<TestRenderFrameHost*>(web_contents()->GetPrimaryMainFrame())
      ->SimulateUserActivation();

  // We must provide a new mock network manager because the passive request
  // consumed the initial one.
  RequestService::GetOrCreateForCurrentDocument(main_test_rfh())
      ->SetNetworkManagerForTests(
          std::make_unique<DelegatedIdpNetworkRequestManager>(
              test_network_request_manager_.get()));

  MockConfiguration active_config = kConfigurationValid;
  active_config.accounts_dialog_action = AccountsDialogAction::kNone;

  RunDontWaitForCallback(active_parameters, active_config,
                         active_request_helper.get(), &active_request_remote);

  // The active request should now be the pending request.
  EXPECT_FALSE(active_request_helper->was_callback_called());
  // The passive request's destructor should not clear the pending request.
  Request* new_pending_request = page_data->PendingWebIdentityRequest();
  EXPECT_TRUE(new_pending_request);
  EXPECT_NE(new_pending_request, pending_request);

  // Clean up the active request to avoid a use-after-free during TearDown.
  if (new_pending_request) {
    new_pending_request->CompleteRequestWithError(
        blink::mojom::FederatedRequestResult::kError,
        /*token_status=*/std::nullopt,
        /*should_delay_callback=*/false);
    active_request_helper->WaitForCallback();
  }
}

// Verifies that calling LoginToIdP when the login popup is already open
// refocuses the popup without re-registering the IdpSigninStatus observer or
// resetting state, and that query parameters are preserved.
TEST_F(RequestTest, LoginToIdPRefocusesWhenPopupAlreadyOpen) {
  RequestParameters parameters = kDefaultRequestParameters;
  parameters.identity_providers[0].domain_hint = kDomainHint;

  test_permission_delegate_
      ->idp_signin_statuses_[OriginFromString(kProviderUrlFull)] = true;

  auto dialog_controller =
      std::make_unique<TestDialogController>(kConfigurationValid);
  base::WeakPtr<TestDialogController> weak_dialog_controller =
      dialog_controller->AsWeakPtr();
  SetDialogController(std::move(dialog_controller));

  std::string expected_url =
      std::string(kIdpLoginUrl) + "?domain_hint=domain%40corp.com";
  std::unique_ptr<WebContents> modal(CreateTestWebContents());
  EXPECT_CALL(*weak_dialog_controller,
              ShowModalDialog(GURL(expected_url), _, _, _, _))
      .Times(2)
      .WillRepeatedly(::testing::Return(modal.get()));

  // AddIdpSigninStatusObserver should only be called once when opening the
  // popup the first time, and NOT again when LoginToIdP is called while the
  // popup is already open.
  EXPECT_CALL(*test_permission_delegate_, AddIdpSigninStatusObserver).Times(1);

  RunDontWaitForCallback(parameters, kConfigurationValid);
  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());

  // First LoginToIdP call: opens the popup, adds observer, sets dialog_type_.
  SimulateLoginToIdP();
  EXPECT_EQ(request_->GetDialogType(), Request::DialogType::kLoginToIdpPopup);

  // Second LoginToIdP call while popup is already open: refocuses the popup
  // without re-registering observer or resetting state.
  SimulateLoginToIdP();
  EXPECT_EQ(request_->GetDialogType(), Request::DialogType::kLoginToIdpPopup);
}

}  // namespace content::webid
