// Copyright 2014 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/omnibox/browser/zero_suggest_provider.h"

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

#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/history/core/browser/top_sites.h"
#include "components/lens/proto/server/lens_overlay_response.pb.h"
#include "components/omnibox/browser/autocomplete_provider_listener.h"
#include "components/omnibox/browser/mock_aim_eligibility_service.h"
#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
#include "components/omnibox/browser/omnibox_field_trial.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/suggest_inventory_fallback_utils.h"
#include "components/omnibox/browser/test_scheme_classifier.h"
#include "components/omnibox/browser/zero_suggest_cache_service.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/search_engines/search_engines_test_environment.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "components/variations/variations_associated_data.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/metrics_proto/omnibox_focus_type.pb.h"
#include "url/gurl.h"

using testing::_;
using CacheEntry = ZeroSuggestCacheService::CacheEntry;
constexpr bool is_ios = !!BUILDFLAG(IS_IOS);

namespace {

class FakeAutocompleteProviderClient : public MockAutocompleteProviderClient {
 public:
  FakeAutocompleteProviderClient() {
    ZeroSuggestProvider::RegisterProfilePrefs(
        search_engines_test_environment_.pref_service().registry());
    AimEligibilityService::RegisterProfilePrefs(
        search_engines_test_environment_.pref_service().registry());
    zero_suggest_cache_service_ = std::make_unique<ZeroSuggestCacheService>(
        std::make_unique<TestSchemeClassifier>(),
        &search_engines_test_environment_.pref_service());
  }
  FakeAutocompleteProviderClient(const FakeAutocompleteProviderClient&) =
      delete;
  FakeAutocompleteProviderClient& operator=(
      const FakeAutocompleteProviderClient&) = delete;

  bool SearchSuggestEnabled() const override { return true; }

  TemplateURLService* GetTemplateURLService() override {
    return search_engines_test_environment_.template_url_service();
  }

  const TemplateURLService* GetTemplateURLService() const override {
    return search_engines_test_environment_.template_url_service();
  }

  PrefService* GetPrefs() const override {
    return &search_engines_test_environment_.pref_service();
  }

  ZeroSuggestCacheService* GetZeroSuggestCacheService() override {
    return zero_suggest_cache_service_.get();
  }

  const ZeroSuggestCacheService* GetZeroSuggestCacheService() const override {
    return zero_suggest_cache_service_.get();
  }

  bool IsUrlDataCollectionActive() const override {
    return is_url_data_collection_active_;
  }

  void set_is_url_data_collection_active(bool is_url_data_collection_active) {
    is_url_data_collection_active_ = is_url_data_collection_active;
  }

  void Classify(
      const std::u16string& text,
      bool in_keyword_mode,
      bool allow_exact_keyword_match,
      metrics::OmniboxEventProto::PageClassification page_classification,
      AutocompleteMatch* match,
      GURL* alternate_nav_url) override {
    // Populate enough of |match| to keep the ZeroSuggestProvider happy.
    match->type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
    match->destination_url = GURL(text);
  }

  const AutocompleteSchemeClassifier& GetSchemeClassifier() const override {
    return scheme_classifier_;
  }

  AimEligibilityService* GetAimEligibilityService() const override {
    return aim_eligibility_service_;
  }

  void set_aim_eligibility_service(AimEligibilityService* service) {
    aim_eligibility_service_ = service;
  }

 private:
  search_engines::SearchEnginesTestEnvironment search_engines_test_environment_;
  bool is_url_data_collection_active_;
  std::unique_ptr<ZeroSuggestCacheService> zero_suggest_cache_service_;
  TestSchemeClassifier scheme_classifier_;
  raw_ptr<AimEligibilityService> aim_eligibility_service_ = nullptr;
};

}  // namespace

class ZeroSuggestProviderTest : public testing::Test,
                                public AutocompleteProviderListener {
 public:
  ZeroSuggestProviderTest() = default;
  ZeroSuggestProviderTest(const ZeroSuggestProviderTest&) = delete;
  ZeroSuggestProviderTest& operator=(const ZeroSuggestProviderTest&) = delete;

  void SetUp() override;

 protected:
  // AutocompleteProviderListener:
  void OnProviderUpdate(bool updated_matches,
                        const AutocompleteProvider* provider) override;

  network::TestURLLoaderFactory* test_loader_factory() {
    return client_->test_url_loader_factory();
  }

  GURL GetSuggestURL(
      metrics::OmniboxEventProto::PageClassification page_classification,
      metrics::OmniboxFocusType focus_type,
      const std::string& page_url,
      TemplateURLRef::RequestSource request_source =
          TemplateURLRef::RequestSource::SEARCHBOX,
      omnibox::SuggestInventory suggest_inventory =
          omnibox::SuggestInventory::SUGGEST_INVENTORY_DEFAULT) {
    TemplateURLRef::SearchTermsArgs search_terms_args;
    search_terms_args.page_classification = page_classification;
    search_terms_args.focus_type = focus_type;
    search_terms_args.current_page_url = page_url;
    search_terms_args.request_source = request_source;
    search_terms_args.suggest_inventory = suggest_inventory;

    TemplateURLService* template_url_service = client_->GetTemplateURLService();
    return RemoteSuggestionsService::EndpointUrl(
        *template_url_service->GetDefaultSearchProvider(), search_terms_args,
        template_url_service->search_terms_data());
  }

  GURL GetProviderRequestURL(const AutocompleteInput& input) {
    network::ResourceRequest resource_request;
    // Intercept the request to determine full URL actually used by provider.
    test_loader_factory()->SetInterceptor(base::BindLambdaForTesting(
        [&](const network::ResourceRequest& request) {
          resource_request = request;
        }));
    provider_->Start(input, false);
    EXPECT_TRUE(
        base::test::RunUntil([&] { return !resource_request.url.is_empty(); }));
    return resource_request.url;
  }

  // An AutocompleteInput that gets Zero Prefix Suggestions on NTP.
  AutocompleteInput ZeroPrefixInputForNTP(
      const bool is_prefetch,
      const bool user_input_in_progress = false) {
    AutocompleteInput input(u"",
                            is_prefetch
                                ? metrics::OmniboxEventProto::NTP_ZPS_PREFETCH
                                : metrics::OmniboxEventProto::NTP_REALBOX,
                            TestSchemeClassifier());
    input.set_focus_type(user_input_in_progress
                             ? metrics::OmniboxFocusType::INTERACTION_DEFAULT
                             : metrics::OmniboxFocusType::INTERACTION_FOCUS);
    return input;
  }

  // An AutocompleteInput that gets Prefix Suggestions on NTP.
  AutocompleteInput PrefixInputForNTP() {
    AutocompleteInput input(u"foobar", metrics::OmniboxEventProto::NTP_REALBOX,
                            TestSchemeClassifier());
    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_DEFAULT);
    return input;
  }

  // An AutocompleteInput that gets Zero Prefix Suggestions on WEB.
  AutocompleteInput ZeroPrefixInputForWeb(
      const bool is_prefetch,
      const bool user_input_in_progress = false,
      const std::string& input_url = "https://example.com/",
      const std::u16string& input_title = u"Example / Page") {
    // On IOS WEB/SRP, input text is not empty.
    AutocompleteInput input(is_ios ? base::ASCIIToUTF16(input_url) : u"",
                            is_prefetch
                                ? metrics::OmniboxEventProto::OTHER_ZPS_PREFETCH
                                : metrics::OmniboxEventProto::OTHER,
                            TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_current_title(input_title);
    input.set_focus_type(user_input_in_progress
                             ? metrics::OmniboxFocusType::INTERACTION_DEFAULT
                             : metrics::OmniboxFocusType::INTERACTION_FOCUS);
    return input;
  }

  // An AutocompleteInput that gets Prefix Suggestions on WEB.
  AutocompleteInput PrefixInputForWeb(
      const std::string& input_url = "https://example.com/") {
    AutocompleteInput input(u"foobar", metrics::OmniboxEventProto::OTHER,
                            TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_DEFAULT);
    return input;
  }

  // An AutocompleteInput that gets Zero Prefix Suggestions on SRP.
  AutocompleteInput ZeroPrefixInputForSRP(
      const bool is_prefetch,
      const bool user_input_in_progress = false,
      const std::string& input_url = "https://www.google.com/search?q=foo",
      const std::u16string& input_title = u"foo - Google Search") {
    AutocompleteInput input(
        // On IOS WEB/SRP, input text is not empty.
        is_ios ? base::ASCIIToUTF16(input_url) : u"",
        is_prefetch ? metrics::OmniboxEventProto::SRP_ZPS_PREFETCH
                    : metrics::OmniboxEventProto::
                          SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
        TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_current_title(input_title);
    input.set_focus_type(user_input_in_progress
                             ? metrics::OmniboxFocusType::INTERACTION_DEFAULT
                             : metrics::OmniboxFocusType::INTERACTION_FOCUS);
    return input;
  }

  // An AutocompleteInput that gets Prefix Suggestions on SRP.
  AutocompleteInput PrefixInputForSRP(
      const std::string& input_url = "https://www.google.com/search?q=foo") {
    AutocompleteInput input(u"foobar",
                            metrics::OmniboxEventProto::
                                SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
                            TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_DEFAULT);
    return input;
  }

  AutocompleteInput ZeroPrefixInputForLens(
      const std::string& input_url = "https://example.com/") {
    AutocompleteInput input(
        u"", metrics::OmniboxEventProto::LENS_SIDE_PANEL_SEARCHBOX,
        TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
    return input;
  }

  AutocompleteInput ZeroPrefixInputForComposebox(
      const std::string& input_url = "https://example.com/") {
    AutocompleteInput input(u"", metrics::OmniboxEventProto::NTP_COMPOSEBOX,
                            TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
    return input;
  }

  AutocompleteInput ZeroPrefixInputForOmniboxComposebox(
      const std::string& input_url = "https://example.com/") {
    AutocompleteInput input(u"",
                            metrics::OmniboxEventProto::NTP_OMNIBOX_COMPOSEBOX,
                            TestSchemeClassifier());
    input.set_current_url(GURL(input_url));
    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
    return input;
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
  variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
      variations::VariationsIdsProvider::Mode::kUseSignedInState};
  std::unique_ptr<FakeAutocompleteProviderClient> client_;
  scoped_refptr<ZeroSuggestProvider> provider_;
  bool provider_did_notify_;
};

void ZeroSuggestProviderTest::SetUp() {
  client_ = std::make_unique<FakeAutocompleteProviderClient>();

  // Activate URL data collection.
  client_->set_is_url_data_collection_active(true);

  TemplateURLService* template_url_service = client_->GetTemplateURLService();
  template_url_service->Load();

  // Verify that Google is the default search provider.
  ASSERT_EQ(SEARCH_ENGINE_GOOGLE,
            template_url_service->GetDefaultSearchProvider()->GetEngineType(
                template_url_service->search_terms_data()));

  provider_ = ZeroSuggestProvider::Create(client_.get(), this);
  provider_did_notify_ = false;

  // Ensure the prefs-based cache is empty.
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, "");
  prefs->SetDict(omnibox::kZeroSuggestCachedResultsWithURL, base::DictValue());

  // Ensure the cache is empty.
  ZeroSuggestCacheService* cache_svc = client_->GetZeroSuggestCacheService();
  cache_svc->ClearCache();

  scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
  scoped_feature_list_->InitWithFeatures(
      /*enabled_features=*/{omnibox::kZeroSuggestPrefetchingOnSRP,
                            omnibox::kZeroSuggestPrefetchingOnWeb},
      /*disabled_features=*/{});
}

void ZeroSuggestProviderTest::OnProviderUpdate(
    bool updated_matches,
    const AutocompleteProvider* provider) {
  provider_did_notify_ = true;
}

// Tests whether zero-suggest is allowed on NTP.
TEST_F(ZeroSuggestProviderTest, AllowZeroPrefixSuggestionsNTP) {
  AutocompleteInput zero_prefix_ntp_input =
      ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  AutocompleteInput prefix_ntp_input = PrefixInputForNTP();

  // zero-suggest suggestions are allowed on NTP.
  {
    EXPECT_EQ(
        std::make_pair(ZeroSuggestProvider::ResultType::kRemoteNoURL, true),
        ZeroSuggestProvider::GetResultTypeAndEligibility(
            client_.get(), zero_prefix_ntp_input));

    EXPECT_EQ(std::make_pair(ZeroSuggestProvider::ResultType::kNone, false),
              ZeroSuggestProvider::GetResultTypeAndEligibility(
                  client_.get(), prefix_ntp_input));
  }
}

TEST_F(ZeroSuggestProviderTest, AllowZeroPrefixSuggestionsOnSearchActivity) {
  AutocompleteInput input(u"",
                          metrics::OmniboxEventProto::ANDROID_SHORTCUTS_WIDGET,
                          TestSchemeClassifier());
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Offer ZPS when the user focus the omnibox.
  EXPECT_EQ(
      std::make_pair(ZeroSuggestProvider::ResultType::kRemoteNoURL, true),
      ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input));

  // Don't offer ZPS when the user is typing.
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_DEFAULT);
  EXPECT_EQ(
      std::make_pair(ZeroSuggestProvider::ResultType::kNone, false),
      ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input));
}

TEST_F(ZeroSuggestProviderTest, AllowZeroPrefixSuggestionsOnOmniboxEverywhere) {
  AutocompleteInput input(u"", metrics::OmniboxEventProto::OMNIBOX_EVERYWHERE,
                          TestSchemeClassifier());
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Offer ZPS when the user focuses the omnibox.
  EXPECT_EQ(
      std::make_pair(ZeroSuggestProvider::ResultType::kRemoteNoURL, true),
      ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input));

  // Don't offer ZPS when the user is typing.
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_DEFAULT);
  EXPECT_EQ(
      std::make_pair(ZeroSuggestProvider::ResultType::kNone, false),
      ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input));
}

// Tests whether zero-suggest is allowed on Web/SRP when the external request
// conditions are met.
TEST_F(ZeroSuggestProviderTest, AllowZeroPrefixSuggestionsContextualWebAndSRP) {
  AutocompleteInput prefix_web_input = PrefixInputForWeb();
  AutocompleteInput prefix_srp_input = PrefixInputForSRP();
  AutocompleteInput zero_prefix_web_input =
      ZeroPrefixInputForWeb(/*is_prefetch=*/false);
  AutocompleteInput zero_prefix_srp_input =
      ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  AutocompleteInput zero_prefix_lens_input = ZeroPrefixInputForLens();

  {
    EXPECT_EQ(std::make_pair(ZeroSuggestProvider::ResultType::kNone, false),
              ZeroSuggestProvider::GetResultTypeAndEligibility(
                  client_.get(), prefix_web_input));

    EXPECT_EQ(std::make_pair(ZeroSuggestProvider::ResultType::kNone, false),
              ZeroSuggestProvider::GetResultTypeAndEligibility(
                  client_.get(), prefix_srp_input));

    EXPECT_EQ(
        std::make_pair(ZeroSuggestProvider::ResultType::kRemoteNoURL, true),
        ZeroSuggestProvider::GetResultTypeAndEligibility(
            client_.get(), zero_prefix_lens_input));

    EXPECT_EQ(
        std::make_pair(ZeroSuggestProvider::ResultType::kRemoteSendURL, true),
        ZeroSuggestProvider::GetResultTypeAndEligibility(
            client_.get(), zero_prefix_srp_input));

    EXPECT_EQ(
        std::make_pair(ZeroSuggestProvider::ResultType::kRemoteSendURL, true),
        ZeroSuggestProvider::GetResultTypeAndEligibility(
            client_.get(), zero_prefix_web_input));
  }
}

// Tests whether zero-suggest is allowed on NTP/Web/SRP with various external
TEST_F(ZeroSuggestProviderTest, AllowZeroPrefixSuggestionsRequestEligibility) {
  using ResultType = ZeroSuggestProvider::ResultType;

  // Keep a reference to the Google default search provider.
  TemplateURLService* template_url_service = client_->GetTemplateURLService();
  const TemplateURL* google_provider =
      template_url_service->GetDefaultSearchProvider();

  // Benchmark test for NTP.
  auto test_ntp = [this](bool user_input_in_progress = false,
                         ResultType expected_result_type =
                             ResultType::kRemoteNoURL) {
    const auto& input = ZeroPrefixInputForNTP(
        /*is_prefetch=*/false,
        /*user_input_in_progress=*/user_input_in_progress);
    const auto [result_type, eligible] =
        ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input);
    EXPECT_EQ(expected_result_type, result_type);
    return eligible;
  };

  // Benchmark test for Lens searchboxes.
  auto test_lens = [this]() {
    const auto& input = ZeroPrefixInputForLens();
    const auto [result_type, eligible] =
        ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input);
    EXPECT_EQ(ResultType::kRemoteNoURL, result_type);
    return eligible;
  };

  // Benchmark test for valid page URL.
  auto test_other = [this](bool user_input_in_progress = false,
                           ResultType expected_result_type =
                               ResultType::kRemoteSendURL) {
    const auto& input = ZeroPrefixInputForWeb(
        /*is_prefetch=*/false,
        /*user_input_in_progress=*/user_input_in_progress);
    const auto [result_type, eligible] =
        ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input);
    EXPECT_EQ(expected_result_type, result_type);
    return eligible;
  };

  // Benchmark test for Search Results Page URL.
  auto test_srp = [this](const TemplateURL* template_url,
                         bool user_input_in_progress = false,
                         ResultType expected_result_type =
                             ResultType::kRemoteSendURL) {
    const auto& input = ZeroPrefixInputForSRP(
        /*is_prefetch=*/false,
        /*user_input_in_progress=*/user_input_in_progress,
        /*input_url= */
        template_url->GenerateSearchURL(SearchTermsData()).spec());
    const auto [result_type, eligible] =
        ZeroSuggestProvider::GetResultTypeAndEligibility(client_.get(), input);
    EXPECT_EQ(expected_result_type, result_type);
    return eligible;
  };

  // Enable URL data collection.
  client_->set_is_url_data_collection_active(true);

  {
    // Zero-suggest is generally not allowed for invalid or non-HTTP(S) URLs.
    AutocompleteInput on_focus_ineligible_url_input = ZeroPrefixInputForWeb(
        /*is_prefetch=*/false,
        /*user_input_in_progress=*/false,
        /*input_url= */ "chrome://history");
    EXPECT_EQ(std::make_pair(ResultType::kNone, false),
              ZeroSuggestProvider::GetResultTypeAndEligibility(
                  client_.get(), on_focus_ineligible_url_input));
  }
  {
    // Zero-suggest is generally not allowed for non-empty inputs.
    EXPECT_EQ(std::make_pair(ResultType::kNone, false),
              ZeroSuggestProvider::GetResultTypeAndEligibility(
                  client_.get(), PrefixInputForNTP()));
  }
  {
    // Zero-suggest is generally not allowed for non-empty inputs.
    EXPECT_EQ(std::make_pair(ResultType::kNone, false),
              ZeroSuggestProvider::GetResultTypeAndEligibility(
                  client_.get(), PrefixInputForSRP()));
  }
  {
    // Zero-suggest request can be made on NTP.
    EXPECT_TRUE(test_ntp());

    // Non-zero-prefix input should NOT result in zero-suggest request.
    EXPECT_FALSE(test_ntp(/*user_input_in_progress=*/true,
                          /*expected_result_type=*/ResultType::kNone));
  }
  {
    // Zero-suggest request can be made from Lens searchboxes.
    EXPECT_TRUE(test_lens());
  }

  {
    // Valid SRP URLs can be sent in the zero-suggest request.
    EXPECT_TRUE(test_srp(google_provider));

    // Non-zero-prefix input should NOT result in a zero-suggest request.
    EXPECT_FALSE(test_srp(google_provider,
                          /*user_input_in_progress=*/true,
                          /*expected_result_type=*/ResultType::kNone));
  }
  {
    // Valid page URLs can be sent in the zero-suggest request.
    EXPECT_TRUE(test_other());

    // Non-zero-prefix input should NOT result in a zero-suggest request.
    EXPECT_FALSE(test_other(/*user_input_in_progress=*/true,
                            /*expected_result_type=*/ResultType::kNone));
  }

  // Deactivate URL data collection. This ensures that the page URL
  // cannot be sent and zero-suggest is disallowed unless the URL is the SRP or
  // the request is being made from the Lens searchboxes.
  client_->set_is_url_data_collection_active(false);

  {
    // Zero-suggest request can be made on NTP.
    EXPECT_TRUE(test_ntp());
  }
  {
    // Zero-suggest request can be made from Lens searchboxes.
    EXPECT_TRUE(test_lens());
  }
  {
    // Valid SRP URLs can be sent in the zero-suggest request.
    EXPECT_TRUE(test_srp(google_provider));
  }
  {
    // Valid page URLs cannot be sent in the zero-suggest request.
    EXPECT_FALSE(test_other());
  }

  // Reactivate URL data collection.
  client_->set_is_url_data_collection_active(true);

  // Change the default search provider to a non-Google one.
  TemplateURLData non_google_provider_data;
  non_google_provider_data.SetURL("https://www.nongoogle.com/?q={searchTerms}");
  non_google_provider_data.suggestions_url =
      "https://www.nongoogle.com/suggest/?q={searchTerms}";
  auto* non_google_provider = template_url_service->Add(
      std::make_unique<TemplateURL>(non_google_provider_data));
  template_url_service->SetUserSelectedDefaultSearchProvider(
      non_google_provider);

  {
    // Zero-suggest request cannot be made on NTP.
    EXPECT_FALSE(test_ntp());
  }
  {
    // Zero-suggest request cannot be made from Lens searchboxes.
    EXPECT_FALSE(test_lens());
  }
  {
    // Valid SRP URLs cannot be sent in the zero-suggest request.
    EXPECT_FALSE(test_srp(non_google_provider));
  }
  {
    // Valid page URLs cannot be sent in the zero-suggest request.
    EXPECT_FALSE(test_other());
  }

  // Change the default search provider back to Google.
  template_url_service->SetUserSelectedDefaultSearchProvider(
      const_cast<TemplateURL*>(google_provider));
}

TEST_F(ZeroSuggestProviderTest, EligibilityHistogram) {
  {
    base::HistogramTester histogram_tester;

    provider_->Start(ZeroPrefixInputForNTP(/*is_prefetch=*/false), false);

    // Make sure the default provider's suggest endpoint was queried without the
    // current page URL.
    EXPECT_FALSE(provider_->done());
    GURL suggest_url = GetSuggestURL(
        metrics::OmniboxEventProto::NTP_REALBOX,
        metrics::OmniboxFocusType::INTERACTION_FOCUS, std::string());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

    test_loader_factory()->AddResponse(
        test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
        R"(["",[],[],[],{}])");
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount("Omnibox.ZeroSuggestProvider.Eligibility",
                                      1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.Eligibility", 0 /*kEligible*/, 1);
  }
  {
    base::HistogramTester histogram_tester;

    provider_->Start(PrefixInputForNTP(), false);

    // Make sure the default provider's suggest endpoint was not queried.
    EXPECT_TRUE(provider_->done());
    EXPECT_EQ(0, test_loader_factory()->NumPending());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount("Omnibox.ZeroSuggestProvider.Eligibility",
                                      1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.Eligibility", 3 /*kGenerallyIneligible*/,
        1);
  }
}

TEST_F(ZeroSuggestProviderTest, SendRequestWithoutLensInteractionResponse) {
  AutocompleteInput input = ZeroPrefixInputForLens();
  provider_->Start(input, false);

  // Make sure the default provider's suggest endpoint was queried with the
  // expected client and without Lens Suggest signals.
  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());
  EXPECT_FALSE(base::EndsWith(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      "iil=", base::CompareCase::SENSITIVE));

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      R"(["",[],[],[],{}])");
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());
}

TEST_F(ZeroSuggestProviderTest, SendRequestWithAimToolMode) {
  AutocompleteInput input = ZeroPrefixInputForComposebox();
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
  omnibox::InputState input_state;
  input_state.active_tool = omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH;
  input.set_input_state(input_state);
  provider_->Start(input, false);

  // Make sure the default provider's suggest endpoint was queried with the
  // expected client and Lens Suggest signals.
  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());
  EXPECT_TRUE(base::EndsWith(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(), "azm=1",
      base::CompareCase::SENSITIVE));

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      R"(["",[],[],[],{}])");
  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));
}

TEST_F(ZeroSuggestProviderTest,
       SendRequestForThreadsSuggestionOmniboxComposebox) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));
  AutocompleteInput input = ZeroPrefixInputForOmniboxComposebox();
  provider_->Start(input, false);

  // Make sure the default provider's suggest endpoint was queried with the
  // expected client
  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());

  std::string json_response(
      R"(["",["", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300,)"
      R"("google:suggestdetail": [)"
      R"({"google:suggesttemplate": "CAIQCRobChlWaWV3IHlvdXIgQUkgTW9kZSBoaXN0b3J5MgoKA2FlcBIDMTMxMgkKBGF0dm0SATM="},)"
      R"({}, {}]}])");

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      json_response);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // Expect no matches were dropped even though the query/suggestion is empty.
  EXPECT_EQ(3U, provider_->matches().size());
}

TEST_F(ZeroSuggestProviderTest, SendRequestForThreadsSuggestionNtpComposebox) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));
  AutocompleteInput input = ZeroPrefixInputForComposebox();
  provider_->Start(input, false);

  // Make sure the default provider's suggest endpoint was queried with the
  // expected client
  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());

  std::string json_response(
      R"(["",["", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300,)"
      R"("google:suggestdetail": [)"
      R"({"google:suggesttemplate": "CAIQCRobChlWaWV3IHlvdXIgQUkgTW9kZSBoaXN0b3J5MgoKA2FlcBIDMTMxMgkKBGF0dm0SATM="},)"
      R"({}, {}]}])");

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      json_response);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // Expect no matches were dropped even though the query/suggestion is empty.
  EXPECT_EQ(3U, provider_->matches().size());
}

#if !BUILDFLAG(IS_ANDROID)
TEST_F(ZeroSuggestProviderTest, FallbackMatchesOnEmptyResponse) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));
  AutocompleteInput input = ZeroPrefixInputForComposebox();
  input.set_suggest_inventory(
      omnibox::SuggestInventory::SUGGEST_INVENTORY_BRAINSTORM);
  provider_->Start(input, false);

  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());

  std::string json_response(R"(["", [], [], [], {}])");

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      json_response);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // The network response is empty, so it should fallback to canned matches.
  EXPECT_EQ(omnibox::kDefaultFallbackNumSuggestions,
            provider_->matches().size());
  for (const auto& match : provider_->matches()) {
    EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST, match.type);
    EXPECT_EQ(omnibox::GROUP_AI_MODE_ZERO_SUGGEST_CANNED,
              match.suggestion_group_id);
  }
}

TEST_F(ZeroSuggestProviderTest, FallbackMatchesOnNetworkError) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));
  AutocompleteInput input = ZeroPrefixInputForComposebox();
  input.set_suggest_inventory(
      omnibox::SuggestInventory::SUGGEST_INVENTORY_BRAINSTORM);
  provider_->Start(input, false);

  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());

  // Simulate a network error by returning an HTTP 404 response.
  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      /*content=*/"", net::HTTP_NOT_FOUND);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // The network response failed, so it should fallback to canned matches.
  EXPECT_EQ(omnibox::kDefaultFallbackNumSuggestions,
            provider_->matches().size());
  for (const auto& match : provider_->matches()) {
    EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST, match.type);
    EXPECT_EQ(omnibox::GROUP_AI_MODE_ZERO_SUGGEST_CANNED,
              match.suggestion_group_id);
  }
}

TEST_F(ZeroSuggestProviderTest, FallbackMatchesOnParseFailure) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));
  AutocompleteInput input = ZeroPrefixInputForComposebox();
  input.set_suggest_inventory(
      omnibox::SuggestInventory::SUGGEST_INVENTORY_BRAINSTORM);
  provider_->Start(input, false);

  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());

  // Simulate a parsing error by returning invalid JSON.
  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      R"(this is not valid json)");

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // The network response failed to parse, so it should fallback to canned
  // matches.
  EXPECT_EQ(omnibox::kDefaultFallbackNumSuggestions,
            provider_->matches().size());
  for (const auto& match : provider_->matches()) {
    EXPECT_EQ(AutocompleteMatchType::SEARCH_SUGGEST, match.type);
    EXPECT_EQ(omnibox::GROUP_AI_MODE_ZERO_SUGGEST_CANNED,
              match.suggestion_group_id);
  }
}
#endif  // !BUILDFLAG(IS_ANDROID)

TEST_F(ZeroSuggestProviderTest, SendRequestWithLensInteractionResponse) {
  AutocompleteInput input = ZeroPrefixInputForLens();
  lens::proto::LensOverlaySuggestInputs lens_overlay_suggest_inputs;
  lens_overlay_suggest_inputs.set_encoded_image_signals("xyz");
  input.set_lens_overlay_suggest_inputs(lens_overlay_suggest_inputs);
  provider_->Start(input, false);

  // Make sure the default provider's suggest endpoint was queried with the
  // expected client and Lens Suggest signals.
  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());
  EXPECT_TRUE(base::EndsWith(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      "iil=xyz", base::CompareCase::SENSITIVE));

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      R"(["",[],[],[],{}])");
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());
}

TEST_F(ZeroSuggestProviderTest, StartStopNTP) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

  GURL suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");

  // Make sure valid input starts the provider.
  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure valid input restarts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure invalid input stops the provider.
  AutocompleteInput prefix_input = PrefixInputForNTP();
  provider_->Start(prefix_input, false);
  EXPECT_TRUE(provider_->done());
  // Expect that matches did not get populated out of cache.
  EXPECT_TRUE(provider_->matches().empty());
  // Expect that network request was not sent.
  EXPECT_FALSE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener since the
  // request was invalidated.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure valid input restarts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);
}

TEST_F(ZeroSuggestProviderTest, StartStopSRP) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
      prefs, input.current_url().spec(), json_response);

  GURL suggest_url = GetSuggestURL(
      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
      metrics::OmniboxFocusType::INTERACTION_FOCUS, input.current_url().spec());

  // Make sure valid input starts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure valid input restarts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure invalid input stops the provider.
  AutocompleteInput prefix_input = PrefixInputForWeb();
  provider_->Start(prefix_input, false);
  EXPECT_TRUE(provider_->done());
  // Expect that matches did not get populated out of cache.
  EXPECT_TRUE(provider_->matches().empty());
  // Expect that network request was not sent.
  EXPECT_FALSE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener since the
  // request was invalidated.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure valid input restarts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);
}

TEST_F(ZeroSuggestProviderTest, StartStopWeb) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  AutocompleteInput input = ZeroPrefixInputForWeb(
      /*is_prefetch=*/false);
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
      prefs, input.current_url().spec(), json_response);

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   input.current_url().spec());

  // Make sure valid input starts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure valid input restarts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure invalid input stops the provider.
  AutocompleteInput prefix_input = PrefixInputForWeb();
  provider_->Start(prefix_input, false);
  EXPECT_TRUE(provider_->done());
  // Expect that matches did not get populated out of cache.
  EXPECT_TRUE(provider_->matches().empty());
  // Expect that network request was not sent.
  EXPECT_FALSE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener since the
  // request was invalidated.
  EXPECT_FALSE(provider_did_notify_);

  // Make sure valid input restarts the provider.
  provider_->Start(input, false);
  EXPECT_FALSE(provider_->done());
  // Expect that matches got populated out of cache.
  EXPECT_FALSE(provider_->matches().empty());
  // Expect that network request was sent.
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  // Expect the provider to not have notified the provider listener yet.
  EXPECT_FALSE(provider_did_notify_);
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRunNTP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  EXPECT_TRUE(provider_->matches().empty());

  GURL suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  EXPECT_EQ(3U, provider_->matches().size());
  PrefService* prefs = client_->GetPrefs();
  EXPECT_EQ(json_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResults));
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRunSRP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on SRP.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnSRP);

  AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  provider_->Start(input, false);

  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  EXPECT_TRUE(provider_->matches().empty());

  GURL suggest_url = GetSuggestURL(
      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
      metrics::OmniboxFocusType::INTERACTION_FOCUS, input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  EXPECT_EQ(3U, provider_->matches().size());
  PrefService* prefs = client_->GetPrefs();
  EXPECT_EQ(json_response,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, input.current_url().spec()));
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestCachingFirstRunWeb) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on Web.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnWeb);

  AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  EXPECT_TRUE(provider_->matches().empty());

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  EXPECT_EQ(3U, provider_->matches().size());
  PrefService* prefs = client_->GetPrefs();
  EXPECT_EQ(json_response,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, input.current_url().spec()));
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestOmitAsynchronousMatchesTrueNTP) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  input.set_omit_asynchronous_matches(true);

  GURL suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");

  // Ensure the cache is empty.
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, "");
  prefs->SetDict(omnibox::kZeroSuggestCachedResultsWithURL, base::DictValue());

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());
  EXPECT_TRUE(provider_->done());
  EXPECT_TRUE(provider_->matches().empty());

  // There should be no pending network requests, given that asynchronous logic
  // has been explicitly disabled (`omit_asynchronous_matches_ == true`).
  ASSERT_FALSE(test_loader_factory()->IsPending(suggest_url.spec()));

  // Expect the provider to not have notified the provider listener since the
  // request was not sent.
  EXPECT_FALSE(provider_did_notify_);
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestOmitAsynchronousMatchesTrueSRP) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  input.set_omit_asynchronous_matches(true);

  GURL suggest_url = GetSuggestURL(
      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
      metrics::OmniboxFocusType::INTERACTION_FOCUS, input.current_url().spec());

  // Ensure the cache is empty.
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, "");
  prefs->SetDict(omnibox::kZeroSuggestCachedResultsWithURL, base::DictValue());

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());
  EXPECT_TRUE(provider_->done());
  EXPECT_TRUE(provider_->matches().empty());

  // There should be no pending network requests, given that asynchronous logic
  // has been explicitly disabled (`omit_asynchronous_matches_ == true`).
  ASSERT_FALSE(test_loader_factory()->IsPending(suggest_url.spec()));

  // Expect the provider to not have notified the provider listener since the
  // request was not sent.
  EXPECT_FALSE(provider_did_notify_);
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestOmitAsynchronousMatchesTrueWeb) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
  input.set_omit_asynchronous_matches(true);

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   input.current_url().spec());

  // Ensure the cache is empty.
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, "");
  prefs->SetDict(omnibox::kZeroSuggestCachedResultsWithURL, base::DictValue());

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());
  EXPECT_TRUE(provider_->done());
  EXPECT_TRUE(provider_->matches().empty());

  // There should be no pending network requests, given that asynchronous logic
  // has been explicitly disabled (`omit_asynchronous_matches_ == true`).
  ASSERT_FALSE(test_loader_factory()->IsPending(suggest_url.spec()));

  // Expect the provider to not have notified the provider listener since the
  // request was not sent.
  EXPECT_FALSE(provider_did_notify_);
}

// Disabled on iOS due to crbug.com/441269008.
#if BUILDFLAG(IS_IOS)
#define MAYBE_SyncMatchesOnly DISABLED_SyncMatchesOnly
#else
#define MAYBE_SyncMatchesOnly SyncMatchesOnly
#endif
TEST_F(ZeroSuggestProviderTest, MAYBE_SyncMatchesOnly) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  base::test::ScopedFeatureList features;
  features.InitWithFeatures(
      /*enabled_features=*/
      {omnibox_feature_configs::ContextualSearch::kOmniboxContextualSuggestions,
       omnibox::kZeroSuggestPrefetchingOnSRP,
       omnibox::kZeroSuggestPrefetchingOnWeb},
      /*disabled_features=*/{});

  omnibox_feature_configs::ScopedConfigForTesting<
      omnibox_feature_configs::ContextualSearch>
      config;
  config.Get().zero_suggest_synchronous_matches_only = true;

  auto clear_matches = [&]() {
    while (!provider_->matches().empty()) {
      provider_->DeleteMatch(provider_->matches().front());
    }
  };

  // ZPS via CSB (Lens overlay)
  {
    // Set up the pref to cache the response from the previous run.
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:suggestdetail":[{"du":"a.com"}, {"du":"a.com"}, {"du":"a.com"}],)"
        R"("google:verbatimrelevance":1300}])");
    PrefService* prefs = client_->GetPrefs();
    prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

    AutocompleteInput input = ZeroPrefixInputForLens();
    provider_->Start(input, false);
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
              provider_->GetResultTypeRunningForTesting());
    ASSERT_EQ(1, test_loader_factory()->NumPending());

    // Expect that matches DO NOT get populated synchronously out of the cache
    // for CSB.
    EXPECT_TRUE(provider_->matches().empty());

    clear_matches();

    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(
        test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
        json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect that matches get populated using the async ZPS response.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search4", provider_->matches()[0].contents);
    EXPECT_EQ(u"search5", provider_->matches()[1].contents);
    EXPECT_EQ(u"search6", provider_->matches()[2].contents);
  }

  // ZPS on NTP
  {
    // Set up the pref to cache the response from the previous run.
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:suggestdetail":[{"du":"a.com"}, {"du":"a.com"}, {"du":"a.com"}],)"
        R"("google:verbatimrelevance":1300}])");
    PrefService* prefs = client_->GetPrefs();
    prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

    AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
    provider_->Start(input, false);
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
              provider_->GetResultTypeRunningForTesting());

    // Expect that matches get populated synchronously out of the cache.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search1", provider_->matches()[0].contents);
    EXPECT_EQ(u"search2", provider_->matches()[1].contents);
    EXPECT_EQ(u"search3", provider_->matches()[2].contents);

    clear_matches();

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect that matches get populated using the async ZPS response.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search4", provider_->matches()[0].contents);
    EXPECT_EQ(u"search5", provider_->matches()[1].contents);
    EXPECT_EQ(u"search6", provider_->matches()[2].contents);
  }

  // ZPS on SRP
  {
    // Set up the pref to cache the response from the previous run.
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:suggestdetail":[{"du":"a.com"}, {"du":"a.com"}, {"du":"a.com"}],)"
        R"("google:verbatimrelevance":1300}])");
    PrefService* prefs = client_->GetPrefs();
    AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
    omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
        prefs, input.current_url().spec(), json_response);

    provider_->Start(input, false);
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
              provider_->GetResultTypeRunningForTesting());

    // Expect that matches get populated synchronously out of the cache.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search1", provider_->matches()[0].contents);
    EXPECT_EQ(u"search2", provider_->matches()[1].contents);
    EXPECT_EQ(u"search3", provider_->matches()[2].contents);

    clear_matches();

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::
                          SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS,
                      input.current_url().spec());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect that matches get populated using the async ZPS response.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search4", provider_->matches()[0].contents);
    EXPECT_EQ(u"search5", provider_->matches()[1].contents);
    EXPECT_EQ(u"search6", provider_->matches()[2].contents);
  }

  // ZPS on Web
  {
    // Set up the pref to cache the response from the previous run.
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:suggestdetail":[{"du":"a.com"}, {"du":"a.com"}, {"du":"a.com"}],)"
        R"("google:verbatimrelevance":1300}])");
    PrefService* prefs = client_->GetPrefs();
    AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
    omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
        prefs, input.current_url().spec(), json_response);

    provider_->Start(input, false);
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
              provider_->GetResultTypeRunningForTesting());

    // Expect that matches get populated synchronously out of the cache.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search1", provider_->matches()[0].contents);
    EXPECT_EQ(u"search2", provider_->matches()[1].contents);
    EXPECT_EQ(u"search3", provider_->matches()[2].contents);

    clear_matches();

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS,
                      input.current_url().spec());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect that matches DO NOT get populated using the async ZPS response,
    // since "sync matches only" targets the Contextual Search in Omnibox
    // experience on Web.
    EXPECT_TRUE(provider_->matches().empty());
  }
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResultsNTP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect the provider to not have notified the provider listener when using
  // the cached response.
  EXPECT_FALSE(provider_did_notify_);

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the same results after the response has been handled.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Expect the new results to have been stored.
  EXPECT_EQ(json_response2,
            prefs->GetString(omnibox::kZeroSuggestCachedResults));
}

TEST_F(ZeroSuggestProviderTest, TestZeroSuggestHasCachedResultsNTP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the cache with the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  ZeroSuggestCacheService* cache_svc = client_->GetZeroSuggestCacheService();
  cache_svc->StoreZeroSuggestResponse("", json_response);

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect the provider to not have notified the provider listener when using
  // the cached response.
  EXPECT_FALSE(provider_did_notify_);

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the same results after the response has been handled.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Expect the new results to have been stored.
  EXPECT_EQ(json_response2,
            cache_svc->ReadZeroSuggestResponse("").response_json);
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResultsSRP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on SRP.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnSRP);

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
      prefs, input.current_url().spec(), json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(
      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
      metrics::OmniboxFocusType::INTERACTION_FOCUS, input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect the provider to not have notified the provider listener when using
  // the cached response.
  EXPECT_FALSE(provider_did_notify_);

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the same results after the response has been handled.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Expect the new results to have been stored.
  EXPECT_EQ(json_response2,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, input.current_url().spec()));
}

TEST_F(ZeroSuggestProviderTest, TestZeroSuggestHasCachedResultsSRP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on SRP.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnSRP);

  // Set up the cache with the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  ZeroSuggestCacheService* cache_svc = client_->GetZeroSuggestCacheService();
  cache_svc->StoreZeroSuggestResponse(input.current_url().spec(),
                                      json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(
      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
      metrics::OmniboxFocusType::INTERACTION_FOCUS, input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect the provider to not have notified the provider listener when using
  // the cached response.
  EXPECT_FALSE(provider_did_notify_);

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the same results after the response has been handled.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Expect the new results to have been stored.
  EXPECT_EQ(json_response2,
            cache_svc->ReadZeroSuggestResponse(input.current_url().spec())
                .response_json);
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestHasCachedResultsWeb) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on Web.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnWeb);

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
      prefs, input.current_url().spec(), json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect the provider to not have notified the provider listener when using
  // the cached response.
  EXPECT_FALSE(provider_did_notify_);

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the same results after the response has been handled.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Expect the new results to have been stored.
  EXPECT_EQ(json_response2,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, input.current_url().spec()));
}

TEST_F(ZeroSuggestProviderTest, TestZeroSuggestHasCachedResultsWeb) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on Web.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnWeb);

  // Set up the cache with the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
  ZeroSuggestCacheService* cache_svc = client_->GetZeroSuggestCacheService();
  cache_svc->StoreZeroSuggestResponse(input.current_url().spec(),
                                      json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect the provider to not have notified the provider listener when using
  // the cached response.
  EXPECT_FALSE(provider_did_notify_);

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 4);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the same results after the response has been handled.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Expect the new results to have been stored.
  EXPECT_EQ(json_response2,
            cache_svc->ReadZeroSuggestResponse(input.current_url().spec())
                .response_json);
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestReceivedEmptyResultsNTP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string empty_response(R"(["",[],[],[],{}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), empty_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 5);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  // Expect that the matches have been cleared.
  ASSERT_TRUE(provider_->matches().empty());

  // Expect the new results to have been stored.
  EXPECT_EQ(empty_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResults));
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestReceivedEmptyResultsSRP) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on SRP.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnSRP);

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
      prefs, input.current_url().spec(), json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(
      metrics::OmniboxEventProto::SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
      metrics::OmniboxFocusType::INTERACTION_FOCUS, input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string empty_response(R"(["",[],[],[],{}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), empty_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 5);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  // Expect that the matches have been cleared.
  ASSERT_TRUE(provider_->matches().empty());

  // Expect the new results to have been stored.
  EXPECT_EQ(empty_response,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, input.current_url().spec()));
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestReceivedEmptyResultsWeb) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Enable prefetching on Web.
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingOnWeb);

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
      prefs, input.current_url().spec(), json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string empty_response(R"(["",[],[],[],{}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), empty_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 5);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
      1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  // Expect that the matches have been cleared.
  ASSERT_TRUE(provider_->matches().empty());

  // Expect the new results to have been stored.
  EXPECT_EQ(empty_response,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, input.current_url().spec()));
}

TEST_F(ZeroSuggestProviderTest, TestZeroSuggestReceivedInvalidResults) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  std::vector<std::string> invalid_responses = {"", "}bro|ken{", "[]",
                                                R"(["",{}])"};

  // Verify that none of the invalid ZPS responses trigger storage of ZPS data.
  for (auto response : invalid_responses) {
    provider_->Start(input, false);
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
              provider_->GetResultTypeRunningForTesting());

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

    test_loader_factory()->AddResponse(suggest_url.spec(), response);

    // Spin event loop to allow network request to go through.
    base::RunLoop().RunUntilIdle();

    EXPECT_TRUE(provider_->done());
    EXPECT_TRUE(provider_->matches().empty());

    // Provider shouldn't have notified any provider listeners.
    EXPECT_FALSE(provider_did_notify_);

    test_loader_factory()->ClearResponses();
  }

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      2 * invalid_responses.size());
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1,
      invalid_responses.size());
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kResponseReceived*/ 3, invalid_responses.size());
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 0);
}

TEST_F(ZeroSuggestProviderTest, TestPsuggestZeroSuggestPrefetchThenNTPOnFocus) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

  {
    base::HistogramTester histogram_tester;

    // Start a prefetch request.
    AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch = */ true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    // Expect the results to be empty.
    ASSERT_EQ(0U, provider_->matches().size());

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
        /*kRemoteResponseCached*/ 4, 1);

    // Expect the provider to not have notified the provider listener since the
    // matches were not updated.
    EXPECT_FALSE(provider_did_notify_);

    // Expect the same empty results after the response has been handled.
    ASSERT_EQ(0U, provider_->matches().size());

    // Expect the new response to have been stored in the pref.
    EXPECT_EQ(json_response2,
              prefs->GetString(omnibox::kZeroSuggestCachedResults));
  }
  {
    base::HistogramTester histogram_tester;

    // Start a non-prefetch request.
    AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
    provider_->Start(input, false);
    EXPECT_FALSE(provider_->done());
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
              provider_->GetResultTypeRunningForTesting());

    // Expect the results from the cached response.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search4", provider_->matches()[0].contents);
    EXPECT_EQ(u"search5", provider_->matches()[1].contents);
    EXPECT_EQ(u"search6", provider_->matches()[2].contents);

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response3(
        R"(["",["search7", "search8", "search9"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response3);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 4);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
        /*kCachedResponseConvertedToMatches*/ 0, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
        /*kRemoteResponseCached*/ 4, 1);

    // Expect the provider to not have notified the provider listener since the
    // matches were not updated.
    EXPECT_FALSE(provider_did_notify_);

    // Expect the same results after the response has been handled.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search4", provider_->matches()[0].contents);
    EXPECT_EQ(u"search5", provider_->matches()[1].contents);
    EXPECT_EQ(u"search6", provider_->matches()[2].contents);

    // Expect the new response to have been stored in the pref.
    EXPECT_EQ(json_response3,
              prefs->GetString(omnibox::kZeroSuggestCachedResults));
  }
}

TEST_F(ZeroSuggestProviderTest,
       TestPsuggestZeroSuggestPrefetchThenNTPOnFocusWithComposebox) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingForComposebox);

  PrefService* prefs = client_->GetPrefs();

  base::HistogramTester histogram_tester;

  MockAimEligibilityService aim_service(
      *client_->GetPrefs(), client_->GetTemplateURLService(), nullptr, nullptr);
  EXPECT_CALL(aim_service, IsFuseboxEligible())
      .WillRepeatedly(testing::Return(true));
  client_->set_aim_eligibility_service(&aim_service);

  // Start a prefetch request.
  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch = */ true);
  provider_->StartPrefetch(input);
  EXPECT_TRUE(provider_->done());

  // Verify both loaders are populated!
  GURL ntp_suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
  GURL composebox_suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_COMPOSEBOX_PREFETCH,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "",
                    TemplateURLRef::RequestSource::COMPOSEBOX);

  EXPECT_TRUE(test_loader_factory()->IsPending(ntp_suggest_url.spec()));
  EXPECT_TRUE(test_loader_factory()->IsPending(composebox_suggest_url.spec()));

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  test_loader_factory()->AddResponse(ntp_suggest_url.spec(), json_response);
  test_loader_factory()->AddResponse(composebox_suggest_url.spec(),
                                     json_response);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // Verify metrics for two separate prefetch requests
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 6);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRequestSent*/ 1, 2);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kResponseReceived*/ 3, 2);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRemoteResponseCached*/ 4,
      2);

  // Verify storage isolated correctly!
  EXPECT_EQ(json_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResults));
  EXPECT_EQ(json_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResultsComposebox));
}

TEST_F(ZeroSuggestProviderTest, TestComposeboxPrefetchWithSuggestInventory) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(omnibox::kZeroSuggestPrefetchingForComposebox);

  PrefService* prefs = client_->GetPrefs();

  MockAimEligibilityService aim_service(
      *client_->GetPrefs(), client_->GetTemplateURLService(), nullptr, nullptr);
  EXPECT_CALL(aim_service, IsFuseboxEligible())
      .WillRepeatedly(testing::Return(true));
  client_->set_aim_eligibility_service(&aim_service);

  // Start a prefetch request with non-default suggest inventory.
  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch = */ true);
  input.set_suggest_inventory(
      omnibox::SuggestInventory::SUGGEST_INVENTORY_TRAVEL);
  provider_->StartPrefetch(input);
  EXPECT_TRUE(provider_->done());

  GURL ntp_suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
  GURL composebox_suggest_url =
      GetSuggestURL(metrics::OmniboxEventProto::NTP_COMPOSEBOX_PREFETCH,
                    metrics::OmniboxFocusType::INTERACTION_FOCUS, "",
                    TemplateURLRef::RequestSource::COMPOSEBOX,
                    omnibox::SuggestInventory::SUGGEST_INVENTORY_TRAVEL);

  EXPECT_TRUE(test_loader_factory()->IsPending(ntp_suggest_url.spec()));
  EXPECT_TRUE(test_loader_factory()->IsPending(composebox_suggest_url.spec()));

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  test_loader_factory()->AddResponse(ntp_suggest_url.spec(), json_response);
  test_loader_factory()->AddResponse(composebox_suggest_url.spec(),
                                     json_response);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // Both NTP cache and Composebox cache should be populated. The composebox
  // input for prefetching always uses default suggest inventory.
  EXPECT_EQ(json_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResults));
  EXPECT_EQ(json_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResultsComposebox));
}

#if !BUILDFLAG(IS_ANDROID)
TEST_F(ZeroSuggestProviderTest,
       FallbackSuggestionsForSuggestInventoryOnFailure) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  MockAimEligibilityService aim_service(
      *client_->GetPrefs(), client_->GetTemplateURLService(), nullptr, nullptr);
  EXPECT_CALL(aim_service, IsFuseboxEligible())
      .WillRepeatedly(testing::Return(true));
  client_->set_aim_eligibility_service(&aim_service);

  AutocompleteInput input(u"", metrics::OmniboxEventProto::NTP_COMPOSEBOX,
                          TestSchemeClassifier());
  input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_FOCUS);
  input.set_suggest_inventory(
      omnibox::SuggestInventory::SUGGEST_INVENTORY_BRAINSTORM);

  GURL suggest_url = GetProviderRequestURL(input);
  EXPECT_FALSE(suggest_url.is_empty());

  test_loader_factory()->AddResponse(suggest_url.spec(), "",
                                     net::HTTP_NOT_FOUND);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // Should contain fallback matches.
  EXPECT_EQ(provider_->matches().size(),
            omnibox::kDefaultFallbackNumSuggestions);
  for (const auto& match : provider_->matches()) {
    EXPECT_EQ(match.type, AutocompleteMatchType::SEARCH_SUGGEST);
    EXPECT_FALSE(match.contents.empty());
  }
}
#endif  // !BUILDFLAG(IS_ANDROID)

TEST_F(ZeroSuggestProviderTest, TestCacheStateWithSRPPrefetchDisabled) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Disable prefetching on SRP.
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(omnibox::kZeroSuggestPrefetchingOnSRP);

  PrefService* prefs = client_->GetPrefs();

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  {
    base::HistogramTester histogram_tester;

    // Start a prefetch request.
    AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/true);
    // Set up the pref to cache the response from the previous run.
    omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
        prefs, input.current_url().spec(), json_response);
    // Call RunZeroSuggestPrefetch() instead of StartPrefetch() since the latter
    // won't work when kZeroSuggestPrefetchingOnSRP is disabled.
    provider_->RunZeroSuggestPrefetch(
        input,
        provider_->GetResultTypeAndEligibility(client_.get(), input).first);
    EXPECT_TRUE(provider_->done());

    // Expect the results to be empty.
    ASSERT_EQ(0U, provider_->matches().size());

    GURL suggest_url =
        GetSuggestURL(input.current_page_classification(), input.focus_type(),
                      input.current_url().spec());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 2);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kResponseReceived*/ 3, 1);

    // Expect the provider to not have notified the provider listener since the
    // matches were not updated.
    EXPECT_FALSE(provider_did_notify_);

    // Expect the same empty results after the response has been handled since
    // response should not have been read from cache.
    ASSERT_EQ(0U, provider_->matches().size());

    // Expect the response to not have been stored in the prefs.
    EXPECT_EQ(json_response,
              omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                  prefs, input.current_url().spec()));
  }
  {
    base::HistogramTester histogram_tester;

    // Start a non-prefetch request.
    AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/false);
    provider_->Start(input, false);
    EXPECT_FALSE(provider_->done());
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
              provider_->GetResultTypeRunningForTesting());

    // Expect the results to be empty.
    ASSERT_EQ(0U, provider_->matches().size());

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::
                          SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS,
                      input.current_url().spec());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response3(
        R"(["",["search7", "search8", "search9"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response3);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
        1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
        /*kRemoteResponseConvertedToMatches*/ 5, 1);

    // Expect the provider to have notified the provider listener.
    EXPECT_TRUE(provider_did_notify_);

    // Expect the results after the response has been handled.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search7", provider_->matches()[0].contents);
    EXPECT_EQ(u"search8", provider_->matches()[1].contents);
    EXPECT_EQ(u"search9", provider_->matches()[2].contents);

    // Expect the response to not have been stored in the prefs.
    EXPECT_EQ(json_response,
              omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                  prefs, input.current_url().spec()));
  }
}

TEST_F(ZeroSuggestProviderTest, TestCacheStateWithWebPrefetchDisabled) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Disable prefetching on Web.
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(omnibox::kZeroSuggestPrefetchingOnWeb);

  PrefService* prefs = client_->GetPrefs();

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  {
    base::HistogramTester histogram_tester;

    // Start a prefetch request.
    AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/true);
    // Set up the pref to cache the response from the previous run.
    omnibox::SetUserPreferenceForZeroSuggestCachedResponse(
        prefs, input.current_url().spec(), json_response);
    // Call RunZeroSuggestPrefetch() instead of StartPrefetch() since the latter
    // won't work when kZeroSuggestPrefetchingOnWeb is disabled.
    provider_->RunZeroSuggestPrefetch(
        input,
        provider_->GetResultTypeAndEligibility(client_.get(), input).first);
    EXPECT_TRUE(provider_->done());

    // Expect the results to be empty.
    ASSERT_EQ(0U, provider_->matches().size());

    GURL suggest_url =
        GetSuggestURL(input.current_page_classification(), input.focus_type(),
                      input.current_url().spec());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response2(
        R"(["",["search4", "search5", "search6"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 2);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kResponseReceived*/ 3, 1);

    // Expect the provider to not have notified the provider listener since the
    // matches were not updated.
    EXPECT_FALSE(provider_did_notify_);

    // Expect the same empty results after the response has been handled since
    // response should not have been read from the cache.
    ASSERT_EQ(0U, provider_->matches().size());

    // Expect the response to not have been stored in the prefs.
    EXPECT_EQ(json_response,
              omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                  prefs, input.current_url().spec()));
  }
  {
    base::HistogramTester histogram_tester;

    // Start a non-prefetch request.
    AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/false);
    provider_->Start(input, false);
    EXPECT_FALSE(provider_->done());
    ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteSendURL,
              provider_->GetResultTypeRunningForTesting());

    // Expect the results to be empty.
    ASSERT_EQ(0U, provider_->matches().size());

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::OTHER,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS,
                      input.current_url().spec());
    EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
    std::string json_response3(
        R"(["",["search7", "search8", "search9"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response3);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", /*kRequestSent*/ 1,
        1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch",
        /*kRemoteResponseConvertedToMatches*/ 5, 1);

    // Expect the provider to have notified the provider listener.
    EXPECT_TRUE(provider_did_notify_);

    // Expect the results after the response has been handled.
    ASSERT_EQ(3U, provider_->matches().size());
    EXPECT_EQ(u"search7", provider_->matches()[0].contents);
    EXPECT_EQ(u"search8", provider_->matches()[1].contents);
    EXPECT_EQ(u"search9", provider_->matches()[2].contents);

    // Expect the response to not have been stored in the prefs.
    EXPECT_EQ(json_response,
              omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                  prefs, input.current_url().spec()));
  }
}

TEST_F(ZeroSuggestProviderTest, TestZeroSuggestPrefetchingOnSRPCounterfactual) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // ZPS on NTP (prefetch w/ CF param).
  {
    base::test::ScopedFeatureList features;
    features.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {
            {omnibox::kZeroSuggestPrefetchingOnSRP,
             {{"ZeroSuggestPrefetchingOnSRPCounterfactual", "true"}}},
            {omnibox::kZeroSuggestPrefetchingOnWeb, {}},
        },
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;

    AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    // In particular, enabling the CF param should NOT have impacted this case.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
        /*kRemoteResponseCached*/ 4, 1);
  }
  // ZPS on NTP (prefetch w/o CF param).
  {
    base::test::ScopedFeatureList features;
    features.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {
            {omnibox::kZeroSuggestPrefetchingOnSRP,
             {{"ZeroSuggestPrefetchingOnSRPCounterfactual", "false"}}},
            {omnibox::kZeroSuggestPrefetchingOnWeb, {}},
        },
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;

    AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    GURL suggest_url =
        GetSuggestURL(metrics::OmniboxEventProto::NTP_ZPS_PREFETCH,
                      metrics::OmniboxFocusType::INTERACTION_FOCUS, "");
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
        /*kRemoteResponseCached*/ 4, 1);
  }

  // ZPS on SRP (prefetch w/ CF param).
  {
    base::test::ScopedFeatureList features;
    features.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {
            {omnibox::kZeroSuggestPrefetchingOnSRP,
             {{"ZeroSuggestPrefetchingOnSRPCounterfactual", "true"}}},
            {omnibox::kZeroSuggestPrefetchingOnWeb, {}},
        },
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;

    AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    GURL suggest_url =
        GetSuggestURL(input.current_page_classification(), input.focus_type(),
                      input.current_url().spec());
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    // In particular, enabling the CF param should have impacted this case.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 2);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kRemoteResponseCached*/ 4, 0);
  }
  // ZPS on SRP (prefetch w/o CF param).
  {
    base::test::ScopedFeatureList features;
    features.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {
            {omnibox::kZeroSuggestPrefetchingOnSRP,
             {{"ZeroSuggestPrefetchingOnSRPCounterfactual", "false"}}},
            {omnibox::kZeroSuggestPrefetchingOnWeb, {}},
        },
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;

    AutocompleteInput input = ZeroPrefixInputForSRP(/*is_prefetch=*/true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    GURL suggest_url =
        GetSuggestURL(input.current_page_classification(), input.focus_type(),
                      input.current_url().spec());
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kRemoteResponseCached*/ 4, 1);
  }

  // ZPS on Web (prefetch w/ CF param).
  {
    base::test::ScopedFeatureList features;
    features.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {
            {omnibox::kZeroSuggestPrefetchingOnSRP,
             {{"ZeroSuggestPrefetchingOnSRPCounterfactual", "true"}}},
            {omnibox::kZeroSuggestPrefetchingOnWeb, {}},
        },
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;

    AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    GURL suggest_url =
        GetSuggestURL(input.current_page_classification(), input.focus_type(),
                      input.current_url().spec());
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    // In particular, enabling the CF param should NOT have impacted this case.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kRemoteResponseCached*/ 4, 1);
  }
  // ZPS on Web (prefetch w/o CF param).
  {
    base::test::ScopedFeatureList features;
    features.InitWithFeaturesAndParameters(
        /*enabled_features=*/
        {
            {omnibox::kZeroSuggestPrefetchingOnSRP,
             {{"ZeroSuggestPrefetchingOnSRPCounterfactual", "false"}}},
            {omnibox::kZeroSuggestPrefetchingOnWeb, {}},
        },
        /*disabled_features=*/{});

    base::HistogramTester histogram_tester;

    AutocompleteInput input = ZeroPrefixInputForWeb(/*is_prefetch=*/true);
    provider_->StartPrefetch(input);
    EXPECT_TRUE(provider_->done());

    GURL suggest_url =
        GetSuggestURL(input.current_page_classification(), input.focus_type(),
                      input.current_url().spec());
    std::string json_response(
        R"(["",["search1", "search2", "search3"],)"
        R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
        R"("google:verbatimrelevance":1300}])");
    test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(provider_->done());

    // Expect correct histograms to have been logged.
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
    histogram_tester.ExpectTotalCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 3);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kResponseReceived*/ 3, 1);
    histogram_tester.ExpectBucketCount(
        "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
        /*kRemoteResponseCached*/ 4, 1);
  }
}

TEST_F(ZeroSuggestProviderTest, TestMultipleZeroSuggestPrefetchesInFlight) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  PrefService* prefs = client_->GetPrefs();
  base::HistogramTester histogram_tester;

  // Start a prefetch request on NTP.
  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch =*/true);
  provider_->StartPrefetch(input);
  EXPECT_TRUE(provider_->done());

  // Expect the results to be empty.
  ASSERT_EQ(0U, provider_->matches().size());

  // Verify that there's an in-flight prefetch request for NTP context.
  GURL suggest_url = GetSuggestURL(input.current_page_classification(),
                                   input.focus_type(), "");
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  test_loader_factory()->AddResponse(suggest_url.spec(), json_response);

  // Start a prefetch request on SRP.
  input = ZeroPrefixInputForSRP(/*is_prefetch=*/true);
  provider_->StartPrefetch(input);
  EXPECT_TRUE(provider_->done());

  // Expect the results to be empty.
  ASSERT_EQ(0U, provider_->matches().size());

  // Verify that there's an in-flight prefetch request for SRP context.
  suggest_url = GetSuggestURL(input.current_page_classification(),
                              input.focus_type(), input.current_url().spec());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));

  std::string json_response2(
      R"(["",["search4", "search5", "search6"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  test_loader_factory()->AddResponse(suggest_url.spec(), json_response2);

  // Resolve all in-flight ZPS prefetch requests.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 3);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 3);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 0);

  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
      /*kRequestInvalidated*/ 2, 0);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch",
      /*kRemoteResponseCached*/ 4, 1);

  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", /*kRequestInvalidated*/ 2,
      0);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch",
      /*kRemoteResponseCached*/ 4, 1);

  // Expect the provider to not have notified the provider listener since the
  // matches were not updated.
  EXPECT_FALSE(provider_did_notify_);

  // Expect the same empty results after the response has been handled.
  ASSERT_EQ(0U, provider_->matches().size());

  // Expect the responses to have been stored in the appropriate prefs.
  EXPECT_EQ(json_response,
            prefs->GetString(omnibox::kZeroSuggestCachedResults));

  const std::string current_url = input.current_url().spec();
  const std::string* stored_response =
      prefs->GetDict(omnibox::kZeroSuggestCachedResultsWithURL)
          .FindString(current_url);
  ASSERT_TRUE(stored_response && *stored_response == json_response2);
}

TEST_F(ZeroSuggestProviderTest, TestNoURLResultTypeWithNonEmptyURLInput) {
  base::HistogramTester histogram_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Configure the "No URL" input with a non-empty URL.
  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  input.set_current_url(GURL("https://www.google.com"));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");
  PrefService* prefs = client_->GetPrefs();

  // Store cached ZPS response for NTP (which is associated with an empty page
  // URL).
  omnibox::SetUserPreferenceForZeroSuggestCachedResponse(prefs, std::string(),
                                                         json_response);

  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Despite the input having a non-empty URL, since the provider enforces an
  // empty string as the key for the cached response on NTP, the matches get
  // populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  GURL suggest_url = GetSuggestURL(metrics::OmniboxEventProto::NTP_REALBOX,
                                   metrics::OmniboxFocusType::INTERACTION_FOCUS,
                                   std::string());
  EXPECT_TRUE(test_loader_factory()->IsPending(suggest_url.spec()));
  std::string empty_response(R"(["",[],[],[],{}])");
  test_loader_factory()->AddResponse(suggest_url.spec(), empty_response);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(provider_->done());

  // Expect correct histograms to have been logged.
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.URLBased.NonPrefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.Prefetch", 0);
  histogram_tester.ExpectTotalCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", 5);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kCachedResponseConvertedToMatches*/ 0, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch", /*kRequestSent*/ 1, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kResponseReceived*/ 3, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseCached*/ 4, 1);
  histogram_tester.ExpectBucketCount(
      "Omnibox.ZeroSuggestProvider.NoURL.NonPrefetch",
      /*kRemoteResponseConvertedToMatches*/ 5, 1);

  // Expect the provider to have notified the provider listener.
  EXPECT_TRUE(provider_did_notify_);

  // Expect that the matches have been cleared.
  ASSERT_TRUE(provider_->matches().empty());

  // Expect the new results to have been stored, keyed off an empty page URL.
  EXPECT_EQ(empty_response,
            omnibox::GetUserPreferenceForZeroSuggestCachedResponse(
                prefs, std::string()));
}

TEST_F(ZeroSuggestProviderTest, TestDeleteMatchClearsPrefsBasedCache) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the pref to cache the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300,)"
      R"("google:suggestdetail":)"
      R"([{"du": "https://www.google.com/s1"},)"
      R"({"du": "https://www.google.com/s2"},)"
      R"({"du": "https://www.google.com/s3"}]}])");

  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

  base::DictValue new_dict;
  new_dict.Set("https://www.google.com", json_response);
  prefs->SetDict(omnibox::kZeroSuggestCachedResultsWithURL,
                 std::move(new_dict));

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Ensure that both cache prefs start off with non-empty values.
  ASSERT_FALSE(prefs->GetString(omnibox::kZeroSuggestCachedResults).empty());
  ASSERT_FALSE(
      prefs->GetDict(omnibox::kZeroSuggestCachedResultsWithURL).empty());

  provider_->DeleteMatch(provider_->matches()[0]);

  // Verify that cache prefs for "ZPS on NTP" and "ZPS on SRP/Web" have both
  // been cleared.
  ASSERT_TRUE(prefs->GetString(omnibox::kZeroSuggestCachedResults).empty());
  ASSERT_TRUE(
      prefs->GetDict(omnibox::kZeroSuggestCachedResultsWithURL).empty());
}

TEST_F(ZeroSuggestProviderTest, TestDeleteMatchClearsCache) {
  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the cache with the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300,)"
      R"("google:suggestdetail":)"
      R"([{"du": "https://www.google.com/s1"},)"
      R"({"du": "https://www.google.com/s2"},)"
      R"({"du": "https://www.google.com/s3"}]}])");

  ZeroSuggestCacheService* cache_svc = client_->GetZeroSuggestCacheService();
  cache_svc->StoreZeroSuggestResponse("", json_response);
  cache_svc->StoreZeroSuggestResponse("https://www.google.com", json_response);

  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Ensure that both cache entries have non-empty values.
  ASSERT_FALSE(cache_svc->ReadZeroSuggestResponse("").response_json.empty());
  ASSERT_FALSE(cache_svc->ReadZeroSuggestResponse("https://www.google.com")
                   .response_json.empty());

  provider_->DeleteMatch(provider_->matches()[0]);
}

TEST_F(ZeroSuggestProviderTest, TestDeleteMatchTriggersDeletionRequest) {
  base::UserActionTester user_action_tester;

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  // Set up the cache with the response from the previous run.
  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300,)"
      R"("google:suggestdetail":)"
      R"([{"du": "https://www.google.com/s1"},)"
      R"({"du": "https://www.google.com/s2"},)"
      R"({"du": "https://www.google.com/s3"}]}])");

  PrefService* prefs = client_->GetPrefs();
  prefs->SetString(omnibox::kZeroSuggestCachedResults, json_response);

  // Trigger a non-prefetch ZPS provider run.
  AutocompleteInput input = ZeroPrefixInputForNTP(/*is_prefetch=*/false);
  provider_->Start(input, false);
  ASSERT_EQ(ZeroSuggestProvider::ResultType::kRemoteNoURL,
            provider_->GetResultTypeRunningForTesting());

  // Expect that matches get populated synchronously out of the cache.
  ASSERT_EQ(3U, provider_->matches().size());  // 3 results, no verbatim match
  EXPECT_EQ(u"search1", provider_->matches()[0].contents);
  EXPECT_EQ(u"search2", provider_->matches()[1].contents);
  EXPECT_EQ(u"search3", provider_->matches()[2].contents);

  // Test a successful deletion request.
  provider_->DeleteMatch(provider_->matches()[0]);

  const std::string del_url1 = "https://www.google.com/s1";
  ASSERT_TRUE(test_loader_factory()->IsPending(del_url1));
  test_loader_factory()->AddResponse(del_url1, "");

  // Spin event loop to allow deletion request to go through.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, user_action_tester.GetActionCount(
                   "Omnibox.ZeroSuggestDelete.Success"));

  // Test a failed deletion request.
  test_loader_factory()->ClearResponses();
  provider_->DeleteMatch(provider_->matches()[0]);

  const std::string del_url2 = "https://www.google.com/s2";
  ASSERT_TRUE(test_loader_factory()->IsPending(del_url2));

  auto head = network::mojom::URLResponseHead::New();
  std::string headers(
      "HTTP/1.1 500 Server Failure\nContent-type: application/json\n\n");
  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(headers));
  head->mime_type = "application/json";
  test_loader_factory()->AddResponse(GURL(del_url2), std::move(head), "",
                                     network::URLLoaderCompletionStatus());

  // Spin event loop to allow deletion request to go through.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(1, user_action_tester.GetActionCount(
                   "Omnibox.ZeroSuggestDelete.Failure"));
}

TEST_F(ZeroSuggestProviderTest, SuggestUrlIncludesCtxus) {
  // Ensure it's not included by default.
  {
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("ctxus="), std::string::npos);
  }

  // Ensure it is conditionally included when enabled.
  omnibox_feature_configs::ScopedConfigForTesting<
      omnibox_feature_configs::ContextualSearch>
      config;
  config.Get().contextual_url_suggest_param = "1";

  EXPECT_CALL(*client_, ShouldSendContextualUrlSuggestParam())
      .WillRepeatedly(testing::Return(true));

  // Web gets the param when Lens is enabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_NE(url.spec().find("ctxus=1"), std::string::npos);
  }
  // Web does not get the param when Lens is disabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(false));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("ctxus=1"), std::string::npos);
  }
  // NTP does not, even when enabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForNTP(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("ctxus=1"), std::string::npos);
  }
  // SRP does not, even when enabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForSRP(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("ctxus=1"), std::string::npos);
  }
}

TEST_F(ZeroSuggestProviderTest, SuggestUrlIncludesPageTitle) {
  // Ensure it's not included by default.
  {
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("pageTitle="), std::string::npos);
  }

  // Ensure it is conditionally included when enabled.
  omnibox_feature_configs::ScopedConfigForTesting<
      omnibox_feature_configs::ContextualSearch>
      config;
  config.Get().send_page_title_suggest_param = true;

  EXPECT_CALL(*client_, ShouldSendPageTitleSuggestParam())
      .WillRepeatedly(testing::Return(true));

  // Web gets the param (URL-encoded page title).
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    EXPECT_CALL(*client_, IsPersonalizedUrlDataCollectionActive())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_NE(url.spec().find("pageTitle=Example%20%2F%20Page"),
              std::string::npos);
  }
  // Web does not get the param when Lens is disabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(false));
    EXPECT_CALL(*client_, IsPersonalizedUrlDataCollectionActive())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("pageTitle="), std::string::npos);
  }
  // Web does not get the param when personalized URL data collection is
  // disabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    EXPECT_CALL(*client_, IsPersonalizedUrlDataCollectionActive())
        .WillRepeatedly(testing::Return(false));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForWeb(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("pageTitle="), std::string::npos);
  }
  // NTP does not, even when enabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    EXPECT_CALL(*client_, IsPersonalizedUrlDataCollectionActive())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForNTP(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("pageTitle="), std::string::npos);
  }
  // SRP does not, even when enabled.
  {
    EXPECT_CALL(*client_, IsLensEnabled())
        .WillRepeatedly(testing::Return(true));
    EXPECT_CALL(*client_, IsPersonalizedUrlDataCollectionActive())
        .WillRepeatedly(testing::Return(true));
    GURL url =
        GetProviderRequestURL(ZeroPrefixInputForSRP(/*is_prefetch=*/false));
    EXPECT_EQ(url.spec().find("pageTitle="), std::string::npos);
  }
}

TEST_F(ZeroSuggestProviderTest,
       TestZeroSuggestDoesNotCacheForComposeboxWhenFlagDisabled) {
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(omnibox::kZeroSuggestPrefetchingForComposebox);

  EXPECT_CALL(*client_, IsAuthenticated())
      .WillRepeatedly(testing::Return(true));

  AutocompleteInput input = ZeroPrefixInputForComposebox();
  provider_->Start(input, false);

  EXPECT_FALSE(provider_->done());
  EXPECT_EQ(1, test_loader_factory()->NumPending());

  std::string json_response(
      R"(["",["search1", "search2", "search3"],)"
      R"([],[],{"google:suggestrelevance":[602, 601, 600],)"
      R"("google:verbatimrelevance":1300}])");

  test_loader_factory()->AddResponse(
      test_loader_factory()->GetPendingRequest(0)->request.url.spec(),
      json_response);

  EXPECT_TRUE(base::test::RunUntil([&] { return provider_->done(); }));

  // Ensure nothing is stored in the cache for composebox when the flag is
  // disabled.
  PrefService* prefs = client_->GetPrefs();
  EXPECT_EQ(std::string(),
            prefs->GetString(omnibox::kZeroSuggestCachedResultsComposebox));
}
