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

#include "chrome/browser/ui/webui/settings/settings_secure_dns_handler.h"

#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/dns_probe_test_util.h"
#include "chrome/browser/net/dns_util.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/country_codes/country_codes.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/test_web_ui.h"
#include "net/dns/public/resolve_error_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_WIN)
#include "base/win/win_util.h"
#endif

using net::DohProviderEntry;
using testing::_;
using testing::Return;

namespace settings {

namespace {

constexpr char kGetSecureDnsResolverList[] = "getSecureDnsResolverList";
constexpr char kValidateCustomDnsEntry[] = "validateCustomDnsEntry";
constexpr char kProbeCustomDnsTemplate[] = "probeCustomDnsTemplate";
constexpr char kRecordUserDropdownInteraction[] =
    "recordUserDropdownInteraction";
constexpr char kWebUiFunctionName[] = "webUiCallbackName";

const std::vector<DohProviderEntry>& GetDohProviderListForTesting() {
  static const base::NoDestructor<std::vector<DohProviderEntry>> test_providers{
      {
          DohProviderEntry(
              "Provider_Global1", net::DohProviderIdForHistogram(-1),
              {} /*ip_strs */, {} /* dot_hostnames */,
              "https://global1.provider/dns-query{?dns}",
              "Global Provider 1" /* ui_name */,
              "https://global1.provider/privacy_policy/" /* privacy_policy */,
              true /* display_globally */, {} /* display_countries */),
          DohProviderEntry(
              "Provider_NoDisplay", net::DohProviderIdForHistogram(-2),
              {} /*ip_strs */, {} /* dot_hostnames */,
              "https://nodisplay.provider/dns-query{?dns}",
              "No Display Provider" /* ui_name */,
              "https://nodisplay.provider/privacy_policy/" /* privacy_policy */,
              false /* display_globally */, {} /* display_countries */),
          DohProviderEntry(
              "Provider_EE_FR", net::DohProviderIdForHistogram(-3),
              {} /*ip_strs */, {} /* dot_hostnames */,
              "https://ee.fr.provider/dns-query{?dns}",
              "EE/FR Provider" /* ui_name */,
              "https://ee.fr.provider/privacy_policy/" /* privacy_policy */,
              false /* display_globally */,
              {"EE", "FR"} /* display_countries */),
          DohProviderEntry(
              "Provider_FR", net::DohProviderIdForHistogram(-4),
              {} /*ip_strs */, {} /* dot_hostnames */,
              "https://fr.provider/dns-query{?dns}",
              "FR Provider" /* ui_name */,
              "https://fr.provider/privacy_policy/" /* privacy_policy */,
              false /* display_globally */, {"FR"} /* display_countries */),
          DohProviderEntry(
              "Provider_Global2", net::DohProviderIdForHistogram(-5),
              {} /*ip_strs */, {} /* dot_hostnames */,
              "https://global2.provider/dns-query{?dns}",
              "Global Provider 2" /* ui_name */,
              "https://global2.provider/privacy_policy/" /* privacy_policy */,
              true /* display_globally */, {} /* display_countries */),
      }};
  return *test_providers;
}

bool FindDropdownItem(const base::Value& resolvers,
                      const std::string& name,
                      const std::string& value,
                      const std::string& policy) {
  base::Value dict(base::Value::Type::DICTIONARY);
  dict.SetKey("name", base::Value(name));
  dict.SetKey("value", base::Value(value));
  dict.SetKey("policy", base::Value(policy));

  return std::find(resolvers.GetList().begin(), resolvers.GetList().end(),
                   dict) != resolvers.GetList().end();
}

}  // namespace

class TestSecureDnsHandler : public SecureDnsHandler {
 public:
  // Pull WebUIMessageHandler::set_web_ui() into public so tests can call it.
  using SecureDnsHandler::set_web_ui;
};

class SecureDnsHandlerTest : public InProcessBrowserTest {
 protected:
#if defined(OS_WIN)
  SecureDnsHandlerTest()
      // Mark as not enterprise managed to prevent the secure DNS mode from
      // being downgraded to off.
      : scoped_domain_(false) {}
#else
  SecureDnsHandlerTest() = default;
#endif
  ~SecureDnsHandlerTest() override = default;

  // InProcessBrowserTest:
  void SetUpInProcessBrowserTestFixture() override {
    // Initialize user policy.
    ON_CALL(provider_, IsInitializationComplete(_)).WillByDefault(Return(true));
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
  }

  void SetUpOnMainThread() override {
    handler_ = std::make_unique<TestSecureDnsHandler>();
    handler_->set_web_ui(&web_ui_);
    handler_->RegisterMessages();
    handler_->AllowJavascriptForTesting();
    base::RunLoop().RunUntilIdle();
  }

  void TearDownOnMainThread() override { handler_.reset(); }

  // Updates out-params from the last message sent to WebUI about a secure DNS
  // change. Returns false if the message was invalid or not found.
  bool GetLastSettingsChangedMessage(
      std::string* secure_dns_mode,
      std::vector<std::string>* secure_dns_templates,
      int* management_mode) {
    for (auto it = web_ui_.call_data().rbegin();
         it != web_ui_.call_data().rend(); ++it) {
      const content::TestWebUI::CallData* data = it->get();
      if (data->function_name() != "cr.webUIListenerCallback" ||
          !data->arg1()->is_string() ||
          data->arg1()->GetString() != "secure-dns-setting-changed") {
        continue;
      }

      const base::DictionaryValue* dict = nullptr;
      if (!data->arg2()->GetAsDictionary(&dict))
        return false;

      // Get the secure DNS mode.
      if (!dict->FindStringPath("mode"))
        return false;
      *secure_dns_mode = *dict->FindStringPath("mode");

      // Get the secure DNS templates.
      if (!dict->FindListPath("templates"))
        return false;
      secure_dns_templates->clear();
      for (const auto& template_str :
           dict->FindListPath("templates")->GetList()) {
        if (!template_str.is_string())
          return false;
        secure_dns_templates->push_back(template_str.GetString());
      }

      // Get the forced management description.
      if (!dict->FindIntPath("managementMode"))
        return false;
      *management_mode = *dict->FindIntPath("managementMode");

      return true;
    }
    return false;
  }

  // Sets a policy update which will cause power pref managed change.
  void SetPolicyForPolicyKey(policy::PolicyMap* policy_map,
                             const std::string& policy_key,
                             std::unique_ptr<base::Value> value) {
    policy_map->Set(policy_key, policy::POLICY_LEVEL_MANDATORY,
                    policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
                    std::move(value), nullptr);
    provider_.UpdateChromePolicy(*policy_map);
    base::RunLoop().RunUntilIdle();
  }

  std::unique_ptr<TestSecureDnsHandler> handler_;
  content::TestWebUI web_ui_;
  policy::MockConfigurationPolicyProvider provider_;

 private:
#if defined(OS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain_;
#endif

  DISALLOW_COPY_AND_ASSIGN(SecureDnsHandlerTest);
};

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, SecureDnsModes) {
  PrefService* local_state = g_browser_process->local_state();
  std::string secure_dns_mode;
  std::vector<std::string> secure_dns_templates;
  int management_mode;

  local_state->SetString(prefs::kDnsOverHttpsMode,
                         chrome_browser_net::kDnsOverHttpsModeOff);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeOff, secure_dns_mode);

  local_state->SetString(prefs::kDnsOverHttpsMode,
                         chrome_browser_net::kDnsOverHttpsModeAutomatic);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeAutomatic, secure_dns_mode);

  local_state->SetString(prefs::kDnsOverHttpsMode,
                         chrome_browser_net::kDnsOverHttpsModeSecure);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeSecure, secure_dns_mode);

  local_state->SetString(prefs::kDnsOverHttpsMode, "unknown");
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeOff, secure_dns_mode);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, SecureDnsPolicy) {
  policy::PolicyMap policy_map;
  SetPolicyForPolicyKey(&policy_map, policy::key::kDnsOverHttpsMode,
                        std::make_unique<base::Value>(
                            chrome_browser_net::kDnsOverHttpsModeAutomatic));

  PrefService* local_state = g_browser_process->local_state();
  local_state->SetString(prefs::kDnsOverHttpsMode,
                         chrome_browser_net::kDnsOverHttpsModeSecure);

  std::string secure_dns_mode;
  std::vector<std::string> secure_dns_templates;
  int management_mode;
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeAutomatic, secure_dns_mode);
  EXPECT_EQ(static_cast<int>(
                chrome_browser_net::SecureDnsUiManagementMode::kNoOverride),
            management_mode);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, SecureDnsPolicyChange) {
  policy::PolicyMap policy_map;
  SetPolicyForPolicyKey(&policy_map, policy::key::kDnsOverHttpsMode,
                        std::make_unique<base::Value>(
                            chrome_browser_net::kDnsOverHttpsModeAutomatic));

  std::string secure_dns_mode;
  std::vector<std::string> secure_dns_templates;
  int management_mode;
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeAutomatic, secure_dns_mode);
  EXPECT_EQ(static_cast<int>(
                chrome_browser_net::SecureDnsUiManagementMode::kNoOverride),
            management_mode);

  SetPolicyForPolicyKey(
      &policy_map, policy::key::kDnsOverHttpsMode,
      std::make_unique<base::Value>(chrome_browser_net::kDnsOverHttpsModeOff));
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeOff, secure_dns_mode);
  EXPECT_EQ(static_cast<int>(
                chrome_browser_net::SecureDnsUiManagementMode::kNoOverride),
            management_mode);
}

// On platforms where enterprise policies do not have default values, test
// that DoH is disabled when non-DoH policies are set.
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, OtherPoliciesSet) {
  policy::PolicyMap policy_map;
  SetPolicyForPolicyKey(&policy_map, policy::key::kIncognitoModeAvailability,
                        std::make_unique<base::Value>(1));

  PrefService* local_state = g_browser_process->local_state();
  local_state->SetString(prefs::kDnsOverHttpsMode,
                         chrome_browser_net::kDnsOverHttpsModeSecure);

  std::string secure_dns_mode;
  std::vector<std::string> secure_dns_templates;
  int management_mode;
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(chrome_browser_net::kDnsOverHttpsModeOff, secure_dns_mode);
  EXPECT_EQ(
      static_cast<int>(
          chrome_browser_net::SecureDnsUiManagementMode::kDisabledManaged),
      management_mode);
}
#endif

// This test makes no assumptions about the country or underlying resolver list.
IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, DropdownList) {
  base::ListValue args;
  args.AppendString(kWebUiFunctionName);

  web_ui_.HandleReceivedMessage(kGetSecureDnsResolverList, &args);
  const content::TestWebUI::CallData& call_data = *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data.arg1()->GetString());
  ASSERT_TRUE(call_data.arg2()->GetBool());

  // Check results.
  base::Value::ConstListView resolver_list = call_data.arg3()->GetList();
  ASSERT_GE(resolver_list.size(), 1U);
  EXPECT_EQ("custom", resolver_list[0].FindKey("value")->GetString());
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, DropdownListForCountry) {
  // The 'EE' list should start with the custom entry, followed by the two
  // global providers and the 'EE' provider in some random order.
  base::Value resolver_list = handler_->GetSecureDnsResolverListForCountry(
      country_codes::CountryCharsToCountryID('E', 'E'),
      GetDohProviderListForTesting());
  EXPECT_EQ(4u, resolver_list.GetList().size());
  EXPECT_EQ("custom", resolver_list.GetList()[0].FindKey("value")->GetString());
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 1",
                               "https://global1.provider/dns-query{?dns}",
                               "https://global1.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 2",
                               "https://global2.provider/dns-query{?dns}",
                               "https://global2.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "EE/FR Provider",
                               "https://ee.fr.provider/dns-query{?dns}",
                               "https://ee.fr.provider/privacy_policy/"));

  // The 'FR' list should start with the custom entry, followed by the two
  // global providers and the two 'FR' providers in some random order.
  resolver_list = handler_->GetSecureDnsResolverListForCountry(
      country_codes::CountryCharsToCountryID('F', 'R'),
      GetDohProviderListForTesting());
  EXPECT_EQ(5u, resolver_list.GetList().size());
  EXPECT_EQ("custom", resolver_list.GetList()[0].FindKey("value")->GetString());
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 1",
                               "https://global1.provider/dns-query{?dns}",
                               "https://global1.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 2",
                               "https://global2.provider/dns-query{?dns}",
                               "https://global2.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "EE/FR Provider",
                               "https://ee.fr.provider/dns-query{?dns}",
                               "https://ee.fr.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "FR Provider",
                               "https://fr.provider/dns-query{?dns}",
                               "https://fr.provider/privacy_policy/"));

  // The 'CA' list should start with the custom entry, followed by the two
  // global providers.
  resolver_list = handler_->GetSecureDnsResolverListForCountry(
      country_codes::CountryCharsToCountryID('C', 'A'),
      GetDohProviderListForTesting());
  EXPECT_EQ(3u, resolver_list.GetList().size());
  EXPECT_EQ("custom", resolver_list.GetList()[0].FindKey("value")->GetString());
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 1",
                               "https://global1.provider/dns-query{?dns}",
                               "https://global1.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 2",
                               "https://global2.provider/dns-query{?dns}",
                               "https://global2.provider/privacy_policy/"));
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, DropdownListChange) {
  // Populate the map for recording dropdown change metrics.
  base::Value resolver_list = handler_->GetSecureDnsResolverListForCountry(
      country_codes::CountryCharsToCountryID('E', 'E'),
      GetDohProviderListForTesting());
  EXPECT_EQ(4u, resolver_list.GetList().size());

  base::HistogramTester histograms;
  base::ListValue args;
  args.AppendString("custom" /* old_provider */);
  args.AppendString(
      "https://global1.provider/dns-query{?dns}" /* new_provider */);
  web_ui_.HandleReceivedMessage(kRecordUserDropdownInteraction, &args);

  const std::string uma_base("Net.DNS.UI.DropdownSelectionEvent");
  histograms.ExpectTotalCount(uma_base + ".Ignored", 2u);
  histograms.ExpectTotalCount(uma_base + ".Selected", 1u);
  histograms.ExpectTotalCount(uma_base + ".Unselected", 1u);
}

class SecureDnsHandlerTestWithDisabledProviders : public SecureDnsHandlerTest {
 protected:
  SecureDnsHandlerTestWithDisabledProviders() {
    scoped_features_.InitAndEnableFeatureWithParameters(
        features::kDnsOverHttps,
        {{"DisabledProviders",
          "Provider_Global2, , Provider_EE_FR,Unexpected"}});
  }

 private:
  base::test::ScopedFeatureList scoped_features_;

  DISALLOW_COPY_AND_ASSIGN(SecureDnsHandlerTestWithDisabledProviders);
};

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTestWithDisabledProviders,
                       DropdownListDisabledProviders) {
  // The 'FR' list should start with the custom entry, followed by the two
  // global providers and the two 'FR' providers in some random order.
  base::Value resolver_list = handler_->GetSecureDnsResolverListForCountry(
      country_codes::CountryCharsToCountryID('F', 'R'),
      GetDohProviderListForTesting());
  EXPECT_EQ(3u, resolver_list.GetList().size());
  EXPECT_EQ("custom", resolver_list.GetList()[0].FindKey("value")->GetString());
  EXPECT_TRUE(FindDropdownItem(resolver_list, "Global Provider 1",
                               "https://global1.provider/dns-query{?dns}",
                               "https://global1.provider/privacy_policy/"));
  EXPECT_TRUE(FindDropdownItem(resolver_list, "FR Provider",
                               "https://fr.provider/dns-query{?dns}",
                               "https://fr.provider/privacy_policy/"));
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTestWithDisabledProviders,
                       SecureDnsTemplates) {
  std::string good_post_template = "https://foo.test/";
  std::string good_get_template = "https://bar.test/dns-query{?dns}";
  std::string bad_template = "dns-query{?dns}";

  std::string secure_dns_mode;
  std::vector<std::string> secure_dns_templates;
  int management_mode;
  PrefService* local_state = g_browser_process->local_state();
  local_state->SetString(prefs::kDnsOverHttpsTemplates, good_post_template);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(1u, secure_dns_templates.size());
  EXPECT_EQ(good_post_template, secure_dns_templates[0]);

  local_state->SetString(prefs::kDnsOverHttpsTemplates,
                         good_post_template + " " + good_get_template);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(2u, secure_dns_templates.size());
  EXPECT_EQ(good_post_template, secure_dns_templates[0]);
  EXPECT_EQ(good_get_template, secure_dns_templates[1]);

  local_state->SetString(prefs::kDnsOverHttpsTemplates, bad_template);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(0u, secure_dns_templates.size());

  local_state->SetString(prefs::kDnsOverHttpsTemplates,
                         bad_template + " " + good_post_template);
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(1u, secure_dns_templates.size());
  EXPECT_EQ(good_post_template, secure_dns_templates[0]);

  // Should still return a provider that was disabled.
  local_state->SetString(prefs::kDnsOverHttpsTemplates,
                         "https://global2.provider/dns-query{?dns}");
  EXPECT_TRUE(GetLastSettingsChangedMessage(
      &secure_dns_mode, &secure_dns_templates, &management_mode));
  EXPECT_EQ(1u, secure_dns_templates.size());
  EXPECT_EQ("https://global2.provider/dns-query{?dns}",
            secure_dns_templates[0]);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, TemplateValid) {
  base::ListValue args;
  args.AppendString(kWebUiFunctionName);
  args.AppendString("https://example.template/dns-query");

  base::HistogramTester histograms;
  web_ui_.HandleReceivedMessage(kValidateCustomDnsEntry, &args);
  const content::TestWebUI::CallData& call_data = *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data.arg1()->GetString());
  // The request should be successful.
  ASSERT_TRUE(call_data.arg2()->GetBool());
  // The template should be valid.
  ASSERT_EQ("https://example.template/dns-query",
            call_data.arg3()->GetString());
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", false, 0);
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", true, 1);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, TemplateInvalid) {
  base::ListValue args;
  args.AppendString(kWebUiFunctionName);
  args.AppendString("invalid_template");

  base::HistogramTester histograms;
  web_ui_.HandleReceivedMessage(kValidateCustomDnsEntry, &args);
  const content::TestWebUI::CallData& call_data = *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data.arg1()->GetString());
  // The request should be successful.
  ASSERT_TRUE(call_data.arg2()->GetBool());
  // The template should be invalid.
  ASSERT_EQ(std::string(), call_data.arg3()->GetString());
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", false, 1);
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", true, 0);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, MultipleTemplates) {
  base::HistogramTester histograms;
  base::ListValue args_valid;
  args_valid.AppendString(kWebUiFunctionName);
  args_valid.AppendString(
      "https://example1.template/dns    https://example2.template/dns-query");
  web_ui_.HandleReceivedMessage(kValidateCustomDnsEntry, &args_valid);
  const content::TestWebUI::CallData& call_data_valid =
      *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data_valid.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data_valid.arg1()->GetString());
  // The request should be successful.
  ASSERT_TRUE(call_data_valid.arg2()->GetBool());
  // Both templates are valid, so validate returns the first.
  ASSERT_EQ("https://example1.template/dns",
            call_data_valid.arg3()->GetString());
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", false, 0);
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", true, 1);

  base::ListValue args_invalid;
  args_invalid.AppendString(kWebUiFunctionName);
  args_invalid.AppendString("invalid_template https://example.template/dns");
  web_ui_.HandleReceivedMessage(kValidateCustomDnsEntry, &args_invalid);
  const content::TestWebUI::CallData& call_data_invalid =
      *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data_invalid.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data_invalid.arg1()->GetString());
  // The request should be successful.
  ASSERT_TRUE(call_data_invalid.arg2()->GetBool());
  // The entry should be invalid.
  ASSERT_EQ(std::string(), call_data_invalid.arg3()->GetString());
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", false, 1);
  histograms.ExpectBucketCount("Net.DNS.UI.ValidationAttemptSuccess", true, 1);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, TemplateProbeSuccess) {
  auto network_context_ =
      std::make_unique<chrome_browser_net::FakeHostResolverNetworkContext>(
          std::vector<chrome_browser_net::FakeHostResolver::SingleResult>(
              {chrome_browser_net::FakeHostResolver::SingleResult(
                  net::OK, net::ResolveErrorInfo(net::OK),
                  chrome_browser_net::FakeHostResolver::
                      kOneAddressResponse)}) /* current_config_result_list */,
          std::vector<chrome_browser_net::FakeHostResolver::
                          SingleResult>() /* google_config_result_list */);
  handler_->SetNetworkContextForTesting(network_context_.get());
  base::HistogramTester histograms;
  base::ListValue args_valid;
  args_valid.AppendString(kWebUiFunctionName);
  args_valid.AppendString("https://example.template/dns-query");
  web_ui_.HandleReceivedMessage(kProbeCustomDnsTemplate, &args_valid);
  base::RunLoop().RunUntilIdle();

  const content::TestWebUI::CallData& call_data_valid =
      *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data_valid.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data_valid.arg1()->GetString());
  // The request should be successful.
  ASSERT_TRUE(call_data_valid.arg2()->GetBool());
  // The probe query should have succeeded.
  ASSERT_TRUE(call_data_valid.arg3()->GetBool());
  histograms.ExpectBucketCount("Net.DNS.UI.ProbeAttemptSuccess", false, 0);
  histograms.ExpectBucketCount("Net.DNS.UI.ProbeAttemptSuccess", true, 1);
}

IN_PROC_BROWSER_TEST_F(SecureDnsHandlerTest, TemplateProbeFailure) {
  auto network_context_ =
      std::make_unique<chrome_browser_net::FakeHostResolverNetworkContext>(
          std::vector<chrome_browser_net::FakeHostResolver::SingleResult>(
              {chrome_browser_net::FakeHostResolver::SingleResult(
                  net::ERR_NAME_NOT_RESOLVED,
                  net::ResolveErrorInfo(net::ERR_DNS_MALFORMED_RESPONSE),
                  chrome_browser_net::FakeHostResolver::
                      kNoResponse)}) /* current_config_result_list */,
          std::vector<chrome_browser_net::FakeHostResolver::
                          SingleResult>() /* google_config_result_list */);
  handler_->SetNetworkContextForTesting(network_context_.get());
  base::HistogramTester histograms;
  base::ListValue args_valid;
  args_valid.AppendString(kWebUiFunctionName);
  args_valid.AppendString("https://example.template/dns-query");
  web_ui_.HandleReceivedMessage(kProbeCustomDnsTemplate, &args_valid);
  base::RunLoop().RunUntilIdle();

  const content::TestWebUI::CallData& call_data_valid =
      *web_ui_.call_data().back();
  EXPECT_EQ("cr.webUIResponse", call_data_valid.function_name());
  EXPECT_EQ(kWebUiFunctionName, call_data_valid.arg1()->GetString());
  // The request should be successful.
  ASSERT_TRUE(call_data_valid.arg2()->GetBool());
  // The probe query should have failed.
  ASSERT_FALSE(call_data_valid.arg3()->GetBool());
  histograms.ExpectBucketCount("Net.DNS.UI.ProbeAttemptSuccess", false, 1);
  histograms.ExpectBucketCount("Net.DNS.UI.ProbeAttemptSuccess", true, 0);
}

}  // namespace settings
