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

#include "chrome/browser/ash/net/secure_dns_manager.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/chrome_pref_names.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "chrome/browser/ash/net/dns_over_https/templates_uri_resolver.h"
#include "chrome/browser/ash/policy/core/device_attributes_fake.h"
#include "chrome/browser/net/secure_dns_config.h"
#include "chrome/browser/net/stub_resolver_config_reader.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_ui_data.h"
#include "components/account_id/account_id.h"
#include "components/account_id/account_id_literal.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/session_manager/test/test_user_session_manager.h"
#include "components/user_manager/user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash {
namespace {

using testing::_;
using testing::Contains;
using testing::Key;
using testing::Return;
using testing::SizeIs;

constexpr const char kGoogleDns[] = "https://dns.google/dns-query{?dns}";
constexpr const char kCloudflareDns[] =
    "https://chrome.cloudflare-dns.com/dns-query";
constexpr const char kMultipleTemplates[] =
    "https://dns.google/dns-query{?dns}  "
    "https://chrome.cloudflare-dns.com/dns-query ";
constexpr AccountId::Literal kTestAccountId =
    AccountId::Literal::FromUserEmailGaiaId("test-user@testdomain.com",
                                            GaiaId::Literal("1234567890"));

TestingPrefServiceSimple* local_state() {
  return TestingBrowserProcess::GetGlobal()->GetTestingLocalState();
}

class MockDoHTemplatesUriResolver
    : public dns_over_https::TemplatesUriResolver {
 public:
  MockDoHTemplatesUriResolver() = default;
  MOCK_METHOD(void,
              Update,
              (const PrefService&, const user_manager::User&),
              (override));
  MOCK_METHOD(bool, GetDohWithIdentifiersActive, (), (override));
  MOCK_METHOD(std::string, GetEffectiveTemplates, (), (override));
  MOCK_METHOD(std::string, GetDisplayTemplates, (), (override));
};

void OnGetDictProperties(const std::string& prop_name,
                         bool* success_out,
                         std::map<std::string, std::string>* props_out,
                         base::OnceClosure callback,
                         std::optional<base::DictValue> result) {
  *success_out = result.has_value();
  if (result) {
    base::DictValue* value = result->FindDict(prop_name);
    if (value != nullptr) {
      for (const auto kv : *value) {
        props_out->emplace(kv.first, kv.second.GetString());
      }
    }
  }
  std::move(callback).Run();
}

void OnGetStringsProperties(const std::string& prop_name,
                            bool* success_out,
                            std::vector<std::string>* props_out,
                            base::OnceClosure callback,
                            std::optional<base::DictValue> result) {
  *success_out = result.has_value();
  if (result) {
    base::ListValue* value = result->FindList(prop_name);
    if (value != nullptr) {
      for (const auto& e : *value) {
        props_out->push_back(e.GetString());
      }
    }
  }
  std::move(callback).Run();
}

std::map<std::string, std::string> GetDOHProviders() {
  bool success = false;
  std::map<std::string, std::string> props;
  ShillManagerClient* shill_manager = ShillManagerClient::Get();
  base::RunLoop run_loop;
  shill_manager->GetProperties(
      base::BindOnce(&OnGetDictProperties, shill::kDNSProxyDOHProvidersProperty,
                     base::Unretained(&success), base::Unretained(&props),
                     run_loop.QuitClosure()));
  run_loop.Run();
  EXPECT_TRUE(success);
  return props;
}

std::vector<std::string> GetDOHIncludedDomains() {
  bool success = false;
  std::vector<std::string> domains;
  ShillManagerClient* shill_manager = ShillManagerClient::Get();
  base::RunLoop run_loop;
  shill_manager->GetProperties(base::BindOnce(
      &OnGetStringsProperties, shill::kDOHIncludedDomainsProperty,
      base::Unretained(&success), base::Unretained(&domains),
      run_loop.QuitClosure()));
  run_loop.Run();
  EXPECT_TRUE(success);
  return domains;
}

std::vector<std::string> GetDOHExcludedDomains() {
  bool success = false;
  std::vector<std::string> domains;
  ShillManagerClient* shill_manager = ShillManagerClient::Get();
  base::RunLoop run_loop;
  shill_manager->GetProperties(base::BindOnce(
      &OnGetStringsProperties, shill::kDOHExcludedDomainsProperty,
      base::Unretained(&success), base::Unretained(&domains),
      run_loop.QuitClosure()));
  run_loop.Run();
  EXPECT_TRUE(success);
  return domains;
}

class SecureDnsManagerObserver : public SecureDnsManager::Observer {
 public:
  explicit SecureDnsManagerObserver(
      raw_ptr<SecureDnsManager> secure_dns_manager)
      : secure_dns_manager_(secure_dns_manager) {
    secure_dns_manager_->AddObserver(this);
  }

  SecureDnsManagerObserver(const SecureDnsManagerObserver&) = delete;
  SecureDnsManagerObserver& operator=(const SecureDnsManagerObserver&) = delete;
  ~SecureDnsManagerObserver() override {
    if (secure_dns_manager_) {
      secure_dns_manager_->RemoveObserver(this);
    }
  }
  std::string doh_mode() const { return doh_mode_; }
  std::string doh_template_uri() const { return doh_template_uri_; }

 private:
  void OnTemplateUrisChanged(const std::string& template_uris) override {
    doh_template_uri_ = template_uris;
  }
  void OnModeChanged(const std::string& mode) override { doh_mode_ = mode; }
  void OnSecureDnsManagerShutdown() override {
    secure_dns_manager_->RemoveObserver(this);
    secure_dns_manager_ = nullptr;
  }

  raw_ptr<SecureDnsManager> secure_dns_manager_;
  std::string doh_mode_;
  std::string doh_template_uri_;
};

class SecureDnsManagerTest : public testing::Test {
 public:
  SecureDnsManagerTest() = default;

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

  void SetUp() override {
    SecureDnsManager::RegisterProfilePrefs(profile_prefs_.registry());

    network_handler_test_helper_.RegisterPrefs(profile_prefs_.registry(),
                                               nullptr);
    network_handler_test_helper_.InitializePrefs(&profile_prefs_,
                                                 local_state());
    network_handler_test_helper_.AddDefaultProfiles();

    test_user_session_manager_ =
        std::make_unique<ash::test::TestUserSessionManager>(local_state());

    // Add a user for test.
    user_ = test_user_session_manager_->AddRegularUser(kTestAccountId);
    ASSERT_TRUE(user_);

    // Simulate login.
    test_user_session_manager_->LogIn(kTestAccountId);
    user_manager::UserManager::Get()->OnUserProfileCreated(kTestAccountId,
                                                           &profile_prefs_);

    // SystemNetworkContextManager cannot be instantiated here,
    // which normally owns the StubResolverConfigReader instance, so
    // inject a StubResolverConfigReader instance here.
    stub_resolver_config_reader_ =
        std::make_unique<StubResolverConfigReader>(local_state());
    SystemNetworkContextManager::set_stub_resolver_config_reader_for_testing(
        stub_resolver_config_reader_.get());

    secure_dns_manager_ = std::make_unique<SecureDnsManager>(
        local_state(), std::make_unique<policy::FakeDeviceAttributes>(), *user_,
        /*is_profile_managed=*/true);
    secure_dns_manager_observer_ =
        std::make_unique<SecureDnsManagerObserver>(secure_dns_manager_.get());
  }

  void TearDown() override {
    secure_dns_manager_observer_.reset();
    secure_dns_manager_.reset();
    SystemNetworkContextManager::set_stub_resolver_config_reader_for_testing(
        nullptr);
    stub_resolver_config_reader_.reset();

    user_manager::UserManager::Get()->OnUserProfileWillBeDestroyed(
        kTestAccountId);
    user_ = nullptr;
    test_user_session_manager_.reset();
    NetworkHandler::Get()->ShutdownPrefServices();
  }

  void ChangeNetworkOncSource(const std::string& path,
                              ::onc::ONCSource onc_source) {
    std::unique_ptr<ash::NetworkUIData> ui_data =
        ash::NetworkUIData::CreateFromONC(onc_source);
    network_handler_test_helper_.SetServiceProperty(
        path, shill::kUIDataProperty, base::Value(ui_data->GetAsJson()));
  }

  void ResetSecureDnsManager() {
    secure_dns_manager_observer_.reset();
    secure_dns_manager_.reset();
  }

  user_manager::User& user() { return *user_; }
  PrefService* profile_prefs() { return &profile_prefs_; }
  SecureDnsManager* secure_dns_manager() { return secure_dns_manager_.get(); }
  SecureDnsManagerObserver* secure_dns_manager_observer() {
    return secure_dns_manager_observer_.get();
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  NetworkHandlerTestHelper network_handler_test_helper_;
  std::unique_ptr<ash::test::TestUserSessionManager> test_user_session_manager_;
  std::unique_ptr<StubResolverConfigReader> stub_resolver_config_reader_;
  raw_ptr<user_manager::User> user_;
  TestingPrefServiceSimple profile_prefs_;
  std::unique_ptr<SecureDnsManager> secure_dns_manager_;
  std::unique_ptr<SecureDnsManagerObserver> secure_dns_manager_observer_;
};

TEST_F(SecureDnsManagerTest, SetModeOff) {
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsMode,
                     base::Value(SecureDnsConfig::kModeOff));

  auto providers = GetDOHProviders();

  EXPECT_TRUE(providers.empty());
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            base::Value(SecureDnsConfig::kModeOff));
}

TEST_F(SecureDnsManagerTest, SetModeOffIgnoresTemplates) {
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsMode,
                     base::Value(SecureDnsConfig::kModeOff));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  auto providers = GetDOHProviders();

  EXPECT_TRUE(providers.empty());
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeOff);
}

TEST_F(SecureDnsManagerTest, SetModeSecure) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  auto providers = GetDOHProviders();

  const auto it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_TRUE(it->second.empty());
  EXPECT_EQ(providers.size(), 1u);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeSecure);
}

TEST_F(SecureDnsManagerTest, SetModeSecureMultipleTemplates) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kMultipleTemplates));

  auto providers = GetDOHProviders();

  EXPECT_TRUE(providers.find(kGoogleDns) != providers.end());
  EXPECT_TRUE(providers.find(kCloudflareDns) != providers.end());
  EXPECT_EQ(providers.size(), 2u);
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            kMultipleTemplates);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
            kMultipleTemplates);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeSecure);
}

TEST_F(SecureDnsManagerTest, SetModeSecureWithFallback) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  auto providers = GetDOHProviders();

  const auto it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_EQ(it->second, "*");
  EXPECT_EQ(providers.size(), 1u);
}

TEST_F(SecureDnsManagerTest, SetModeSecureWithFallbackMultipleTemplates) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kMultipleTemplates));

  auto providers = GetDOHProviders();

  EXPECT_TRUE(providers.find(kGoogleDns) != providers.end());
  EXPECT_TRUE(providers.find(kCloudflareDns) != providers.end());
  EXPECT_EQ(providers.size(), 2u);
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            kMultipleTemplates);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
            kMultipleTemplates);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeAutomatic);
}

TEST_F(SecureDnsManagerTest, SetModeAutomaticWithTemplates) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kMultipleTemplates));

  auto providers = GetDOHProviders();

  auto it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_FALSE(it->second.empty());
  it = providers.find(kCloudflareDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_FALSE(it->second.empty());
  EXPECT_EQ(providers.size(), 2u);
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            kMultipleTemplates);
}

// Tests that the `DoHTemplatesUriResolver` resolver is called when secure DNS
// prefs change and that the result, provided by `GetEffectiveTemplates` is
// read.
TEST_F(SecureDnsManagerTest, DoHTemplatesUriResolverCalled) {
  constexpr char effectiveTemplate[] = "effectiveTemplate";
  // The test will update the four prefs that `SecureDnsManager` is observing.
  constexpr int prefUpdatesCallCount = 4;

  std::unique_ptr<MockDoHTemplatesUriResolver> template_uri_resolver =
      std::make_unique<MockDoHTemplatesUriResolver>();
  EXPECT_CALL(*template_uri_resolver, Update(_, _)).Times(prefUpdatesCallCount);
  EXPECT_CALL(*template_uri_resolver, GetEffectiveTemplates())
      .Times(prefUpdatesCallCount)
      .WillRepeatedly(Return(effectiveTemplate));

  secure_dns_manager()->SetDoHTemplatesUriResolverForTesting(
      std::move(template_uri_resolver));

  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kMultipleTemplates));
  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value(kMultipleTemplates));
  local_state()->Set(ash::prefs::kDnsOverHttpsSalt, base::Value("testsalt"));

  auto providers = GetDOHProviders();

  EXPECT_THAT(providers, SizeIs(1));
  EXPECT_THAT(providers, Contains(Key(effectiveTemplate)));
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            effectiveTemplate);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
            effectiveTemplate);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeAutomatic);
}

TEST_F(SecureDnsManagerTest, NetworkMetadataStoreHasDohWithIdentifiersActive) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value("https://dns.google/dns-query{?dns}"));
  local_state()->Set(ash::prefs::kDnsOverHttpsSalt, base::Value("testsalt"));

  auto providers = GetDOHProviders();

  EXPECT_TRUE(NetworkHandler::Get()
                  ->network_metadata_store()
                  ->secure_dns_templates_with_identifiers_active());

  local_state()->ClearPref(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers);
  providers = GetDOHProviders();

  EXPECT_FALSE(NetworkHandler::Get()
                   ->network_metadata_store()
                   ->secure_dns_templates_with_identifiers_active());
}

TEST_F(SecureDnsManagerTest, kDnsOverHttpsEffectiveTemplatesChromeOS) {
  constexpr char kUriTemplateWithIdentifiers[] =
      "https://dns.google.alternativeuri/"
      "${USER_EMAIL}/{?dns}";

  constexpr char kEffectiveUriTemplateWithIdentifiers[] =
      "https://dns.google.alternativeuri/"
      "B07D2C5D119EB1881671C3B8D84CBE4FE3595C0C9ECBBF7670B18DDFDA072F66/{?dns}";

  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value(kUriTemplateWithIdentifiers));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  auto providers = GetDOHProviders();

  // Verify that the value of kDnsOverHttpsEffectiveTemplatesChromeOS pref is
  // ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers with the hex encoded
  // hashed value of the user identifier.
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            kEffectiveUriTemplateWithIdentifiers);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
            kEffectiveUriTemplateWithIdentifiers);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeAutomatic);

  local_state()->ClearPref(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers);

  providers = GetDOHProviders();

  // Verify that the value of kDnsOverHttpsEffectiveTemplatesChromeOS pref is
  // prefs::kDnsOverHttpsTemplates since the URI template with identifiers pref
  // was cleared.
  EXPECT_EQ(local_state()->GetString(
                ash::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
            kGoogleDns);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeAutomatic);
}

TEST_F(SecureDnsManagerTest, DefaultNetworkObservedForIpAddressPlaceholder) {
  constexpr char kUriTemplateWithEmail[] =
      "https://dns.google.alternativeuri/"
      "${USER_EMAIL}/{?dns}";
  constexpr char kUriTemplateWithIp[] =
      "https://dns.google.alternativeuri/"
      "${DEVICE_IP_ADDRESSES}/{?dns}";

  int expected_uri_template_update_count = 0;
  int actual_uri_template_update_count = 0;

  std::unique_ptr<MockDoHTemplatesUriResolver> template_uri_resolver =
      std::make_unique<MockDoHTemplatesUriResolver>();

  ON_CALL(*template_uri_resolver, Update(_, _))
      .WillByDefault([&actual_uri_template_update_count]() {
        actual_uri_template_update_count++;
      });
  EXPECT_CALL(*template_uri_resolver, GetDohWithIdentifiersActive())
      .WillRepeatedly(testing::Return(true));

  secure_dns_manager()->SetDoHTemplatesUriResolverForTesting(
      std::move(template_uri_resolver));

  EXPECT_EQ(actual_uri_template_update_count,
            expected_uri_template_update_count);

  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value(kUriTemplateWithEmail));
  // Each pref update above will trigger an update request for the URI
  // templates.
  expected_uri_template_update_count = 2;
  EXPECT_EQ(actual_uri_template_update_count,
            expected_uri_template_update_count);

  const ash::NetworkState* network =
      ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
  ChangeNetworkOncSource(network->path(),
                         ::onc::ONCSource::ONC_SOURCE_USER_POLICY);
  // Default network changes should not trigger a re-evaluation of the templates
  // URI if the DoH policy is not configured to use the device IP addresses.
  EXPECT_EQ(actual_uri_template_update_count,
            expected_uri_template_update_count);

  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value(kUriTemplateWithIp));
  EXPECT_EQ(actual_uri_template_update_count,
            ++expected_uri_template_update_count);

  ChangeNetworkOncSource(network->path(),
                         ::onc::ONCSource::ONC_SOURCE_USER_POLICY);
  EXPECT_EQ(actual_uri_template_update_count,
            ++expected_uri_template_update_count);
}

TEST_F(SecureDnsManagerTest, DefaultTemplateUrisForwardedToShill) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  auto providers = GetDOHProviders();
  // The content of the provider list depends on the current country.
  EXPECT_FALSE(providers.empty());
}

class MockPropertyChangeObserver : public ash::ShillPropertyChangedObserver {
 public:
  MockPropertyChangeObserver() = default;
  ~MockPropertyChangeObserver() override = default;
  MOCK_METHOD(void,
              OnPropertyChanged,
              (const std::string& name, const base::Value& value),
              (override));
};

TEST_F(SecureDnsManagerTest, NoDuplicateShillPropertyUpdateRequests) {
  constexpr char kTemplateUri1[] = "https://dns.google1.com";
  constexpr char kTemplateUri2[] = "https://dns.google2.com";
  constexpr char kEffectiveTemplateUri[] = "https://dns.google2.com";

  // The call to update the shill properties should be invoked just once. For
  // kDOHIncludedDomainsProperty, the mock TemplateUriResolver always returns
  // the same DoH providers.
  testing::StrictMock<MockPropertyChangeObserver> observer;
  EXPECT_CALL(observer,
              OnPropertyChanged(shill::kDOHIncludedDomainsProperty, testing::_))
      .Times(1);
  EXPECT_CALL(observer,
              OnPropertyChanged(shill::kDOHExcludedDomainsProperty, testing::_))
      .Times(1);

  // The two calls are generated:
  // 1. at startup, with an empty value
  // 2. after the template URI resolver is configured
  base::RunLoop provider_property_update_loop;
  int providers_property_update_count = 0;
  EXPECT_CALL(observer, OnPropertyChanged(shill::kDNSProxyDOHProvidersProperty,
                                          testing::_))
      .Times(2)
      .WillRepeatedly(
          [&providers_property_update_count, &provider_property_update_loop](
              const std::string&, const base::Value&) {
            if (++providers_property_update_count == 2) {
              provider_property_update_loop.Quit();
            }
          });

  ash::ShillManagerClient* shill_manager_client =
      ash::ShillManagerClient::Get();
  shill_manager_client->AddPropertyChangedObserver(&observer);

  int actual_uri_template_update_count = 0;

  std::unique_ptr<MockDoHTemplatesUriResolver> template_uri_resolver =
      std::make_unique<MockDoHTemplatesUriResolver>();

  ON_CALL(*template_uri_resolver, Update(_, _))
      .WillByDefault([&actual_uri_template_update_count]() {
        actual_uri_template_update_count++;
      });
  EXPECT_CALL(*template_uri_resolver, GetDohWithIdentifiersActive())
      .WillRepeatedly(testing::Return(true));
  EXPECT_CALL(*template_uri_resolver, GetEffectiveTemplates())
      .WillRepeatedly(testing::Return(kEffectiveTemplateUri));

  secure_dns_manager()->SetDoHTemplatesUriResolverForTesting(
      std::move(template_uri_resolver));

  EXPECT_EQ(actual_uri_template_update_count, 0);

  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value(kTemplateUri1));
  local_state()->Set(ash::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
                     base::Value(kTemplateUri2));
  // Verify that every pref update above will trigger an update request for the
  // DoH providers.
  EXPECT_EQ(actual_uri_template_update_count, 3);
  provider_property_update_loop.Run();
}

TEST_F(SecureDnsManagerTest, SetDOHIncludedDomains) {
  std::vector<std::string> domains = {"test.com", "*.test.com"};
  base::Value pref_value(base::Value::Type::LIST);
  for (const auto& domain : domains) {
    pref_value.GetList().Append(domain);
  }
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);

  EXPECT_EQ(domains, GetDOHIncludedDomains());
}

TEST_F(SecureDnsManagerTest, SetDOHExcludedDomains) {
  std::vector<std::string> domains = {"test.com", "*.test.com"};
  base::Value pref_value(base::Value::Type::LIST);
  for (const auto& domain : domains) {
    pref_value.GetList().Append(domain);
  }
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(domains, GetDOHExcludedDomains());
}
// This test verifies the user-set local_state to user-set profile_prefs
// migration logic for DoH prefs.
TEST_F(SecureDnsManagerTest, LocalStateToProfilePrefMigration) {
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsMode,
                     base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  EXPECT_EQ(profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsMode),
            "");
  EXPECT_EQ(
      profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsTemplates),
      "");

  {
    auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
        local_state(), std::make_unique<policy::FakeDeviceAttributes>(), user(),
        /*is_profile_managed=*/false);
    // Verify that the user-set local state prefs are copied to profile prefs
    // for unmanaged users.
    EXPECT_EQ(profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsMode),
              SecureDnsConfig::kModeSecure);
    EXPECT_EQ(
        profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsTemplates),
        kGoogleDns);
  }

  profile_prefs()->ClearPref(ash::chrome_prefs::kDnsOverHttpsMode);
  profile_prefs()->ClearPref(ash::chrome_prefs::kDnsOverHttpsTemplates);

  {
    auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
        local_state(), std::make_unique<policy::FakeDeviceAttributes>(), user(),
        /*is_profile_managed=*/true);
    // Verify that the user-set local state prefs are not copied to profile
    // prefs for managed users. SecureDnsConfig::kModeAutomatic is the default
    // value for secure DoH mode.
    EXPECT_EQ(profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsMode),
              SecureDnsConfig::kModeAutomatic);
    EXPECT_EQ(
        profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsTemplates),
        "");
  }

  profile_prefs()->Set(ash::chrome_prefs::kDnsOverHttpsMode,
                       base::Value(SecureDnsConfig::kModeSecure));
  profile_prefs()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                       base::Value(kCloudflareDns));

  {
    auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
        local_state(), std::make_unique<policy::FakeDeviceAttributes>(), user(),
        /*is_profile_managed=*/false);
    // When the profile prefs already have DoH prefs configured, verify that the
    // pref migration will not override them.
    EXPECT_EQ(profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsMode),
              SecureDnsConfig::kModeSecure);
    EXPECT_EQ(
        profile_prefs()->GetString(ash::chrome_prefs::kDnsOverHttpsTemplates),
        kCloudflareDns);
  }
}

// This test verifies that the SecureDnsManager updates observers with the
// correct DoH configuration when the user profile is not managed.
TEST_F(SecureDnsManagerTest, ObserverForUnmanagedUsers) {
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsMode,
                     base::Value(SecureDnsConfig::kModeAutomatic));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
      local_state(), std::make_unique<policy::FakeDeviceAttributes>(), user(),
      /*is_profile_managed=*/false);
  auto consumer_observer = std::make_unique<SecureDnsManagerObserver>(
      consumer_secure_dns_manager.get());

  EXPECT_EQ(consumer_observer->doh_template_uri(), kGoogleDns);
  EXPECT_EQ(consumer_observer->doh_mode(), SecureDnsConfig::kModeAutomatic);

  profile_prefs()->Set(ash::chrome_prefs::kDnsOverHttpsMode,
                       base::Value(SecureDnsConfig::kModeSecure));
  profile_prefs()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                       base::Value(kCloudflareDns));

  EXPECT_EQ(consumer_observer->doh_template_uri(), kCloudflareDns);
  EXPECT_EQ(consumer_observer->doh_mode(), SecureDnsConfig::kModeSecure);
}

TEST_F(SecureDnsManagerTest, DohIncludedDomains_ChromeDohConfig) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  // Set DoHIncludedDomains, expect Chrome DoH to be disabled.
  base::Value pref_value(base::Value::Type::LIST);
  pref_value.GetList().Append("test.com");
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeOff);

  // Unset DoHIncludedDomains, expect Chrome DoH to be re-enabled.
  pref_value.GetList().clear();
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeSecure);
}

TEST_F(SecureDnsManagerTest, DohExcludedDomains_ChromeDohConfig) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  // Set DoHExcludedDomains, expect Chrome DoH to be disabled.
  base::Value pref_value(base::Value::Type::LIST);
  pref_value.GetList().Append("test.com");
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeOff);

  // Unset DoHExcludedDomains, expect Chrome DoH to be re-enabled.
  pref_value.GetList().clear();
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeSecure);
}

TEST_F(SecureDnsManagerTest, DohDomainConfig_ChromeDohConfig) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  // Set DoHIncludedDomains, expect Chrome DoH to be disabled.
  base::Value pref_value(base::Value::Type::LIST);
  pref_value.GetList().Append("include.com");
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeOff);

  // Set DoHExcludedDomains, expect Chrome DoH to still be disabled.
  pref_value.GetList().clear();
  pref_value.GetList().Append("exclude.com");
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeOff);

  // Unset DoHIncludedDomains, expect Chrome DoH to still be disabled.
  pref_value.GetList().clear();
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeOff);

  // Unset DoHExcludedDomains, expect Chrome DoH to be re-enabled.
  pref_value.GetList().clear();
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
  EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
            SecureDnsConfig::kModeSecure);
}

TEST_F(SecureDnsManagerTest, DohIncludedDomains_ShillDohConfig) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  // Set DoHIncludedDomains, expect no change to shill DoH config.
  base::Value pref_value(base::Value::Type::LIST);
  pref_value.GetList().Append("test.com");
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);

  auto providers = GetDOHProviders();

  auto it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_TRUE(it->second.empty());
  EXPECT_EQ(providers.size(), 1u);

  // Unset DoHIncludedDomains, expect no change to shill DoH config.
  pref_value.GetList().clear();
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);

  providers = GetDOHProviders();

  it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_TRUE(it->second.empty());
  EXPECT_EQ(providers.size(), 1u);
}

TEST_F(SecureDnsManagerTest, DohExcludedDomains_ShillDohConfig) {
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  // Set DoHExcludedDomains, expect no change to shill DoH config.
  base::Value pref_value(base::Value::Type::LIST);
  pref_value.GetList().Append("test.com");
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  auto providers = GetDOHProviders();

  auto it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_TRUE(it->second.empty());
  EXPECT_EQ(providers.size(), 1u);

  // Unset DoHExcludedDomains, expect no change to shill DoH config.
  pref_value.GetList().clear();
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  providers = GetDOHProviders();

  it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_TRUE(it->second.empty());
  EXPECT_EQ(providers.size(), 1u);
}

TEST_F(SecureDnsManagerTest, ResetShillState) {
  // Set DnsOverHttpsMode and DnsOverHttpsTemplates.
  local_state()->SetManagedPref(ash::chrome_prefs::kDnsOverHttpsMode,
                                base::Value(SecureDnsConfig::kModeSecure));
  local_state()->Set(ash::chrome_prefs::kDnsOverHttpsTemplates,
                     base::Value(kGoogleDns));

  auto providers = GetDOHProviders();

  auto it = providers.find(kGoogleDns);
  EXPECT_TRUE(it != providers.end());
  EXPECT_EQ(it->first, kGoogleDns);
  EXPECT_TRUE(it->second.empty());
  EXPECT_EQ(providers.size(), 1u);

  // Set DnsOverHttpsIncludedDomains and DnsOverHttpsExcludedDomains.
  std::vector<std::string> domains = {"test.com", "*.test.com"};
  base::Value pref_value(base::Value::Type::LIST);
  for (const auto& domain : domains) {
    pref_value.GetList().Append(domain);
  }
  local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
  local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);

  EXPECT_EQ(domains, GetDOHIncludedDomains());
  EXPECT_EQ(domains, GetDOHExcludedDomains());

  // Expect Shill's state to be cleared when the class is destroyed.
  ResetSecureDnsManager();

  EXPECT_TRUE(GetDOHProviders().empty());
  EXPECT_TRUE(GetDOHIncludedDomains().empty());
  EXPECT_TRUE(GetDOHExcludedDomains().empty());
}

}  // namespace
}  // namespace ash
