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

#include "components/optimization_guide/core/hints/hints_fetcher.h"

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/optimization_guide/browser_test_util.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/google/core/common/google_switches.h"
#include "components/google/core/common/google_util.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_handle.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/optimization_guide/core/filters/hints_component_info.h"
#include "components/optimization_guide/core/filters/hints_component_util.h"
#include "components/optimization_guide/core/filters/optimization_hints_component_update_listener.h"
#include "components/optimization_guide/core/filters/test_hints_component_creator.h"
#include "components/optimization_guide/core/hints/command_line_top_host_provider.h"
#include "components/optimization_guide/core/hints/fake_hints_fetcher.h"
#include "components/optimization_guide/core/hints/hints_manager.h"
#include "components/optimization_guide/core/hints/optimization_guide_store.h"
#include "components/optimization_guide/core/hints/top_host_provider.h"
#include "components/optimization_guide/core/optimization_guide_enums.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/core/optimization_guide_permissions_util.h"
#include "components/optimization_guide/core/optimization_guide_prefs.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"
#include "components/optimization_guide/proto/hints.pb.h"
#include "components/prefs/pref_service.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/variations/hashing.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "content/public/test/prerender_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/network_connection_tracker.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "third_party/blink/public/common/features.h"

namespace {

constexpr char kGoogleHost[] = "www.google.com";

constexpr char kGoogleSearchUrlPath[] = "/search?q=search_results_page.html";

// Modifies |relative_url|:
// Scheme of the returned URL matches the scheme of the |server|.
// Host of the returned URL matches kGoogleHost.
// Port number of the returned URL matches the port at which |server| is
// listening.
// Path of the returned URL is set to |relative_url|.
GURL GetURLWithGoogleHost(net::EmbeddedTestServer* server,
                          const std::string& relative_url) {
  GURL server_base_url = server->base_url();
  GURL base_url =
      GURL(base::StrCat({server_base_url.GetScheme(), "://", kGoogleHost, ":",
                         server_base_url.GetPort()}));
  EXPECT_TRUE(base_url.is_valid()) << base_url.possibly_invalid_spec();
  return base_url.Resolve(relative_url);
}

// Handles the server request to Google search URL.
std::unique_ptr<net::test_server::HttpResponse> HandleGoogleSearchUrlRequest(
    const net::test_server::HttpRequest& request) {
  if (base::EqualsCaseInsensitiveASCII(request.relative_url,
                                       kGoogleSearchUrlPath)) {
    // Serve the SRP file.
    std::unique_ptr<net::test_server::BasicHttpResponse> response =
        std::make_unique<net::test_server::BasicHttpResponse>();
    std::string file_contents;
    base::FilePath test_data_directory;
    base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
    if (base::ReadFileToString(test_data_directory.AppendASCII(
                                   "previews/search_results_page.html"),
                               &file_contents)) {
      response->set_content(file_contents);
      response->set_code(net::HTTP_OK);
      return std::move(response);
    }
  }
  return nullptr;
}

}  // namespace

// This test class sets up everything but does not enable any
// HintsFetcher-related features. The parameter selects whether the
// OptimizationGuideKeyedService is enabled (tests should pass in the same way
// for both cases).
class HintsFetcherDisabledBrowserTest : public InProcessBrowserTest {
 public:
  HintsFetcherDisabledBrowserTest() = default;

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

  ~HintsFetcherDisabledBrowserTest() override = default;

  void SetUpOnMainThread() override {
    content::NetworkConnectionChangeSimulator().SetConnectionType(
        net::NetworkChangeNotifier::ConnectionType::CONNECTION_2G);

    // Ensure that kGoogleHost resolves to the localhost where the embedded test
    // server is listening.
    host_resolver()->AddRule("*", "127.0.0.1");

    ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();

    InProcessBrowserTest::SetUpOnMainThread();
  }

  void SetUp() override {
    origin_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    net::EmbeddedTestServer::ServerCertificateConfig origin_server_cert_config;
    origin_server_cert_config.dns_names = {kGoogleHost};
    origin_server_cert_config.ip_addresses = {net::IPAddress::IPv4Localhost()};
    origin_server_->SetSSLConfig(origin_server_cert_config);
    origin_server_->RegisterRequestHandler(
        base::BindRepeating(&HandleGoogleSearchUrlRequest));
    origin_server_->ServeFilesFromSourceDirectory("chrome/test/data/previews");

    ASSERT_TRUE(origin_server_->Start());

    https_url_ = origin_server_->GetURL("/hint_setup.html");
    ASSERT_TRUE(https_url().SchemeIs(url::kHttpsScheme));

    search_results_page_url_ =
        GetURLWithGoogleHost(origin_server_.get(), kGoogleSearchUrlPath);

    hints_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);

    net::EmbeddedTestServer::ServerCertificateConfig hints_server_cert_config;
    hints_server_cert_config.dns_names = {
        GURL(optimization_guide::kOptimizationGuideServiceGetHintsDefaultURL)
            .GetHost()};
    hints_server_->SetSSLConfig(hints_server_cert_config);
    hints_server_->ServeFilesFromSourceDirectory("chrome/test/data/previews");
    hints_server_->RegisterRequestHandler(base::BindRepeating(
        &HintsFetcherDisabledBrowserTest::HandleGetHintsRequest,
        base::Unretained(this)));

    ASSERT_TRUE(hints_server_->Start());

    std::map<std::string, std::string> params;
    params["random_anchor_sampling_period"] = "1";
    params["traffic_client_enabled_percent"] = "100";
    param_feature_list_.InitAndEnableFeatureWithParameters(
        blink::features::kNavigationPredictor, params);

    InProcessBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* cmd) override {
    cmd->AppendSwitch("purge_hint_cache_store");

    cmd->AppendSwitch(
        optimization_guide::
            kDisableCheckingUserPermissionsForTestingSwitch);

    // Set up OptimizationGuideServiceURL, this does not enable HintsFetching,
    // only provides the URL.
    cmd->AppendSwitchASCII(
        optimization_guide::kOptimizationGuideServiceGetHintsURLSwitch,
        hints_server_
            ->GetURL(GURL(optimization_guide::
                              kOptimizationGuideServiceGetHintsDefaultURL)
                         .GetHost(),
                     "/")
            .spec());
    cmd->AppendSwitchASCII("force-variation-ids", "4");

    cmd->AppendSwitchASCII(optimization_guide::kFetchHintsOverrideSwitch,
                           "example1.com, example2.com");

    cmd->AppendSwitch(optimization_guide::kFetchHintsOverrideTimerSwitch);

    // Ignore the port numbers for the Google Search URL check.
    cmd->AppendSwitch(switches::kIgnoreGooglePortNumbers);
  }

  // Creates hint data for the |hint_setup_url|'s so that the fetching of the
  // hints is triggered.
  void SetUpComponentUpdateHints(const GURL& hint_setup_url) {
    optimization_guide::RetryForHistogramUntilCountReached(
        GetHistogramTester(),
        "OptimizationGuide.HintsManager.HintCacheInitialized", 1);

    const optimization_guide::HintsComponentInfo& component_info =
        test_hints_component_creator_.CreateHintsComponentInfoWithPageHints(
            optimization_guide::proto::NOSCRIPT, {hint_setup_url.GetHost()},
            "*");

    base::HistogramTester histogram_tester;

    optimization_guide::OptimizationHintsComponentUpdateListener::GetInstance()
        ->MaybeUpdateHintsComponent(component_info);

    optimization_guide::RetryForHistogramUntilCountReached(
        &histogram_tester,
        optimization_guide::kComponentHintsUpdatedResultHistogramString, 1);
  }

  void SetNetworkConnectionOffline() {
    content::NetworkConnectionChangeSimulator().SetConnectionType(
        net::NetworkChangeNotifier::ConnectionType::CONNECTION_NONE);
  }

  void SetNetworkConnectionOnline() {
    content::NetworkConnectionChangeSimulator().SetConnectionType(
        net::NetworkChangeNotifier::ConnectionType::CONNECTION_2G);
  }

  void SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType response_type) {
    response_type_ = response_type;
  }

  void LoadHintsForUrl(const GURL& url) {
    base::HistogramTester histogram_tester;

    // Navigate to |url| to prime the OptimizationGuide hints for the
    // url's host and ensure that they have been loaded from the store (via
    // histogram) prior to the navigation that tests functionality.
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

    optimization_guide::RetryForHistogramUntilCountReached(
        &histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        1);
  }

  const GURL& https_url() const { return https_url_; }
  const base::HistogramTester* GetHistogramTester() {
    return &histogram_tester_;
  }

  const GURL& search_results_page_url() const {
    return search_results_page_url_;
  }

  void SetExpectedHintsRequestForHostsAndUrls(
      const base::flat_set<std::string>& hosts_or_urls) {
    base::AutoLock lock(lock_);
    expect_hints_request_for_hosts_and_urls_ = hosts_or_urls;
  }

  size_t count_hints_requests_received() {
    base::AutoLock lock(lock_);
    return count_hints_requests_received_;
  }

  void increase_count_hints_requests_received() {
    {
      base::AutoLock lock(lock_);
      ++count_hints_requests_received_;
    }

    if (quit_closure_) {
      content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
                                                   std::move(quit_closure_));
    }
  }

  void SetExpectedBearerAccessToken(
      const std::string& expected_bearer_access_token) {
    expected_bearer_access_token_ = expected_bearer_access_token;
  }

  void WaitUntilHintsFetcherRequestReceived() {
    {
      // Acquire the |lock_| inside to avoid starving other consumers of the
      // lock.
      base::AutoLock lock(lock_);
      if (count_hints_requests_received_ > 0) {
        return;
      }
    }

    base::RunLoop run_loop;
    quit_closure_ = run_loop.QuitClosure();
    run_loop.Run();
  }

  void ResetCountHintsRequestsReceived() {
    base::AutoLock lock(lock_);
    count_hints_requests_received_ = 0;
  }

  // Triggers nostate prefetch of |url|.
  void TriggerNoStatePrefetch(const GURL& url) {
    prerender::NoStatePrefetchManager* no_state_prefetch_manager =
        prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(
            browser()->GetProfile());
    ASSERT_TRUE(no_state_prefetch_manager);

    prerender::test_utils::TestNoStatePrefetchContentsFactory*
        no_state_prefetch_contents_factory =
            new prerender::test_utils::TestNoStatePrefetchContentsFactory();
    no_state_prefetch_manager->SetNoStatePrefetchContentsFactoryForTest(
        no_state_prefetch_contents_factory);

    std::unique_ptr<prerender::test_utils::TestPrerender> test_prerender =
        no_state_prefetch_contents_factory->ExpectNoStatePrefetchContents(
            prerender::FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);

    std::unique_ptr<prerender::NoStatePrefetchHandle> no_state_prefetch_handle =
        no_state_prefetch_manager->StartPrefetchingFromLinkRelPrerender(
            /*process_id=*/-1, /*route_id=*/-1, url,
            blink::mojom::PrerenderTriggerType::kLinkRelPrerender,
            content::Referrer(), url::Origin::Create(url), gfx::Size(640, 480));
    ASSERT_EQ(no_state_prefetch_handle->contents(), test_prerender->contents());

    // The final status may be either  FINAL_STATUS_NOSTATE_PREFETCH_FINISHED or
    // FINAL_STATUS_RECENTLY_VISITED.
    test_prerender->contents()->set_skip_final_checks(true);
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<net::EmbeddedTestServer> origin_server_;
  std::unique_ptr<net::EmbeddedTestServer> hints_server_;
  optimization_guide::HintsFetcherRemoteResponseType response_type_ =
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful;

 private:
  std::unique_ptr<net::test_server::HttpResponse> HandleOriginRequest(
      const net::test_server::HttpRequest& request) {
    EXPECT_EQ(request.method, net::test_server::METHOD_GET);
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    response->set_code(net::HTTP_OK);

    return std::move(response);
  }

  std::unique_ptr<net::test_server::HttpResponse> HandleGetHintsRequest(
      const net::test_server::HttpRequest& request) {
    increase_count_hints_requests_received();
    auto response = std::make_unique<net::test_server::BasicHttpResponse>();
    // If the request is a GET, it corresponds to a navigation so return a
    // normal response.
    EXPECT_EQ(request.method, net::test_server::METHOD_POST);
    EXPECT_NE(request.headers.end(), request.headers.find("X-Client-Data"));

    EXPECT_EQ(
        expected_bearer_access_token_.empty(),
        !request.headers.contains(net::HttpRequestHeaders::kAuthorization));
    if (!expected_bearer_access_token_.empty()) {
      EXPECT_EQ(expected_bearer_access_token_,
                request.headers.at(net::HttpRequestHeaders::kAuthorization));
    }

    // Make sure only one of API key or access token is sent.
    EXPECT_EQ(request.headers.contains("X-Goog-Api-Key"),
              expected_bearer_access_token_.empty());

    optimization_guide::proto::GetHintsRequest hints_request;
    EXPECT_TRUE(hints_request.ParseFromString(request.content));
    EXPECT_FALSE(hints_request.hosts().empty() && hints_request.urls().empty());
    EXPECT_GE(optimization_guide::HintsFetcher::kMaxHosts,
              static_cast<size_t>(hints_request.hosts().size()));

    // Only verify the hints if there are hosts in the request.
    if (!hints_request.hosts().empty()) {
      VerifyHintsMatchExpectedHostsAndUrls(hints_request);
    }

    if (response_type_ ==
        optimization_guide::HintsFetcherRemoteResponseType::kSuccessful) {
      response->set_code(net::HTTP_OK);

      optimization_guide::proto::GetHintsResponse get_hints_response;

      optimization_guide::proto::Hint* hint = get_hints_response.add_hints();
      hint->set_key_representation(optimization_guide::proto::HOST);
      hint->set_key(search_results_page_url_.GetHost());
      hint->add_allowlisted_optimizations()->set_optimization_type(
          optimization_guide::proto::OptimizationType::NOSCRIPT);
      optimization_guide::proto::PageHint* page_hint = hint->add_page_hints();
      page_hint->set_page_pattern("page pattern");

      std::string serialized_request;
      get_hints_response.SerializeToString(&serialized_request);
      response->set_content(serialized_request);
    } else if (response_type_ ==
               optimization_guide::HintsFetcherRemoteResponseType::
                   kUnsuccessful) {
      response->set_code(net::HTTP_NOT_FOUND);

    } else if (response_type_ ==
               optimization_guide::HintsFetcherRemoteResponseType::kMalformed) {
      response->set_code(net::HTTP_OK);

      std::string serialized_request = "Not a proto";
      response->set_content(serialized_request);
    } else if (response_type_ ==
               optimization_guide::HintsFetcherRemoteResponseType::kHung) {
      return std::make_unique<net::test_server::HungResponse>();
    } else {
      NOTREACHED();
    }

    return std::move(response);
  }

  // Verifies that the hosts present in |hints_request| match the expected set
  // of hosts present in |expect_hints_request_for_hosts_|. The ordering of the
  // hosts in not matched.
  void VerifyHintsMatchExpectedHostsAndUrls(
      const optimization_guide::proto::GetHintsRequest& hints_request) const {
    if (!expect_hints_request_for_hosts_and_urls_) {
      return;
    }

    auto hosts_and_urls_requested = base::MakeFlatSet<std::string>(
        hints_request.hosts(), /*comp=*/{},
        [&](const auto& host) { return host.host(); });
    for (const auto& url : hints_request.urls()) {
      // TODO(crbug.com/40118423):  Remove normalization step once nav predictor
      // provides predictable URLs.
      hosts_and_urls_requested.insert(GURL(url.url()).GetAsReferrer().spec());
    }

    EXPECT_EQ(expect_hints_request_for_hosts_and_urls_.value().size(),
              hosts_and_urls_requested.size());
    for (const auto& host_or_url :
         expect_hints_request_for_hosts_and_urls_.value()) {
      hosts_and_urls_requested.erase(host_or_url);
    }
    EXPECT_EQ(0u, hosts_and_urls_requested.size());
  }

  void TearDownOnMainThread() override {
    EXPECT_TRUE(origin_server_->ShutdownAndWaitUntilComplete());
    EXPECT_TRUE(hints_server_->ShutdownAndWaitUntilComplete());

    InProcessBrowserTest::TearDownOnMainThread();
  }

  base::test::ScopedFeatureList param_feature_list_;

  GURL https_url_;

  GURL search_results_page_url_;

  base::HistogramTester histogram_tester_;

  optimization_guide::testing::TestHintsComponentCreator
      test_hints_component_creator_;

  base::Lock lock_;

  // Guarded by |lock_|.
  // Count of hints requests received so far by |hints_server_|.
  size_t count_hints_requests_received_ = 0;

  base::OnceClosure quit_closure_;

  // Guarded by |lock_|. Set of hosts and URLs for which a hints request is
  // expected to arrive. This set is verified to match with the set of hosts and
  // URLs present in the hints request. If null, then the verification is not
  // done.
  std::optional<base::flat_set<std::string>>
      expect_hints_request_for_hosts_and_urls_;

  // The expected authorization header holding the bearer access token.
  std::string expected_bearer_access_token_;

  std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};

IN_PROC_BROWSER_TEST_F(HintsFetcherDisabledBrowserTest, HintsFetcherDisabled) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Expect that the histogram for HintsFetcher to be 0 because the OnePlatform
  // is not enabled.
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0);
}

// This test class enables OnePlatform Hints.
class HintsFetcherBrowserTest : public HintsFetcherDisabledBrowserTest {
 public:
  HintsFetcherBrowserTest() = default;

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

  ~HintsFetcherBrowserTest() override = default;

  void SetUp() override {
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {optimization_guide::features::kOptimizationHints, {}},
        {*optimization_guide::kHintsMaxConcurrentNavigationFetches.feature,
         {{optimization_guide::kHintsMaxConcurrentNavigationFetches.name,
           "2"}}},
        {optimization_guide::kHintsBatchUpdateForActiveTabsAndTopHosts, {}},
    };
    PopulateEnabledFeatures(&enabled_features);
    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
    // Call to inherited class to match same set up with feature flags added.
    HintsFetcherDisabledBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // Register an optimization type, so hints will be fetched at page
    // navigation.
    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->GetProfile())
        ->RegisterOptimizationTypes({optimization_guide::proto::NOSCRIPT});

    HintsFetcherDisabledBrowserTest::SetUpOnMainThread();
  }

  // Allows subclasses to expand on the features to enable.
  virtual void PopulateEnabledFeatures(
      std::vector<base::test::FeatureRefAndParams>* enabled_features) {}

  optimization_guide::TopHostProvider* top_host_provider() {
    OptimizationGuideKeyedService* keyed_service =
        OptimizationGuideKeyedServiceFactory::GetForProfile(
            browser()->GetProfile());
    return keyed_service->GetTopHostProvider();
  }

  void CanApplyOptimizationOnDemand(
      const std::vector<GURL>& urls,
      const std::vector<optimization_guide::proto::OptimizationType>&
          optimization_types,
      optimization_guide::OnDemandOptimizationGuideDecisionRepeatingCallback
          callback) {
    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->GetProfile())
        ->CanApplyOptimizationOnDemand(
            urls, optimization_types,
            optimization_guide::proto::CONTEXT_BOOKMARKS, callback);
  }
};

// This test creates new browser with no profile and loads a random page with
// the feature flags for OptimizationHintsFetching. We confirm that the
// TopHostProvider is called and does not crash by checking UMA
// histograms for the total number of TopEngagementSites and
// the total number of sites returned controlled by the experiments flag
// |max_oneplatform_update_hosts|.
IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest, HintsFetcherEnabled) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       HintsFetcherFetchedHintsLoaded) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();
  GURL url = https_url();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1),
            1);

  LoadHintsForUrl(https_url());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), https_url()));

  // Verifies that the fetched hint is just used in memory and nothing is
  // loaded.
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintCache.HintType.Loaded", 0);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       HintsFetcherWithResponsesSuccessful) {
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  // Wait until histograms have been updated before performing checks for
  // correct behavior based on the response.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       HintsFetcherWithResponsesUnsuccessful) {
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kUnsuccessful);

  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  // Wait until histograms have been updated before performing checks for
  // correct behavior based on the response.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status",
      net::HTTP_NOT_FOUND, 1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 0);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       HintsFetcherWithResponsesMalformed) {
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kMalformed);

  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  // Wait until histograms have been updated before performing checks for
  // correct behavior based on the response.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 0);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       HintsFetcherWithResponsesUnsuccessfulAtNavigationTime) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kUnsuccessful);

  // Set the connection online to force a fetch at navigation time.
  SetNetworkConnectionOnline();

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           GURL("https://unsuccessful.com/")));

  // We expect that we requested hints for 1 URL.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount."
                "PageNavigation",
                1),
            1);
}

IN_PROC_BROWSER_TEST_F(
    HintsFetcherBrowserTest,
    HintsFetcherWithResponsesHungShouldRecordWhenActiveRequestCanceled) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  SetResponseType(optimization_guide::HintsFetcherRemoteResponseType::kHung);

  // Set the connection online to force a fetch at navigation time.
  SetNetworkConnectionOnline();

  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("https://hung.com/1")));
  WaitUntilHintsFetcherRequestReceived();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("https://hung.com/2")));
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("https://hung.com/3")));

  // We expect that one request was canceled.
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "PageNavigation",
      optimization_guide::FetcherRequestStatus::kRequestCanceled, 1);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       DISABLED_HintsFetcherClearFetchedHints) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();
  GURL url = https_url();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as OnePlatform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "PageNavigation",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1),
            1);

  LoadHintsForUrl(https_url());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), https_url()));

  // Verifies that the fetched hint is used in-memory and no hint is loaded
  // from store.
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintCache.HintType.Loaded", 0);

  // Wipe the browser history - clear all the fetched hints.
  browser()->GetProfile()->Wipe();

  // Wait until hint cache stabilizes and clears all the fetched hints.
  base::ThreadPoolInstance::Get()->FlushForTesting();
  base::RunLoop().RunUntilIdle();

  // Try to load the same hint to confirm fetched hints are no longer there.
  LoadHintsForUrl(https_url());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), https_url()));

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintCache.HintType.Loaded",
      static_cast<int>(optimization_guide::OptimizationGuideStore::
                           StoreEntryType::kComponentHint),
      1);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest, HintsFetcherOverrideTimer) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();
  GURL url = https_url();
  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
      optimization_guide::kFetchHintsOverrideSwitch, "whatever.com");
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      optimization_guide::kFetchHintsOverrideTimerSwitch);

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as OnePlatform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  // There should be 2 sites in the engagement service.
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "BatchUpdateActiveTabs",
      2, 1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);
  // There should have been 1 hint returned in the response.
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);

  LoadHintsForUrl(https_url());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), https_url()));

  // Verifies that the fetched hint is used from memory and no hints are loaded.
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintCache.HintType.Loaded", 0);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest, HintsFetcherFetches) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as hints fetching is enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       OnDemandFetchRepeatedlyNoCache) {
  SetNetworkConnectionOnline();

  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  OptimizationGuideKeyedService* ogks =
      OptimizationGuideKeyedServiceFactory::GetForProfile(
          browser()->GetProfile());
  ogks->RegisterOptimizationTypes(
      {optimization_guide::proto::OptimizationType::NOSCRIPT});

  GURL url_with_no_hints("https://urlwithnohints.com/notcached");

  {
    base::HistogramTester histogram_tester;
    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
    CanApplyOptimizationOnDemand(
        {url_with_no_hints},
        {optimization_guide::proto::OptimizationType::NOSCRIPT},
        base::BindRepeating(
            [](base::RunLoop* run_loop, const GURL& url,
               const base::flat_map<
                   optimization_guide::proto::OptimizationType,
                   optimization_guide::OptimizationGuideDecisionWithMetadata>&
                   decisions) {
              // Expect one decision per requested type.
              EXPECT_EQ(decisions.size(), 1u);
              auto it = decisions.find(
                  optimization_guide::proto::OptimizationType::NOSCRIPT);
              EXPECT_NE(it, decisions.end());
              EXPECT_EQ(it->second.decision,
                        optimization_guide::OptimizationGuideDecision::kFalse);

              run_loop->Quit();
            },
            run_loop.get()));
    run_loop->Run();

    histogram_tester.ExpectUniqueSample(
        "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
        "Bookmarks",
        optimization_guide::FetcherRequestStatus::kSuccess, 1);
  }

  {
    base::HistogramTester histogram_tester;
    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
    CanApplyOptimizationOnDemand(
        {url_with_no_hints},
        {optimization_guide::proto::OptimizationType::NOSCRIPT},
        base::BindRepeating(
            [](base::RunLoop* run_loop, const GURL& url,
               const base::flat_map<
                   optimization_guide::proto::OptimizationType,
                   optimization_guide::OptimizationGuideDecisionWithMetadata>&
                   decisions) {
              // Expect one decision per requested type.
              EXPECT_EQ(decisions.size(), 1u);
              auto it = decisions.find(
                  optimization_guide::proto::OptimizationType::NOSCRIPT);
              EXPECT_NE(it, decisions.end());
              EXPECT_EQ(it->second.decision,
                        optimization_guide::OptimizationGuideDecision::kFalse);

              run_loop->Quit();
            },
            run_loop.get()));
    run_loop->Run();

    // Second time should refetch since on-demand always fetches.
    histogram_tester.ExpectUniqueSample(
        "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
        "Bookmarks",
        optimization_guide::FetcherRequestStatus::kSuccess, 1);
  }
}

// Test that the hints are fetched at the time of the navigation.
IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest,
                       HintsFetcher_NavigationFetch_URLKeyedNotRefetched) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  optimization_guide::RetryForHistogramUntilCountReached(
      histogram_tester,
      optimization_guide::kComponentHintsUpdatedResultHistogramString, 1);

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
  EXPECT_EQ(1u, count_hints_requests_received());

  // Enable the connection so the page navigation race fetch is initiated.
  SetNetworkConnectionOnline();
  std::string full_url("https://foo.com/test/");
  {
    // Navigate to a host not in the seeded site engagement service; it
    // should be recorded as a race for both the host and the URL.
    base::flat_set<std::string> expected_request;
    expected_request.insert(GURL(full_url).GetHost());
    expected_request.insert(GURL(full_url).spec());
    SetExpectedHintsRequestForHostsAndUrls(expected_request);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

    EXPECT_EQ(2u, count_hints_requests_received());
    optimization_guide::RetryForHistogramUntilCountReached(
        histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        1);
    histogram_tester->ExpectUniqueSample(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
        optimization_guide::RaceNavigationFetchAttemptStatus::
            kRaceNavigationFetchHostAndURL,
        1);
  }

  // Navigate again to the same webpage, no race should occur.
  {
    // Navigate to a host that was recently fetched. It
    // should be recorded as covered by the hints fetcher.
    base::flat_set<std::string> expected_request;
    SetExpectedHintsRequestForHostsAndUrls(expected_request);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

    // With URL-keyed Hints, every unique URL navigated to will result in a
    // hints fetch if racing is enabled and allowed.
    EXPECT_EQ(2u, count_hints_requests_received());
    optimization_guide::RetryForHistogramUntilCountReached(
        histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        2);

    // Only the host will be attempted to race, the fetcher should block the
    // host from being fetched.
    histogram_tester->ExpectBucketCount(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
        optimization_guide::RaceNavigationFetchAttemptStatus::
            kRaceNavigationFetchNotAttempted,
        1);
  }

  // Incognito page loads should not initiate any fetches.
  {
    base::HistogramTester incognito_histogram_tester;
    // Instantiate off the record Optimization Guide Service.
    OptimizationGuideKeyedServiceFactory::GetForProfile(
        browser()->GetProfile()->GetPrimaryOTRProfile(
            /*create_if_needed=*/true))
        ->RegisterOptimizationTypes({optimization_guide::proto::NOSCRIPT});

    Browser* otr_browser = CreateIncognitoBrowser(browser()->GetProfile());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(otr_browser, GURL(full_url)));

    // Make sure no additional hints requests were received.
    optimization_guide::RetryForHistogramUntilCountReached(
        &incognito_histogram_tester,
        optimization_guide::kLoadedHintLocalHistogramString, 1);
    EXPECT_EQ(2u, count_hints_requests_received());

    incognito_histogram_tester.ExpectTotalCount(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus", 0);
  }
}

// Test that the hints are fetched at the time of the navigation.
IN_PROC_BROWSER_TEST_F(
    HintsFetcherBrowserTest,
    HintsFetcher_NavigationFetch_FetchWithNewlyRegisteredOptType) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  optimization_guide::RetryForHistogramUntilCountReached(
      histogram_tester,
      optimization_guide::kComponentHintsUpdatedResultHistogramString, 1);

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
  EXPECT_EQ(1u, count_hints_requests_received());

  // Enable the network connection so the page navigation race fetch is
  // initiated.
  SetNetworkConnectionOnline();
  std::string full_url("https://foo.com/test/");
  {
    // Navigate to a host not in the seeded site engagement service; it
    // should be recorded as a race for both the host and the URL.
    base::flat_set<std::string> expected_request;
    expected_request.insert(GURL(full_url).GetHost());
    expected_request.insert(GURL(full_url).spec());
    SetExpectedHintsRequestForHostsAndUrls(expected_request);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

    EXPECT_EQ(2u, count_hints_requests_received());
    optimization_guide::RetryForHistogramUntilCountReached(
        histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        1);
    histogram_tester->ExpectUniqueSample(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
        optimization_guide::RaceNavigationFetchAttemptStatus::
            kRaceNavigationFetchHostAndURL,
        1);
  }

  OptimizationGuideKeyedServiceFactory::GetForProfile(
      Profile::FromBrowserContext(browser()
                                      ->GetTabStripModel()
                                      ->GetActiveWebContents()
                                      ->GetBrowserContext()))
      ->RegisterOptimizationTypes(
          {optimization_guide::proto::COMPRESS_PUBLIC_IMAGES});

  // Navigate again to the same webpage, the race should occur because the
  // hints have been cleared.
  {
    // Navigate to a host that was recently fetched. It
    // should be recorded as covered by the hints fetcher.
    base::flat_set<std::string> expected_request;
    expected_request.insert(GURL(full_url).GetHost());
    SetExpectedHintsRequestForHostsAndUrls(expected_request);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

    // With URL-keyed Hints, every unique URL navigated to will result in a
    // hints fetch if racing is enabled and allowed.
    EXPECT_EQ(3u, count_hints_requests_received());
    optimization_guide::RetryForHistogramUntilCountReached(
        histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        2);

    histogram_tester->ExpectBucketCount(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
        optimization_guide::RaceNavigationFetchAttemptStatus::
            kRaceNavigationFetchHost,
        1);
  }
}

// Test that the hints are fetched at the time of the navigation.
IN_PROC_BROWSER_TEST_F(
    HintsFetcherBrowserTest,
    HintsFetcher_NavigationFetch_CacheNotClearedOnLaunchedOptTypes) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  optimization_guide::RetryForHistogramUntilCountReached(
      histogram_tester,
      optimization_guide::kComponentHintsUpdatedResultHistogramString, 1);

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
  EXPECT_EQ(1u, count_hints_requests_received());

  // Enable the network connection so the page navigation race fetch is
  // initiated.
  std::string full_url("https://foo.com/test/");
  {
    // Navigate to a host not in the seeded site engagement service; it
    // should be recorded as a race for both the host and the URL.
    base::flat_set<std::string> expected_request;
    expected_request.insert(GURL(full_url).GetHost());
    expected_request.insert(GURL(full_url).spec());
    SetExpectedHintsRequestForHostsAndUrls(expected_request);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

    EXPECT_EQ(2u, count_hints_requests_received());
    optimization_guide::RetryForHistogramUntilCountReached(
        histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        1);
    histogram_tester->ExpectUniqueSample(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
        optimization_guide::RaceNavigationFetchAttemptStatus::
            kRaceNavigationFetchHostAndURL,
        1);
  }

  OptimizationGuideKeyedServiceFactory::GetForProfile(
      Profile::FromBrowserContext(browser()
                                      ->GetTabStripModel()
                                      ->GetActiveWebContents()
                                      ->GetBrowserContext()))
      ->RegisterOptimizationTypes(
          {optimization_guide::proto::DEFER_ALL_SCRIPT});

  // Navigate again to the same webpage, no race should occur.
  {
    // Navigate to a host that was recently fetched. It
    // should be recorded as covered by the hints fetcher.
    base::flat_set<std::string> expected_request;
    SetExpectedHintsRequestForHostsAndUrls(expected_request);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

    // With URL-keyed Hints, every unique URL navigated to will result in a
    // hints fetch if racing is enabled and allowed.
    EXPECT_EQ(2u, count_hints_requests_received());
    optimization_guide::RetryForHistogramUntilCountReached(
        histogram_tester, optimization_guide::kLoadedHintLocalHistogramString,
        2);

    // Only the host will be attempted to race, the fetcher should block the
    // host from being fetched.
    histogram_tester->ExpectBucketCount(
        "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
        optimization_guide::RaceNavigationFetchAttemptStatus::
            kRaceNavigationFetchNotAttempted,
        1);
  }
}

IN_PROC_BROWSER_TEST_F(HintsFetcherBrowserTest, HintsFetcherDoesntFetchOnNSP) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as hints fetching is enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);

  // Initiate a NSP.
  SetNetworkConnectionOnline();
  ResetCountHintsRequestsReceived();
  TriggerNoStatePrefetch(GURL("https://someotherurl.com/"));

  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus", 0);
}

class HintsFetcherSearchPageBrowserTest : public HintsFetcherBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* cmd) override {
    cmd->AppendSwitch(
        optimization_guide::
            kDisableFetchingHintsAtNavigationStartForTestingSwitch);
    HintsFetcherBrowserTest::SetUpCommandLine(cmd);
  }
};

// TODO(crbug.com/40919396): De-leakify and re-enable.
#if BUILDFLAG(IS_LINUX) && defined(LEAK_SANITIZER)
#define MAYBE_HintsFetcher_SRP_Slow_Connection \
  DISABLED_HintsFetcher_SRP_Slow_Connection
#else
#define MAYBE_HintsFetcher_SRP_Slow_Connection HintsFetcher_SRP_Slow_Connection
#endif
IN_PROC_BROWSER_TEST_F(HintsFetcherSearchPageBrowserTest,
                       MAYBE_HintsFetcher_SRP_Slow_Connection) {
  SetNetworkConnectionOnline();

  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);

  // Populate expected hosts with hosts contained in the html response of
  // search_results_page_url(). example2.com is contained in the HTML
  // response, but hints for example2.com must not be fetched since they
  // were pushed via kFetchHintsOverride switch above.
  base::flat_set<std::string> expected_hosts_and_urls;
  // Unique hosts.
  expected_hosts_and_urls.insert(GURL("https://foo.com").GetHost());
  expected_hosts_and_urls.insert(GURL("https://example.com").GetHost());
  expected_hosts_and_urls.insert(GURL("https://example3.com").GetHost());
  // Unique URLs.
  expected_hosts_and_urls.insert("https://foo.com/");
  expected_hosts_and_urls.insert(
      "https://foo.com/simple_page_with_anchors.html");
  expected_hosts_and_urls.insert("https://example.com/foo.html");
  expected_hosts_and_urls.insert("https://example.com/bar.html");
  expected_hosts_and_urls.insert("https://example.com/baz.html");
  expected_hosts_and_urls.insert("https://example2.com/foo.html");
  expected_hosts_and_urls.insert("https://example3.com/foo.html");
  SetExpectedHintsRequestForHostsAndUrls(expected_hosts_and_urls);

  histogram_tester->ExpectTotalCount(
      optimization_guide::kLoadedHintLocalHistogramString, 0);

  // Navigate to a host not in the seeded site engagement service; it
  // should be recorded as not covered by the hints fetcher.
  ResetCountHintsRequestsReceived();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), search_results_page_url()));

  WaitUntilHintsFetcherRequestReceived();
  EXPECT_EQ(1u, count_hints_requests_received());
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "BatchUpdateGoogleSRP",
      3, 1);
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount."
      "BatchUpdateGoogleSRP",
      7, 1);
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
                "BatchUpdateGoogleSRP",
                1),
            1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "BatchUpdateGoogleSRP",
      1);
}

class HintsFetcherSearchPagePrerenderingBrowserTest
    : public HintsFetcherSearchPageBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    HintsFetcherSearchPageBrowserTest::SetUpCommandLine(command_line);
    // |prerender_helper_| has a ScopedFeatureList so we needed to delay its
    // creation until now because HintsFetcherDisabledBrowserTest also uses a
    // ScopedFeatureList and initialization order matters.
    prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>(
        base::BindRepeating(
            &HintsFetcherSearchPagePrerenderingBrowserTest::web_contents,
            base::Unretained(this)));
  }

 protected:
  content::test::PrerenderTestHelper* prerender_helper() {
    return prerender_helper_.get();
  }

  content::WebContents* web_contents() {
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }

 private:
  std::unique_ptr<content::test::PrerenderTestHelper> prerender_helper_;
};

// Tests that fetching the hints for a prerendered page is deferred until the
// page gets activated.
IN_PROC_BROWSER_TEST_F(HintsFetcherSearchPagePrerenderingBrowserTest,
                       HintsFetcherFetchedHintsLoadedAfterActivate) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Allowlist NoScript for https_url()'s' host.
  SetUpComponentUpdateHints(https_url());

  // Expect that the browser initialization will record at least one sample
  // in each of the following histograms as One Platform Hints are enabled.
  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
                "BatchUpdateActiveTabs",
                1),
            1);

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", 1),
            1);
  GURL initial_url =
      GetURLWithGoogleHost(origin_server_.get(), "/iframe_blank.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.Status", net::HTTP_OK, 1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.NetErrorCode", net::OK,
      1);
  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);

  histogram_tester->ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HintCount", 1, 1);
  histogram_tester->ExpectTotalCount(
      optimization_guide::kLoadedHintLocalHistogramString, 1);

  // Load a page in the prerender.
  GURL prerender_url = search_results_page_url();
  ResetCountHintsRequestsReceived();
  content::PrerenderHostId host_id =
      prerender_helper()->AddPrerender(prerender_url);
  EXPECT_EQ(0u, count_hints_requests_received());
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "BatchUpdateGoogleSRP",
      0, 0);
  histogram_tester->ExpectTotalCount(
      optimization_guide::kLoadedHintLocalHistogramString, 1);

  // Activate the page from the prerendering.
  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
  prerender_helper()->NavigatePrimaryPage(prerender_url);
  EXPECT_TRUE(host_observer.was_activated());
  WaitUntilHintsFetcherRequestReceived();
  EXPECT_EQ(1u, count_hints_requests_received());
  optimization_guide::RetryForHistogramUntilCountReached(
      histogram_tester, optimization_guide::kLoadedHintLocalHistogramString, 2);

  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "BatchUpdateGoogleSRP",
      3, 1);
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount."
      "BatchUpdateGoogleSRP",
      7, 1);
}

class HintsFetcherSearchPageDisabledBrowserTest
    : public HintsFetcherDisabledBrowserTest {
 public:
  HintsFetcherSearchPageDisabledBrowserTest() = default;
  void SetUp() override {
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {optimization_guide::features::kOptimizationHints, {}},
        {blink::features::kNavigationPredictor,
         {{"random_anchor_sampling_period", "1"},
          {"traffic_client_enabled_percent", "100"}}},
        {*optimization_guide::kHintsMaxConcurrentNavigationFetches.feature,
         {{optimization_guide::kHintsMaxConcurrentNavigationFetches.name,
           "2"}}},
        {optimization_guide::kHintsBatchUpdateForActiveTabsAndTopHosts, {}},
    };
    scoped_feature_list_.InitWithFeaturesAndParameters(
        enabled_features,
        // Disabled.
        {optimization_guide::features::kOptimizationGuideFetchingForSRP});
    HintsFetcherDisabledBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // Register an optimization type, so hints will be fetched at page
    // navigation.
    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->GetProfile())
        ->RegisterOptimizationTypes({optimization_guide::proto::NOSCRIPT});

    HintsFetcherDisabledBrowserTest::SetUpOnMainThread();
  }
};

IN_PROC_BROWSER_TEST_F(HintsFetcherSearchPageDisabledBrowserTest,
                       HintsFetcherStillFetchesNavigations) {
  SetNetworkConnectionOnline();
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Navigate to a search results page - should not result in a request with
  // more than 1 url.
  ResetCountHintsRequestsReceived();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), search_results_page_url()));

  // Technically the results page counts as a navigation URL.
  base::flat_set<std::string> srp_request;
  srp_request.insert(GURL(search_results_page_url()).GetHost());
  srp_request.insert(GURL(search_results_page_url()).spec());
  SetExpectedHintsRequestForHostsAndUrls(srp_request);
  EXPECT_EQ(1u, count_hints_requests_received());
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "PageNavigation",
      1, 1);
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount."
      "PageNavigation",
      1, 1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "PageNavigation",
      1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "BatchUpdateGoogleSRP",
      0);

  // Now go to a regular URL.
  ResetCountHintsRequestsReceived();
  std::string full_url = "https://foo.com/test/";
  base::flat_set<std::string> expected_request;
  expected_request.insert(GURL(full_url).GetHost());
  expected_request.insert(GURL(full_url).spec());
  SetExpectedHintsRequestForHostsAndUrls(expected_request);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(full_url)));

  optimization_guide::RetryForHistogramUntilCountReached(
      histogram_tester, optimization_guide::kLoadedHintLocalHistogramString, 1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "PageNavigation",
      2);
  EXPECT_EQ(1u, count_hints_requests_received());
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "PageNavigation",
      1, 2);
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount."
      "PageNavigation",
      1, 2);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "PageNavigation",
      2);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "BatchUpdateGoogleSRP",
      0);
}

// Tests that OptimizationGuideWebContentsObserver limits the results for SRP.
//
// Note that `OptimizationGuideWebContentsObserver::FetchHintsUsingManager`
// limits the urls to `optimization_guide::features::MaxResultsForSRPFetch()`.
// In this test, this method is called with the following URLs but the order
// varies:
//
// - https://example.com/bar.html
// - https://example.com/baz.html
// - https://example.com/foo.html
// - https://example2.com/foo.html
// - https://example3.com/foo.html
// - https://foo.com/
// - https://foo.com/simple_page_with_anchors.html
//
// If we use `max_urls_for_srp_fetch > 1`, the result of
// `OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount` varies. So, we use
// `max_urls_for_srp_fetch = 1`.
class HintsFetcherSearchPageLimitedURLsBrowserTest
    : public HintsFetcherDisabledBrowserTest {
 public:
  HintsFetcherSearchPageLimitedURLsBrowserTest() = default;

  void SetUp() override {
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        {optimization_guide::features::kOptimizationHints, {}},
        {
            optimization_guide::features::kOptimizationGuideFetchingForSRP,
            {{"max_urls_for_srp_fetch", "1"}},
        },
        {*optimization_guide::kHintsMaxConcurrentNavigationFetches.feature,
         {{optimization_guide::kHintsMaxConcurrentNavigationFetches.name,
           "2"}}},
        {optimization_guide::kHintsBatchUpdateForActiveTabsAndTopHosts, {}},
    };
    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});

    HintsFetcherDisabledBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* cmd) override {
    cmd->AppendSwitch(
        optimization_guide::
            kDisableFetchingHintsAtNavigationStartForTestingSwitch);
    HintsFetcherDisabledBrowserTest::SetUpCommandLine(cmd);
  }

  void SetUpOnMainThread() override {
    // Register an optimization type, so hints will be fetched at page
    // navigation.
    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->GetProfile())
        ->RegisterOptimizationTypes({optimization_guide::proto::NOSCRIPT});

    HintsFetcherDisabledBrowserTest::SetUpOnMainThread();
  }
};

// TODO(crbug.com/40067071): Disable limited SRP test on Windows/CrOS for now.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_HintsFetcherLimitedResults DISABLED_HintsFetcherLimitedResults
#else
#define MAYBE_HintsFetcherLimitedResults HintsFetcherLimitedResults
#endif
IN_PROC_BROWSER_TEST_F(HintsFetcherSearchPageLimitedURLsBrowserTest,
                       MAYBE_HintsFetcherLimitedResults) {
  SetNetworkConnectionOnline();
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  const base::HistogramTester* histogram_tester = GetHistogramTester();

  // Navigate to a search results page - should result a batch request with up
  // to 2 urls.
  ResetCountHintsRequestsReceived();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), search_results_page_url()));
  WaitUntilHintsFetcherRequestReceived();

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
                "BatchUpdateGoogleSRP",
                1),
            1);
  EXPECT_EQ(1u, count_hints_requests_received());
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount."
      "BatchUpdateGoogleSRP",
      1, 1);
  histogram_tester->ExpectBucketCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.UrlCount."
      "BatchUpdateGoogleSRP",
      1, 1);
  histogram_tester->ExpectTotalCount(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "BatchUpdateGoogleSRP",
      1);
}

class PersonalizedHintsFetcherBrowserTest : public HintsFetcherBrowserTest {
 public:
  PersonalizedHintsFetcherBrowserTest() = default;

  PersonalizedHintsFetcherBrowserTest(
      const PersonalizedHintsFetcherBrowserTest&) = delete;
  PersonalizedHintsFetcherBrowserTest& operator=(
      const HintsFetcherBrowserTest&) = delete;

  ~PersonalizedHintsFetcherBrowserTest() override = default;

  void SetUpBrowserContextKeyedServices(
      content::BrowserContext* context) override {
    HintsFetcherBrowserTest::SetUpBrowserContextKeyedServices(context);
    IdentityTestEnvironmentProfileAdaptor::
        SetIdentityTestEnvironmentFactoriesOnBrowserContext(context);
  }

  void SetUpOnMainThread() override {
    HintsFetcherBrowserTest::SetUpOnMainThread();
    identity_test_env_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
            browser()->GetProfile());
  }

  void EnableSignin() {
    identity_test_env_adaptor_->identity_test_env()
        ->MakePrimaryAccountAvailable("user@gmail.com",
                                      signin::ConsentLevel::kSignin);
    identity_test_env_adaptor_->identity_test_env()
        ->SetAutomaticIssueOfAccessTokens(true);
  }

  void CanApplyOptimizationOnDemand(
      optimization_guide::proto::OptimizationType optimization_type,
      optimization_guide::proto::RequestContext request_context) {
    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
    OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->GetProfile())
        ->CanApplyOptimizationOnDemand(
            {GURL("https://example.com")}, {optimization_type}, request_context,
            base::BindRepeating(
                [](optimization_guide::proto::OptimizationType
                       optimization_type,
                   base::RunLoop* run_loop, const GURL& url,
                   const base::flat_map<
                       optimization_guide::proto::OptimizationType,
                       optimization_guide::
                           OptimizationGuideDecisionWithMetadata>& decisions) {
                  // Expect one decision per requested type.
                  EXPECT_EQ(decisions.size(), 1u);
                  auto it = decisions.find(optimization_type);
                  EXPECT_NE(it, decisions.end());
                  EXPECT_EQ(
                      it->second.decision,
                      optimization_guide::OptimizationGuideDecision::kFalse);

                  run_loop->Quit();
                },
                optimization_type, run_loop.get()));
    run_loop->Run();
  }

 private:
  // Identity test support.
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_env_adaptor_;
};

IN_PROC_BROWSER_TEST_F(PersonalizedHintsFetcherBrowserTest, NoUserSignIn) {
  base::HistogramTester histogram_tester;
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  // No access token set when user is not signed in.
  SetExpectedBearerAccessToken(std::string());
  CanApplyOptimizationOnDemand(
      optimization_guide::proto::OptimizationType::NOSCRIPT,
      optimization_guide::proto::CONTEXT_PAGE_INSIGHTS_HUB);
  histogram_tester.ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "PageInsightsHub",
      optimization_guide::FetcherRequestStatus::kSuccess, 1);
}

IN_PROC_BROWSER_TEST_F(PersonalizedHintsFetcherBrowserTest, UserSignedIn) {
  base::HistogramTester histogram_tester;
  EnableSignin();
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  SetExpectedBearerAccessToken("Bearer access_token");
  CanApplyOptimizationOnDemand(
      optimization_guide::proto::OptimizationType::NOSCRIPT,
      optimization_guide::proto::CONTEXT_PAGE_INSIGHTS_HUB);
  histogram_tester.ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
      "PageInsightsHub",
      optimization_guide::FetcherRequestStatus::kSuccess, 1);

  // Only the enabled RequestContext will have access token enabled.
  SetExpectedBearerAccessToken(std::string());
  CanApplyOptimizationOnDemand(
      optimization_guide::proto::OptimizationType::NOSCRIPT,
      optimization_guide::proto::CONTEXT_JOURNEYS);
  histogram_tester.ExpectUniqueSample(
      "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus.Journeys",
      optimization_guide::FetcherRequestStatus::kSuccess, 1);
}

// TODO(crbug.com/425936619): Add checks for url, hint counts, context.
class ProactivePersonalizationHintsFetcherBrowserTest
    : public HintsFetcherBrowserTest {
 public:
  ProactivePersonalizationHintsFetcherBrowserTest() = default;

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

  ~ProactivePersonalizationHintsFetcherBrowserTest() override = default;

  void SetUpBrowserContextKeyedServices(
      content::BrowserContext* context) override {
    HintsFetcherBrowserTest::SetUpBrowserContextKeyedServices(context);
    IdentityTestEnvironmentProfileAdaptor::
        SetIdentityTestEnvironmentFactoriesOnBrowserContext(context);
  }

  void SetUpOnMainThread() override {
    HintsFetcherBrowserTest::SetUpOnMainThread();
    identity_test_env_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
            browser()->GetProfile());
  }

  void PopulateEnabledFeatures(
      std::vector<base::test::FeatureRefAndParams>* enabled_features) override {
    base::FieldTrialParams personalized_fetching_params = GetFieldTrialParams();
    enabled_features->emplace_back(
        optimization_guide::features::
            kOptimizationGuideProactivePersonalizedHintsFetching,
        personalized_fetching_params);
  }

  virtual base::FieldTrialParams GetFieldTrialParams() {
    return {
        {"allowed_optimization_types", "NOSCRIPT"},
    };
  }

  void EnableSignin() {
    identity_test_env_adaptor_->identity_test_env()
        ->MakePrimaryAccountAvailable("user@gmail.com",
                                      signin::ConsentLevel::kSignin);
    identity_test_env_adaptor_->identity_test_env()
        ->SetAutomaticIssueOfAccessTokens(true);
  }

 private:
  // Identity test support.
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_env_adaptor_;
};

// Verify access token is attached during navigation fetching if a
// personalizable optimization type is requested.
IN_PROC_BROWSER_TEST_F(ProactivePersonalizationHintsFetcherBrowserTest,
                       OnNavigationFetchesWithAccessToken) {
  SetNetworkConnectionOnline();
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  ResetCountHintsRequestsReceived();
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");

  GURL full_url = GURL("https://foo.com/test/");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), full_url));

  base::flat_set<std::string> expected_request;
  expected_request.insert(full_url.GetHost());
  expected_request.insert(full_url.spec());
  SetExpectedHintsRequestForHostsAndUrls(expected_request);
  EXPECT_EQ(1u, count_hints_requests_received());
}

// Verify access token is attached during active tab fetching if a
// personalizable optimization type is requested.
IN_PROC_BROWSER_TEST_F(ProactivePersonalizationHintsFetcherBrowserTest,
                       ActiveTabFetchesWithAccessToken) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();
  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");

  SetUpComponentUpdateHints(https_url());

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
                "BatchUpdateActiveTabs",
                1),
            1);
}

// Verify access token is attached during url fetching if a
// personalizable optimization type is requested.
// TODO(crbug.com/40919396): De-leakify and re-enable.
// TODO(crbug.com/520436633): Fix timeout issues before re-enabling likely due
// to the wait call.
#if (BUILDFLAG(IS_LINUX) && defined(LEAK_SANITIZER)) || \
    (BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER))
#define MAYBE_FetchingUrlFetchesWithAccessToken \
  DISABLED_FetchingUrlFetchesWithAccessToken
#else
#define MAYBE_FetchingUrlFetchesWithAccessToken \
  FetchingUrlFetchesWithAccessToken
#endif
IN_PROC_BROWSER_TEST_F(ProactivePersonalizationHintsFetcherBrowserTest,
                       MAYBE_FetchingUrlFetchesWithAccessToken) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  EnableSignin();
  SetExpectedBearerAccessToken("Bearer access_token");

  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), search_results_page_url()));

  WaitUntilHintsFetcherRequestReceived();

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
                "BatchUpdateGoogleSRP",
                1),
            1);
}

class ProactivePersonalizationNoAllowedTypesHintsFetcherBrowserTest
    : public ProactivePersonalizationHintsFetcherBrowserTest {
 public:
  base::FieldTrialParams GetFieldTrialParams() override {
    return {
        {"allowed_optimization_types", "PERFORMANCE_HINTS"},
    };
  }
};

// Verify access token is not attached during navigation fetching if no
// personalizable optimization type is requested.
IN_PROC_BROWSER_TEST_F(
    ProactivePersonalizationNoAllowedTypesHintsFetcherBrowserTest,
    OnNavigationDoesNotFetchAccessToken) {
  SetNetworkConnectionOnline();
  SetResponseType(
      optimization_guide::HintsFetcherRemoteResponseType::kSuccessful);

  ResetCountHintsRequestsReceived();
  EnableSignin();
  SetExpectedBearerAccessToken(std::string());

  GURL full_url = GURL("https://foo.com/test/");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), full_url));

  base::flat_set<std::string> expected_request;
  expected_request.insert(full_url.GetHost());
  expected_request.insert(full_url.spec());
  SetExpectedHintsRequestForHostsAndUrls(expected_request);
  EXPECT_EQ(1u, count_hints_requests_received());
}

// Verify access token is not attached during active tab fetching if no
// personalizable optimization type is requested.
IN_PROC_BROWSER_TEST_F(
    ProactivePersonalizationNoAllowedTypesHintsFetcherBrowserTest,
    ActiveTabDoesNotFetchWithAccessToken) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();
  EnableSignin();
  SetExpectedBearerAccessToken(std::string());

  SetUpComponentUpdateHints(https_url());

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
                "BatchUpdateActiveTabs",
                1),
            1);
}

// Verify access token is not attached during url fetching if no personalizable
// optimization type is requested.
// TODO(crbug.com/40919396): De-leakify and re-enable.
#if BUILDFLAG(IS_LINUX) && defined(LEAK_SANITIZER)
#define MAYBE_FetchingUrlDoesNotFetchWithAccessToken \
  DISABLED_FetchingUrlDoesNotFetchWithAccessToken
#else
#define MAYBE_FetchingUrlDoesNotFetchWithAccessToken \
  FetchingUrlDoesNotFetchWithAccessToken
#endif
IN_PROC_BROWSER_TEST_F(
    ProactivePersonalizationNoAllowedTypesHintsFetcherBrowserTest,
    MAYBE_FetchingUrlDoesNotFetchWithAccessToken) {
  const base::HistogramTester* histogram_tester = GetHistogramTester();

  EnableSignin();
  SetExpectedBearerAccessToken(std::string());

  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), search_results_page_url()));

  WaitUntilHintsFetcherRequestReceived();

  EXPECT_GE(optimization_guide::RetryForHistogramUntilCountReached(
                histogram_tester,
                "OptimizationGuide.HintsFetcher.GetHintsRequest.RequestStatus."
                "BatchUpdateGoogleSRP",
                1),
            1);
}
