// 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/idp_network_request_manager.h"

#include <array>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>

#include "base/check.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "content/browser/webid/metrics.h"
#include "content/browser/webid/network_request_manager.h"
#include "content/browser/webid/test/mock_permission_delegate.h"
#include "content/common/features.h"
#include "content/public/browser/manifest_icon_downloader.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/browser/webid/identity_request_dialog_controller.h"
#include "content/public/browser/webid/native_idp_fetcher.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_renderer_host.h"
#include "net/base/isolation_info.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/network_isolation_key.h"
#include "net/base/network_isolation_partition.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "services/network/public/cpp/cors/cors_error_status.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/webid/federated_request.mojom.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/gfx/image/image.h"
#include "url/gurl.h"

namespace content::webid {

using ::testing::_;
using ::testing::ElementsAre;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::WithArg;
using AccountsRequestCallback =
    IdpNetworkRequestManager::AccountsRequestCallback;
using AccountsResponseInvalidReason =
    IdpNetworkRequestManager::AccountsResponseInvalidReason;
using Endpoints = IdpNetworkRequestManager::Endpoints;
using ErrorDialogType = IdpNetworkRequestManager::FedCmErrorDialogType;
using ErrorUrlType = IdpNetworkRequestManager::FedCmErrorUrlType;
using IdpClientMetadata = IdpNetworkRequestManager::ClientMetadata;
using LoginState = IdentityRequestAccount::LoginState;
using TokenResponseType = IdpNetworkRequestManager::FedCmTokenResponseType;
using TokenResult = IdpNetworkRequestManager::TokenResult;

namespace {

// Values for testing. Real minimum and ideal sizes are different.
constexpr int kTestBrandIconMinimumSize = 16;
constexpr int kTestBrandIconIdealSize = 32;

constexpr char kTestIdpUrl[] = "https://idp.test";
constexpr char kTestRpUrl[] = "https://rp.test";
constexpr char kTestWellKnownUrl[] =
    "https://idp.test/.well-known/web-identity";
constexpr char kTestConfigUrl[] = "https://idp.test/fedcm.json";
constexpr char kTestAccountsEndpoint[] = "https://idp.test/accounts_endpoint";
constexpr char kTestTokenEndpoint[] = "https://idp.test/token_endpoint";
constexpr char kTestClientMetadataEndpoint[] =
    "https://idp.test/client_metadata_endpoint";
constexpr char kTestDisconnectEndpoint[] =
    "https://idp.test/revocation_endpoint";
constexpr char kTestLocalHostTokenEndpoint[] =
    "http://localhost/token_endpoint";
constexpr char kWellKnownPath[] = ".well-known/web-identity";

constexpr char kSingleAccountEndpointValidJson[] = R"({
  "accounts" : [
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "given_name": "Ken",
      "picture": "https://idp.test/profile/1"
    }
  ]
  })";

// Replaces the first line with the passed-in JSON key in `input` with
// `new_line`.
std::string ReplaceFirstLineWithKeyFromJson(const std::string& key,
                                            const std::string& new_line,
                                            const std::string& input,
                                            bool replace_all) {
  std::string pattern = R"(^[ ]*")" + key + R"(".*$)";
  std::string result = input;
  RE2::Options options;
  options.set_posix_syntax(true);
  options.set_one_line(false);
  RE2 re2(pattern, options);
  if (replace_all)
    RE2::GlobalReplace(&result, re2, new_line);
  else
    RE2::Replace(&result, re2, new_line);
  return result;
}

url::Origin GetOriginHeader(const network::ResourceRequest& request) {
  return url::Origin::Create(
      GURL(request.headers.GetHeader(net::HttpRequestHeaders::kOrigin)
               .value_or(std::string())));
}

class IdpNetworkRequestManagerTest : public RenderViewHostTestHarness {
 public:
  std::unique_ptr<IdpNetworkRequestManager> CreateTestManager(
      const char* top_level_origin = nullptr) {
    CHECK(main_rfh());
    test_permission_delegate_ =
        std::make_unique<NiceMock<MockPermissionDelegate>>();
    url::Origin top_level_origin_obj;
    if (top_level_origin) {
      top_level_origin_obj = url::Origin::Create(GURL(top_level_origin));
    }
    return std::make_unique<IdpNetworkRequestManager>(
        url::Origin::Create(GURL(kTestRpUrl)), top_level_origin_obj,
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_),
        test_permission_delegate_.get(),
        network::mojom::ClientSecurityState::New(),
        main_rfh()->GetFrameTreeNodeId(), main_rfh()->GetWeakDocumentPtr());
  }

  void AddResponse(const GURL& url,
                   net::HttpStatusCode http_status,
                   const std::string& mime_type,
                   const std::string& content,
                   bool cors_error = false) {
    auto head = network::mojom::URLResponseHead::New();
    std::string raw_header =
        base::StrCat({"HTTP/1.1 ", base::NumberToString(http_status), " ",
                      net::GetHttpReasonPhrase(http_status), "\n",
                      "Content-type: ", mime_type, "\n\n"});
    head->headers = net::HttpResponseHeaders::TryToCreate(raw_header);
    test_url_loader_factory().AddResponse(
        url, std::move(head), content,
        cors_error
            ? network::URLLoaderCompletionStatus(network::CorsErrorStatus(
                  network::mojom::CorsError::kMissingAllowOriginHeader))
            : network::URLLoaderCompletionStatus());
  }

  std::tuple<FetchStatus, IdpNetworkRequestManager::WellKnown>
  SendWellKnownRequestAndWaitForResponse(
      const char* test_data,
      net::HttpStatusCode http_status = net::HTTP_OK,
      const std::string& mime_type = "application/json") {
    GURL well_known_url(kTestWellKnownUrl);
    AddResponse(well_known_url, http_status, mime_type, test_data);

    base::RunLoop run_loop;
    FetchStatus parsed_fetch_status;
    IdpNetworkRequestManager::WellKnown parsed_wellknow;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus fetch_status,
            const IdpNetworkRequestManager::WellKnown& well_known) {
          parsed_fetch_status = fetch_status;
          parsed_wellknow = well_known;
          run_loop.Quit();
        });

    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
    manager->FetchWellKnown(GURL(kTestIdpUrl), std::move(callback));
    run_loop.Run();

    return {parsed_fetch_status, parsed_wellknow};
  }

  std::tuple<FetchStatus, IdentityProviderMetadata>
  SendConfigRequestAndWaitForResponse(
      const char* test_data,
      net::HttpStatusCode http_status = net::HTTP_OK,
      const std::string& mime_type = "application/json",
      blink::mojom::RpMode rp_mode = blink::mojom::RpMode::kPassive) {
    GURL config_url(kTestConfigUrl);
    AddResponse(config_url, http_status, mime_type, test_data);

    base::RunLoop run_loop;
    FetchStatus parsed_fetch_status;
    IdentityProviderMetadata parsed_idp_metadata;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus fetch_status, Endpoints endpoints,
            IdentityProviderMetadata idp_metadata) {
          parsed_fetch_status = fetch_status;
          parsed_idp_metadata = std::move(idp_metadata);
          run_loop.Quit();
        });

    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
    manager->FetchConfig(GURL(kTestConfigUrl), kTestBrandIconIdealSize,
                         kTestBrandIconMinimumSize, std::move(callback));
    run_loop.Run();

    return {parsed_fetch_status, parsed_idp_metadata};
  }

  std::tuple<FetchStatus, IdpNetworkRequestManager::AccountsResponse>
  SendAccountsRequestWithStoredAccounts(base::ListValue test_accounts) {
    GURL accounts_endpoint(kTestAccountsEndpoint);
    url::Origin idp_origin = url::Origin::Create(accounts_endpoint);

    base::RunLoop run_loop;
    FetchStatus parsed_accounts_response;
    IdpNetworkRequestManager::AccountsResponse parsed_accounts;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus response,
            IdpNetworkRequestManager::AccountsResponse accounts) {
          parsed_accounts_response = response;
          parsed_accounts = std::move(accounts);
          run_loop.Quit();
        });

    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();

    EXPECT_CALL(*test_permission_delegate_, GetAccounts(_))
        .WillOnce(Return(test_accounts.Clone()));
    manager->SendAccountsRequest(idp_origin, GURL(), std::move(callback));
    run_loop.Run();

    return {parsed_accounts_response, parsed_accounts};
  }

  std::tuple<FetchStatus, IdpNetworkRequestManager::AccountsResponse>
  SendAccountsRequestAndWaitForResponse(
      const std::string& test_accounts,
      net::HttpStatusCode response_code = net::HTTP_OK,
      const std::string& mime_type = "application/json") {
    GURL accounts_endpoint(kTestAccountsEndpoint);
    AddResponse(accounts_endpoint, response_code, mime_type, test_accounts);

    base::RunLoop run_loop;
    FetchStatus parsed_accounts_response;
    IdpNetworkRequestManager::AccountsResponse parsed_accounts;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus response,
            IdpNetworkRequestManager::AccountsResponse accounts) {
          parsed_accounts_response = response;
          parsed_accounts = std::move(accounts);
          run_loop.Quit();
        });

    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
    manager->SendAccountsRequest(url::Origin::Create(accounts_endpoint),
                                 accounts_endpoint, std::move(callback));
    run_loop.Run();

    return {parsed_accounts_response, parsed_accounts};
  }

  IdpNetworkRequestManager::RecordErrorMetricsCallback
  CreateErrorMetricsCallback(base::RunLoop& run_loop) {
    return base::BindLambdaForTesting(
        [&](TokenResponseType token_response_type,
            std::optional<ErrorDialogType> error_dialog_type,
            std::optional<ErrorUrlType> error_url_type) {
          token_response_type_ = token_response_type;
          error_dialog_type_ = error_dialog_type;
          error_url_type_ = error_url_type;
          run_loop.Quit();
        });
  }

  std::tuple<FetchStatus, TokenResult> SendTokenRequestAndWaitForResponse(
      const char* account,
      const char* request,
      net::HttpStatusCode http_status = net::HTTP_OK,
      const std::string& mime_type = "application/json",
      const char* response = R"({"token": "token"})",
      bool idp_blindness = false,
      const char* token_endpoint_str = kTestTokenEndpoint,
      bool cors_error = false) {
    GURL token_endpoint{token_endpoint_str};
    AddResponse(token_endpoint, http_status, mime_type, response, cors_error);

    FetchStatus fetch_status;
    TokenResult token_result;
    base::RunLoop run_loop;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus status, TokenResult&& result) {
          fetch_status = status;
          token_result = std::move(result);
          run_loop.Quit();
        });

    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
    manager->SendTokenRequest(token_endpoint, account, request, idp_blindness,
                              std::move(callback), base::DoNothing(),
                              base::DoNothing(),
                              CreateErrorMetricsCallback(run_loop));
    run_loop.Run();
    return {std::move(fetch_status), std::move(token_result)};
  }

  IdpClientMetadata SendClientMetadataRequestAndWaitForResponse(
      const char* client_id,
      const std::string& response = R"({})",
      const char* top_level_origin = nullptr) {
    GURL client_id_endpoint(kTestClientMetadataEndpoint);
    std::string url_string =
        client_id_endpoint.spec() + "?client_id=" + client_id;
    if (top_level_origin) {
      url_string +=
          std::string("&top_frame_origin=") +
          base::EscapeQueryParamValue(top_level_origin, /*use_plus=*/false);
    }
    AddResponse(GURL(url_string), net::HTTP_OK, "application/json", response);

    IdpClientMetadata data;
    base::RunLoop run_loop;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus status, IdpClientMetadata metadata) {
          data = metadata;
          run_loop.Quit();
        });

    std::unique_ptr<IdpNetworkRequestManager> manager =
        CreateTestManager(top_level_origin);
    manager->FetchClientMetadata(
        client_id_endpoint, client_id, kTestBrandIconIdealSize,
        kTestBrandIconMinimumSize, std::move(callback));
    run_loop.Run();
    return data;
  }

  network::TestURLLoaderFactory& test_url_loader_factory() {
    return test_url_loader_factory_;
  }

  base::HistogramTester* histogram_tester() { return &histogram_tester_; }
  TokenResponseType token_response_type() { return token_response_type_; }
  std::optional<ErrorDialogType> error_dialog_type() {
    return error_dialog_type_;
  }
  std::optional<ErrorUrlType> error_url_type() { return error_url_type_; }

 private:
  network::TestURLLoaderFactory test_url_loader_factory_;
  data_decoder::test::InProcessDataDecoder in_process_data_decoder;
  base::HistogramTester histogram_tester_;
  TokenResponseType token_response_type_;
  std::optional<ErrorDialogType> error_dialog_type_;
  std::optional<ErrorUrlType> error_url_type_;
  std::unique_ptr<NiceMock<MockPermissionDelegate>> test_permission_delegate_;
};

TEST_F(IdpNetworkRequestManagerTest, ParseAccountEmpty) {
  const auto* test_empty_account_json = R"({
  "accounts" : []
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_empty_account_json);

  EXPECT_EQ(ParseStatus::kEmptyListError, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_TRUE(accounts.accounts.empty());

  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.Status.AccountsResponseInvalidReason",
      AccountsResponseInvalidReason::kAccountListIsEmpty, 1);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountSingle) {
  const auto* test_single_account_json = kSingleAccountEndpointValidJson;

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_single_account_json);

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ(1UL, accounts.accounts.size());
  EXPECT_EQ("1234", accounts.accounts[0]->id);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountMultiple) {
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "given_name": "Ken",
      "picture": "https://idp.test/profile/1"
    },
    {
      "id" : "5678",
      "email": "sam@idp.test",
      "name": "Sam G. Test",
      "given_name": "Sam",
      "picture": "https://idp.test/profile/2"
    }
  ]
  })";
  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ(2UL, accounts.accounts.size());
  EXPECT_EQ("1234", accounts.accounts[0]->id);
  EXPECT_EQ("5678", accounts.accounts[1]->id);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountOptionalFields) {
  // given_name and picture fields are optional
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example"
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ("1234", accounts.accounts[0]->id);
}

// Test that parsing accounts fails if two accounts have the same account id.
TEST_F(IdpNetworkRequestManagerTest, ParseAccountDuplicateIds) {
  const auto* accounts_json = R"({
  "accounts" : [
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example"
    },
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example"
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(accounts_json);

  EXPECT_EQ(ParseStatus::kInvalidResponseError, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_TRUE(accounts.accounts.empty());
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.Status.AccountsResponseInvalidReason",
      AccountsResponseInvalidReason::kAccountsShareSameId, 1);

  // Test that JSON is valid with exception of duplicate id.
  std::string accounts_json_different_account_ids =
      ReplaceFirstLineWithKeyFromJson("id", R"("id": "5678",)", accounts_json,
                                      /*replace_all=*/false);

  std::tie(accounts_response, accounts) = SendAccountsRequestAndWaitForResponse(
      accounts_json_different_account_ids);
  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountPictureUrl) {
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "picture": "https://idp.test/profile/1234"
    },
    {
      "id" : "567",
      "email": "sam@idp.test",
      "name": "Sam R. Example",
      "picture": "invalid_url"
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_TRUE(accounts.accounts[0]->picture.is_valid());
  EXPECT_EQ(GURL("https://idp.test/profile/1234"),
            accounts.accounts[0]->picture);
  EXPECT_FALSE(accounts.accounts[1]->picture.is_valid());
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountUnicode) {
  auto TestAccountWithKeyValue = [](const std::string& key,
                                    const std::string& value) {
    static constexpr char kJson[] = R"({
      "accounts" : [
        {
          "id" : "1234",
          "email": "ken@idp.test",
          "%s": "%s"
        }
      ]
    })";
    return base::StringPrintf(kJson, key.c_str(), value.c_str());
  };

  std::array<std::string, 3> test_values{"ascii", "🦖", "مجید"};

  for (auto& test_value : test_values) {
    const auto& accounts_json = TestAccountWithKeyValue("name", test_value);

    FetchStatus accounts_response;
    IdpNetworkRequestManager::AccountsResponse accounts;
    std::tie(accounts_response, accounts) =
        SendAccountsRequestAndWaitForResponse(accounts_json.c_str());

    EXPECT_EQ(1UL, accounts.accounts.size());
    EXPECT_EQ(test_value, accounts.accounts[0]->display_name);
  }
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountInvalid) {
  const auto* test_invalid_account_json = "{}";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_invalid_account_json);

  EXPECT_EQ(ParseStatus::kInvalidResponseError, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_TRUE(accounts.accounts.empty());
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.Status.AccountsResponseInvalidReason",
      AccountsResponseInvalidReason::kNoAccountsKey, 1);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountMalformed) {
  const auto* test_invalid_account_json = "malformed_json";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_invalid_account_json);

  EXPECT_EQ(ParseStatus::kInvalidResponseError, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_TRUE(accounts.accounts.empty());
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.Status.AccountsResponseInvalidReason",
      AccountsResponseInvalidReason::kResponseIsNotJsonOrDict, 1);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountLabelsOldAndNewSyntax) {
  // label_hints should take precedence.
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id": "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "label_hints": ["l1", 42, "l2"],
      "labels": ["x1", 42, "x2"]
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ("1234", accounts.accounts[0]->id);
  // The integer in the second position should be ignored.
  ASSERT_EQ(2u, accounts.accounts[0]->labels.size());
  EXPECT_EQ("l1", accounts.accounts[0]->labels[0]);
  EXPECT_EQ("l2", accounts.accounts[0]->labels[1]);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountLabelHints) {
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id": "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "label_hints": ["l1", 42, "l2"]
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ("1234", accounts.accounts[0]->id);
  // The integer in the second position should be ignored.
  ASSERT_EQ(2u, accounts.accounts[0]->labels.size());
  EXPECT_EQ("l1", accounts.accounts[0]->labels[0]);
  EXPECT_EQ("l2", accounts.accounts[0]->labels[1]);
}

TEST_F(IdpNetworkRequestManagerTest, ComputeWellKnownUrl) {
  EXPECT_EQ("https://localhost:8000/.well-known/web-identity",
            ComputeWellKnownUrl(GURL("https://localhost:8000/test/"),
                                kWellKnownPath));

  EXPECT_EQ("https://google.com/.well-known/web-identity",
            ComputeWellKnownUrl(GURL("https://www.google.com:8000/test/"),
                                kWellKnownPath));

  EXPECT_EQ(std::nullopt, ComputeWellKnownUrl(GURL("https://192.101.0.1/test/"),
                                              kWellKnownPath));
}

TEST_F(IdpNetworkRequestManagerTest, ComputeWebIdentitySubdomainWellKnownUrl) {
  // Localhost: subdomain form is not applicable; helper returns nullopt so the
  // legacy apex URL is used.
  EXPECT_EQ(std::nullopt,
            ComputeWebIdentitySubdomainWellKnownUrl(
                GURL("https://localhost:8000/test/"), kWellKnownPath));

  // Standard registrable-domain provider: subdomain prepended.
  EXPECT_EQ(
      "https://web-identity.well-known.google.com/.well-known/web-identity",
      ComputeWebIdentitySubdomainWellKnownUrl(
          GURL("https://www.google.com:8000/test/"), kWellKnownPath));

  // Provider already on a subdomain still uses eTLD+1 with
  // web-identity.well-known. label.
  EXPECT_EQ(
      "https://web-identity.well-known.example.com/.well-known/web-identity",
      ComputeWebIdentitySubdomainWellKnownUrl(
          GURL("https://idp.example.com/foo"), kWellKnownPath));

  // IP literal: no eTLD+1 -> nullopt.
  EXPECT_EQ(std::nullopt,
            ComputeWebIdentitySubdomainWellKnownUrl(
                GURL("https://192.101.0.1/test/"), kWellKnownPath));
}

TEST_F(IdpNetworkRequestManagerTest, ParseUsername) {
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id": "1234",
      "email": "ken@idp.test",
      "username": "ken"
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ("1234", accounts.accounts[0]->id);
  EXPECT_EQ("ken@idp.test", accounts.accounts[0]->email);
  EXPECT_EQ("ken@idp.test", accounts.accounts[0]->display_identifier);
  EXPECT_EQ("ken", accounts.accounts[0]->display_name);
}

TEST_F(IdpNetworkRequestManagerTest, ParsePhoneNumber) {
  const auto* test_accounts_json = R"({
  "accounts" : [
    {
      "id": "1234",
      "tel": "111-111-1111"
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  ASSERT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ("1234", accounts.accounts[0]->id);
  EXPECT_EQ("", accounts.accounts[0]->email);
  EXPECT_EQ("", accounts.accounts[0]->display_identifier);
  EXPECT_EQ("111-111-1111", accounts.accounts[0]->display_name);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountPotentiallyApprovedSites) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmEmbedderInitiatedLogin);
  // given_name and picture fields are optional
  const auto* test_accounts_json = R"({
  "accounts": [
    {
      "id": "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "potentially_approved_site_hashes": [
        "870f48f3c28efb5dbf46d14881d802a4c34141a36ef9e66d28cec211b1969f7d"
      ]
    }
  ],
  "site_salt": "fc432178f9155c4e24762de5b9505f2e"
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ("fc432178f9155c4e24762de5b9505f2e", accounts.site_salt);
  ASSERT_THAT(
      accounts.accounts[0]->potentially_approved_site_hashes,
      ElementsAre(
          "870f48f3c28efb5dbf46d14881d802a4c34141a36ef9e66d28cec211b1969f7d"));

  const auto& filtered_accounts =
      accounts.PotentialAccountsForSite("example.com");
  EXPECT_EQ(1ul, filtered_accounts.size());
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountSingleLightweightFedcm) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmLightweightMode);

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) = SendAccountsRequestWithStoredAccounts(
      base::ListValue().Append(base::DictValue()
                                   .Set("id", "1234")
                                   .Set("email", "ken@idp.test")
                                   .Set("name", "Ken R. Example")));

  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ(1UL, accounts.accounts.size());

  EXPECT_EQ("1234", accounts.accounts[0]->id);
  EXPECT_EQ("ken@idp.test", accounts.accounts[0]->email);
  EXPECT_EQ("Ken R. Example", accounts.accounts[0]->name);
}

TEST_F(IdpNetworkRequestManagerTest, ParseAccountEmptyLightweightFedcm) {
  base::test::ScopedFeatureList list;
  list.InitAndEnableFeature(features::kFedCmLightweightMode);

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestWithStoredAccounts(base::ListValue());

  EXPECT_EQ(ParseStatus::kEmptyListError, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_EQ(0UL, accounts.accounts.size());
}

TEST_F(IdpNetworkRequestManagerTest, CacheProfilePictures) {
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  url::Origin idp_origin = url::Origin::Create(GURL(kTestIdpUrl));
  GURL picture_url("https://idp.cdn.test/profile/1234");

  manager->CacheAccountPictures(idp_origin, {picture_url});
  EXPECT_EQ(1, test_url_loader_factory().NumPending());
  network::TestURLLoaderFactory::PendingRequest* pending_request =
      test_url_loader_factory().GetPendingRequest(0);
  ASSERT_TRUE(pending_request);
  const network::ResourceRequest& resource_request = pending_request->request;
  EXPECT_FALSE(resource_request.load_flags & net::LOAD_ONLY_FROM_CACHE);
  net::IsolationInfo expected_isolation_info = net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther,
      /*top_frame_origin=*/idp_origin,
      /*frame_origin=*/url::Origin::Create(GURL("https://idp.cdn.test/")),
      net::SiteForCookies(),
      /*nonce=*/std::nullopt,
      net::NetworkIsolationPartition::kFedCmUncredentialedRequests);
  EXPECT_TRUE(expected_isolation_info.IsEqualForTesting(
      resource_request.trusted_params->isolation_info));
}

TEST_F(IdpNetworkRequestManagerTest, DownloadAndDecodeCachedImage) {
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  url::Origin idp_origin = url::Origin::Create(GURL(kTestIdpUrl));
  GURL picture_url("https://idp.cdn.test/profile/1234");

  manager->DownloadAndDecodeCachedImage(
      idp_origin, picture_url, base::DoNothingAs<void(const gfx::Image&)>());

  EXPECT_EQ(1, test_url_loader_factory().NumPending());
  network::TestURLLoaderFactory::PendingRequest* pending_request =
      test_url_loader_factory().GetPendingRequest(0);
  ASSERT_TRUE(pending_request);
  const network::ResourceRequest& resource_request = pending_request->request;
  EXPECT_TRUE(resource_request.load_flags & net::LOAD_ONLY_FROM_CACHE);
  net::IsolationInfo expected_isolation_info = net::IsolationInfo::Create(
      net::IsolationInfo::RequestType::kOther,
      /*top_frame_origin=*/idp_origin,
      /*frame_origin=*/url::Origin::Create(GURL("https://idp.cdn.test/")),
      net::SiteForCookies(),
      /*nonce=*/std::nullopt,
      net::NetworkIsolationPartition::kFedCmUncredentialedRequests);
  EXPECT_TRUE(expected_isolation_info.IsEqualForTesting(
      resource_request.trusted_params->isolation_info));
}

// Test that the request destination is set to kWebIdentity.
TEST_F(IdpNetworkRequestManagerTest, FetchWellKnownRequestDestination) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["https://idp.test/fedcm.json"]
  })");
  EXPECT_TRUE(called);
}

TEST_F(IdpNetworkRequestManagerTest, FetchConfigRequestDestination) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  SendConfigRequestAndWaitForResponse(R"({})");
  EXPECT_TRUE(called);
}

TEST_F(IdpNetworkRequestManagerTest, AccountsRequestDestination) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  SendAccountsRequestAndWaitForResponse(kSingleAccountEndpointValidJson);
  EXPECT_TRUE(called);
}

TEST_F(IdpNetworkRequestManagerTest, TokenRequestDestination) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  SendTokenRequestAndWaitForResponse("account", "request");
  EXPECT_TRUE(called);
}

TEST_F(IdpNetworkRequestManagerTest, ClientMetadataRequestDestination) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  SendClientMetadataRequestAndWaitForResponse("xxx");
  EXPECT_TRUE(called);
}

TEST_F(IdpNetworkRequestManagerTest, ImageRequestDestination) {
  base::RunLoop run_loop;
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
        run_loop.Quit();
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->DownloadAndDecodeImage(GURL("https://idp.test/image.png"),
                                  base::DoNothing());
  run_loop.Run();
  EXPECT_TRUE(called);
}

TEST_F(IdpNetworkRequestManagerTest, CachedImageRequestDestination) {
  base::RunLoop run_loop;
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(network::mojom::RequestDestination::kWebIdentity,
                  request.destination);
        run_loop.Quit();
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  url::Origin idp_origin = url::Origin::Create(GURL(kTestIdpUrl));
  GURL picture_url("https://idp.cdn.test/profile/1234");
  manager->DownloadAndDecodeCachedImage(
      idp_origin, picture_url, base::DoNothingAs<void(const gfx::Image&)>());
  run_loop.Run();
  EXPECT_TRUE(called);
}
// Test that IdpNetworkRequestManager::FetchWellKnown() fails when the
// identity provider domain is empty.
TEST_F(IdpNetworkRequestManagerTest, FetchWellKnownIllegalDomainFails) {
  GURL illegal_idp_url("https://192.101.0.1/test/");

  network::TestURLLoaderFactory test_url_loader_factory;
  std::unique_ptr<MockPermissionDelegate> test_permission_delegate_ =
      std::make_unique<MockPermissionDelegate>();

  auto network_manager = std::make_unique<IdpNetworkRequestManager>(
      url::Origin::Create(GURL(kTestRpUrl)),
      /*rp_embedding_origin=*/url::Origin(),
      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
          &test_url_loader_factory),
      test_permission_delegate_.get(),
      network::mojom::ClientSecurityState::New(), FrameTreeNodeId(),
      WeakDocumentPtr());

  base::RunLoop run_loop;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus fetch_status,
          const IdpNetworkRequestManager::WellKnown& well_known) {
        EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status);
        // We receive OK here because ComputeWellKnownUrl() fails.
        EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
        run_loop.Quit();
      });
  network_manager->FetchWellKnown(illegal_idp_url, std::move(callback));
  run_loop.Run();

  // Well-known download should not have been attempted.
  EXPECT_EQ(0, test_url_loader_factory.NumPending());
}

TEST_F(IdpNetworkRequestManagerTest, ParseWellKnown) {
  FetchStatus fetch_status;
  IdpNetworkRequestManager::WellKnown well_known;

  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["https://idp.test/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);

  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["https://idp.test/path/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")},
            well_known.provider_urls);

  // Value not a list
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": "https://idp.test/fedcm.json"
  })");
  EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);

  // Toplevel not a dictionary
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"(
  ["https://idp.test/fedcm.json"]
  )");
  EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);

  // Incorrect key
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "providers": ["https://idp.test/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);

  // Array entry not a string
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": [1]
  })");
  EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);

  // Relative URLs
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);

  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/.well-known/fedcm.json")},
            well_known.provider_urls);

  // Empty well known list
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": []
  })");
  EXPECT_EQ(ParseStatus::kEmptyListError, fetch_status.parse_status);

  // well-known file having valid account endpoints,
  // login url and provider_urls
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "accounts_endpoint": "/accounts.php",
  "login_url": "/login",
  "provider_urls": ["https://idp.test/path/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(GURL("https://idp.test/accounts.php"), well_known.accounts);
  EXPECT_EQ(GURL("https://idp.test/login"), well_known.login_url);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")},
            well_known.provider_urls);

  // well-known file having empty provider_urls and
  // valid account endpoints and login url
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "accounts_endpoint": "/accounts.php",
  "login_url": "/login"
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(GURL("https://idp.test/accounts.php"), well_known.accounts);
  EXPECT_EQ(GURL("https://idp.test/login"), well_known.login_url);
  EXPECT_TRUE(well_known.provider_urls.empty());

  // well-known file having empty account endpoints and valid
  // login url and provider_urls
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "accounts_endpoint": "",
  "login_url": "/login",
  "provider_urls": ["https://idp.test/path/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_TRUE(well_known.accounts.is_empty());
  EXPECT_EQ(GURL("https://idp.test/login"), well_known.login_url);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")},
            well_known.provider_urls);

  // well-known file having empty login url and valid
  // account endpoints and provider_urls
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "accounts_endpoint": "/accounts.php",
  "login_url": "",
  "provider_urls": ["https://idp.test/path/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(GURL("https://idp.test/accounts.php"), well_known.accounts);
  EXPECT_TRUE(well_known.login_url.is_empty());
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")},
            well_known.provider_urls);

  // well-known file having valid provider urls with empty
  // login url and account endpoints
  std::tie(fetch_status, well_known) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "accounts_endpoint": "",
  "login_url": "",
  "provider_urls": ["https://idp.test/path/fedcm.json"]
  })");
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_TRUE(well_known.accounts.is_empty());
  EXPECT_TRUE(well_known.login_url.is_empty());
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/path/fedcm.json")},
            well_known.provider_urls);
}

// Test that the "alpha" value in the "branding" JSON is ignored.
TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingRemoveAlpha) {
  const char test_json[] = R"({
  "branding" : {
    "background_color": "#20202020"
  }
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(SkColorSetARGB(0xff, 0x20, 0x20, 0x20),
            idp_metadata.brand_background_color);
}

TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingInvalidColor) {
  const char test_json[] = R"({
  "branding" : {
    "background_color": "fake_color"
  }
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(std::nullopt, idp_metadata.brand_background_color);
}

TEST_F(IdpNetworkRequestManagerTest,
       ParseConfigWithInsufficientContrastTextColor) {
  const char test_json[] = R"({
  "branding" : {
    "background_color": "#000000",
    "color": "#010101"
  }
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(SkColorSetRGB(0, 0, 0), idp_metadata.brand_background_color);
  EXPECT_EQ(SkColorSetRGB(1, 1, 1), idp_metadata.brand_text_color);
}

TEST_F(IdpNetworkRequestManagerTest,
       ParseConfigBrandingWithTextColorAndNoBackgroundColor) {
  const char test_json[] = R"({
  "branding" : {
    "color": "blue"
  }
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(std::nullopt, idp_metadata.brand_background_color);
  EXPECT_EQ(SkColorSetRGB(0, 0, 255), idp_metadata.brand_text_color);
}

TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingSelectBestSize) {
  const char test_json[] = R"({
  "branding" : {
    "icons": [
      {
        "url": "https://example.com/10.png",
        "size": 10
      },
      {
        "url": "https://example.com/16.png",
        "size": 16
      },
      {
        "url": "https://example.com/31.png",
        "size": 31
      },
      {
        "url": "https://example.com/32.png",
        "size": 32
      },
      {
        "url": "https://example.com/33.png",
        "size": 33
      }
    ]
  }
  })";

  ASSERT_EQ(32, kTestBrandIconIdealSize);

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ("https://example.com/32.png", idp_metadata.brand_icon_url.spec());
}

// Test that the icon is rejected if there is an explicit brand icon size in the
// config and it is smaller than the `idp_brand_icon_minimum_size` parameter
// passed to IdpNetworkRequestManager::FetchConfig().
TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingMinSize) {
  ASSERT_EQ(16, kTestBrandIconMinimumSize);

  {
    const char test_json[] = R"({
    "branding" : {
      "icons": [
        {
          "url": "https://example.com/15.png",
          "size": 15
        }
      ]
    }
    })";

    FetchStatus fetch_status;
    IdentityProviderMetadata idp_metadata;
    std::tie(fetch_status, idp_metadata) =
        SendConfigRequestAndWaitForResponse(test_json);

    EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
    EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
    EXPECT_EQ(GURL(), idp_metadata.brand_icon_url);
  }

  {
    const char test_json[] = R"({
    "branding" : {
      "icons": [
        {
          "url": "https://example.com/16.png",
          "size": 16
        }
      ]
    }
    })";

    FetchStatus fetch_status;
    IdentityProviderMetadata idp_metadata;
    std::tie(fetch_status, idp_metadata) =
        SendConfigRequestAndWaitForResponse(test_json);

    EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
    EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
    EXPECT_EQ("https://example.com/16.png", idp_metadata.brand_icon_url.spec());
  }
}

// Tests various scenarios on resolving branding icon's url for given config
// url.
TEST_F(IdpNetworkRequestManagerTest, ParseConfigBrandingIconReltivePath) {
  // branding icon url domain matches with config url domain
  {
    const char test_json[] = R"({
    "branding" : {
      "icons": [
        {
          "url": "/16.png",
          "size": 16
        }
      ]
    }
    })";

    FetchStatus fetch_status;
    IdentityProviderMetadata idp_metadata;
    std::tie(fetch_status, idp_metadata) =
        SendConfigRequestAndWaitForResponse(test_json);

    EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
    EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
    EXPECT_EQ("https://idp.test/16.png", idp_metadata.brand_icon_url);
  }

  // branding icon url domain doesnt match with config url domain
  {
    const char test_json[] = R"({
    "branding" : {
      "icons": [
        {
          "url": "https://example.com/16.png",
          "size": 16
        }
      ]
    }
    })";

    FetchStatus fetch_status;
    IdentityProviderMetadata idp_metadata;
    std::tie(fetch_status, idp_metadata) =
        SendConfigRequestAndWaitForResponse(test_json);

    EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
    EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
    EXPECT_EQ("https://example.com/16.png", idp_metadata.brand_icon_url);
  }
}

TEST_F(IdpNetworkRequestManagerTest, ParseConfigSupportsOtherAccountNewSyntax) {
  const char test_json[] = R"({
  "supports_use_other_account": true
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse(
      test_json, net::HTTP_OK, "application/json",
      blink::mojom::RpMode::kPassive);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(true, idp_metadata.supports_add_account);
}

TEST_F(IdpNetworkRequestManagerTest,
       ParseConfigSupportsOtherAccountDifferentMode) {
  const char test_json[] = R"({
  "modes": {
    "active": {
      "supports_use_other_account": true
    }
  }
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse(
      test_json, net::HTTP_OK, "application/json",
      blink::mojom::RpMode::kPassive);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(false, idp_metadata.supports_add_account);
}

TEST_F(IdpNetworkRequestManagerTest, ParseConfigSupportsOtherAccountBothModes) {
  const char test_json[] = R"({
  "modes": {
    "active": {
      "supports_use_other_account": false
    },
    "passive": {
      "supports_use_other_account": true
    }
  }
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse(
      test_json, net::HTTP_OK, "application/json",
      blink::mojom::RpMode::kActive);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(false, idp_metadata.supports_add_account);
}

TEST_F(IdpNetworkRequestManagerTest,
       ParseConfigSupportsUseOtherAccountMissing) {
  const char test_json[] = R"({
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(false, idp_metadata.supports_add_account);
}

TEST_F(IdpNetworkRequestManagerTest, ParseConfigRequestedLabel) {
  const char test_json[] = R"({
    "account_label": "l1"
  })";

  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(test_json);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ("l1", idp_metadata.requested_label);
}

// Tests that we send the correct origin for account requests.
TEST_F(IdpNetworkRequestManagerTest, AccountRequestOrigin) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url);
        EXPECT_EQ(request.request_body, nullptr);
        EXPECT_FALSE(request.referrer.is_valid());
        EXPECT_FALSE(
            request.headers.HasHeader(net::HttpRequestHeaders::kOrigin));
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  const char test_accounts_json[] = R"({
  "accounts" : [
    {
      "id" : "1234",
      "email": "ken@idp.test",
      "name": "Ken R. Example"
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  ASSERT_TRUE(called);
  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
}

// Verifies that we correctly check the signed-in status.
TEST_F(IdpNetworkRequestManagerTest, AccountSignedInStatus) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url);
        EXPECT_EQ(request.request_body, nullptr);
        EXPECT_FALSE(request.referrer.is_valid());
        EXPECT_FALSE(
            request.headers.HasHeader(net::HttpRequestHeaders::kOrigin));
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  const char test_accounts_json[] = R"({
  "accounts" : [
    {
      "id" : "1",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "approved_clients": ["xxx"]
    },
    {
      "id" : "2",
      "email": "jim@idp.test",
      "name": "Jim R. Example",
      "approved_clients": []
    },
    {
      "id" : "3",
      "email": "rashida@idp.test",
      "name": "Rashida R. Example",
      "approved_clients": ["yyy"]
    },
    {
      "id" : "4",
      "email": "wei@idp.test",
      "name": "Wei R. Example"
    },
    {
      "id" : "5",
      "email": "hans@idp.test",
      "name": "Hans R. Example",
      "approved_clients": ["xxx", "yyy"]
    }
  ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_TRUE(called);
  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  ASSERT_EQ(5ul, accounts.accounts.size());
  ASSERT_TRUE(accounts.accounts[0]->approved_clients.has_value());
  EXPECT_EQ(std::vector<std::string>{"xxx"},
            *accounts.accounts[0]->approved_clients);
  ASSERT_TRUE(accounts.accounts[1]->approved_clients.has_value());
  EXPECT_TRUE((*accounts.accounts[1]->approved_clients).empty());
  ASSERT_TRUE(accounts.accounts[2]->approved_clients.has_value());
  EXPECT_EQ(std::vector<std::string>{"yyy"},
            *accounts.accounts[2]->approved_clients);
  EXPECT_FALSE(accounts.accounts[3]->approved_clients.has_value());
  ASSERT_TRUE(accounts.accounts[4]->approved_clients.has_value());
  EXPECT_EQ((std::vector<std::string>{"xxx", "yyy"}),
            *accounts.accounts[4]->approved_clients);
}

// Tests the token request implementation.
TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequest) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(GURL(kTestTokenEndpoint), request.url);
        EXPECT_FALSE(request.referrer.is_valid());
        EXPECT_EQ(url::Origin::Create(GURL(kTestRpUrl)),
                  GetOriginHeader(request));

        // Check that the request body is correct (should be "request")
        ASSERT_NE(request.request_body, nullptr);
        ASSERT_EQ(1ul, request.request_body->elements()->size());
        const network::DataElement& elem =
            request.request_body->elements()->at(0);
        ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type());
        const network::DataElementBytes& byte_elem =
            elem.As<network::DataElementBytes>();
        EXPECT_EQ("request", byte_elem.AsStringPiece());
      });
  test_url_loader_factory().SetInterceptor(interceptor);
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) =
      SendTokenRequestAndWaitForResponse("account", "request");
  ASSERT_TRUE(called);
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  ASSERT_EQ("token", token_result.token);
  ASSERT_EQ(false, fetch_status.cors_error);
}

// Tests the ID assertion request implementation when CORS is enforced on the
// endpoint.
TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestWithCORS) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(GURL(kTestTokenEndpoint), request.url);
        EXPECT_FALSE(request.referrer.is_valid());
        url::Origin rpOrigin = url::Origin::Create(GURL(kTestRpUrl));
        EXPECT_EQ(GetOriginHeader(request), rpOrigin);

        // Check that the request body is correct (should be "request")
        ASSERT_NE(request.request_body, nullptr);
        ASSERT_EQ(1ul, request.request_body->elements()->size());
        const network::DataElement& elem =
            request.request_body->elements()->at(0);
        ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type());
        const network::DataElementBytes& byte_elem =
            elem.As<network::DataElementBytes>();
        EXPECT_EQ("request", byte_elem.AsStringPiece());
        ASSERT_EQ(request.mode, network::mojom::RequestMode::kCors);
        ASSERT_EQ(request.request_initiator, rpOrigin);
      });
  test_url_loader_factory().SetInterceptor(interceptor);
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) =
      SendTokenRequestAndWaitForResponse("account", "request");
  ASSERT_TRUE(called);
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  ASSERT_EQ("token", token_result.token);
  ASSERT_EQ(false, fetch_status.cors_error);
}

// Tests the ID assertion request implementation when CORS is enforced on the
// endpoint and server responds with CORS Error
TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestWithCORSError) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_FORBIDDEN, "application/json",
      R"({"token": ""})", false, kTestTokenEndpoint, true);

  EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status);
  EXPECT_EQ(net::ERR_FAILED, fetch_status.response_code);
  ASSERT_FALSE(token_result.token.has_value());
  ASSERT_EQ(true, fetch_status.cors_error);
}

// Test that flexible token formats (JSON objects, primitives) are
// properly handled
TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestJsonObjectToken) {
  FetchStatus fetch_status;
  TokenResult token_result;

  // Test JSON object token
  const char* json_object_response = R"({
    "token": {
      "access_token": "abc123",
      "token_type": "Bearer",
      "expires_in": 3600
    }
  })";

  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json",
      json_object_response);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_TRUE(token_result.token.has_value() &&
              (token_result.token->is_dict()));
  const base::DictValue& value = token_result.token->GetDict();

  const base::Value* access_token = value.Find("access_token");
  ASSERT_TRUE(access_token && access_token->is_string());
  EXPECT_EQ("abc123", access_token->GetString());

  const base::Value* token_type = value.Find("token_type");
  ASSERT_TRUE(token_type && token_type->is_string());
  EXPECT_EQ("Bearer", token_type->GetString());

  const base::Value* expires_in = value.Find("expires_in");
  ASSERT_TRUE(expires_in && expires_in->is_int());
  EXPECT_EQ(3600, expires_in->GetInt());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestNumberToken) {
  FetchStatus fetch_status;
  TokenResult token_result;

  const char* number_response = R"({"token": 12345})";

  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", number_response);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_TRUE(
      token_result.token.has_value() &&
      (token_result.token->is_int() && token_result.token->GetInt() == 12345));
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestBooleanToken) {
  FetchStatus fetch_status;
  TokenResult token_result;

  const char* boolean_response = R"({"token": true})";

  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", boolean_response);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_TRUE(token_result.token.has_value() && token_result.token->is_bool() &&
              token_result.token->GetBool() == true);
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestNullToken) {
  FetchStatus fetch_status;
  TokenResult token_result;

  const char* null_response = R"({"token": null})";

  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", null_response);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_TRUE(token_result.token.has_value() && token_result.token->is_none());
}

// Tests the client metadata implementation.
TEST_F(IdpNetworkRequestManagerTest, ClientMetadata) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        std::string url_string =
            std::string(kTestClientMetadataEndpoint) + "?client_id=xxx";
        EXPECT_EQ(GURL(url_string), request.url);
        EXPECT_EQ(request.request_body, nullptr);
        EXPECT_FALSE(request.referrer.is_valid());
        EXPECT_EQ(url::Origin::Create(GURL(kTestRpUrl)),
                  GetOriginHeader(request));
      });
  test_url_loader_factory().SetInterceptor(interceptor);
  IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse("xxx");
  ASSERT_TRUE(called);
  ASSERT_EQ(GURL(), data.privacy_policy_url);
  ASSERT_EQ(GURL(), data.terms_of_service_url);
  ASSERT_EQ(GURL(), data.brand_icon_url);
  EXPECT_FALSE(data.client_is_third_party_to_top_frame_origin);
}

// Tests client_is_third_party_to_top_frame_origin: true
TEST_F(IdpNetworkRequestManagerTest, ClientIsThirdPartyToTopFrameOrigin) {
  IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
      "clientid", R"({"client_is_third_party_to_top_frame_origin": true})",
      "https://toplevel.example");
  EXPECT_TRUE(data.client_is_third_party_to_top_frame_origin);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.CrossSiteIframeType",
      webid::CrossSiteIframeType::kIframeIsThirdParty, 1);
}

// Tests client_is_third_party_to_top_frame_origin: false
TEST_F(IdpNetworkRequestManagerTest, ClientIsSamePartyToTopFrameOrigin) {
  IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
      "clientid", R"({"client_is_third_party_to_top_frame_origin": false})",
      "https://toplevel.example");
  EXPECT_FALSE(data.client_is_third_party_to_top_frame_origin);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.CrossSiteIframeType",
      webid::CrossSiteIframeType::kIframeIsSameParty, 1);
}

// Tests client_is_third_party_to_top_frame_origin is not sent
TEST_F(IdpNetworkRequestManagerTest,
       ClientIsThirdPartyToTopFrameOriginNotSent) {
  IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
      "clientid", R"({})", "https://toplevel.example");
  EXPECT_FALSE(data.client_is_third_party_to_top_frame_origin);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.CrossSiteIframeType",
      webid::CrossSiteIframeType::kNoValueReceived, 1);
}

// Tests that we don't record the histogram for
// client_is_third_party_to_top_frame_origin if there is no cross-site iframe.
TEST_F(IdpNetworkRequestManagerTest,
       ClientIsThirdPartyToTopFrameOriginNoIframe) {
  // We pass no top_level_origin here.
  IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
      "clientid", R"({"client_is_third_party_to_top_frame_origin": true})");
  EXPECT_FALSE(data.client_is_third_party_to_top_frame_origin);
  histogram_tester()->ExpectTotalCount("Blink.FedCm.CrossSiteIframeType", 0);
}

// Tests that we correctly records metrics regarding approved_clients.
TEST_F(IdpNetworkRequestManagerTest, RecordApprovedClientsMetrics) {
  base::HistogramTester histogram_tester;
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(GURL(kTestAccountsEndpoint), request.url);
        EXPECT_EQ(request.request_body, nullptr);
        EXPECT_FALSE(request.referrer.is_valid());
        EXPECT_FALSE(
            request.headers.HasHeader(net::HttpRequestHeaders::kOrigin));
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  const char test_accounts_json[] = R"({
  "accounts" : [
    {
      "id" : "1",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "approved_clients": []
    },
    {
      "id" : "2",
      "email": "jim@idp.test",
      "name": "Jim R. Example",
      "approved_clients": ["xxx"]
    },
    {
      "id" : "3",
      "email": "rashida@idp.test",
      "name": "Rashida R. Example",
      "approved_clients": ["xxx", "yyy"]
    },
    {
      "id" : "4",
      "email": "wei@idp.test",
      "name": "Wei R. Example"
    }
   ]
  })";

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) =
      SendAccountsRequestAndWaitForResponse(test_accounts_json);

  EXPECT_TRUE(called);
  EXPECT_EQ(ParseStatus::kSuccess, accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  ASSERT_EQ(4ul, accounts.accounts.size());

  histogram_tester.ExpectTotalCount("Blink.FedCm.ApprovedClientsExistence", 4);
  histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsExistence", 1,
                                     3);
  histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsExistence", 0,
                                     1);

  histogram_tester.ExpectTotalCount("Blink.FedCm.ApprovedClientsSize", 3);
  histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsSize", 0, 1);
  histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsSize", 1, 1);
  histogram_tester.ExpectBucketCount("Blink.FedCm.ApprovedClientsSize", 2, 1);
}

// Test that the callback is not called after IdpNetworkRequestManager is
// destroyed.
TEST_F(IdpNetworkRequestManagerTest, DontCallCallbackAfterManagerDeletion) {
  const char test_accounts_json[] = R"({
  "accounts" : [
    {
      "id" : "1",
      "email": "ken@idp.test",
      "name": "Ken R. Example",
      "approved_clients": []
    }
   ]
  })";

  GURL accounts_endpoint(kTestAccountsEndpoint);
  AddResponse(accounts_endpoint, net::HTTP_OK, "application/json",
              test_accounts_json);

  bool callback_called = false;
  auto callback = base::BindLambdaForTesting(
      [&callback_called](FetchStatus response,
                         IdpNetworkRequestManager::AccountsResponse accounts) {
        callback_called = true;
      });

  {
    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
    manager->SendAccountsRequest(url::Origin::Create(accounts_endpoint),
                                 accounts_endpoint, std::move(callback));
    // Destroy `manager`.
  }
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(callback_called);
}

TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingWellKnown) {
  FetchStatus fetch_status;
  IdpNetworkRequestManager::WellKnown wellknown;
  std::tie(fetch_status, wellknown) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["https://idp.test/fedcm.json"]
  })",
                                             net::HTTP_REQUEST_TIMEOUT);
  EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_REQUEST_TIMEOUT, fetch_status.response_code);
  EXPECT_EQ(std::set<GURL>{}, wellknown.provider_urls);
}

// Tests for the FedCmWebIdentitySubdomain flag covering FetchWellKnown()
// (exercised via SendWellKnownRequestAndWaitForResponse) across the various
// subdomain-discovery scenarios.
class IdpNetworkRequestManagerWebIdentitySubdomainTest
    : public IdpNetworkRequestManagerTest {
 public:
  IdpNetworkRequestManagerWebIdentitySubdomainTest() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kFedCmWebIdentitySubdomain);
  }

 protected:
  // The subdomain (preferred) and apex (fallback) URLs that the implementation
  // computes for kTestIdpUrl ("https://idp.test"). The
  // "web-identity.well-known" label is the spec-defined prefix; not arbitrary.
  static constexpr char kSubdomainWellKnownUrl[] =
      "https://web-identity.well-known.idp.test/.well-known/web-identity";

  // Wires a response for the subdomain URL, then runs FetchWellKnown() through
  // the existing kTestIdpUrl helper. If `apex_data` is non-null, an apex
  // response is also wired so the fallback can complete.
  std::tuple<FetchStatus, IdpNetworkRequestManager::WellKnown>
  FetchWithSubdomainResponse(const std::string& subdomain_data,
                             net::HttpStatusCode subdomain_status,
                             const char* apex_data = nullptr,
                             net::HttpStatusCode apex_status = net::HTTP_OK) {
    AddResponse(GURL(kSubdomainWellKnownUrl), subdomain_status,
                "application/json", subdomain_data);
    if (apex_data) {
      AddResponse(GURL(kTestWellKnownUrl), apex_status, "application/json",
                  apex_data);
    }

    base::RunLoop run_loop;
    FetchStatus fetch_status;
    IdpNetworkRequestManager::WellKnown well_known;
    auto callback = base::BindLambdaForTesting(
        [&](FetchStatus status,
            const IdpNetworkRequestManager::WellKnown& result) {
          fetch_status = status;
          well_known = result;
          run_loop.Quit();
        });
    std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
    manager->FetchWellKnown(GURL(kTestIdpUrl), std::move(callback));
    run_loop.Run();
    return {fetch_status, well_known};
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Subdomain fetch returns a single provider_urls entry: discovery succeeds
// without falling back to apex. An apex response is also wired with a
// *different* payload to prove the subdomain payload (not the apex one) reaches
// the caller.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainSucceedsWithSingleProvider) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      R"({"provider_urls": ["https://idp.test/fedcm.json"]})", net::HTTP_OK,
      R"({"provider_urls": ["https://idp.test/apex-only.json"]})",
      net::HTTP_OK);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls)
      << "subdomain payload must take precedence over apex payload";
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", true, 1);
}

// Subdomain fetch returns more than one provider_urls entry: implementation
// rejects it and falls back to the apex well-known.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainRejectedWhenMultipleProviders) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      R"({"provider_urls": ["https://idp.test/a.json",
                            "https://idp.test/b.json"]})",
      net::HTTP_OK, R"({"provider_urls": ["https://idp.test/fedcm.json"]})",
      net::HTTP_OK);

  // The apex result is what reaches the caller.
  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", false, 1);
}

// Subdomain fetch returns 404: implementation falls back to apex.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainHttpErrorFallsBackToApex) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      /*subdomain_data=*/std::string(), net::HTTP_NOT_FOUND,
      R"({"provider_urls": ["https://idp.test/fedcm.json"]})", net::HTTP_OK);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", false, 1);
}

// Subdomain fetch returns malformed JSON: implementation falls back to apex.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainMalformedJsonFallsBackToApex) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      "not valid json", net::HTTP_OK,
      R"({"provider_urls": ["https://idp.test/fedcm.json"]})", net::HTTP_OK);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", false, 1);
}

// Subdomain fetch fails AND apex fetch fails: caller observes the apex error.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainAndApexBothFail) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      /*subdomain_data=*/std::string(), net::HTTP_NOT_FOUND,
      /*apex_data=*/"", net::HTTP_REQUEST_TIMEOUT);

  EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_REQUEST_TIMEOUT, fetch_status.response_code);
  EXPECT_TRUE(well_known.provider_urls.empty());
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", false, 1);
}

// Subdomain fetch returns valid JSON but with an empty provider_urls list:
// implementation rejects it (the subdomain contract requires exactly one entry)
// and falls back to the apex well-known.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainEmptyProviderListFallsBackToApex) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      R"({"provider_urls": []})", net::HTTP_OK,
      R"({"provider_urls": ["https://idp.test/fedcm.json"]})", net::HTTP_OK);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", false, 1);
}

// Subdomain fetch returns parseable JSON missing the provider_urls key:
// implementation rejects it and falls back to the apex well-known.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       SubdomainMissingProviderUrlsFallsBackToApex) {
  auto [fetch_status, well_known] = FetchWithSubdomainResponse(
      R"({"unrelated_key": "ignored"})", net::HTTP_OK,
      R"({"provider_urls": ["https://idp.test/fedcm.json"]})", net::HTTP_OK);

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);
  histogram_tester()->ExpectUniqueSample(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", false, 1);
}

// When the flag is enabled but the provider URL has no eTLD+1 (IP literal),
// no subdomain URL can be computed, so the implementation goes straight to
// apex without recording the discovery histogram.
TEST_F(IdpNetworkRequestManagerWebIdentitySubdomainTest,
       NoSubdomainAttemptedForIpLiteralProvider) {
  GURL illegal_idp_url("https://192.101.0.1/test/");

  base::RunLoop run_loop;
  FetchStatus fetch_status;
  IdpNetworkRequestManager::WellKnown well_known;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus status,
          const IdpNetworkRequestManager::WellKnown& result) {
        fetch_status = status;
        well_known = result;
        run_loop.Quit();
      });
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->FetchWellKnown(illegal_idp_url, std::move(callback));
  run_loop.Run();

  // ComputeWellKnownUrl() also rejects this URL, so the manager short-circuits
  // to a synthetic kHttpNotFoundError without issuing any network request.
  EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status);
  EXPECT_EQ(0, test_url_loader_factory().NumPending())
      << "no subdomain (or apex) request must be issued for an IP literal IdP";
  histogram_tester()->ExpectTotalCount(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", 0);
}

// With the flag *disabled* the manager must not attempt the subdomain URL
// or record the discovery histogram, even if the provider has a valid eTLD+1.
// This test deliberately lives in the base fixture (which leaves the feature
// flag at its default-disabled value) rather than the enabled-flag fixture.
TEST_F(IdpNetworkRequestManagerTest,
       FetchWellKnownDoesNotAttemptSubdomainWhenFlagDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(features::kFedCmWebIdentitySubdomain);

  // Only the apex URL is wired; if the implementation tried the subdomain URL
  // the request would hang and the test would time out.
  auto [fetch_status, well_known] = SendWellKnownRequestAndWaitForResponse(
      R"({"provider_urls": ["https://idp.test/fedcm.json"]})");

  EXPECT_EQ(ParseStatus::kSuccess, fetch_status.parse_status);
  EXPECT_EQ(std::set<GURL>{GURL("https://idp.test/fedcm.json")},
            well_known.provider_urls);
  histogram_tester()->ExpectTotalCount(
      "Blink.FedCm.WebIdentitySubdomain.DiscoverySucceeded", 0);
}

TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingConfig) {
  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) =
      SendConfigRequestAndWaitForResponse(R"({
  "branding" : {
    "color": "blue"
  }
  })",
                                          net::HTTP_NOT_FOUND);
  EXPECT_EQ(ParseStatus::kHttpNotFoundError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_NOT_FOUND, fetch_status.response_code);
}

TEST_F(IdpNetworkRequestManagerTest, ErrorFetchingAccounts) {
  FetchStatus fetch_status;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(fetch_status, accounts) =
      SendAccountsRequestAndWaitForResponse(R"({
  "accounts" : []
  })",
                                            net::HTTP_BAD_REQUEST);
  EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_BAD_REQUEST, fetch_status.response_code);
}

TEST_F(IdpNetworkRequestManagerTest, FetchClientMetadataValidUrls) {
  // Both HTTPS and HTTP URLs are allowed.
  {
    const std::string privacy_policy_url = "https://privacy.policy";
    const std::string terms_of_service_url = "http://terms.of.service";
    const std::string brand_icon_url = "http://rp.brand/icon.ico";

    IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
        /*client_id=*/"123", R"({"privacy_policy_url": ")" +
                                 privacy_policy_url +
                                 R"(", "terms_of_service_url": ")" +
                                 terms_of_service_url +
                                 R"(", "icons": [
      {
        "url":  ")" + brand_icon_url +
                                 R"(",
        "size": 40
      }
    ]})");
    ASSERT_EQ(GURL(privacy_policy_url), data.privacy_policy_url);
    ASSERT_EQ(GURL(terms_of_service_url), data.terms_of_service_url);
    ASSERT_EQ(GURL(brand_icon_url), data.brand_icon_url);
  }

  // local host URL is allowed.
  {
    const std::string privacy_policy_url = "http://localhost";
    const std::string terms_of_service_url = "http://127.0.0.1";
    const std::string brand_icon_url = "http://localhost/icon.ico";

    IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
        /*client_id=*/"123", R"({"privacy_policy_url": ")" +
                                 privacy_policy_url +
                                 R"(", "terms_of_service_url": ")" +
                                 terms_of_service_url +
                                 R"(", "icons": [
      {
        "url":  ")" + brand_icon_url +
                                 R"(",
        "size": 40
      }
    ]})");
    ASSERT_EQ(GURL(privacy_policy_url), data.privacy_policy_url);
    ASSERT_EQ(GURL(terms_of_service_url), data.terms_of_service_url);
    ASSERT_EQ(GURL(brand_icon_url), data.brand_icon_url);
  }
}

TEST_F(IdpNetworkRequestManagerTest, FetchClientMetadataInvalidUrls) {
  // Non-HTTP(S) URLs should not be allowed.
  const std::string privacy_policy_url = "chrome://settings";
  const std::string terms_of_service_url = "file:///Users/you/file.html";
  const std::string brand_icon_url = "about:blank";

  IdpClientMetadata data = SendClientMetadataRequestAndWaitForResponse(
      /*client_id=*/"123", R"({"privacy_policy_url": ")" + privacy_policy_url +
                               R"(", "terms_of_service_url": ")" +
                               terms_of_service_url +
                               R"(", "icons": [
      {
        "url":  ")" + brand_icon_url +
                               R"(",
        "size": 40
      }
    ]})");
  ASSERT_EQ(GURL(), data.privacy_policy_url);
  ASSERT_EQ(GURL(), data.terms_of_service_url);
  ASSERT_EQ(GURL(), data.brand_icon_url);
}

TEST_F(IdpNetworkRequestManagerTest, WellKnownWrongMimeType) {
  FetchStatus fetch_status;
  IdpNetworkRequestManager::WellKnown wellknown;
  std::tie(fetch_status, wellknown) =
      SendWellKnownRequestAndWaitForResponse(R"({
  "provider_urls": ["https://idp.test/fedcm.json"]
  })",
                                             net::HTTP_OK, "text/html");
  EXPECT_EQ(ParseStatus::kInvalidContentTypeError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
  EXPECT_EQ(std::set<GURL>{}, wellknown.provider_urls);
}

TEST_F(IdpNetworkRequestManagerTest, ConfigWrongMimeType) {
  FetchStatus fetch_status;
  IdentityProviderMetadata idp_metadata;
  std::tie(fetch_status, idp_metadata) = SendConfigRequestAndWaitForResponse(
      R"({"branding" : { "color": "blue" } })", net::HTTP_OK, "text/html");
  EXPECT_EQ(ParseStatus::kInvalidContentTypeError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
}

TEST_F(IdpNetworkRequestManagerTest, AccountsWrongMimeType) {
  const auto* test_single_account_json = kSingleAccountEndpointValidJson;

  FetchStatus accounts_response;
  IdpNetworkRequestManager::AccountsResponse accounts;
  std::tie(accounts_response, accounts) = SendAccountsRequestAndWaitForResponse(
      test_single_account_json, net::HTTP_OK, "text/html");

  EXPECT_EQ(ParseStatus::kInvalidContentTypeError,
            accounts_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, accounts_response.response_code);
  EXPECT_TRUE(accounts.accounts.empty());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionWrongMimeType) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "text/html");
  ASSERT_FALSE(token_result.token.has_value());
  EXPECT_EQ(ParseStatus::kInvalidContentTypeError, fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, fetch_status.response_code);
}

TEST_F(IdpNetworkRequestManagerTest, FetchingTokenLeadsToAContinuationUrl) {
  net::HttpStatusCode http_status = net::HTTP_OK;
  const std::string& mime_type = "application/json";

  const char response[] =
      R"({"continue_on": "https://idp.test/an-absolute-url-for-continuation"})";
  GURL token_endpoint(kTestTokenEndpoint);
  AddResponse(token_endpoint, http_status, mime_type, response);

  base::RunLoop run_loop;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus status, TokenResult&& result) {});

  auto on_continue = base::BindLambdaForTesting([&](FetchStatus status,
                                                    const GURL& url) {
    // Checks that we got a continuation url event back.
    EXPECT_EQ("https://idp.test/an-absolute-url-for-continuation", url.spec());
    run_loop.Quit();
  });

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendTokenRequest(token_endpoint, "account", "request", false,
                            std::move(callback), std::move(on_continue),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorNotReceivedAndContinueOnReceived,
            token_response_type());
}

TEST_F(IdpNetworkRequestManagerTest, FetchingTokenLeadsToARedirectToUrl) {
  net::HttpStatusCode http_status = net::HTTP_OK;
  const std::string& mime_type = "application/json";

  const char response[] =
      R"({"redirect_to": "https://idp.test/an-absolute-url-for-redirect"})";
  GURL token_endpoint(kTestTokenEndpoint);
  AddResponse(token_endpoint, http_status, mime_type, response);

  base::RunLoop run_loop;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus status, TokenResult&& result) {});

  auto on_redirect = base::BindLambdaForTesting(
      [&](FetchStatus status, blink::mojom::RedirectParams::Tag method,
          const GURL& url, const std::string& request_body) {
        // Checks that we got a redirect url event back.
        EXPECT_EQ("https://idp.test/an-absolute-url-for-redirect", url.spec());
        run_loop.Quit();
      });

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendTokenRequest(token_endpoint, "account", "request", false,
                            std::move(callback), base::DoNothing(),
                            std::move(on_redirect),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();
  EXPECT_EQ(
      TokenResponseType::
          kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceivedAndRedirectToReceived,
      token_response_type());
}

TEST_F(IdpNetworkRequestManagerTest, ContinueOnWithToken) {
  net::HttpStatusCode http_status = net::HTTP_OK;
  const std::string& mime_type = "application/json";

  const char response[] = R"({"continue_on": "/", "token": "a_token"})";
  GURL token_endpoint(kTestTokenEndpoint);
  AddResponse(token_endpoint, http_status, mime_type, response);

  base::RunLoop run_loop;
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendTokenRequest(token_endpoint, "account", "request", false,
                            base::DoNothing(), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();
  EXPECT_EQ(
      TokenResponseType::kTokenReceivedAndErrorNotReceivedAndContinueOnReceived,
      token_response_type());
}

TEST_F(IdpNetworkRequestManagerTest, ContinueOnWithErrorAndToken) {
  net::HttpStatusCode http_status = net::HTTP_OK;
  const std::string& mime_type = "application/json";

  const char response[] =
      R"({"continue_on": "/", "token": "a_token", "error": {"code": "foo"}})";
  GURL token_endpoint(kTestTokenEndpoint);
  AddResponse(token_endpoint, http_status, mime_type, response);

  base::RunLoop run_loop;
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendTokenRequest(token_endpoint, "account", "request", false,
                            base::DoNothing(), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();
  EXPECT_EQ(
      TokenResponseType::kTokenReceivedAndErrorReceivedAndContinueOnReceived,
      token_response_type());
}

TEST_F(IdpNetworkRequestManagerTest, ContinueOnWithError) {
  net::HttpStatusCode http_status = net::HTTP_OK;
  const std::string& mime_type = "application/json";

  const char response[] = R"({"continue_on": "/", "error": {"code": "foo"}})";
  GURL token_endpoint(kTestTokenEndpoint);
  AddResponse(token_endpoint, http_status, mime_type, response);

  base::RunLoop run_loop;
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendTokenRequest(token_endpoint, "account", "request", false,
                            base::DoNothing(), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();
  EXPECT_EQ(
      TokenResponseType::kTokenNotReceivedAndErrorReceivedAndContinueOnReceived,
      token_response_type());
}

TEST_F(IdpNetworkRequestManagerTest, ContinueOnCanBeRelativeUrl) {
  net::HttpStatusCode http_status = net::HTTP_OK;
  const std::string& mime_type = "application/json";

  const char response[] =
      R"({"continue_on": "/a-relative-url-for-continuation"})";
  GURL token_endpoint(kTestTokenEndpoint);
  AddResponse(token_endpoint, http_status, mime_type, response);

  base::RunLoop run_loop;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus status, TokenResult&& result) {});

  auto on_continue = base::BindLambdaForTesting([&](FetchStatus status,
                                                    const GURL& url) {
    // Checks that we got a continuation url event back.
    EXPECT_EQ("https://idp.test/a-relative-url-for-continuation", url.spec());
    run_loop.Quit();
  });

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendTokenRequest(token_endpoint, "account", "request", false,
                            std::move(callback), std::move(on_continue),
                            base::DoNothing(), base::DoNothing());
  run_loop.Run();
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithProperField) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": "https://idp.test/error"
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("invalid_request", token_result.error->code);
  EXPECT_EQ("https://idp.test/error", token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type());
  EXPECT_TRUE(error_url_type());
  EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithRelativePath) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": "/error"
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("invalid_request", token_result.error->code);
  EXPECT_EQ("https://idp.test/error", token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type());
  EXPECT_TRUE(error_url_type());
  EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithCrossSiteUrl) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": "https://cross-site-idp.test/error"
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("invalid_request", token_result.error->code);
  EXPECT_EQ(GURL(), token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kInvalidRequestWithoutUrl, *error_dialog_type());
  EXPECT_TRUE(error_url_type());
  EXPECT_EQ(ErrorUrlType::kCrossSite, *error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest,
       IdAssertionRequestErrorWithSameSiteCrossOriginUrl) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": "https://cross-origin.idp.test/error"
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("invalid_request", token_result.error->code);
  EXPECT_EQ("https://cross-origin.idp.test/error", token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type());
  EXPECT_TRUE(error_url_type());
  EXPECT_EQ(ErrorUrlType::kCrossOriginSameSite, *error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest,
       IdAssertionRequestErrorWithUntrustworthyUrl) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": "http://idp.test/error"
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("invalid_request", token_result.error->code);
  EXPECT_EQ(GURL(), token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kInvalidRequestWithoutUrl, *error_dialog_type());
  ASSERT_TRUE(error_url_type());
  EXPECT_EQ(ErrorUrlType::kCrossSite, *error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithEmptyUrl) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": ""
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("invalid_request", token_result.error->code);
  EXPECT_EQ(GURL(), token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kInvalidRequestWithoutUrl, *error_dialog_type());
  EXPECT_FALSE(error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionRequestErrorWithLocalHostUrl) {
  // allow localhost for error url
  {
    FetchStatus fetch_status;
    TokenResult token_result;
    std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
        "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "url": "http://localhost/error"
        }
      })",
        false, kTestLocalHostTokenEndpoint);

    EXPECT_TRUE(token_result.error);
    EXPECT_EQ("", token_result.error->code);
    EXPECT_EQ(GURL("http://localhost/error"), token_result.error->url);
    EXPECT_EQ(TokenResponseType::
                  kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
              token_response_type());
    EXPECT_TRUE(error_dialog_type());
    EXPECT_EQ(ErrorDialogType::kGenericEmptyWithUrl, *error_dialog_type());
    ASSERT_TRUE(error_url_type());
    EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type());
  }

  {
    FetchStatus fetch_status;
    TokenResult token_result;
    std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
        "account", "request", net::HTTP_OK, "application/json", R"({
        "error": {
          "error": "invalid_request",
          "url": "http://localhost/error"
        }
      })",
        false, kTestLocalHostTokenEndpoint);

    EXPECT_TRUE(token_result.error);
    EXPECT_EQ("invalid_request", token_result.error->code);
    EXPECT_EQ(GURL("http://localhost/error"), token_result.error->url);
    EXPECT_EQ(TokenResponseType::
                  kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
              token_response_type());
    EXPECT_TRUE(error_dialog_type());
    EXPECT_EQ(ErrorDialogType::kInvalidRequestWithUrl, *error_dialog_type());
    ASSERT_TRUE(error_url_type());
    EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type());
  }
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponse200NonParsable) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_OK, "application/json", R"({}})");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("", token_result.error->code);
  EXPECT_EQ(GURL(), token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kGenericEmptyWithoutUrl, *error_dialog_type());
  EXPECT_FALSE(error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponse500NonParsable) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_INTERNAL_SERVER_ERROR, "application/json",
      R"({}})");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("server_error", token_result.error->code);
  EXPECT_EQ(GURL(), token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kServerErrorWithoutUrl, *error_dialog_type());
  EXPECT_FALSE(error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponse503NonParsable) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_SERVICE_UNAVAILABLE, "application/json",
      R"({}})");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("temporarily_unavailable", token_result.error->code);
  EXPECT_EQ(GURL(), token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kTemporarilyUnavailableWithoutUrl,
            *error_dialog_type());
  EXPECT_FALSE(error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponseWithErrorAndHttpError) {
  FetchStatus fetch_status;
  TokenResult token_result;
  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_SERVICE_UNAVAILABLE, "application/json",
      R"({
        "error": {
          "error": "temporarily_unavailable",
          "url": "https://idp.test/error"
        }
      })");

  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("temporarily_unavailable", token_result.error->code);
  EXPECT_EQ("https://idp.test/error", token_result.error->url);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kTemporarilyUnavailableWithUrl,
            *error_dialog_type());
  EXPECT_TRUE(error_url_type());
  EXPECT_EQ(ErrorUrlType::kSameOrigin, *error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponseWithTokenAndHttpError) {
  FetchStatus fetch_status;
  TokenResult token_result;

  std::tie(fetch_status, token_result) = SendTokenRequestAndWaitForResponse(
      "account", "request", net::HTTP_FORBIDDEN);

  ASSERT_FALSE(token_result.token.has_value());
  EXPECT_TRUE(token_result.error);
  EXPECT_EQ("", token_result.error->code);
  EXPECT_EQ("", token_result.error->url);
  EXPECT_EQ(net::HTTP_FORBIDDEN, fetch_status.response_code);
  EXPECT_EQ(ParseStatus::kInvalidResponseError, fetch_status.parse_status);
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorNotReceivedAndContinueOnNotReceived,
            token_response_type());
  EXPECT_TRUE(error_dialog_type());
  EXPECT_EQ(ErrorDialogType::kGenericEmptyWithoutUrl, *error_dialog_type());
  EXPECT_FALSE(error_url_type());
}

TEST_F(IdpNetworkRequestManagerTest, DisconnectRequest) {
  bool called = false;
  auto interceptor =
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        called = true;
        EXPECT_EQ(GURL(kTestDisconnectEndpoint), request.url);
        EXPECT_FALSE(request.referrer.is_valid());
        url::Origin rpOrigin = url::Origin::Create(GURL(kTestRpUrl));
        EXPECT_EQ(GetOriginHeader(request), rpOrigin);

        // Check that the request body is correct.
        ASSERT_NE(request.request_body, nullptr);
        ASSERT_EQ(1ul, request.request_body->elements()->size());
        const network::DataElement& elem =
            request.request_body->elements()->at(0);
        ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type());
        const network::DataElementBytes& byte_elem =
            elem.As<network::DataElementBytes>();
        EXPECT_EQ("client_id=clientId&account_hint=hint",
                  byte_elem.AsStringPiece());
        ASSERT_EQ(request.mode, network::mojom::RequestMode::kCors);
        ASSERT_EQ(request.request_initiator, rpOrigin);
      });
  test_url_loader_factory().SetInterceptor(interceptor);

  const char test_disconnect_json[] = R"({
  "account_id" : "accountId"
  })";

  GURL disconnect_endpoint(kTestDisconnectEndpoint);
  AddResponse(disconnect_endpoint, net::HTTP_OK, "application/json",
              test_disconnect_json);

  base::RunLoop run_loop;
  FetchStatus disconnect_response;
  std::optional<std::string> disconnect_account_id;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus response, const std::string& account_id) {
        disconnect_response = response;
        disconnect_account_id = account_id;
        run_loop.Quit();
      });

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendDisconnectRequest(disconnect_endpoint, "hint", "clientId",
                                 std::move(callback));
  run_loop.Run();

  EXPECT_TRUE(called);
  EXPECT_EQ(ParseStatus::kSuccess, disconnect_response.parse_status);
  EXPECT_EQ(net::HTTP_OK, disconnect_response.response_code);
  ASSERT_TRUE(disconnect_account_id.has_value());
  EXPECT_EQ(*disconnect_account_id, "accountId");
}

TEST_F(IdpNetworkRequestManagerTest, DisconnectRequestInjection) {
  bool called = false;
  auto interceptor = base::BindLambdaForTesting([&](const network::
                                                        ResourceRequest&
                                                            request) {
    called = true;
    EXPECT_EQ(GURL(kTestDisconnectEndpoint), request.url);

    // Check that the request body is escaped.
    ASSERT_NE(request.request_body, nullptr);
    ASSERT_EQ(1ul, request.request_body->elements()->size());
    const network::DataElement& elem = request.request_body->elements()->at(0);
    ASSERT_EQ(network::DataElement::Tag::kBytes, elem.type());
    const network::DataElementBytes& byte_elem =
        elem.As<network::DataElementBytes>();
    // If it's not escaped, it would be
    // "client_id=client&inject=id&account_hint=hint&inject=account" If it's
    // escaped, it should be
    // "client_id=client%26inject%3Did&account_hint=hint%26inject%3Daccount"
    EXPECT_EQ(
        "client_id=client%26inject%3Did&account_hint=hint%26inject%3Daccount",
        byte_elem.AsStringPiece());
  });
  test_url_loader_factory().SetInterceptor(interceptor);

  const char test_disconnect_json[] = R"({
  "account_id" : "accountId"
  })";

  GURL disconnect_endpoint(kTestDisconnectEndpoint);
  AddResponse(disconnect_endpoint, net::HTTP_OK, "application/json",
              test_disconnect_json);

  base::RunLoop run_loop;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus response, const std::string& account_id) {
        run_loop.Quit();
      });

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SendDisconnectRequest(disconnect_endpoint, "hint&inject=account",
                                 "client&inject=id", std::move(callback));
  run_loop.Run();

  EXPECT_TRUE(called);
}

// If the frame is invalidated during the request initiation, the request is
// aborted.
TEST_F(IdpNetworkRequestManagerTest, RequestAbortedOnInvalidFrame) {
  network::TestURLLoaderFactory test_url_loader_factory;
  auto test_permission_delegate = std::make_unique<MockPermissionDelegate>();

  // Simulate the initiator frame being invalidated by passing a default
  // constructed `WeakDocumentPtr`.
  auto network_manager = std::make_unique<IdpNetworkRequestManager>(
      url::Origin::Create(GURL(kTestRpUrl)),
      /*rp_embedding_origin=*/url::Origin(),
      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
          &test_url_loader_factory),
      test_permission_delegate.get(),
      network::mojom::ClientSecurityState::New(), FrameTreeNodeId(),
      WeakDocumentPtr());

  base::RunLoop run_loop;
  auto callback = base::BindLambdaForTesting(
      [&](FetchStatus fetch_status,
          const IdpNetworkRequestManager::WellKnown& well_known) {
        // The request should be aborted.
        EXPECT_EQ(ParseStatus::kNoResponseError, fetch_status.parse_status);
        EXPECT_EQ(net::ERR_ABORTED, fetch_status.response_code);
        run_loop.Quit();
      });

  network_manager->FetchWellKnown(GURL("https://idp.example"),
                                  std::move(callback));
  run_loop.Run();

  EXPECT_EQ(0, test_url_loader_factory.NumPending());
}

class MockNativeIdpFetcher : public NativeIdpFetcher {
 public:
  MockNativeIdpFetcher() = default;
  ~MockNativeIdpFetcher() override = default;

  MOCK_METHOD(void,
              Fetch,
              (const NativeIdpFetcher::RequestParams& params,
               FetchCallback callback),
              (override));
};

TEST_F(IdpNetworkRequestManagerTest, NativeIdpTokenSuccess) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  EXPECT_CALL(
      *mock_fetcher,
      Fetch(testing::AllOf(
                testing::Field(&NativeIdpFetcher::RequestParams::url,
                               token_endpoint),
                testing::Field(&NativeIdpFetcher::RequestParams::body,
                               std::optional<std::string>("request_body"))),
            _))
      .WillOnce(WithArg<1>([](NativeIdpFetcher::FetchCallback callback) {
        std::move(callback).Run(base::ok(R"({"token": "native_token_123"})"));
      }));

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SetNativeIdpFetcherForTesting(url::Origin::Create(token_endpoint),
                                         std::move(mock_fetcher));

  base::RunLoop run_loop;
  FetchStatus token_fetch_status;
  TokenResult token_result;
  auto callback =
      base::BindLambdaForTesting([&](FetchStatus status, TokenResult&& result) {
        token_fetch_status = status;
        token_result = std::move(result);
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            std::move(callback), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();

  EXPECT_EQ(ParseStatus::kSuccess, token_fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, token_fetch_status.response_code);
  ASSERT_TRUE(token_result.token.has_value());
  EXPECT_EQ("native_token_123", token_result.token->GetString());
  EXPECT_EQ(0, test_url_loader_factory().NumPending());
}

TEST_F(IdpNetworkRequestManagerTest, NativeIdpTokenFetchError) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  EXPECT_CALL(*mock_fetcher, Fetch)
      .WillOnce(WithArg<1>([](NativeIdpFetcher::FetchCallback callback) {
        std::move(callback).Run(
            base::unexpected(NativeIdpFetcher::FetchError::kFetchFailed));
      }));

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SetNativeIdpFetcherForTesting(url::Origin::Create(token_endpoint),
                                         std::move(mock_fetcher));

  base::RunLoop run_loop;
  FetchStatus token_fetch_status;
  TokenResult token_result;
  auto callback =
      base::BindLambdaForTesting([&](FetchStatus status, TokenResult&& result) {
        token_fetch_status = status;
        token_result = std::move(result);
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            std::move(callback), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();

  EXPECT_EQ(ParseStatus::kNoResponseError, token_fetch_status.parse_status);
  EXPECT_EQ(net::ERR_FAILED, token_fetch_status.response_code);
  EXPECT_FALSE(token_result.token.has_value());
  ASSERT_TRUE(token_result.error.has_value());
  EXPECT_EQ(0, test_url_loader_factory().NumPending());
}

TEST_F(IdpNetworkRequestManagerTest, NativeIdpTokenInvalidJson) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  EXPECT_CALL(*mock_fetcher, Fetch)
      .WillOnce(WithArg<1>([](NativeIdpFetcher::FetchCallback callback) {
        std::move(callback).Run(base::ok("invalid json"));
      }));

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SetNativeIdpFetcherForTesting(url::Origin::Create(token_endpoint),
                                         std::move(mock_fetcher));

  base::RunLoop run_loop;
  FetchStatus token_fetch_status;
  TokenResult token_result;
  auto callback =
      base::BindLambdaForTesting([&](FetchStatus status, TokenResult&& result) {
        token_fetch_status = status;
        token_result = std::move(result);
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            std::move(callback), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();

  EXPECT_EQ(ParseStatus::kInvalidResponseError,
            token_fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, token_fetch_status.response_code);
  EXPECT_FALSE(token_result.token.has_value());
  ASSERT_TRUE(token_result.error.has_value());
  EXPECT_EQ(0, test_url_loader_factory().NumPending());
}

TEST_F(IdpNetworkRequestManagerTest, NativeIdpTokenErrorResponse) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  EXPECT_CALL(*mock_fetcher, Fetch)
      .WillOnce(WithArg<1>([](NativeIdpFetcher::FetchCallback callback) {
        std::move(callback).Run(
            base::ok(R"({"error": {"code": "access_denied"}})"));
      }));

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SetNativeIdpFetcherForTesting(url::Origin::Create(token_endpoint),
                                         std::move(mock_fetcher));

  base::RunLoop run_loop;
  FetchStatus token_fetch_status;
  TokenResult token_result;
  auto callback =
      base::BindLambdaForTesting([&](FetchStatus status, TokenResult&& result) {
        token_fetch_status = status;
        token_result = std::move(result);
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            std::move(callback), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();

  EXPECT_EQ(ParseStatus::kSuccess, token_fetch_status.parse_status);
  EXPECT_EQ(net::HTTP_OK, token_fetch_status.response_code);
  EXPECT_FALSE(token_result.token.has_value());
  ASSERT_TRUE(token_result.error.has_value());
  EXPECT_EQ("access_denied", token_result.error->code);
  EXPECT_EQ(0, test_url_loader_factory().NumPending());
}

TEST_F(IdpNetworkRequestManagerTest, NativeIdpTokenContinueOn) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  EXPECT_CALL(*mock_fetcher, Fetch)
      .WillOnce(WithArg<1>([](NativeIdpFetcher::FetchCallback callback) {
        std::move(callback).Run(
            base::ok(R"({"continue_on": "https://idp.test/continue_on_url"})"));
      }));

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SetNativeIdpFetcherForTesting(url::Origin::Create(token_endpoint),
                                         std::move(mock_fetcher));

  base::RunLoop run_loop;
  auto on_continue =
      base::BindLambdaForTesting([&](FetchStatus status, const GURL& url) {
        EXPECT_EQ("https://idp.test/continue_on_url", url.spec());
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            base::DoNothing(), std::move(on_continue),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();
  EXPECT_EQ(TokenResponseType::
                kTokenNotReceivedAndErrorNotReceivedAndContinueOnReceived,
            token_response_type());
  EXPECT_EQ(0, test_url_loader_factory().NumPending());
}

TEST_F(IdpNetworkRequestManagerTest,
       NativeIdpTokenFallbackWhenNoNativeFetcher) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();

  AddResponse(token_endpoint, net::HTTP_OK, "application/json",
              R"({"token": "http_token"})");

  base::RunLoop run_loop;
  FetchStatus token_fetch_status;
  TokenResult token_result;
  auto callback =
      base::BindLambdaForTesting([&](FetchStatus status, TokenResult&& result) {
        token_fetch_status = status;
        token_result = std::move(result);
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            std::move(callback), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();

  EXPECT_EQ(ParseStatus::kSuccess, token_fetch_status.parse_status);
  ASSERT_TRUE(token_result.token.has_value());
  EXPECT_EQ("http_token", token_result.token->GetString());
}

TEST_F(IdpNetworkRequestManagerTest, NativeIdpTokenFallbackWhenDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(features::kFedCmNativeIdPs);

  GURL token_endpoint(kTestTokenEndpoint);
  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  EXPECT_CALL(*mock_fetcher, Fetch).Times(0);

  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
  manager->SetNativeIdpFetcherForTesting(url::Origin::Create(token_endpoint),
                                         std::move(mock_fetcher));

  AddResponse(token_endpoint, net::HTTP_OK, "application/json",
              R"({"token": "http_token"})");

  base::RunLoop run_loop;
  FetchStatus token_fetch_status;
  TokenResult token_result;
  auto callback =
      base::BindLambdaForTesting([&](FetchStatus status, TokenResult&& result) {
        token_fetch_status = status;
        token_result = std::move(result);
        run_loop.Quit();
      });

  manager->SendTokenRequest(token_endpoint, "account", "request_body", false,
                            std::move(callback), base::DoNothing(),
                            base::DoNothing(),
                            CreateErrorMetricsCallback(run_loop));
  run_loop.Run();

  EXPECT_EQ(ParseStatus::kSuccess, token_fetch_status.parse_status);
  ASSERT_TRUE(token_result.token.has_value());
  EXPECT_EQ("http_token", token_result.token->GetString());
}

TEST_F(IdpNetworkRequestManagerTest, GetOrCreateNativeIdpFetcherIdempotent) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kFedCmNativeIdPs);

  url::Origin idp_origin = url::Origin::Create(GURL("https://idp.example"));
  std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();

  auto mock_fetcher = std::make_unique<NiceMock<MockNativeIdpFetcher>>();
  NativeIdpFetcher* raw_fetcher = mock_fetcher.get();
  manager->SetNativeIdpFetcherForTesting(idp_origin, std::move(mock_fetcher));

  EXPECT_EQ(raw_fetcher, manager->GetNativeIdpFetcher(idp_origin));
  EXPECT_EQ(raw_fetcher, manager->GetOrCreateNativeIdpFetcher(idp_origin));
}

}  // namespace

}  // namespace content::webid
