chromium/chromeos/ash/services/network_config/cros_network_config_unittest.cc

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

#include "chromeos/ash/services/network_config/cros_network_config.h"

#include <tuple>

#include "ash/components/arc/arc_prefs.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/carrier_lock/carrier_lock_manager.h"
#include "chromeos/ash/components/carrier_lock/fake_fcm_topic_subscriber.h"
#include "chromeos/ash/components/carrier_lock/fake_provisioning_config_fetcher.h"
#include "chromeos/ash/components/carrier_lock/fake_psm_claim_verifier.h"
#include "chromeos/ash/components/dbus/shill/fake_shill_device_client.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/cellular_inhibitor.h"
#include "chromeos/ash/components/network/cellular_metrics_logger.h"
#include "chromeos/ash/components/network/fake_network_3gpp_handler.h"
#include "chromeos/ash/components/network/fake_stub_cellular_networks_provider.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
#include "chromeos/ash/components/network/network_cert_loader.h"
#include "chromeos/ash/components/network/network_certificate_handler.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_device_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_profile_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/ash/components/network/policy_util.h"
#include "chromeos/ash/components/network/prohibited_technologies_handler.h"
#include "chromeos/ash/components/network/proxy/ui_proxy_config_service.h"
#include "chromeos/ash/components/network/system_token_cert_db_storage.h"
#include "chromeos/ash/components/network/technology_state_controller.h"
#include "chromeos/ash/components/network/traffic_counters_handler.h"
#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h"
#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_observer.h"
#include "chromeos/ash/services/network_config/test_apn_data.h"
#include "chromeos/ash/services/network_config/test_network_configuration_observer.h"
#include "chromeos/components/onc/onc_utils.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom-forward.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom-shared.h"
#include "chromeos/services/network_config/public/mojom/network_types.mojom-shared.h"
#include "components/captive_portal/core/captive_portal_detector.h"
#include "components/onc/onc_constants.h"
#include "components/onc/onc_pref_names.h"
#include "components/prefs/testing_pref_service.h"
#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "net/base/ip_address.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "third_party/re2/src/re2/re2.h"

namespace ash::network_config {

namespace {

namespace mojom = ::chromeos::network_config::mojom;
using ApnTypes = CellularNetworkMetricsLogger::ApnTypes;

constexpr int kSimRetriesLeft = 3;
constexpr char kCellularGuid[] = "cellular_guid";
constexpr char kCellularDevicePath[] = "/device/stub_cellular_device";
constexpr char kCellularTestIccid[] = "1234567890";
constexpr char kCellularTestImei[] = "1234567890";
constexpr char kCellularTestSerial[] = "ABCD";

constexpr char kCellularTestApn1[] = "TEST.APN1";
constexpr char kCellularTestApnName1[] = "Test Apn 1";
constexpr char kCellularTestApnUsername1[] = "Test User";
constexpr char kCellularTestApnPassword1[] = "Test Pass";
constexpr char kCellularTestApnAttach1[] = "";
constexpr char kCellularTestApnId1[] = "1";
constexpr char kCellularTestApnAuthenticationType1[] = "";
constexpr char kCellularTestApnTypes1[] = "Default";

constexpr char kCellularTestApn2[] = "TEST.APN2";
constexpr char kCellularTestApnName2[] = "Test Apn 2";
constexpr char kCellularTestApnUsername2[] = "Test User";
constexpr char kCellularTestApnPassword2[] = "Test Pass";
constexpr char kCellularTestApnAttach2[] = "";

constexpr char kCellularTestApn3[] = "TEST.APN3";
constexpr char kCellularTestApnName3[] = "Test Apn 3";
constexpr char kCellularTestApnUsername3[] = "Test User";
constexpr char kCellularTestApnPassword3[] = "Test Pass";
constexpr char kCellularTestApnAttach3[] = "attach";

constexpr char kTestApnCellularGuid[] = "test_apn_cellular_guid";
constexpr char kTestApnCellularShillDictFmt[] =
    R"({"GUID": "%s", "Type": "cellular",  "State": "%s",
            "Strength": 0, "Cellular.NetworkTechnology": "LTE",
            "Cellular.ActivationState": "activated", "Cellular.ICCID": "%s",
            "Profile": "%s", "Cellular.LastGoodAPN": %s})";

static const re2::RE2 kApnIdRegex("[0-9a-fA-F]{32}");

// Escaped twice, as it will be embedded as part of a JSON string, which should
// have a single level of escapes still present.
const char kOpenVPNTLSAuthContents[] =
    "-----BEGIN OpenVPN Static key V1-----\\n"
    "83f8e7ccd99be189b4663e18615f9166\\n"
    "d885cdea6c8accb0ebf5be304f0b8081\\n"
    "5404f2a6574e029815d7a2fb65b83d0c\\n"
    "676850714c6a56b23415a78e06aad6b1\\n"
    "34900dd512049598382039e4816cb5ff\\n"
    "1848532b71af47578c9b4a14b5bca49f\\n"
    "99e0ae4dae2f4e5eadfea374aeb8fb1e\\n"
    "a6fdf02adc73ea778dfd43d64bf7bc75\\n"
    "7779d629498f8c2fbfd32812bfdf6df7\\n"
    "8cebafafef3e5496cb13202274f2768a\\n"
    "1959bc53d67a70945c4c8c6f34b63327\\n"
    "fb60dc84990ffec1243461e0b6310f61\\n"
    "e90aee1f11fb6292d6f5fcd7cd508aab\\n"
    "50d80f9963589c148cb4b933ec86128d\\n"
    "ed77d3fad6005b62f36369e2319f52bd\\n"
    "09c6d2e52cce2362a05009dc29b6b39a\\n"
    "-----END OpenVPN Static key V1-----\\n";

enum ComparisonType {
  INTEGER = 0,
  DOUBLE,
};

struct ApnHistogramCounts {
  size_t num_modify_success = 0u;
  size_t num_modify_failure = 0u;
  size_t num_modify_type_default = 0u;
  size_t num_modify_type_attach = 0u;
  size_t num_modify_type_default_and_attach = 0u;

  size_t num_enable_success = 0u;
  size_t num_enable_failure = 0u;
  size_t num_enable_type_default = 0u;
  size_t num_enable_type_attach = 0u;
  size_t num_enable_type_default_and_attach = 0u;

  size_t num_disable_success = 0u;
  size_t num_disable_failure = 0u;
  size_t num_disable_type_default = 0u;
  size_t num_disable_type_attach = 0u;
  size_t num_disable_type_default_and_attach = 0u;
};

void CompareTrafficCounters(
    const std::vector<mojom::TrafficCounterPtr>& actual_traffic_counters,
    const base::Value::List& expected_traffic_counters,
    enum ComparisonType comparison_type) {
  EXPECT_EQ(actual_traffic_counters.size(), expected_traffic_counters.size());
  for (size_t i = 0; i < actual_traffic_counters.size(); i++) {
    const auto& actual_tc = actual_traffic_counters[i];
    const auto& expected_tc = expected_traffic_counters[i].GetDict();
    EXPECT_EQ(actual_tc->source,
              CrosNetworkConfig::GetTrafficCounterEnumForTesting(
                  *expected_tc.FindString("source")));
    if (comparison_type == ComparisonType::INTEGER) {
      EXPECT_EQ(actual_tc->rx_bytes, (size_t)*expected_tc.FindInt("rx_bytes"));
      EXPECT_EQ(actual_tc->tx_bytes, (size_t)*expected_tc.FindInt("tx_bytes"));
    } else if (comparison_type == ComparisonType::DOUBLE) {
      EXPECT_EQ(actual_tc->rx_bytes,
                (size_t)*expected_tc.FindDouble("rx_bytes"));
      EXPECT_EQ(actual_tc->tx_bytes,
                (size_t)*expected_tc.FindDouble("tx_bytes"));
    }
  }
}

void AddSimSlotInfoToList(base::Value::List& ordered_sim_slot_info_list,
                          const std::string& eid,
                          const std::string& iccid,
                          bool primary = false) {
  base::Value::Dict item;
  item.Set(shill::kSIMSlotInfoEID, eid);
  item.Set(shill::kSIMSlotInfoICCID, iccid);
  item.Set(shill::kSIMSlotInfoPrimary, primary);
  ordered_sim_slot_info_list.Append(std::move(item));
}

bool ContainsVpnDeviceState(
    std::vector<mojom::DeviceStatePropertiesPtr> devices) {
  for (auto& device : devices) {
    if (device->type == mojom::NetworkType::kVPN) {
      return true;
    }
  }
  return false;
}

std::string CreateApnShillDict() {
  TestApnData test_apn_data;
  test_apn_data.access_point_name = kCellularTestApn1;
  test_apn_data.name = kCellularTestApnName1;
  test_apn_data.username = kCellularTestApnUsername1;
  test_apn_data.password = kCellularTestApnPassword1;
  test_apn_data.attach = kCellularTestApnAttach1;
  test_apn_data.id = kCellularTestApnId1;
  test_apn_data.onc_authentication = kCellularTestApnAuthenticationType1;
  test_apn_data.onc_ip_type = ::onc::cellular_apn::kIpTypeIpv4;
  test_apn_data.onc_source = ::onc::cellular_apn::kSourceModb;
  test_apn_data.onc_apn_types.emplace_back(kCellularTestApnTypes1);
  return test_apn_data.AsApnShillDict();
}

mojom::ConfigPropertiesPtr CreateFakeVpnConfig(std::string name,
                                               std::string host,
                                               mojom::VpnType type) {
  auto vpn = mojom::VPNConfigProperties::New();
  vpn->host = host;
  vpn->type = mojom::VpnTypeConfig::New();
  vpn->type->value = type;

  auto config = mojom::ConfigProperties::New();
  config->name = name;
  config->type_config =
      mojom::NetworkTypeConfigProperties::NewVpn(std::move(vpn));
  return config;
}

bool OncApnHasId(const base::Value::Dict& apn) {
  if (const std::string* id = apn.FindString(::onc::cellular_apn::kId)) {
    return re2::RE2::FullMatch(*id, kApnIdRegex);
  }
  return false;
}

bool ApnListsMatch(const std::vector<TestApnData*>& expected_apns,
                   const base::Value::List& actual_apns,
                   bool has_state_field,
                   bool is_password_masked) {
  if (expected_apns.size() != actual_apns.size()) {
    return false;
  }

  for (size_t i = 0; i < expected_apns.size(); i++) {
    DCHECK(actual_apns[i].is_dict());
    const base::Value::Dict& actual_apn = actual_apns[i].GetDict();
    if (!OncApnHasId(actual_apn)) {
      return false;
    }
    if (!expected_apns[i]->OncApnEquals(actual_apn, has_state_field,
                                        is_password_masked)) {
      return false;
    }
  }
  return true;
}

bool MojoApnHasId(const mojom::ApnPropertiesPtr& apn) {
  if (!apn->id.has_value()) {
    return false;
  }
  return re2::RE2::FullMatch(*apn->id, kApnIdRegex);
}

}  // namespace

class CrosNetworkConfigTest : public testing::Test {
 public:
  CrosNetworkConfigTest() {
    // TODO(b/278643115) Remove LoginState dependency.
    LoginState::Initialize();
    SystemTokenCertDbStorage::Initialize();

    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
        std::make_unique<user_manager::FakeUserManager>());

    NetworkCertLoader::Initialize();
    helper_ = std::make_unique<NetworkHandlerTestHelper>();
    helper_->AddDefaultProfiles();
    helper_->ResetDevicesAndServices();

    helper_->RegisterPrefs(user_prefs_.registry(), local_state_.registry());
    PrefProxyConfigTrackerImpl::RegisterProfilePrefs(user_prefs_.registry());
    PrefProxyConfigTrackerImpl::RegisterPrefs(local_state_.registry());

    helper_->InitializePrefs(&user_prefs_, &local_state_);
    NetworkHandler* network_handler = NetworkHandler::Get();
    cros_network_config_test_helper_ =
        std::make_unique<CrosNetworkConfigTestHelper>(true);
    SetupNetworkConfig(network_handler);
  }

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

  ~CrosNetworkConfigTest() override {
    carrier_lock_manager_.reset();
    cros_network_config_test_helper_.reset();
    cros_network_config_.reset();
    helper_.reset();
    if (traffic_counters::TrafficCountersHandler::IsInitialized()) {
      traffic_counters::TrafficCountersHandler::Shutdown();
    }
    NetworkCertLoader::Shutdown();
    scoped_user_manager_.reset();
    SystemTokenCertDbStorage::Shutdown();
    LoginState::Shutdown();
  }

  void SetupNetworkConfig(NetworkHandler* network_handler) {
    cros_network_config_ = std::make_unique<CrosNetworkConfig>(
        network_handler->network_state_handler(),
        network_handler->network_device_handler(),
        network_handler->cellular_inhibitor(),
        network_handler->cellular_esim_profile_handler(),
        network_handler->managed_network_configuration_handler(),
        network_handler->network_connection_handler(),
        network_handler->network_certificate_handler(),
        network_handler->network_profile_handler(),
        network_handler->technology_state_controller());
    SetupPolicy();
    SetupNetworks();
  }

  void SetupCarrierLock(bool is_locked) {
    ash::carrier_lock::CarrierLockManager::RegisterLocalPrefs(
        local_state_.registry());
    if (is_locked) {
      local_state_.SetBoolean(carrier_lock::kDisableManagerPref, false);
      local_state_.SetString(carrier_lock::kFcmTopicPref, "testtopic");
    }
    fake_modem_handler_ = std::make_unique<FakeNetwork3gppHandler>();
    fake_config_fetcher_ =
        std::make_unique<carrier_lock::FakeProvisioningConfigFetcher>();
    fake_psm_verifier_ = std::make_unique<carrier_lock::FakePsmClaimVerifier>();
    fake_fcm_subscriber_ =
        std::make_unique<carrier_lock::FakeFcmTopicSubscriber>();

    carrier_lock_manager_ = carrier_lock::CarrierLockManager::CreateForTesting(
        &local_state_, fake_modem_handler_.get(),
        std::move(fake_fcm_subscriber_), std::move(fake_psm_verifier_),
        std::move(fake_config_fetcher_));
  }

  void SetupPolicy() {
    ManagedNetworkConfigurationHandler* managed_network_configuration_handler =
        NetworkHandler::Get()->managed_network_configuration_handler();
    managed_network_configuration_handler->SetPolicy(
        ::onc::ONC_SOURCE_DEVICE_POLICY,
        /*userhash=*/std::string(),
        /*network_configs_onc=*/base::Value::List(),
        /*global_network_config=*/base::Value::Dict());

    const std::string user_policy_ssid = "wifi2";
    std::optional<base::Value::Dict> wifi2_onc =
        chromeos::onc::ReadDictionaryFromJson(base::StringPrintf(
            R"({"GUID": "wifi2_guid", "Type": "WiFi",
                "Name": "wifi2", "Priority": 0,
                "WiFi": { "Passphrase": "fake", "SSID": "%s", "HexSSID": "%s",
                          "Security": "WPA-PSK", "AutoConnect": true}})",
            user_policy_ssid.c_str(),
            base::HexEncode(user_policy_ssid).c_str()));
    ASSERT_TRUE(wifi2_onc.has_value());

    std::optional<base::Value::Dict> wifi_eap_onc =
        chromeos::onc::ReadDictionaryFromJson(
            R"({ "GUID": "wifi_eap",
             "Name": "wifi_eap",
             "Type": "WiFi",
             "WiFi": {
                "AutoConnect": true,
                "EAP": {
                   "Inner": "MD5",
                   "Outer": "PEAP",
                   "SubjectAlternativeNameMatch": [
                     { "Type": "DNS" , "Value" : "example.com"},
                     {"Type" : "EMAIL", "Value" : "[email protected]"}],
                   "DomainSuffixMatch": ["example1.com","example2.com"],
                   "Recommended": [ "AnonymousIdentity", "Identity", "Password",
                     "DomainSuffixMatch" , "SubjectAlternativeNameMatch"],
                   "UseSystemCAs": true
                },
                "SSID": "wifi_eap",
                "Security": "WPA-EAP"
             }
           })");
    ASSERT_TRUE(wifi_eap_onc.has_value());

    std::optional<base::Value::Dict> openvpn_onc =
        chromeos::onc::ReadDictionaryFromJson(base::StringPrintf(
            R"({ "GUID": "openvpn_guid", "Name": "openvpn", "Type": "VPN", "VPN": {
          "Host": "my.vpn.example.com", "Type": "OpenVPN", "OpenVPN": {
          "Auth": "MD5", "Cipher": "AES-192-CBC", "ClientCertType": "None",
          "CompressionAlgorithm": "LZO", "KeyDirection": "1",
          "TLSAuthContents": "%s"}}})",
            kOpenVPNTLSAuthContents));
    ASSERT_TRUE(openvpn_onc.has_value());

    base::Value::List user_policy_onc;
    user_policy_onc.Append(std::move(*wifi2_onc));
    user_policy_onc.Append(std::move(*wifi_eap_onc));
    user_policy_onc.Append(std::move(*openvpn_onc));
    managed_network_configuration_handler->SetPolicy(
        ::onc::ONC_SOURCE_USER_POLICY, helper()->UserHash(), user_policy_onc,
        /*global_network_config=*/base::Value::Dict());
    base::RunLoop().RunUntilIdle();
  }

  void SetupNetworks() {
    // Wifi device exists by default, add Ethernet and Cellular.
    helper()->device_test()->AddDevice("/device/stub_eth_device",
                                       shill::kTypeEthernet, "stub_eth_device");
    helper()->manager_test()->AddTechnology(shill::kTypeCellular,
                                            true /* enabled */);
    helper()->device_test()->AddDevice(
        kCellularDevicePath, shill::kTypeCellular, "stub_cellular_device");
    base::Value::Dict sim_value;
    sim_value.Set(shill::kSIMLockEnabledProperty, true);
    sim_value.Set(shill::kSIMLockTypeProperty, shill::kSIMLockPin);
    sim_value.Set(shill::kSIMLockRetriesLeftProperty, kSimRetriesLeft);
    helper()->device_test()->SetDeviceProperty(
        kCellularDevicePath, shill::kSIMLockStatusProperty,
        base::Value(std::move(sim_value)),
        /*notify_changed=*/false);
    helper()->device_test()->SetDeviceProperty(kCellularDevicePath,
                                               shill::kIccidProperty,
                                               base::Value(kCellularTestIccid),
                                               /*notify_changed=*/false);
    helper()->device_test()->SetDeviceProperty(kCellularDevicePath,
                                               shill::kImeiProperty,
                                               base::Value(kCellularTestImei),
                                               /*notify_changed=*/false);
    helper()->device_test()->SetDeviceProperty(
        kCellularDevicePath, shill::kSIMPresentProperty, base::Value(true),
        /*notify_changed=*/false);

    // Setup SimSlotInfo
    base::Value::List ordered_sim_slot_info_list;
    AddSimSlotInfoToList(ordered_sim_slot_info_list, /*eid=*/"",
                         kCellularTestIccid,
                         /*primary=*/true);
    helper()->device_test()->SetDeviceProperty(
        kCellularDevicePath, shill::kSIMSlotInfoProperty,
        base::Value(std::move(ordered_sim_slot_info_list)),
        /*notify_changed=*/false);

    // Note: These are Shill dictionaries, not ONC.
    helper()->ConfigureService(
        R"({"GUID": "eth_guid", "Type": "ethernet", "State": "online"})");
    wifi1_path_ = helper()->ConfigureService(
        R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "ready",
            "Strength": 50, "AutoConnect": true, "WiFi.HiddenSSID": false,
            "TrafficCounterResetTime": 123456789987654})");
    helper()->ConfigureService(
        R"({"GUID": "wifi2_guid", "Type": "wifi", "SSID": "wifi2",
            "State": "idle", "SecurityClass": "psk", "Strength": 100,
            "Profile": "user_profile_path", "WiFi.HiddenSSID": true})");
    helper()->ConfigureService(base::StringPrintf(
        R"({"GUID": "%s", "Type": "cellular",  "State": "idle",
            "Strength": 0, "Cellular.NetworkTechnology": "LTE",
            "Cellular.ActivationState": "activated", "Cellular.ICCID": "%s",
            "Profile": "%s"})",
        kCellularGuid, kCellularTestIccid,
        NetworkProfileHandler::GetSharedProfilePath().c_str()));
    vpn_path_ = helper()->ConfigureService(
        R"({"GUID": "vpn_l2tp_guid", "Type": "vpn", "State": "association",
            "Provider": {"Type": "l2tpipsec"}})");
    helper()->ConfigureService(base::StringPrintf(
        R"({"GUID":"openvpn_guid", "Type": "vpn", "Name": "openvpn",
          "Provider.Host": "vpn.my.domain.com", "Provider.Type": "openvpn",
          "OpenVPN.Auth": "MD5", "OpenVPN.Cipher": "AES-192-CBC",
          "OpenVPN.Compress": "lzo", "OpenVPN.KeyDirection": "1",
          "OpenVPN.TLSAuthContents": "%s"})",
        kOpenVPNTLSAuthContents));
    helper()->ConfigureService(R"({"GUID": "vpn_ikev2_guid", "Type": "vpn",
            "State": "idle", "Provider": {"Type": "ikev2"}})");

    // Add a non visible configured wifi service.
    std::string wifi3_path = helper()->ConfigureService(
        R"({"GUID": "wifi3_guid", "Type": "wifi", "SecurityClass": "psk",
            "Visible": false})");
    helper()->profile_test()->AddService(
        NetworkProfileHandler::GetSharedProfilePath(), wifi3_path);

    // Syncable wifi network:
    std::string service_path = helper()->ConfigureService(
        R"({"GUID": "wifi4_guid", "Type": "wifi", "SSID": "wifi4",
            "State": "idle", "SecurityClass": "psk", "Strength": 100,
            "Profile": "user_profile_path", "Connectable": true})");
    NetworkHandler::Get()->network_metadata_store()->ConnectSucceeded(
        service_path);

    base::RunLoop().RunUntilIdle();
  }

  void SetCellularFlashing(bool flashing) {
    helper()->device_test()->SetDeviceProperty(
        kCellularDevicePath, shill::kFlashingProperty, base::Value(flashing),
        /*notify_changed=*/true);
    base::RunLoop().RunUntilIdle();
  }

  void SetupAPNList() {
    base::Value::List apn_entries;
    TestApnData apn_entry1;
    apn_entry1.access_point_name = kCellularTestApn1;
    apn_entry1.name = kCellularTestApnName1;
    apn_entry1.username = kCellularTestApnUsername1;
    apn_entry1.password = kCellularTestApnPassword1;
    apn_entries.Append(apn_entry1.AsShillApn());

    TestApnData apn_entry2;
    apn_entry2.access_point_name = kCellularTestApn2;
    apn_entry2.name = kCellularTestApnName2;
    apn_entry2.username = kCellularTestApnUsername2;
    apn_entry2.password = kCellularTestApnPassword2;
    apn_entry2.attach = kCellularTestApnAttach2;
    apn_entries.Append(apn_entry2.AsShillApn());

    helper()->device_test()->SetDeviceProperty(
        kCellularDevicePath, shill::kCellularApnListProperty,
        base::Value(std::move(apn_entries)),
        /*notify_changed=*/true);
    base::RunLoop().RunUntilIdle();
  }

  void SetupEthernetEAP() {
    std::string eap_path = helper()->ConfigureService(
        R"({"GUID": "eth_eap_guid", "Type": "etherneteap",
            "State": "online", "EAP.EAP": "TTLS", "EAP.Identity": "user1"})");
    helper()->profile_test()->AddService(
        NetworkProfileHandler::GetSharedProfilePath(), eap_path);
    base::RunLoop().RunUntilIdle();
  }

  void SetupTestESimProfile(const std::string& eid,
                            const std::string& iccid,
                            const std::string& service_path,
                            const std::string& profile_name,
                            const std::string& profile_nickname) {
    const char kTestEuiccPath[] = "euicc_path";
    const char kTestESimProfilePath[] = "profile_path";

    helper()->hermes_manager_test()->AddEuicc(dbus::ObjectPath(kTestEuiccPath),
                                              eid, /*is_active=*/true,
                                              /*physical_slot=*/0);
    helper()->hermes_euicc_test()->AddCarrierProfile(
        dbus::ObjectPath(kTestESimProfilePath),
        dbus::ObjectPath(kTestEuiccPath), iccid, profile_name, profile_nickname,
        "service_provider", "activation_code", service_path,
        hermes::profile::State::kInactive,
        hermes::profile::ProfileClass::kOperational,
        HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
            kAddProfileWithService);
    base::RunLoop().RunUntilIdle();
  }

  void SetupObserver() {
    observer_ = std::make_unique<CrosNetworkConfigTestObserver>();
    cros_network_config_->AddObserver(observer_->GenerateRemote());
  }

  mojom::NetworkStatePropertiesPtr GetNetworkState(const std::string& guid) {
    mojom::NetworkStatePropertiesPtr result;
    base::RunLoop run_loop;
    cros_network_config()->GetNetworkState(
        guid, base::BindOnce(
                  [](mojom::NetworkStatePropertiesPtr* result,
                     base::OnceClosure quit_closure,
                     mojom::NetworkStatePropertiesPtr network) {
                    *result = std::move(network);
                    std::move(quit_closure).Run();
                  },
                  &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  std::vector<mojom::NetworkStatePropertiesPtr> GetNetworkStateList(
      mojom::NetworkFilterPtr filter) {
    std::vector<mojom::NetworkStatePropertiesPtr> result;
    base::RunLoop run_loop;
    cros_network_config()->GetNetworkStateList(
        std::move(filter),
        base::BindOnce(
            [](std::vector<mojom::NetworkStatePropertiesPtr>* result,
               base::OnceClosure quit_closure,
               std::vector<mojom::NetworkStatePropertiesPtr> networks) {
              for (auto& network : networks)
                result->push_back(std::move(network));
              std::move(quit_closure).Run();
            },
            &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  std::vector<mojom::DeviceStatePropertiesPtr> GetDeviceStateList() {
    std::vector<mojom::DeviceStatePropertiesPtr> result;
    base::RunLoop run_loop;
    cros_network_config()->GetDeviceStateList(base::BindOnce(
        [](std::vector<mojom::DeviceStatePropertiesPtr>* result,
           base::OnceClosure quit_closure,
           std::vector<mojom::DeviceStatePropertiesPtr> devices) {
          for (auto& device : devices)
            result->push_back(std::move(device));
          std::move(quit_closure).Run();
        },
        &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  mojom::DeviceStatePropertiesPtr GetDeviceStateFromList(
      mojom::NetworkType type) {
    std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
    for (auto& device : devices) {
      if (device->type == type)
        return std::move(device);
    }
    return nullptr;
  }

  mojom::ManagedPropertiesPtr GetManagedProperties(const std::string& guid) {
    mojom::ManagedPropertiesPtr result;
    base::RunLoop run_loop;
    cros_network_config()->GetManagedProperties(
        guid, base::BindOnce(
                  [](mojom::ManagedPropertiesPtr* result,
                     base::OnceClosure quit_closure,
                     mojom::ManagedPropertiesPtr properties) {
                    *result = std::move(properties);
                    std::move(quit_closure).Run();
                  },
                  &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  bool SetProperties(const std::string& guid,
                     mojom::ConfigPropertiesPtr properties) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->SetProperties(
        guid, std::move(properties),
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success,
               const std::string& message) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  std::string ConfigureNetwork(mojom::ConfigPropertiesPtr properties,
                               bool shared) {
    std::string guid;
    base::RunLoop run_loop;
    cros_network_config()->ConfigureNetwork(
        std::move(properties), shared,
        base::BindOnce(
            [](std::string* guidp, base::OnceClosure quit_closure,
               const std::optional<std::string>& guid,
               const std::string& message) {
              if (guid)
                *guidp = *guid;
              std::move(quit_closure).Run();
            },
            &guid, run_loop.QuitClosure()));
    run_loop.Run();
    return guid;
  }

  bool ForgetNetwork(const std::string& guid) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->ForgetNetwork(
        guid,
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  bool SetCellularSimState(const std::string& current_pin_or_puk,
                           std::optional<std::string> new_pin,
                           bool require_pin) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->SetCellularSimState(
        mojom::CellularSimState::New(current_pin_or_puk, new_pin, require_pin),
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  bool SelectCellularMobileNetwork(const std::string& guid,
                                   const std::string& network_id) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->SelectCellularMobileNetwork(
        guid, network_id,
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  mojom::GlobalPolicyPtr GetGlobalPolicy() {
    mojom::GlobalPolicyPtr result;
    base::RunLoop run_loop;
    cros_network_config()->GetGlobalPolicy(base::BindOnce(
        [](mojom::GlobalPolicyPtr* result, base::OnceClosure quit_closure,
           mojom::GlobalPolicyPtr global_policy) {
          *result = std::move(global_policy);
          std::move(quit_closure).Run();
        },
        &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  mojom::StartConnectResult StartConnect(const std::string& guid) {
    mojom::StartConnectResult result;
    base::RunLoop run_loop;
    cros_network_config()->StartConnect(
        guid,
        base::BindOnce(
            [](mojom::StartConnectResult* resultp,
               base::OnceClosure quit_closure, mojom::StartConnectResult result,
               const std::string& message) {
              *resultp = result;
              std::move(quit_closure).Run();
            },
            &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  bool StartDisconnect(const std::string& guid) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->StartDisconnect(
        guid,
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  std::vector<mojom::VpnProviderPtr> GetVpnProviders() {
    std::vector<mojom::VpnProviderPtr> result;
    base::RunLoop run_loop;
    cros_network_config()->GetVpnProviders(base::BindOnce(
        [](std::vector<mojom::VpnProviderPtr>* result,
           base::OnceClosure quit_closure,
           std::vector<mojom::VpnProviderPtr> providers) {
          *result = std::move(providers);
          std::move(quit_closure).Run();
        },
        &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  void GetNetworkCertificates(
      std::vector<mojom::NetworkCertificatePtr>* server_cas,
      std::vector<mojom::NetworkCertificatePtr>* user_certs) {
    base::RunLoop run_loop;
    cros_network_config()->GetNetworkCertificates(base::BindOnce(
        [](std::vector<mojom::NetworkCertificatePtr>* server_cas_result,
           std::vector<mojom::NetworkCertificatePtr>* user_certs_result,
           base::OnceClosure quit_closure,
           std::vector<mojom::NetworkCertificatePtr> server_cas,
           std::vector<mojom::NetworkCertificatePtr> user_certs) {
          *server_cas_result = std::move(server_cas);
          *user_certs_result = std::move(user_certs);
          std::move(quit_closure).Run();
        },
        server_cas, user_certs, run_loop.QuitClosure()));
    run_loop.Run();
  }

  mojom::AlwaysOnVpnPropertiesPtr GetAlwaysOnVpn() {
    mojom::AlwaysOnVpnPropertiesPtr result;
    base::RunLoop run_loop;
    cros_network_config()->GetAlwaysOnVpn(base::BindOnce(
        [](mojom::AlwaysOnVpnPropertiesPtr* result,
           base::OnceClosure quit_closure,
           mojom::AlwaysOnVpnPropertiesPtr properties) {
          *result = std::move(properties);
          std::move(quit_closure).Run();
        },
        &result, run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  void SetAlwaysOnVpn(mojom::AlwaysOnVpnPropertiesPtr properties) {
    cros_network_config()->SetAlwaysOnVpn(std::move(properties));
    base::RunLoop().RunUntilIdle();
  }

  void SetArcAlwaysOnUserPrefs(std::string package_name,
                               bool vpn_configured_allowed = false) {
    user_prefs_.SetUserPref(arc::prefs::kAlwaysOnVpnPackage,
                            base::Value(package_name));
    user_prefs_.SetUserPref(prefs::kVpnConfigAllowed,
                            base::Value(vpn_configured_allowed));
  }

  std::vector<std::string> GetSupportedVpnTypes() {
    std::vector<std::string> result;
    base::RunLoop run_loop;
    cros_network_config()->GetSupportedVpnTypes(base::BindOnce(
        [](std::vector<std::string>& result, base::OnceClosure quit_closure,
           const std::vector<std::string>& return_value) {
          result = std::move(return_value);
          std::move(quit_closure).Run();
        },
        std::ref(result), run_loop.QuitClosure()));
    run_loop.Run();
    return result;
  }

  std::unique_ptr<CellularInhibitor::InhibitLock> InhibitCellularScanning(
      CellularInhibitor::InhibitReason inhibit_reason) {
    base::RunLoop run_loop;

    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock;
    NetworkHandler::Get()->cellular_inhibitor()->InhibitCellularScanning(
        inhibit_reason,
        base::BindLambdaForTesting(
            [&](std::unique_ptr<CellularInhibitor::InhibitLock> result) {
              inhibit_lock = std::move(result);
              run_loop.Quit();
            }));

    run_loop.Run();
    return inhibit_lock;
  }

  void RequestTrafficCountersAndCompareTrafficCounters(
      const std::string& guid,
      const base::Value::List& traffic_counters,
      ComparisonType comparison_type) {
    base::RunLoop run_loop;
    cros_network_config()->RequestTrafficCounters(
        guid,
        base::BindOnce(
            [](const base::Value::List* expected_traffic_counters,
               ComparisonType type, base::OnceClosure quit_closure,
               std::vector<mojom::TrafficCounterPtr> actual_traffic_counters) {
              CompareTrafficCounters(actual_traffic_counters,
                                     *expected_traffic_counters, type);
              std::move(quit_closure).Run();
            },
            &traffic_counters, comparison_type, run_loop.QuitClosure()));
    run_loop.Run();
  }

  void SetTrafficCountersResetDayAndCompare(const std::string& guid,
                                            mojom::UInt32ValuePtr day,
                                            bool expected_success,
                                            base::Value* expected_reset_day) {
    base::RunLoop run_loop;
    cros_network_config()->SetTrafficCountersResetDay(
        guid, day ? std::move(day) : nullptr,
        base::BindOnce(
            [](const std::string* const guid, bool* expected_success,
               base::Value* expected_reset_day,
               NetworkMetadataStore* network_metadata_store,
               base::OnceClosure quit_closure, bool success) {
              EXPECT_EQ(*expected_success, success);
              const base::Value* actual_reset_day =
                  network_metadata_store->GetDayOfTrafficCountersAutoReset(
                      *guid);
              if (expected_reset_day) {
                EXPECT_TRUE(actual_reset_day);
                EXPECT_EQ(*expected_reset_day, *actual_reset_day);
              } else {
                EXPECT_EQ(actual_reset_day, nullptr);
              }
              std::move(quit_closure).Run();
            },
            &guid, &expected_success, expected_reset_day,
            network_metadata_store(), run_loop.QuitClosure()));
    run_loop.Run();
  }

  bool CreateCustomApn(const std::string& guid, mojom::ApnPropertiesPtr apn) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->CreateCustomApn(
        guid, std::move(apn),
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  bool CreateExclusivelyEnabledCustomApn(const std::string& guid,
                                         mojom::ApnPropertiesPtr apn) {
    bool success = false;
    base::RunLoop run_loop;
    cros_network_config()->CreateExclusivelyEnabledCustomApn(
        guid, std::move(apn),
        base::BindOnce(
            [](bool* successp, base::OnceClosure quit_closure, bool success) {
              *successp = success;
              std::move(quit_closure).Run();
            },
            &success, run_loop.QuitClosure()));
    run_loop.Run();
    return success;
  }

  void RemoveCustomApn(const std::string& guid, const std::string& apn_id) {
    cros_network_config()->RemoveCustomApn(guid, apn_id);
    base::RunLoop().RunUntilIdle();
  }

  void ModifyCustomApn(const std::string& guid, mojom::ApnPropertiesPtr apn) {
    cros_network_config()->ModifyCustomApn(guid, std::move(apn));
    base::RunLoop().RunUntilIdle();
  }

  void SetAllowApnModification(bool allow_apn_modification) {
    base::Value::Dict global_config;
    global_config.Set(::onc::global_network_config::kAllowAPNModification,
                      allow_apn_modification);
    managed_network_configuration_handler()->SetPolicy(
        ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
        /*network_configs_onc=*/base::Value::List(), global_config);
    base::RunLoop().RunUntilIdle();
  }

  bool CustomApnsInNetworkMetadataStoreMatch(
      const std::string& guid,
      const std::vector<TestApnData*>& expected_apns) {
    if (const base::Value::List* custom_apns =
            network_metadata_store()->GetCustomApnList(guid)) {
      return ApnListsMatch(expected_apns, *custom_apns,
                           /*has_state_field=*/true,
                           /*is_password_masked=*/false);
    }
    return expected_apns.empty();
  }

  bool CustomApnsInCellularConfigMatch(
      const std::string& guid,
      const std::vector<TestApnData*>& expected_apns,
      const TestNetworkConfigurationObserver& observer) {
    const base::Value::Dict* user_settings = observer.GetUserSettings(guid);
    if (!user_settings) {
      return expected_apns.empty();
    }

    const base::Value::Dict* cellular_settings =
        user_settings->FindDict(::onc::network_type::kCellular);
    if (!cellular_settings) {
      return false;
    }

    const base::Value::List* custom_apns =
        cellular_settings->FindList(::onc::cellular::kCustomAPNList);
    if (!custom_apns) {
      return false;
    }

    return ApnListsMatch(expected_apns, *custom_apns,
                         /*has_state_field=*/true,
                         /*is_password_masked=*/true);
  }

  bool CustomApnsInManagedPropertiesMatch(
      const std::string& guid,
      const std::vector<TestApnData*>& expected_apns) {
    mojom::ManagedPropertiesPtr props = GetManagedProperties(guid);
    if (!props) {
      return expected_apns.empty();
    }
    if (!props->type_properties->is_cellular()) {
      return false;
    }
    if (!props->type_properties->get_cellular()->custom_apn_list.has_value()) {
      return expected_apns.empty();
    }

    const std::vector<mojom::ApnPropertiesPtr>& mojo_apn_list =
        props->type_properties->get_cellular()->custom_apn_list.value();
    if (expected_apns.size() != mojo_apn_list.size()) {
      return false;
    }

    for (size_t i = 0; i < expected_apns.size(); i++) {
      if (!MojoApnHasId(mojo_apn_list[i])) {
        return false;
      }
      if (!expected_apns[i]->MojoApnEquals(*mojo_apn_list[i])) {
        return false;
      }
    }
    return true;
  }

  void AssertCreateCustomApnResultBucketCount(size_t num_success,
                                              size_t num_failure) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kCreateCustomApnResultHistogram, true,
        num_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kCreateCustomApnResultHistogram, false,
        num_failure);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::
            kCreateCustomApnAuthenticationTypeHistogram,
        num_success);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::kCreateCustomApnIpTypeHistogram,
        num_success);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::kCreateCustomApnApnTypesHistogram,
        num_success);
  }

  void AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType auth_type,
      size_t auth_type_count,
      mojom::ApnIpType ip_type,
      size_t ip_type_count,
      ApnTypes apn_types,
      size_t apn_types_count) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::
            kCreateCustomApnAuthenticationTypeHistogram,
        auth_type, auth_type_count);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kCreateCustomApnIpTypeHistogram, ip_type,
        ip_type_count);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kCreateCustomApnApnTypesHistogram,
        apn_types, apn_types_count);
  }

  void AssertCreateExclusivelyEnabledCustomApnResultBucketCount(
      size_t num_success,
      size_t num_failure) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnResultHistogram,
        true, num_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnResultHistogram,
        false, num_failure);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnAuthenticationTypeHistogram,
        num_success);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnIpTypeHistogram,
        num_success);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnApnTypesHistogram,
        num_success);
  }

  void AssertCreateExclusivelyEnabledCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType auth_type,
      size_t auth_type_count,
      mojom::ApnIpType ip_type,
      size_t ip_type_count,
      ApnTypes apn_types,
      size_t apn_types_count) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnAuthenticationTypeHistogram,
        auth_type, auth_type_count);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnIpTypeHistogram,
        ip_type, ip_type_count);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::
            kCreateExclusivelyEnabledCustomApnApnTypesHistogram,
        apn_types, apn_types_count);
  }

  void AssertRemoveCustomApnResultBucketCount(size_t num_success,
                                              size_t num_failure) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kRemoveCustomApnResultHistogram, true,
        num_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kRemoveCustomApnResultHistogram, false,
        num_failure);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::kRemoveCustomApnApnTypesHistogram,
        num_success);
  }

  void AssertRemoveCustomApnPropertiesBucketCount(ApnTypes apn_types,
                                                  size_t apn_types_count) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kRemoveCustomApnApnTypesHistogram,
        apn_types, apn_types_count);
  }

  void AssertApnHistogramCounts(const ApnHistogramCounts& count) {
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kModifyCustomApnResultHistogram, true,
        count.num_modify_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kModifyCustomApnResultHistogram, false,
        count.num_modify_failure);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::kModifyCustomApnApnTypesHistogram,
        count.num_modify_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kModifyCustomApnApnTypesHistogram,
        ApnTypes::kAttach, count.num_modify_type_attach);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kModifyCustomApnApnTypesHistogram,
        ApnTypes::kDefault, count.num_modify_type_default);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kModifyCustomApnApnTypesHistogram,
        ApnTypes::kDefaultAndAttach, count.num_modify_type_default_and_attach);

    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kEnableCustomApnResultHistogram, true,
        count.num_enable_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kEnableCustomApnResultHistogram, false,
        count.num_enable_failure);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::kEnableCustomApnApnTypesHistogram,
        count.num_enable_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kEnableCustomApnApnTypesHistogram,
        ApnTypes::kAttach, count.num_enable_type_attach);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kEnableCustomApnApnTypesHistogram,
        ApnTypes::kDefault, count.num_enable_type_default);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kEnableCustomApnApnTypesHistogram,
        ApnTypes::kDefaultAndAttach, count.num_enable_type_default_and_attach);

    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kDisableCustomApnResultHistogram, true,
        count.num_disable_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kDisableCustomApnResultHistogram, false,
        count.num_disable_failure);
    histogram_tester_.ExpectTotalCount(
        CellularNetworkMetricsLogger::kDisableCustomApnApnTypesHistogram,
        count.num_disable_success);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kDisableCustomApnApnTypesHistogram,
        ApnTypes::kAttach, count.num_disable_type_attach);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kDisableCustomApnApnTypesHistogram,
        ApnTypes::kDefault, count.num_disable_type_default);
    histogram_tester_.ExpectBucketCount(
        CellularNetworkMetricsLogger::kDisableCustomApnApnTypesHistogram,
        ApnTypes::kDefaultAndAttach, count.num_disable_type_default_and_attach);
  }

  void AssertCellularAllowTextMessages(
      const std::string& guid,
      std::optional<bool> expected_active_value,
      std::optional<bool> expected_policy_value,
      ::chromeos::network_config::mojom::PolicySource policy_source) {
    mojom::ManagedPropertiesPtr properties = GetManagedProperties(guid);
    mojom::GlobalPolicyPtr policy = GetGlobalPolicy();

    ASSERT_TRUE(properties);
    ASSERT_EQ(guid, properties->guid);
    ASSERT_TRUE(properties->type_properties->is_cellular());
    // If there isn't an expected policy or actual value, that means the
    // property is undefined.
    if (!expected_active_value.has_value() &&
        !expected_policy_value.has_value()) {
      EXPECT_FALSE(
          properties->type_properties->get_cellular()->allow_text_messages);
      EXPECT_EQ(mojom::SuppressionType::kUnset, policy->allow_text_messages);
      return;
    }

    ASSERT_TRUE(
        properties->type_properties->get_cellular()->allow_text_messages);
    EXPECT_EQ(policy_source, properties->type_properties->get_cellular()
                                 ->allow_text_messages->policy_source);

    if (expected_policy_value.has_value()) {
      EXPECT_EQ(*expected_policy_value,
                properties->type_properties->get_cellular()
                    ->allow_text_messages->policy_value);

      mojom::SuppressionType expected_global_policy_type =
          expected_policy_value.value() ? mojom::SuppressionType::kAllow
                                        : mojom::SuppressionType::kSuppress;
      EXPECT_EQ(expected_global_policy_type, policy->allow_text_messages);
    } else {
      EXPECT_EQ(mojom::SuppressionType::kUnset, policy->allow_text_messages);
    }

    if (expected_active_value.has_value()) {
      EXPECT_EQ(*expected_active_value,
                properties->type_properties->get_cellular()
                    ->allow_text_messages->active_value);
    }
  }

  void SetSerialNumber(const std::string& serial_number) {
    cros_network_config_test_helper_->SetSerialNumber(serial_number);
  }

  NetworkHandlerTestHelper* helper() { return helper_.get(); }
  CrosNetworkConfigTestObserver* observer() { return observer_.get(); }
  CrosNetworkConfig* cros_network_config() {
    return cros_network_config_.get();
  }
  ManagedNetworkConfigurationHandler* managed_network_configuration_handler() {
    return NetworkHandler::Get()->managed_network_configuration_handler();
  }
  NetworkCertificateHandler* network_certificate_handler() {
    return NetworkHandler::Get()->network_certificate_handler();
  }
  NetworkMetadataStore* network_metadata_store() {
    return NetworkHandler::Get()->network_metadata_store();
  }
  NetworkConfigurationHandler* network_configuration_handler() {
    return NetworkHandler::Get()->network_configuration_handler();
  }
  std::string wifi1_path() { return wifi1_path_; }
  std::string vpn_path() { return vpn_path_; }

 protected:
  sync_preferences::TestingPrefServiceSyncable user_prefs_;

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;
  base::HistogramTester histogram_tester_;
  std::unique_ptr<NetworkHandlerTestHelper> helper_;
  TestingPrefServiceSimple local_state_;
  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
  std::unique_ptr<CrosNetworkConfig> cros_network_config_;
  std::unique_ptr<CrosNetworkConfigTestHelper> cros_network_config_test_helper_;
  std::unique_ptr<CrosNetworkConfigTestObserver> observer_;
  std::unique_ptr<carrier_lock::CarrierLockManager> carrier_lock_manager_;
  std::unique_ptr<FakeNetwork3gppHandler> fake_modem_handler_;
  std::unique_ptr<carrier_lock::FakeFcmTopicSubscriber> fake_fcm_subscriber_;
  std::unique_ptr<carrier_lock::FakePsmClaimVerifier> fake_psm_verifier_;
  std::unique_ptr<carrier_lock::FakeProvisioningConfigFetcher>
      fake_config_fetcher_;

  std::string wifi1_path_;
  std::string vpn_path_;
};

TEST_F(CrosNetworkConfigTest, GetNetworkState) {
  mojom::NetworkStatePropertiesPtr network = GetNetworkState("eth_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ("eth_guid", network->guid);
  EXPECT_EQ(mojom::NetworkType::kEthernet, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kOnline, network->connection_state);
  EXPECT_EQ(mojom::OncSource::kNone, network->source);

  network = GetNetworkState("wifi1_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ("wifi1_guid", network->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kConnected, network->connection_state);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_wifi());
  EXPECT_EQ(mojom::SecurityType::kNone,
            network->type_state->get_wifi()->security);
  EXPECT_EQ(50, network->type_state->get_wifi()->signal_strength);
  EXPECT_FALSE(network->type_state->get_wifi()->hidden_ssid);
  EXPECT_EQ(mojom::OncSource::kNone, network->source);

  network = GetNetworkState("wifi2_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ("wifi2_guid", network->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            network->connection_state);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_wifi());
  EXPECT_EQ(mojom::SecurityType::kWpaPsk,
            network->type_state->get_wifi()->security);
  EXPECT_EQ(100, network->type_state->get_wifi()->signal_strength);
  EXPECT_TRUE(network->type_state->get_wifi()->hidden_ssid);
  EXPECT_EQ(mojom::OncSource::kUserPolicy, network->source);

  network = GetNetworkState("wifi3_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ("wifi3_guid", network->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            network->connection_state);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_wifi());
  EXPECT_EQ(mojom::SecurityType::kWpaPsk,
            network->type_state->get_wifi()->security);
  EXPECT_EQ(0, network->type_state->get_wifi()->signal_strength);
  EXPECT_EQ(mojom::OncSource::kDevice, network->source);

  network = GetNetworkState(kCellularGuid);
  ASSERT_TRUE(network);
  EXPECT_EQ(kCellularGuid, network->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            network->connection_state);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_cellular());
  mojom::CellularStatePropertiesPtr& cellular =
      network->type_state->get_cellular();
  EXPECT_EQ(0, cellular->signal_strength);
  EXPECT_EQ("LTE", cellular->network_technology);
  EXPECT_EQ(mojom::ActivationStateType::kActivated, cellular->activation_state);
  EXPECT_EQ(kCellularTestIccid, cellular->iccid);
  EXPECT_EQ(mojom::OncSource::kDevice, network->source);
  EXPECT_TRUE(cellular->sim_locked);
  EXPECT_TRUE(cellular->sim_lock_enabled);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_type);

  network = GetNetworkState("vpn_l2tp_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ("vpn_l2tp_guid", network->guid);
  EXPECT_EQ(mojom::NetworkType::kVPN, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kConnecting, network->connection_state);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_vpn());
  EXPECT_EQ(mojom::VpnType::kL2TPIPsec, network->type_state->get_vpn()->type);
  EXPECT_EQ(mojom::OncSource::kNone, network->source);

  network = GetNetworkState("vpn_ikev2_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ("vpn_ikev2_guid", network->guid);
  EXPECT_EQ(mojom::NetworkType::kVPN, network->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            network->connection_state);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_vpn());
  EXPECT_EQ(mojom::VpnType::kIKEv2, network->type_state->get_vpn()->type);
  EXPECT_EQ(mojom::OncSource::kNone, network->source);

  // TODO(crbug.com/41434332): Test ProxyMode once UIProxyConfigService logic is
  // improved.
}

TEST_F(CrosNetworkConfigTest, PortalState) {
  mojom::NetworkStatePropertiesPtr network = GetNetworkState("eth_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::ConnectionStateType::kOnline, network->connection_state);
  EXPECT_EQ(mojom::PortalState::kOnline, network->portal_state);
  EXPECT_FALSE(network->portal_probe_url);

  helper()->ConfigureService(
      R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "portal-suspected",
          "Strength": 90, "AutoConnect": true})");
  network = GetNetworkState("wifi1_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::ConnectionStateType::kPortal, network->connection_state);
  EXPECT_EQ(mojom::PortalState::kPortalSuspected, network->portal_state);
  ASSERT_TRUE(network->portal_probe_url);
  EXPECT_EQ(captive_portal::CaptivePortalDetector::kDefaultURL,
            *network->portal_probe_url);

  helper()->ConfigureService(
      R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "redirect-found",
          "Strength": 90, "AutoConnect": true, "ProbeUrl": "http://foo.com"})");
  network = GetNetworkState("wifi1_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::ConnectionStateType::kPortal, network->connection_state);
  EXPECT_EQ(mojom::PortalState::kPortal, network->portal_state);
  ASSERT_TRUE(network->portal_probe_url);
  EXPECT_EQ("http://foo.com/", network->portal_probe_url->spec());

  helper()->ConfigureService(
      R"({"GUID": "wifi1_guid", "Type": "wifi", "State": "no-connectivity",
          "Strength": 90, "AutoConnect": true})");
  network = GetNetworkState("wifi1_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::ConnectionStateType::kPortal, network->connection_state);
  EXPECT_EQ(mojom::PortalState::kNoInternet, network->portal_state);
  EXPECT_FALSE(network->portal_probe_url);
}

TEST_F(CrosNetworkConfigTest, GetNetworkStateList) {
  mojom::NetworkFilterPtr filter = mojom::NetworkFilter::New();
  // All active networks
  filter->filter = mojom::FilterType::kActive;
  filter->network_type = mojom::NetworkType::kAll;
  filter->limit = mojom::kNoLimit;
  std::vector<mojom::NetworkStatePropertiesPtr> networks =
      GetNetworkStateList(filter.Clone());
  ASSERT_EQ(3u, networks.size());
  EXPECT_EQ("eth_guid", networks[0]->guid);
  EXPECT_EQ("wifi1_guid", networks[1]->guid);
  EXPECT_EQ("vpn_l2tp_guid", networks[2]->guid);

  // First active network
  filter->limit = 1;
  networks = GetNetworkStateList(filter.Clone());
  ASSERT_EQ(1u, networks.size());
  EXPECT_EQ("eth_guid", networks[0]->guid);

  // All wifi networks
  filter->filter = mojom::FilterType::kAll;
  filter->network_type = mojom::NetworkType::kWiFi;
  filter->limit = mojom::kNoLimit;
  networks = GetNetworkStateList(filter.Clone());
  ASSERT_EQ(5u, networks.size());
  EXPECT_EQ("wifi1_guid", networks[0]->guid);
  EXPECT_EQ("wifi2_guid", networks[1]->guid);
  EXPECT_EQ("wifi4_guid", networks[2]->guid);
  EXPECT_EQ("wifi_eap", networks[3]->guid);
  EXPECT_EQ("wifi3_guid", networks[4]->guid);

  // Visible wifi networks
  filter->filter = mojom::FilterType::kVisible;
  networks = GetNetworkStateList(filter.Clone());
  ASSERT_EQ(4u, networks.size());
  EXPECT_EQ("wifi1_guid", networks[0]->guid);
  EXPECT_EQ("wifi2_guid", networks[1]->guid);
  EXPECT_EQ("wifi4_guid", networks[2]->guid);
  EXPECT_EQ("wifi_eap", networks[3]->guid);

  // Configured wifi networks
  filter->filter = mojom::FilterType::kConfigured;
  networks = GetNetworkStateList(filter.Clone());
  ASSERT_EQ(4u, networks.size());
  EXPECT_EQ("wifi2_guid", networks[0]->guid);
  EXPECT_EQ("wifi4_guid", networks[1]->guid);
  EXPECT_EQ("wifi_eap", networks[2]->guid);
  EXPECT_EQ("wifi3_guid", networks[3]->guid);
}

TEST_F(CrosNetworkConfigTest, ESimAndPSimSlotInfo) {
  const char kTestEuiccPath1[] = "euicc_path_1";
  const char kTestEuiccPath2[] = "euicc_path_2";

  // pSIM slot info (existing ICCID).
  const char kTestPSimIccid[] = "test_psim_iccid";
  const int32_t psim_physical_slot = 1;

  // eSIM 1 slot info (existing ICCID).
  const char kTestEid1[] = "test_eid_1_esim_only";
  const char kTestESimIccid[] = "test_esim_iccid";
  const int32_t esim_1_physical_slot = 2;

  // eSIM 2 slot info (no ICCID).
  const char kTestEid2[] = "test_eid_2_esim_only";
  const int32_t esim_2_physical_slot = 3;

  // Add eSIM 1 and 2 info to Hermes.
  helper()->hermes_manager_test()->AddEuicc(
      dbus::ObjectPath(kTestEuiccPath1), kTestEid1, /*is_active=*/false,
      /*esim_1_physical_slot=*/esim_1_physical_slot);
  helper()->hermes_manager_test()->AddEuicc(
      dbus::ObjectPath(kTestEuiccPath2), kTestEid2, /*is_active=*/true,
      /*esim_1_physical_slot=*/esim_2_physical_slot);
  base::RunLoop().RunUntilIdle();

  // Add pSIM and eSIM slot info to Shill.
  base::Value::List ordered_sim_slot_info_list;
  // Add pSIM first to correspond to |psim_physical_slot| index. Note that
  // pSIMs do not have EIDs.
  AddSimSlotInfoToList(ordered_sim_slot_info_list, /*eid=*/"", kTestPSimIccid,
                       /*primary=*/true);
  // Add eSIM next to correspond to |esim_1_physical_slot| index. Intentionally
  // exclude the EID; it's expected that Hermes will fill in the missing EID.
  AddSimSlotInfoToList(ordered_sim_slot_info_list, /*eid=*/"", kTestESimIccid);
  // Add eSIM next to correspond to |esim_2_physical_slot| index. Intentionally
  // exclude the EID and ICCID; it's expected that Hermes will still fill in
  // the missing EID.
  AddSimSlotInfoToList(ordered_sim_slot_info_list, /*eid=*/"", /*iccid=*/"");
  helper()->device_test()->SetDeviceProperty(
      kCellularDevicePath, shill::kSIMSlotInfoProperty,
      base::Value(std::move(ordered_sim_slot_info_list)),
      /*notify_changed=*/true);
  base::RunLoop().RunUntilIdle();

  mojom::DeviceStatePropertiesPtr cellular =
      GetDeviceStateFromList(mojom::NetworkType::kCellular);

  // Check pSIM slot info.
  EXPECT_EQ(psim_physical_slot, (*cellular->sim_infos)[0]->slot_id);
  ASSERT_TRUE((*cellular->sim_infos)[0]->eid.empty());
  EXPECT_EQ(kTestPSimIccid, (*cellular->sim_infos)[0]->iccid);
  EXPECT_TRUE((*cellular->sim_infos)[0]->is_primary);

  // Check eSIM 1 slot info.
  EXPECT_EQ(esim_1_physical_slot, (*cellular->sim_infos)[1]->slot_id);
  EXPECT_EQ(kTestEid1, (*cellular->sim_infos)[1]->eid);
  EXPECT_EQ(kTestESimIccid, (*cellular->sim_infos)[1]->iccid);
  EXPECT_FALSE((*cellular->sim_infos)[1]->is_primary);

  // Check eSIM 2 slot info. Note that the ICCID is empty here but the
  // EID still exists.
  EXPECT_EQ(esim_2_physical_slot, (*cellular->sim_infos)[2]->slot_id);
  EXPECT_EQ(kTestEid2, (*cellular->sim_infos)[2]->eid);
  ASSERT_TRUE((*cellular->sim_infos)[2]->iccid.empty());
  EXPECT_FALSE((*cellular->sim_infos)[2]->is_primary);
}

TEST_F(CrosNetworkConfigTest, ESimNetworkNameComesFromHermes) {
  const char kTestEuiccPath[] = "euicc_path";
  const char kTestProfileServicePath[] = "esim_service_path";
  const char kTestIccid[] = "iccid";

  const char kTestProfileName[] = "test_profile_name";
  const char kTestProfileNickname[] = "test_profile_nickname";
  const char kTestNameFromShill[] = "shill_network_name";

  // Add a fake eSIM with name kTestProfileName.
  helper()->hermes_manager_test()->AddEuicc(dbus::ObjectPath(kTestEuiccPath),
                                            "eid", /*is_active=*/true,
                                            /*physical_slot=*/0);
  helper()->hermes_euicc_test()->AddCarrierProfile(
      dbus::ObjectPath(kTestProfileServicePath),
      dbus::ObjectPath(kTestEuiccPath), kTestIccid, kTestProfileName,
      kTestProfileNickname, "service_provider", "activation_code",
      kTestProfileServicePath, hermes::profile::State::kInactive,
      hermes::profile::ProfileClass::kOperational,
      HermesEuiccClient::TestInterface::AddCarrierProfileBehavior::
          kAddProfileWithService);
  base::RunLoop().RunUntilIdle();

  // Change the network's name in Shill. Now, Hermes and Shill have different
  // names associated with the profile.
  helper()->SetServiceProperty(kTestProfileServicePath, shill::kNameProperty,
                               base::Value(kTestNameFromShill));
  base::RunLoop().RunUntilIdle();

  // Fetch the Cellular network for the eSIM profile.
  std::string esim_guid = std::string("esim_guid") + kTestIccid;
  mojom::NetworkStatePropertiesPtr network = GetNetworkState(esim_guid);

  // The network's name should be the profile name (from Hermes), not the name
  // from Shill.
  EXPECT_EQ(kTestProfileNickname, network->name);
}

TEST_F(CrosNetworkConfigTest, GetDeviceStateList) {
  std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());
  EXPECT_EQ(mojom::NetworkType::kWiFi, devices[0]->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, devices[0]->device_state);

  // IP address match those set up in FakeShillManagerClient::
  // SetupDefaultEnvironment(). TODO(stevenjb): Support setting
  // expectations explicitly in NetworkStateTestHelper.
  net::IPAddress ipv4_expected;
  ASSERT_TRUE(ipv4_expected.AssignFromIPLiteral("100.0.0.1"));
  EXPECT_EQ(ipv4_expected, devices[0]->ipv4_address);
  net::IPAddress ipv6_expected;
  ASSERT_TRUE(ipv6_expected.AssignFromIPLiteral("0:0:0:0:100:0:0:1"));
  EXPECT_EQ(ipv6_expected, devices[0]->ipv6_address);

  EXPECT_EQ(mojom::NetworkType::kEthernet, devices[1]->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, devices[1]->device_state);

  mojom::DeviceStateProperties* cellular = devices[2].get();
  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_FALSE(cellular->sim_absent);
  ASSERT_TRUE(cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(3, cellular->sim_lock_status->retries_left);
  EXPECT_EQ(kCellularTestImei, cellular->imei);
  EXPECT_EQ(std::nullopt, cellular->serial);

  mojom::DeviceStateProperties* vpn = devices[3].get();
  EXPECT_EQ(mojom::NetworkType::kVPN, vpn->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, vpn->device_state);

  // Disable WiFi
  NetworkHandler::Get()->technology_state_controller()->SetTechnologiesEnabled(
      NetworkTypePattern::WiFi(), false, network_handler::ErrorCallback());
  base::RunLoop().RunUntilIdle();
  devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());
  EXPECT_EQ(mojom::NetworkType::kWiFi, devices[0]->type);
  EXPECT_EQ(mojom::DeviceStateType::kDisabled, devices[0]->device_state);
}

TEST_F(CrosNetworkConfigTest, GetDeviceStateListSerial) {
  SetSerialNumber(kCellularTestSerial);
  NetworkHandler* network_handler = NetworkHandler::Get();
  SetupNetworkConfig(network_handler);

  std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());
  mojom::DeviceStateProperties* cellular = devices[2].get();
  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_FALSE(cellular->sim_absent);
  ASSERT_TRUE(cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(3, cellular->sim_lock_status->retries_left);
  EXPECT_EQ(kCellularTestImei, cellular->imei);
  EXPECT_EQ(kCellularTestSerial, cellular->serial);
}

TEST_F(CrosNetworkConfigTest, GetDeviceStateListCarrierLocked) {
  SetupCarrierLock(true);

  std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());

  mojom::DeviceStateProperties* cellular = devices[2].get();
  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_FALSE(cellular->sim_absent);
  ASSERT_TRUE(cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(3, cellular->sim_lock_status->retries_left);
  EXPECT_EQ(kCellularTestImei, cellular->imei);
  ASSERT_TRUE(cellular->is_carrier_locked);
}

TEST_F(CrosNetworkConfigTest, GetDeviceStateListCarrierUnlocked) {
  SetupCarrierLock(false);

  std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());

  mojom::DeviceStateProperties* cellular = devices[2].get();
  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_FALSE(cellular->sim_absent);
  ASSERT_TRUE(cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(3, cellular->sim_lock_status->retries_left);
  EXPECT_EQ(kCellularTestImei, cellular->imei);
  ASSERT_FALSE(cellular->is_carrier_locked);
}

TEST_F(CrosNetworkConfigTest, GetDeviceStateListFlashing) {
  SetCellularFlashing(true);

  std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());

  mojom::DeviceStateProperties* cellular = devices[2].get();
  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  ASSERT_TRUE(cellular->is_flashing);

  SetCellularFlashing(false);

  devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());

  cellular = devices[2].get();
  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  ASSERT_FALSE(cellular->is_flashing);
}

TEST_F(CrosNetworkConfigTest, GetManagedPropertiesCellularProvider) {
  auto set_home_provider = [this](const std::string_view name,
                                  const std::string_view code,
                                  const std::string_view country) {
    base::Value::Dict home_provider;
    home_provider.Set("name", name);
    home_provider.Set("code", code);
    home_provider.Set("country", country);
    helper()->device_test()->SetDeviceProperty(
        kCellularDevicePath, shill::kHomeProviderProperty,
        base::Value(home_provider.Clone()),
        /*notify_changed=*/true);
    base::RunLoop().RunUntilIdle();
  };

  auto check_home_provider = [this](const std::string_view name,
                                    const std::string_view code,
                                    const std::string_view country) {
    mojom::ManagedPropertiesPtr properties =
        GetManagedProperties(kCellularGuid);
    ASSERT_TRUE(properties);

    const mojom::ManagedCellularPropertiesPtr& cellular =
        properties->type_properties->get_cellular();
    ASSERT_TRUE(cellular);
    const mojom::CellularProviderPropertiesPtr& provider =
        cellular->home_provider;
    ASSERT_TRUE(provider);
    EXPECT_EQ(name, provider->name);
    EXPECT_EQ(code, provider->code);
    ASSERT_TRUE(provider->country.has_value());
    EXPECT_EQ(country, *provider->country);
  };

  const std::string kDefaultName = "MobileNetwork";
  const std::string kDefaultCode = "000000";
  set_home_provider(/*name=*/"", /*code=*/"", /*country=*/"");
  check_home_provider(kDefaultName, kDefaultCode, /*country=*/"");

  const std::string kName = "ProviderName";
  const std::string kCode = "ProviderCode";
  const std::string kCountry = "ProviderCountry";
  set_home_provider(kName, kCode, kCountry);
  check_home_provider(kName, kCode, kCountry);
}

TEST_F(CrosNetworkConfigTest, GetManagedPropertiesCarrierLocked) {
  /* Lock the SIM using network-pin */
  base::Value::Dict sim_value;
  sim_value.Set(shill::kSIMLockEnabledProperty, true);
  sim_value.Set(shill::kSIMLockTypeProperty, shill::kSIMLockNetworkPin);
  sim_value.Set(shill::kSIMLockRetriesLeftProperty, kSimRetriesLeft);
  helper()->device_test()->SetDeviceProperty(kCellularDevicePath,
                                             shill::kSIMLockStatusProperty,
                                             base::Value(std::move(sim_value)),
                                             /*notify_changed=*/true);
  base::RunLoop().RunUntilIdle();

  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedCellularPropertiesPtr& cellular =
      properties->type_properties->get_cellular();
  ASSERT_TRUE(cellular);
  EXPECT_TRUE(cellular->sim_locked);
  EXPECT_EQ(shill::kSIMLockNetworkPin, cellular->sim_lock_type);
}

TEST_F(CrosNetworkConfigTest, GetManagedPropertiesCarrierLockedDisabled) {
  /* Lock the SIM using network-pin */
  base::Value::Dict sim_value;
  sim_value.Set(shill::kSIMLockEnabledProperty, true);
  sim_value.Set(shill::kSIMLockTypeProperty, shill::kSIMLockPin);
  sim_value.Set(shill::kSIMLockRetriesLeftProperty, kSimRetriesLeft);
  helper()->device_test()->SetDeviceProperty(kCellularDevicePath,
                                             shill::kSIMLockStatusProperty,
                                             base::Value(std::move(sim_value)),
                                             /*notify_changed=*/true);
  base::RunLoop().RunUntilIdle();

  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedCellularPropertiesPtr& cellular =
      properties->type_properties->get_cellular();
  ASSERT_TRUE(cellular);
  EXPECT_TRUE(cellular->sim_locked);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_type);
}

TEST_F(CrosNetworkConfigTest, SimStateCarrierLocked) {
  /* Lock the SIM using network-pin */
  base::Value::Dict sim_value;
  sim_value.Set(shill::kSIMLockEnabledProperty, true);
  sim_value.Set(shill::kSIMLockTypeProperty, shill::kSIMLockNetworkPin);
  sim_value.Set(shill::kSIMLockRetriesLeftProperty, kSimRetriesLeft);
  helper()->device_test()->SetDeviceProperty(kCellularDevicePath,
                                             shill::kSIMLockStatusProperty,
                                             base::Value(std::move(sim_value)),
                                             /*notify_changed=*/true);
  base::RunLoop().RunUntilIdle();

  mojom::DeviceStatePropertiesPtr cellular =
      GetDeviceStateFromList(mojom::NetworkType::kCellular);

  EXPECT_EQ(mojom::NetworkType::kCellular, cellular->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  ASSERT_TRUE(cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockNetworkPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(3, cellular->sim_lock_status->retries_left);

  // Any attempt to unlock carrier locked sim with the pin should fail and
  // should not change the carrier lock status.
  EXPECT_FALSE(SetCellularSimState(FakeShillDeviceClient::kDefaultSimPin,
                                   /*new_pin=*/std::nullopt,
                                   /*require_pin=*/false));

  // Sim should continue to be carrier locked.
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockNetworkPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(3, cellular->sim_lock_status->retries_left);
}

// Tests that no VPN device state is returned by GetDeviceStateList if no VPN
// services exist and built-in VPN is not prohibited.
TEST_F(CrosNetworkConfigTest, GetDeviceStateListNoVpnServices) {
  helper()->ClearServices();

  std::vector<std::string> prohibited_technologies =
      NetworkHandler::Get()
          ->prohibited_technologies_handler()
          ->GetCurrentlyProhibitedTechnologies();
  ASSERT_FALSE(base::Contains(prohibited_technologies, shill::kTypeVPN));

  EXPECT_FALSE(ContainsVpnDeviceState(GetDeviceStateList()));
}

// Tests that a VPN device state is returned by GetDeviceStateList if built-in
// VPN is not prohibited even if no VPN services exist.
TEST_F(CrosNetworkConfigTest, GetDeviceStateListNoVpnServicesAndVpnProhibited) {
  helper()->ClearServices();

  NetworkHandler::Get()
      ->prohibited_technologies_handler()
      ->AddGloballyProhibitedTechnology(shill::kTypeVPN);

  EXPECT_TRUE(ContainsVpnDeviceState(GetDeviceStateList()));
}

// Test a sampling of properties, ensuring that string property types are
// translated as strings and not enum values (See ManagedProperties definition
// in cros_network_config.mojom for details).
TEST_F(CrosNetworkConfigTest, GetManagedProperties) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kTrafficCountersEnabled,
                            features::kTrafficCountersForWiFiTesting},
      /*disabled_features=*/{});
  traffic_counters::TrafficCountersHandler::InitializeForTesting();
  SetTrafficCountersResetDayAndCompare("eth_guid",
                                       /*day=*/mojom::UInt32Value::New(32),
                                       /*expected_success=*/false,
                                       /*expected_reset_day=*/nullptr);
  mojom::ManagedPropertiesPtr properties = GetManagedProperties("eth_guid");
  ASSERT_TRUE(properties);
  EXPECT_EQ("eth_guid", properties->guid);
  EXPECT_EQ(mojom::NetworkType::kEthernet, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kOnline, properties->connection_state);
  // Traffic counters are not presented for Ethernet networks.
  ASSERT_FALSE(properties->traffic_counter_properties);

  helper()->SetServiceProperty(wifi1_path(), shill::kStateProperty,
                               base::Value(shill::kStateOnline));
  base::RunLoop().RunUntilIdle();

  base::Value expected_reset_day(2);
  SetTrafficCountersResetDayAndCompare("wifi1_guid",
                                       /*day=*/mojom::UInt32Value::New(2),
                                       /*expected_success=*/true,
                                       &expected_reset_day);
  properties = GetManagedProperties("wifi1_guid");
  ASSERT_TRUE(properties);
  EXPECT_EQ("wifi1_guid", properties->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kOnline, properties->connection_state);
  ASSERT_TRUE(properties->type_properties);
  ASSERT_TRUE(properties->type_properties->is_wifi());
  EXPECT_EQ(50, properties->type_properties->get_wifi()->signal_strength);
  EXPECT_EQ(mojom::OncSource::kNone, properties->source);
  EXPECT_FALSE(properties->type_properties->get_wifi()->is_syncable);
  ASSERT_TRUE(properties->traffic_counter_properties &&
              properties->traffic_counter_properties->last_reset_time);
  EXPECT_EQ(123456789987654,
            properties->traffic_counter_properties->last_reset_time
                ->ToDeltaSinceWindowsEpoch()
                .InMilliseconds());
  EXPECT_EQ(static_cast<uint32_t>(2),
            properties->traffic_counter_properties->user_specified_reset_day);

  properties = GetManagedProperties("wifi2_guid");
  ASSERT_TRUE(properties);
  EXPECT_EQ("wifi2_guid", properties->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedWiFiPropertiesPtr& wifi =
      properties->type_properties->get_wifi();
  ASSERT_TRUE(wifi);
  EXPECT_EQ(mojom::SecurityType::kWpaPsk, wifi->security);
  EXPECT_EQ(100, wifi->signal_strength);
  EXPECT_EQ(mojom::OncSource::kUserPolicy, properties->source);
  EXPECT_FALSE(properties->type_properties->get_wifi()->is_syncable);

  properties = GetManagedProperties(kCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedCellularPropertiesPtr& cellular =
      properties->type_properties->get_cellular();
  ASSERT_TRUE(cellular);
  EXPECT_EQ(0, cellular->signal_strength);
  EXPECT_EQ("LTE", cellular->network_technology);
  EXPECT_TRUE(cellular->sim_locked);
  EXPECT_EQ(mojom::ActivationStateType::kActivated, cellular->activation_state);

  properties = GetManagedProperties("vpn_l2tp_guid");
  ASSERT_TRUE(properties);
  EXPECT_EQ("vpn_l2tp_guid", properties->guid);
  EXPECT_EQ(mojom::NetworkType::kVPN, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kConnecting,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties);
  ASSERT_TRUE(properties->type_properties->is_vpn());
  EXPECT_EQ(mojom::VpnType::kL2TPIPsec,
            properties->type_properties->get_vpn()->type);

  properties = GetManagedProperties("wifi_eap");
  ASSERT_TRUE(properties);
  EXPECT_EQ("wifi_eap", properties->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, properties->type);
  ASSERT_TRUE(properties->type_properties);
  ASSERT_TRUE(properties->type_properties->is_wifi());
  mojom::ManagedEAPPropertiesPtr eap =
      std::move(properties->type_properties->get_wifi()->eap);
  ASSERT_TRUE(eap);
  ASSERT_TRUE(eap->subject_alt_name_match);
  ASSERT_EQ(2u, eap->subject_alt_name_match->active_value.size());
  EXPECT_EQ(mojom::SubjectAltName::Type::kDns,
            eap->subject_alt_name_match->active_value[0]->type);
  EXPECT_EQ("example.com", eap->subject_alt_name_match->active_value[0]->value);
  EXPECT_EQ(mojom::SubjectAltName::Type::kEmail,
            eap->subject_alt_name_match->active_value[1]->type);
  EXPECT_EQ("[email protected]",
            eap->subject_alt_name_match->active_value[1]->value);

  ASSERT_TRUE(eap->domain_suffix_match);
  ASSERT_EQ(2u, eap->domain_suffix_match->active_value.size());
  EXPECT_EQ("example1.com", eap->domain_suffix_match->active_value[0]);
  EXPECT_EQ("example2.com", eap->domain_suffix_match->active_value[1]);
}

// Test managed property policy values.
TEST_F(CrosNetworkConfigTest, GetManagedPropertiesPolicy) {
  mojom::ManagedPropertiesPtr properties = GetManagedProperties("wifi1_guid");
  ASSERT_TRUE(properties);
  ASSERT_EQ("wifi1_guid", properties->guid);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedWiFiProperties* wifi =
      properties->type_properties->get_wifi().get();
  ASSERT_TRUE(wifi);
  ASSERT_TRUE(wifi->auto_connect);
  EXPECT_TRUE(wifi->auto_connect->active_value);
  EXPECT_EQ(mojom::PolicySource::kNone, wifi->auto_connect->policy_source);

  properties = GetManagedProperties("wifi2_guid");
  ASSERT_TRUE(properties);
  ASSERT_EQ("wifi2_guid", properties->guid);
  ASSERT_TRUE(properties->type_properties);
  wifi = properties->type_properties->get_wifi().get();
  ASSERT_TRUE(wifi);
  ASSERT_TRUE(wifi->auto_connect);
  EXPECT_TRUE(wifi->auto_connect->active_value);
  EXPECT_EQ(mojom::PolicySource::kUserPolicyEnforced,
            wifi->auto_connect->policy_source);
  EXPECT_TRUE(wifi->auto_connect->policy_value);
  ASSERT_TRUE(properties->name);
  EXPECT_EQ("wifi2", properties->name->active_value);
  EXPECT_EQ(mojom::PolicySource::kUserPolicyEnforced,
            properties->name->policy_source);
  EXPECT_EQ("wifi2", properties->name->policy_value);
  ASSERT_TRUE(properties->priority);
  EXPECT_EQ(0, properties->priority->active_value);
  EXPECT_EQ(mojom::PolicySource::kUserPolicyEnforced,
            properties->priority->policy_source);
  EXPECT_EQ(0, properties->priority->policy_value);

  properties = GetManagedProperties("openvpn_guid");
  ASSERT_TRUE(properties);
  ASSERT_EQ("openvpn_guid", properties->guid);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedVPNProperties* vpn =
      properties->type_properties->get_vpn().get();
  ASSERT_TRUE(vpn);
  ASSERT_TRUE(vpn->open_vpn);
  std::vector<std::tuple<mojom::ManagedString*, std::string>> checks = {
      {vpn->open_vpn->auth.get(), "MD5"},
      {vpn->open_vpn->cipher.get(), "AES-192-CBC"},
      {vpn->open_vpn->compression_algorithm.get(), "LZO"},
      {vpn->open_vpn->tls_auth_contents.get(), policy_util::kFakeCredential},
      {vpn->open_vpn->key_direction.get(), "1"}};
  for (const auto& [property, expected] : checks) {
    ASSERT_TRUE(property);
    EXPECT_EQ(expected, property->active_value);
    EXPECT_EQ(mojom::PolicySource::kUserPolicyEnforced,
              property->policy_source);
  }
}

// Test managed EAP properties which are merged from a separate EthernetEAP
// Shill service.
TEST_F(CrosNetworkConfigTest, GetManagedPropertiesEAP) {
  SetupEthernetEAP();
  mojom::ManagedPropertiesPtr properties = GetManagedProperties("eth_guid");
  ASSERT_TRUE(properties);
  EXPECT_EQ("eth_guid", properties->guid);
  EXPECT_EQ(mojom::NetworkType::kEthernet, properties->type);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedEthernetProperties* ethernet =
      properties->type_properties->get_ethernet().get();
  ASSERT_TRUE(ethernet);
  ASSERT_TRUE(ethernet->authentication);
  EXPECT_EQ("8021X", ethernet->authentication->active_value);
  ASSERT_TRUE(ethernet->eap);
  ASSERT_TRUE(ethernet->eap->identity);
  EXPECT_EQ("user1", ethernet->eap->identity->active_value);
}

TEST_F(CrosNetworkConfigTest, SetProperties) {
  // Use wifi3 since it has a profile path (i.e. it is 'saved'). and is not
  // policy controoled.
  const char* kGUID = "wifi3_guid";
  // Assert initial state.
  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kGUID);
  ASSERT_TRUE(properties);
  ASSERT_EQ(kGUID, properties->guid);
  ASSERT_TRUE(properties->type_properties);
  mojom::ManagedWiFiProperties* wifi =
      properties->type_properties->get_wifi().get();
  ASSERT_TRUE(wifi);
  ASSERT_FALSE(wifi->auto_connect);
  ASSERT_FALSE(properties->priority);

  // Set priority.
  auto config = mojom::ConfigProperties::New();
  config->type_config = mojom::NetworkTypeConfigProperties::NewWifi(
      mojom::WiFiConfigProperties::New());
  config->priority = mojom::PriorityConfig::New();
  config->priority->value = 1;
  bool success = SetProperties(kGUID, std::move(config));
  ASSERT_TRUE(success);
  properties = GetManagedProperties(kGUID);
  ASSERT_TRUE(properties);
  ASSERT_EQ(kGUID, properties->guid);
  ASSERT_TRUE(properties->type_properties);
  wifi = properties->type_properties->get_wifi().get();
  ASSERT_TRUE(wifi);
  ASSERT_FALSE(wifi->auto_connect);
  ASSERT_TRUE(properties->priority);
  EXPECT_EQ(1, properties->priority->active_value);

  // Set auto connect only. Priority should remain unchanged. Also provide a
  // matching |guid|.
  config = mojom::ConfigProperties::New();
  config->type_config = mojom::NetworkTypeConfigProperties::NewWifi(
      mojom::WiFiConfigProperties::New());
  config->auto_connect = mojom::AutoConnectConfig::New();
  config->auto_connect->value = true;
  config->guid = kGUID;
  success = SetProperties(kGUID, std::move(config));
  ASSERT_TRUE(success);
  properties = GetManagedProperties(kGUID);
  ASSERT_TRUE(properties);
  ASSERT_EQ(kGUID, properties->guid);
  ASSERT_TRUE(properties->type_properties);
  wifi = properties->type_properties->get_wifi().get();
  ASSERT_TRUE(wifi);
  ASSERT_TRUE(wifi->auto_connect);
  EXPECT_TRUE(wifi->auto_connect->active_value);
  ASSERT_TRUE(properties->priority);
  EXPECT_EQ(1, properties->priority->active_value);

  // Set auto connect with a mismatched guid; call should fail.
  config = mojom::ConfigProperties::New();
  config->type_config = mojom::NetworkTypeConfigProperties::NewWifi(
      mojom::WiFiConfigProperties::New());
  config->auto_connect = mojom::AutoConnectConfig::New();
  config->auto_connect->value = false;
  config->guid = "Mismatched guid";
  success = SetProperties(kGUID, std::move(config));
  EXPECT_FALSE(success);

  // Set Eap.SubjectAlternativeNameMatch and Eap.DomainSuffixMatch.
  config = mojom::ConfigProperties::New();
  config->type_config = mojom::NetworkTypeConfigProperties::NewWifi(
      mojom::WiFiConfigProperties::New());
  auto eap = mojom::EAPConfigProperties::New();
  eap->domain_suffix_match = {"example1.com", "example2.com"};
  auto san = mojom::SubjectAltName::New();
  san->type = mojom::SubjectAltName::Type::kDns;
  san->value = "test.example.com";
  eap->subject_alt_name_match.push_back(std::move(san));

  config->type_config->get_wifi()->eap = std::move(eap);
  config->guid = kGUID;
  success = SetProperties(kGUID, std::move(config));
  ASSERT_TRUE(success);
  properties = GetManagedProperties(kGUID);
  ASSERT_TRUE(properties);
  ASSERT_EQ(kGUID, properties->guid);
  ASSERT_TRUE(properties->type_properties);
  wifi = properties->type_properties->get_wifi().get();
  ASSERT_TRUE(wifi);
  ASSERT_TRUE(wifi->eap);
  EXPECT_EQ(2u, wifi->eap->domain_suffix_match->active_value.size());
  EXPECT_EQ("example1.com", wifi->eap->domain_suffix_match->active_value[0]);
  EXPECT_EQ("example2.com", wifi->eap->domain_suffix_match->active_value[1]);
  EXPECT_EQ(1u, wifi->eap->subject_alt_name_match->active_value.size());
  EXPECT_EQ(mojom::SubjectAltName::Type::kDns,
            wifi->eap->subject_alt_name_match->active_value[0]->type);
  EXPECT_EQ("test.example.com",
            wifi->eap->subject_alt_name_match->active_value[0]->value);
}

TEST_F(CrosNetworkConfigTest, FillInCustomAPNList) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(/*enabled_features=*/
                                       {features::kApnRevamp,
                                        features::kAllowApnModificationPolicy},
                                       /*disabled_features=*/{});

  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  test_apn1.onc_state = ::onc::cellular_apn::kStateEnabled;
  test_apn1.id = "apn_id_1";

  auto populated_apn_list = base::Value::List().Append(test_apn1.AsOncApn());

  NetworkHandler::Get()->network_metadata_store()->SetCustomApnList(
      kCellularGuid, populated_apn_list.Clone());

  std::string service_path = helper()->ConfigureService(base::StringPrintf(
      kTestApnCellularShillDictFmt, kCellularGuid, shill::kStateIdle,
      kCellularTestIccid, NetworkProfileHandler::GetSharedProfilePath().c_str(),
      CreateApnShillDict().c_str()));

  std::optional<base::Value::List> shill_custom_apns =
      helper()->GetServiceListProperty(service_path,
                                       shill::kCellularCustomApnListProperty);
  ASSERT_FALSE(shill_custom_apns.has_value());

  auto config = mojom::ConfigProperties::New();
  auto cellular_config = mojom::CellularConfigProperties::New();
  auto new_roaming = mojom::RoamingProperties::New();
  new_roaming->allow_roaming = false;
  cellular_config->roaming = std::move(new_roaming);
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));
  SetProperties(kCellularGuid, std::move(config));

  shill_custom_apns = helper()->GetServiceListProperty(
      service_path, shill::kCellularCustomApnListProperty);

  ASSERT_TRUE(shill_custom_apns.has_value());
  EXPECT_EQ(1u, shill_custom_apns->size());
  const std::string* apn_name =
      shill_custom_apns->front().GetDict().FindString(shill::kApnNameProperty);
  EXPECT_EQ(kCellularTestApnName1, *apn_name);
  const std::string* apn_type =
      shill_custom_apns->front().GetDict().FindString(shill::kApnTypesProperty);
  EXPECT_EQ(shill::kApnTypeDefault, *apn_type);
}

TEST_F(CrosNetworkConfigTest, CustomAPN) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(features::kApnRevamp);
  SetupAPNList();
  // Verify that setting APN to an entry that already exists in apn list
  // does not update the custom apn list.
  auto config = mojom::ConfigProperties::New();
  auto cellular_config = mojom::CellularConfigProperties::New();
  TestApnData test_apn_data1;
  test_apn_data1.access_point_name = kCellularTestApn1;
  test_apn_data1.name = kCellularTestApnName1;
  test_apn_data1.username = kCellularTestApnUsername1;
  test_apn_data1.password = kCellularTestApnPassword1;
  test_apn_data1.attach = kCellularTestApnAttach1;
  cellular_config->apn = test_apn_data1.AsMojoApn();
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));
  SetProperties(kCellularGuid, std::move(config));
  const base::Value::List* apn_list =
      NetworkHandler::Get()->network_metadata_store()->GetCustomApnList(
          kCellularGuid);
  ASSERT_FALSE(apn_list);

  // Verify that custom APN list is updated properly.
  config = mojom::ConfigProperties::New();
  cellular_config = mojom::CellularConfigProperties::New();
  TestApnData test_apn_data3;
  test_apn_data3.access_point_name = kCellularTestApn3;
  test_apn_data3.name = kCellularTestApnName3;
  test_apn_data3.username = kCellularTestApnUsername3;
  test_apn_data3.password = kCellularTestApnPassword3;
  test_apn_data3.attach = kCellularTestApnAttach3;
  cellular_config->apn = test_apn_data3.AsMojoApn();
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));
  SetProperties(kCellularGuid, std::move(config));
  apn_list = NetworkHandler::Get()->network_metadata_store()->GetCustomApnList(
      kCellularGuid);
  ASSERT_TRUE(apn_list);

  // Verify that custom APN list is returned properly in managed properties.
  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kCellularGuid);
  ASSERT_TRUE(properties);
  ASSERT_EQ(kCellularGuid, properties->guid);
  ASSERT_TRUE(properties->type_properties->is_cellular());
  ASSERT_TRUE(
      properties->type_properties->get_cellular()->custom_apn_list.has_value());
  ASSERT_EQ(
      1u, properties->type_properties->get_cellular()->custom_apn_list->size());
  const mojom::ApnPropertiesPtr& first_apn =
      properties->type_properties->get_cellular()->custom_apn_list->front();
  EXPECT_TRUE(test_apn_data3.MojoApnEquals(*first_apn));
}

TEST_F(CrosNetworkConfigTest,
       CanCreateDisabledAttachApnWithoutExistingDefaultApn) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  ASSERT_FALSE(network_metadata_store()->GetCustomApnList(kCellularGuid));

  // CreateCustomApn with an exclusively attach APN in the disabled state, and
  // verify that it is created even though a default APN does not exist.
  TestApnData test_apn1;
  test_apn1.mojo_state = mojom::ApnState::kDisabled;
  test_apn1.onc_state = ::onc::cellular_apn::kStateDisabled;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  ASSERT_TRUE(network_metadata_store()->GetCustomApnList(kCellularGuid));
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/1, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/1, ApnTypes::kAttach, /*apn_types_count=*/1);
}

TEST_F(CrosNetworkConfigTest, CreateCustomApnList) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  // CreateCustomApn with attach only and verify that it doesn't get added
  // because its missing a default.
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_FALSE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));

  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> empty_apn_list({});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, empty_apn_list));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, empty_apn_list,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, empty_apn_list));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/0, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/0, ApnTypes::kAttach, /*apn_types_count=*/0);

  // CreateCustomApn with attach and default and mock a failure.
  ShillServiceClient::Get()
      ->GetTestInterface()
      ->SetErrorForNextSetPropertiesAttempt("Error.NotReady");
  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn1;
  test_apn2.name = kCellularTestApnName1;
  test_apn2.username = kCellularTestApnUsername1;
  test_apn2.password = kCellularTestApnPassword1;
  test_apn2.attach = kCellularTestApnAttach1;
  test_apn2.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  EXPECT_FALSE(CreateCustomApn(kCellularGuid, test_apn2.AsMojoApn()));
  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> empty_apn_list({});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, empty_apn_list));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, empty_apn_list,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, empty_apn_list));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/1);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/0, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/0, ApnTypes::kDefaultAndAttach, /*apn_types_count=*/0);

  // Try again to create the APN without mocking a failure.
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn2.AsMojoApn()));

  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/1);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/1, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/1, ApnTypes::kDefaultAndAttach, /*apn_types_count=*/1);

  // CreateCustomApn with attach and make sure that it gets added because
  // there’s an APN with default added in |test_apn2|.
  TestApnData test_apn3;
  test_apn3.access_point_name = kCellularTestApn1;
  test_apn3.name = kCellularTestApnName1;
  test_apn3.username = kCellularTestApnUsername1;
  test_apn3.password = kCellularTestApnPassword1;
  test_apn3.attach = kCellularTestApnAttach1;
  test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn3.AsMojoApn()));

  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn2});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/1);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/2, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/2, ApnTypes::kAttach, /*apn_types_count=*/1);
}

TEST_F(CrosNetworkConfigTest, CreateExclusivelyEnabledCustomApnList) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  // CreateExclusivelyEnabledCustomApn with attach only and verify that it
  // doesn't get added because its missing a default.
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_FALSE(
      CreateExclusivelyEnabledCustomApn(kCellularGuid, test_apn1.AsMojoApn()));

  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> empty_apn_list({});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, empty_apn_list));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, empty_apn_list,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, empty_apn_list));
  }
  AssertCreateExclusivelyEnabledCustomApnResultBucketCount(/*num_success=*/0,
                                                           /*num_failure=*/0);
  AssertCreateExclusivelyEnabledCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/0, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/0, ApnTypes::kAttach, /*apn_types_count=*/0);

  // CreateExclusivelyEnabledCustomApn with attach and default and mock a
  // failure.
  ShillServiceClient::Get()
      ->GetTestInterface()
      ->SetErrorForNextSetPropertiesAttempt("Error.NotReady");
  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn1;
  test_apn2.name = kCellularTestApnName1;
  test_apn2.username = kCellularTestApnUsername1;
  test_apn2.password = kCellularTestApnPassword1;
  test_apn2.attach = kCellularTestApnAttach1;
  test_apn2.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  EXPECT_FALSE(
      CreateExclusivelyEnabledCustomApn(kCellularGuid, test_apn2.AsMojoApn()));
  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> empty_apn_list({});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, empty_apn_list));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, empty_apn_list,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, empty_apn_list));
  }
  AssertCreateExclusivelyEnabledCustomApnResultBucketCount(/*num_success=*/0,
                                                           /*num_failure=*/1);
  AssertCreateExclusivelyEnabledCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/0, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/0, ApnTypes::kDefaultAndAttach, /*apn_types_count=*/0);

  // Try again to create the APN without mocking a failure.
  EXPECT_TRUE(
      CreateExclusivelyEnabledCustomApn(kCellularGuid, test_apn2.AsMojoApn()));

  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateExclusivelyEnabledCustomApnResultBucketCount(/*num_success=*/1,
                                                           /*num_failure=*/1);
  AssertCreateExclusivelyEnabledCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/1, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/1, ApnTypes::kDefaultAndAttach, /*apn_types_count=*/1);

  // CreateExclusivelyEnabledCustomApn with attach and make sure that it gets
  // added because there’s an APN with default added in |test_apn2|.
  TestApnData test_apn3;
  test_apn3.access_point_name = kCellularTestApn1;
  test_apn3.name = kCellularTestApnName1;
  test_apn3.username = kCellularTestApnUsername1;
  test_apn3.password = kCellularTestApnPassword1;
  test_apn3.attach = kCellularTestApnAttach1;
  test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(
      CreateExclusivelyEnabledCustomApn(kCellularGuid, test_apn3.AsMojoApn()));

  // All other APNs should be disabled.
  test_apn2.mojo_state = mojom::ApnState::kDisabled;
  test_apn2.onc_state = ::onc::cellular_apn::kStateDisabled;

  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn2});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateExclusivelyEnabledCustomApnResultBucketCount(/*num_success=*/2,
                                                           /*num_failure=*/1);
  AssertCreateExclusivelyEnabledCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/2, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/2, ApnTypes::kAttach, /*apn_types_count=*/1);
}

TEST_F(CrosNetworkConfigTest, RemoveCustomApnList) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(ash::features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  // Create a custom default APN.
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));

  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/1, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/1, ApnTypes::kDefault, /*apn_types_count=*/1);

  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  const std::string first_apn_id = std::string(
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId));

  // Create a new custom attach APN.
  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn2;
  test_apn2.name = kCellularTestApnName2;
  test_apn2.username = kCellularTestApnUsername2;
  test_apn2.password = kCellularTestApnPassword2;
  test_apn2.attach = "attach";
  test_apn2.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn2.AsMojoApn()));

  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/2, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/2, ApnTypes::kAttach, /*apn_types_count=*/1);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(2u, custom_apns->size());
  const std::string second_apn_id = std::string(
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId));

  // Try to remove the default APN |test_apn1| which will not work because there
  // would only be an attach APN left.
  RemoveCustomApn(kCellularGuid, first_apn_id);
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/0);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kDefault,
                                             /*apn_types_count=*/0);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_EQ(2u, custom_apns->size());

  // Create another new custom default APN.
  TestApnData test_apn3;
  test_apn3.access_point_name = kCellularTestApn3;
  test_apn3.name = kCellularTestApnName3;
  test_apn3.username = kCellularTestApnUsername3;
  test_apn3.password = kCellularTestApnPassword3;
  test_apn3.attach = "";
  test_apn3.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn3.AsMojoApn()));

  EXPECT_EQ(3u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns(
        {&test_apn3, &test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/3, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/3, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/3, ApnTypes::kDefault, /*apn_types_count=*/2);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(3u, custom_apns->size());
  const std::string third_apn_id = std::string(
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId));

  // Try to remove the default APN |test_apn3| which is OK because there is
  // another APN that is default but mock a failure.
  ShillServiceClient::Get()
      ->GetTestInterface()
      ->SetErrorForNextSetPropertiesAttempt("Error.NotReady");
  RemoveCustomApn(kCellularGuid, third_apn_id);
  EXPECT_EQ(3u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns(
        {&test_apn3, &test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/1);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kDefault,
                                             /*apn_types_count=*/0);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(3u, custom_apns->size());

  // Try again to remove the APN which is OK because there is another APN that
  // is default and we did not mock a failure.
  RemoveCustomApn(kCellularGuid, third_apn_id);
  EXPECT_EQ(4u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/1);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kDefault,
                                             /*apn_types_count=*/1);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(2u, custom_apns->size());

  // Remove the custom attach APN which is OK because there is a default APN.
  RemoveCustomApn(kCellularGuid, second_apn_id);
  EXPECT_EQ(5u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/1);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kAttach,
                                             /*apn_types_count=*/1);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_EQ(1u, custom_apns->size());

  // Remove the custom default APN which is OK because there is no other APN.
  RemoveCustomApn(kCellularGuid, first_apn_id);
  EXPECT_EQ(6u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> empty_apn_list({});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, empty_apn_list));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, empty_apn_list,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, empty_apn_list));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/3, /*num_failure=*/1);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kDefault,
                                             /*apn_types_count=*/2);
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_EQ(0u, custom_apns->size());
}

TEST_F(CrosNetworkConfigTest, CreateCustomApn_NoListSaved) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));

  // Verify that the API called sent the right values to Shill
  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/1, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/1, ApnTypes::kDefault, /*apn_types_count=*/1);
}

TEST_F(CrosNetworkConfigTest, ModifyCustomApnList) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(ash::features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  // Create a custom default APN.
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }

  // Try to modify the APN type to be attach which will not work because there
  // would be no default APN left.
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  const std::string first_apn_id =
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn2;
  test_apn2.name = kCellularTestApnName2;
  test_apn2.username = kCellularTestApnUsername2;
  test_apn2.password = kCellularTestApnPassword2;
  test_apn2.attach = "attach";
  test_apn2.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  test_apn2.id = first_apn_id;

  ModifyCustomApn(kCellularGuid, test_apn2.AsMojoApn());
  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }

  // Create a custom attach APN.
  TestApnData test_apn3;
  test_apn3.access_point_name = kCellularTestApn3;
  test_apn3.name = kCellularTestApnName3;
  test_apn3.username = kCellularTestApnUsername3;
  test_apn3.password = kCellularTestApnPassword3;
  test_apn3.attach = kCellularTestApnAttach1;
  test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn3.AsMojoApn()));
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kAutomatic,
      /*auth_type_count=*/2, mojom::ApnIpType::kAutomatic,
      /*ip_type_count=*/2, ApnTypes::kAttach, /*apn_types_count=*/1);

  // Try to modify default APN to be attach which will not work because there
  // would be no default APN left.
  ModifyCustomApn(kCellularGuid, test_apn2.AsMojoApn());
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }

  // Try to modify attach APN type to default which is OK but mock a failure.
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(2u, custom_apns->size());
  const std::string second_apn_id =
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  ShillServiceClient::Get()
      ->GetTestInterface()
      ->SetErrorForNextSetPropertiesAttempt("Error.NotReady");
  TestApnData test_apn4;
  test_apn4.access_point_name = "TEST.APN4";
  test_apn4.name = "Test Apn 4";
  test_apn4.username = kCellularTestApnUsername1;
  test_apn4.password = kCellularTestApnPassword1;
  test_apn4.attach = "";
  test_apn4.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn4.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  test_apn4.id = second_apn_id;

  ApnHistogramCounts counts;
  AssertApnHistogramCounts(counts);
  ModifyCustomApn(kCellularGuid, test_apn4.AsMojoApn());
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_failure++;
  AssertApnHistogramCounts(counts);

  // Try again to modify attach APN type to default which is OK without mocking
  // a failure.
  ModifyCustomApn(kCellularGuid, test_apn4.AsMojoApn());
  EXPECT_EQ(3u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn4, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_success++;
  counts.num_modify_type_attach++;
  AssertApnHistogramCounts(counts);

  // Modify first default APN to be attach which is OK.
  TestApnData test_apn5;
  test_apn5.access_point_name = "TEST.APN5";
  test_apn5.name = "Test Apn 5";
  test_apn5.username = kCellularTestApnUsername1;
  test_apn5.password = kCellularTestApnPassword1;
  test_apn5.attach = "attach";
  test_apn5.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn5.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  test_apn5.id = first_apn_id;

  ModifyCustomApn(kCellularGuid, test_apn5.AsMojoApn());
  EXPECT_EQ(4u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn4, &test_apn5});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
}

TEST_F(CrosNetworkConfigTest, CreateCustomApn_EmptyList) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  network_metadata_store()->SetCustomApnList(kCellularGuid,
                                             base::Value::List());

  EXPECT_TRUE(CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, {}));
  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Call the API to create a new user APN
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_ip_type = mojom::ApnIpType::kIpv4;
  test_apn1.onc_ip_type = ::onc::cellular_apn::kIpTypeIpv4;
  test_apn1.mojo_source = mojom::ApnSource::kUi;
  test_apn1.onc_source = ::onc::cellular_apn::kSourceUi;
  test_apn1.mojo_authentication = mojom::ApnAuthenticationType::kPap;
  test_apn1.onc_authentication = ::onc::cellular_apn::kAuthenticationPap;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));

  // Verify that the API called sent the right values to Shill
  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kPap,
      /*auth_type_count=*/1, mojom::ApnIpType::kIpv4,
      /*ip_type_count=*/1, ApnTypes::kDefaultAndAttach, /*apn_types_count=*/1);

  // Call the API to create a second user APN
  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn2;
  test_apn2.name = kCellularTestApnName2;
  test_apn2.username = kCellularTestApnUsername2;
  test_apn2.password = kCellularTestApnPassword2;
  test_apn2.attach = kCellularTestApnAttach2;
  test_apn2.mojo_ip_type = mojom::ApnIpType::kIpv4Ipv6;
  test_apn2.onc_ip_type = ::onc::cellular_apn::kIpTypeIpv4Ipv6;
  test_apn2.onc_source = ::onc::cellular_apn::kSourceUi;
  test_apn2.mojo_source = mojom::ApnSource::kUi;
  test_apn2.mojo_authentication = mojom::ApnAuthenticationType::kChap;
  test_apn2.onc_authentication = ::onc::cellular_apn::kAuthenticationChap;
  test_apn2.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn2.AsMojoApn()));

  // Verify that the API called sent the right values to Shill
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/0);
  AssertCreateCustomApnPropertiesBucketCount(
      mojom::ApnAuthenticationType::kChap,
      /*auth_type_count=*/1, mojom::ApnIpType::kIpv4Ipv6,
      /*ip_type_count=*/1, ApnTypes::kDefaultAndAttach, /*apn_types_count=*/2);
}

TEST_F(CrosNetworkConfigTest, CreateCustomApn_InvalidGuid) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const std::string guid = "invalid";
  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(guid);
  ASSERT_FALSE(custom_apns);

  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  EXPECT_FALSE(CreateCustomApn(guid, test_apn1.AsMojoApn()));

  // Verify that no values were sent to Shill
  EXPECT_EQ(0u, network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns;
    EXPECT_TRUE(CustomApnsInNetworkMetadataStoreMatch(guid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(guid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(CustomApnsInManagedPropertiesMatch(guid, expected_apns));
  }
  AssertCreateCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/1);
}

TEST_F(CrosNetworkConfigTest, RemoveCustomApn) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  ASSERT_FALSE(network_metadata_store()->GetCustomApnList(kCellularGuid));
  // Verify RemoveCustomApn reports an error and return when the
  // network_metadata_store has a nullptr custom APN list
  size_t expected_network_config_calls = 0u;
  std::string id_to_delete("apn_id_1");
  RemoveCustomApn(kCellularGuid, id_to_delete);
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  ASSERT_FALSE(network_metadata_store()->GetCustomApnList(kCellularGuid));
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/0, /*num_failure=*/1);

  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};

  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn2;
  test_apn2.name = kCellularTestApnName2;
  test_apn2.username = kCellularTestApnUsername2;
  test_apn2.password = kCellularTestApnPassword2;
  test_apn2.attach = kCellularTestApnAttach2;
  test_apn2.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};

  // Add two custom APNs using the official API
  {
    EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
    EXPECT_EQ(++expected_network_config_calls,
              network_config_observer.GetOnConfigurationModifiedCallCount());
    EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn2.AsMojoApn()));
    EXPECT_EQ(++expected_network_config_calls,
              network_config_observer.GetOnConfigurationModifiedCallCount());
  }

  // Verify that RemoveCustomApn deletes the second custom APN
  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(2u, custom_apns->size());
  const std::string* second_apn_id =
      custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);
  ASSERT_TRUE(second_apn_id);

  id_to_delete = std::string(*second_apn_id);
  RemoveCustomApn(kCellularGuid, id_to_delete);
  EXPECT_EQ(++expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/1);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kDefaultAndAttach,
                                             /*apn_types_count=*/1);

  // Try to remove an ID not found in the list, API should do nothing
  RemoveCustomApn(kCellularGuid, id_to_delete);
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/1, /*num_failure=*/2);

  // Remove the first test APN
  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  const std::string* first_apn_id =
      custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);
  ASSERT_TRUE(first_apn_id);
  id_to_delete = std::string(*first_apn_id);
  RemoveCustomApn(kCellularGuid, id_to_delete);
  EXPECT_EQ(++expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns;
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/2);
  AssertRemoveCustomApnPropertiesBucketCount(ApnTypes::kDefault,
                                             /*apn_types_count=*/1);

  // Try to delete an APN when the custom APN list is empty, it should do
  // nothing
  RemoveCustomApn(kCellularGuid, id_to_delete);
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns;
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  AssertRemoveCustomApnResultBucketCount(/*num_success=*/2, /*num_failure=*/3);
}

TEST_F(CrosNetworkConfigTest, CreateCustomApn_MaxAmountAllowed) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault, mojom::ApnType::kAttach,
                              mojom::ApnType::kTether};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach,
                             ::onc::cellular_apn::kApnTypeTether};

  // Verify that the API creates as many APNs as allowed
  for (size_t i = 0; i < mojom::kMaxNumCustomApns; ++i) {
    EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
    EXPECT_EQ(i + 1,
              network_config_observer.GetOnConfigurationModifiedCallCount());
  }
  // Verify that the next call does nothing
  EXPECT_FALSE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(mojom::kMaxNumCustomApns,
            network_config_observer.GetOnConfigurationModifiedCallCount());

  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  EXPECT_EQ(mojom::kMaxNumCustomApns, custom_apns->size());
}

TEST_F(CrosNetworkConfigTest, ModifyCustomApn) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Register an observer to capture values sent to Shill
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  ApnHistogramCounts counts;
  ASSERT_FALSE(network_metadata_store()->GetCustomApnList(kCellularGuid));
  // Verify ModifyCustomApn reports an error and return when the
  // network_metadata_store has a nullptr custom APN list
  size_t expected_network_config_calls = 0u;
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.attach = kCellularTestApnAttach1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  test_apn1.mojo_state = mojom::ApnState::kEnabled;
  test_apn1.onc_state = ::onc::cellular_apn::kStateEnabled;

  test_apn1.id = "apn_id_1";
  AssertApnHistogramCounts(counts);
  ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  ASSERT_FALSE(network_metadata_store()->GetCustomApnList(kCellularGuid));
  counts.num_modify_failure++;
  AssertApnHistogramCounts(counts);
  // Try to replace an APN when the custom APN list is empty, it should do
  // nothing
  network_metadata_store()->SetCustomApnList(kCellularGuid,
                                             base::Value::List());
  ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  EXPECT_TRUE(CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, {}));
  counts.num_modify_failure++;
  AssertApnHistogramCounts(counts);

  TestApnData test_apn2;
  test_apn2.access_point_name = kCellularTestApn2;
  test_apn2.name = kCellularTestApnName2;
  test_apn2.username = kCellularTestApnUsername2;
  test_apn2.password = kCellularTestApnPassword2;
  test_apn2.attach = kCellularTestApnAttach2;
  test_apn2.mojo_apn_types = {mojom::ApnType::kDefault,
                              mojom::ApnType::kAttach};
  test_apn2.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault,
                             ::onc::cellular_apn::kApnTypeAttach};
  test_apn2.mojo_state = mojom::ApnState::kEnabled;
  test_apn2.onc_state = ::onc::cellular_apn::kStateEnabled;

  // Add two custom APNs using the official API
  {
    EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
    EXPECT_EQ(++expected_network_config_calls,
              network_config_observer.GetOnConfigurationModifiedCallCount());
    EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn2.AsMojoApn()));
    EXPECT_EQ(++expected_network_config_calls,
              network_config_observer.GetOnConfigurationModifiedCallCount());
  }

  // Verify that ModifyCustomApn replaces the first custom APN
  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(2u, custom_apns->size());
  const std::string first_apn_id =
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  TestApnData test_apn3;
  test_apn3.access_point_name = kCellularTestApn3;
  test_apn3.name = kCellularTestApnName3;
  test_apn3.username = kCellularTestApnUsername3;
  test_apn3.password = kCellularTestApnPassword3;
  test_apn3.attach = kCellularTestApnAttach3;
  test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  test_apn3.mojo_state = mojom::ApnState::kEnabled;
  test_apn3.onc_state = ::onc::cellular_apn::kStateEnabled;

  // Verify that ModifyCustomApn does nothing if the input APN does not have an
  // ID.
  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn2, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_failure++;
  AssertApnHistogramCounts(counts);
  // Verify that ModifyCustomApn replaces the first custom APN
  test_apn3.id = first_apn_id;
  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
  EXPECT_EQ(++expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));

    counts.num_modify_success++;
    counts.num_modify_type_default_and_attach++;
    AssertApnHistogramCounts(counts);
  }

  // Try to update an ID not found in the list, API should do nothing
  test_apn3.id = "invalid_apn_id";
  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
  EXPECT_EQ(expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_failure++;
  AssertApnHistogramCounts(counts);

  // Verify that disabling a custom APN changes its ApnState to disabled and
  // logs metrics
  test_apn3.id = first_apn_id;
  test_apn3.mojo_state = mojom::ApnState::kDisabled;
  test_apn3.onc_state = ::onc::cellular_apn::kStateDisabled;
  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
  EXPECT_EQ(++expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_success++;
  counts.num_modify_type_attach++;
  counts.num_disable_success++;
  counts.num_disable_type_attach++;
  AssertApnHistogramCounts(counts);

  // Verify that enabling a custom APN changes its ApnState to enabled and
  // logs metrics
  test_apn3.id = first_apn_id;
  test_apn3.mojo_state = mojom::ApnState::kEnabled;
  test_apn3.onc_state = ::onc::cellular_apn::kStateEnabled;
  test_apn3.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
  EXPECT_EQ(++expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_success++;
  counts.num_modify_type_attach++;
  counts.num_enable_success++;
  counts.num_enable_type_attach++;
  AssertApnHistogramCounts(counts);

  // Verify that changing the APN type logs an event when changing from default
  // APN type to a different type
  test_apn3.id = first_apn_id;
  test_apn3.mojo_apn_types = {mojom::ApnType::kAttach};
  test_apn3.onc_apn_types = {::onc::cellular_apn::kApnTypeAttach};
  test_apn3.mojo_state = mojom::ApnState::kDisabled;
  test_apn3.onc_state = ::onc::cellular_apn::kStateDisabled;
  ModifyCustomApn(kCellularGuid, test_apn3.AsMojoApn());
  EXPECT_EQ(++expected_network_config_calls,
            network_config_observer.GetOnConfigurationModifiedCallCount());
  {
    std::vector<TestApnData*> expected_apns({&test_apn3, &test_apn1});
    EXPECT_TRUE(
        CustomApnsInNetworkMetadataStoreMatch(kCellularGuid, expected_apns));
    EXPECT_TRUE(CustomApnsInCellularConfigMatch(kCellularGuid, expected_apns,
                                                network_config_observer));
    EXPECT_TRUE(
        CustomApnsInManagedPropertiesMatch(kCellularGuid, expected_apns));
  }
  counts.num_modify_success++;
  counts.num_modify_type_default++;
  counts.num_disable_success++;
  counts.num_disable_type_default++;
  AssertApnHistogramCounts(counts);
}

TEST_F(CrosNetworkConfigTest,
       ApnOperationsDisallowApnModificationFlagDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list
      .InitWithFeatures(/*enabled_features=*/
                        {features::kApnRevamp},
                        /*disabled_features=*/{
                            features::kAllowApnModificationPolicy});

  // Register an observer to capture values sent to Shill.
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  // Set AllowAPNModification to false.
  SetAllowApnModification(false);

  // Create APN with kAllowApnModificationPolicy flag disabled should succeed.
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());

  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  const std::string apn_id =
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  // Modifying the APN should succeed.
  test_apn1.id = apn_id;
  ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Removing the APN should succeed.
  RemoveCustomApn(kCellularGuid, apn_id);
  EXPECT_EQ(3u, network_config_observer.GetOnConfigurationModifiedCallCount());
}

TEST_F(CrosNetworkConfigTest, ApnOperationsDisallowApnModification) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(/*enabled_features=*/
                                       {features::kApnRevamp,
                                        features::kAllowApnModificationPolicy},
                                       /*disabled_features=*/{});

  // Register an observer to capture values sent to Shill.
  TestNetworkConfigurationObserver network_config_observer(
      network_configuration_handler());

  const base::Value::List* custom_apns =
      network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_FALSE(custom_apns);

  // APN operations with AllowAPNModification unset should succeed.
  TestApnData test_apn1;
  test_apn1.access_point_name = kCellularTestApn1;
  test_apn1.name = kCellularTestApnName1;
  test_apn1.username = kCellularTestApnUsername1;
  test_apn1.password = kCellularTestApnPassword1;
  test_apn1.mojo_apn_types = {mojom::ApnType::kDefault};
  test_apn1.onc_apn_types = {::onc::cellular_apn::kApnTypeDefault};
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(1u, network_config_observer.GetOnConfigurationModifiedCallCount());

  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  std::string apn_id =
      *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  // Modifying the APN should succeed.
  test_apn1.id = apn_id;
  ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
  EXPECT_EQ(2u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Removing the APN should succeed.
  RemoveCustomApn(kCellularGuid, apn_id);
  EXPECT_EQ(3u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Set AllowAPNModification to true. Operations should succeed.
  SetAllowApnModification(true);
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(5u, network_config_observer.GetOnConfigurationModifiedCallCount());

  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  apn_id = *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  // Modifying the APN should succeed.
  test_apn1.id = apn_id;
  ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
  EXPECT_EQ(6u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Removing the APN should succeed.
  RemoveCustomApn(kCellularGuid, apn_id);
  EXPECT_EQ(7u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Add another custom APN.
  EXPECT_TRUE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(8u, network_config_observer.GetOnConfigurationModifiedCallCount());

  custom_apns = network_metadata_store()->GetCustomApnList(kCellularGuid);
  ASSERT_TRUE(custom_apns);
  ASSERT_EQ(1u, custom_apns->size());
  apn_id = *custom_apns->front().GetDict().FindString(::onc::cellular_apn::kId);

  // Set AllowAPNModification to false. Operations should not succeed and custom
  // apn list should be set to empty.
  SetAllowApnModification(false);
  EXPECT_FALSE(CreateCustomApn(kCellularGuid, test_apn1.AsMojoApn()));
  EXPECT_EQ(9u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Modifying the APN shouldn't succeed.
  test_apn1.id = apn_id;
  ModifyCustomApn(kCellularGuid, test_apn1.AsMojoApn());
  EXPECT_EQ(9u, network_config_observer.GetOnConfigurationModifiedCallCount());

  // Removing the APN shouldn't succeed.
  RemoveCustomApn(kCellularGuid, apn_id);
  EXPECT_EQ(9u, network_config_observer.GetOnConfigurationModifiedCallCount());
}

TEST_F(CrosNetworkConfigTest, ConnectedAPN_ApnRevampEnabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kApnRevamp);

  // Configure a cellular network with a last good APN and disconnected
  // as connection status
  helper()->ConfigureService(base::StringPrintf(
      kTestApnCellularShillDictFmt, kTestApnCellularGuid, shill::kStateIdle,
      kCellularTestIccid, NetworkProfileHandler::GetSharedProfilePath().c_str(),
      CreateApnShillDict().c_str()));

  // Verify the connection state
  mojom::ManagedPropertiesPtr properties =
      GetManagedProperties(kTestApnCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kTestApnCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties->is_cellular());
  mojom::ManagedCellularProperties* cellular_props =
      properties->type_properties->get_cellular().get();
  ASSERT_TRUE(cellular_props);

  // Check that last_good_apn was set, but not the connected_apn
  EXPECT_TRUE(cellular_props->last_good_apn);
  EXPECT_FALSE(cellular_props->connected_apn);

  // Simulate an update where Shill was able to connect to the cellular network
  helper()->ConfigureService(base::StringPrintf(
      kTestApnCellularShillDictFmt, kTestApnCellularGuid, shill::kStateReady,
      kCellularTestIccid, NetworkProfileHandler::GetSharedProfilePath().c_str(),
      CreateApnShillDict().c_str()));

  // Verify the new connection state
  properties = GetManagedProperties(kTestApnCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kTestApnCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kConnected,
            properties->connection_state);
  EXPECT_TRUE(properties->type_properties->is_cellular());

  // Check now that last_good_apn is set, and matches with connected_apn
  cellular_props = properties->type_properties->get_cellular().get();
  ASSERT_TRUE(cellular_props);
  EXPECT_TRUE(cellular_props->last_good_apn);
  const mojom::ApnPropertiesPtr& connected_apn = cellular_props->connected_apn;
  EXPECT_EQ(connected_apn, cellular_props->last_good_apn);
  EXPECT_EQ(kCellularTestApn1, connected_apn->access_point_name);
  EXPECT_EQ(kCellularTestApnName1, connected_apn->name);
  EXPECT_EQ(kCellularTestApnUsername1, connected_apn->username);
  EXPECT_EQ(kCellularTestApnPassword1, connected_apn->password);
  EXPECT_EQ(kCellularTestApnAttach1, connected_apn->attach);
}

TEST_F(CrosNetworkConfigTest, ConnectedAPN_ApnRevampDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(features::kApnRevamp);
  // Configure a cellular network with a last good APN and disconnected
  // as connection status
  helper()->ConfigureService(base::StringPrintf(
      kTestApnCellularShillDictFmt, kTestApnCellularGuid, shill::kStateIdle,
      kCellularTestIccid, NetworkProfileHandler::GetSharedProfilePath().c_str(),
      CreateApnShillDict().c_str()));

  // Verify the connection state
  mojom::ManagedPropertiesPtr properties =
      GetManagedProperties(kTestApnCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kTestApnCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            properties->connection_state);
  ASSERT_TRUE(properties->type_properties->is_cellular());
  mojom::ManagedCellularProperties* cellular_props =
      properties->type_properties->get_cellular().get();
  ASSERT_TRUE(cellular_props);

  // Check that last_good_apn was set, but not the connected_apn
  EXPECT_TRUE(cellular_props->last_good_apn);
  EXPECT_FALSE(cellular_props->connected_apn);

  // Simulate an update where Shill was able to connect to the cellular network
  helper()->ConfigureService(base::StringPrintf(
      kTestApnCellularShillDictFmt, kTestApnCellularGuid, shill::kStateReady,
      kCellularTestIccid, NetworkProfileHandler::GetSharedProfilePath().c_str(),
      CreateApnShillDict().c_str()));

  // Verify the new connection state
  properties = GetManagedProperties(kTestApnCellularGuid);
  ASSERT_TRUE(properties);
  EXPECT_EQ(kTestApnCellularGuid, properties->guid);
  EXPECT_EQ(mojom::NetworkType::kCellular, properties->type);
  EXPECT_EQ(mojom::ConnectionStateType::kConnected,
            properties->connection_state);
  EXPECT_TRUE(properties->type_properties->is_cellular());

  // Check that last_good_apn was set, and the connected_apn is still not set
  cellular_props = properties->type_properties->get_cellular().get();
  ASSERT_TRUE(cellular_props);
  EXPECT_TRUE(cellular_props->last_good_apn);
  EXPECT_FALSE(cellular_props->connected_apn);
}

TEST_F(CrosNetworkConfigTest, UnrecognizedAttachApnValue) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(features::kApnRevamp);
  SetupAPNList();
  const char kUnrecognizedTestApnAttachStr[] = "unrecognized attach value";

  // Verify that custom APN list is updated properly.
  auto config = mojom::ConfigProperties::New();
  auto cellular_config = mojom::CellularConfigProperties::New();
  auto new_apn = mojom::ApnProperties::New();

  new_apn->attach = kUnrecognizedTestApnAttachStr;
  cellular_config->apn = std::move(new_apn);
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));
  SetProperties(kCellularGuid, std::move(config));

  // Unrecognized values are still saved without incident.
  ASSERT_EQ(kUnrecognizedTestApnAttachStr, GetManagedProperties(kCellularGuid)
                                               ->type_properties->get_cellular()
                                               ->custom_apn_list->front()
                                               ->attach);
}

TEST_F(CrosNetworkConfigTest, AllowRoaming) {
  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kCellularGuid);

  ASSERT_FALSE(properties->type_properties->get_cellular()->allow_roaming);

  auto config = mojom::ConfigProperties::New();
  auto cellular_config = mojom::CellularConfigProperties::New();
  auto new_roaming = mojom::RoamingProperties::New();

  new_roaming->allow_roaming = true;
  cellular_config->roaming = std::move(new_roaming);
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));
  ASSERT_TRUE(SetProperties(kCellularGuid, std::move(config)));

  properties = GetManagedProperties(kCellularGuid);

  ASSERT_TRUE(properties);
  ASSERT_EQ(kCellularGuid, properties->guid);
  ASSERT_TRUE(properties->type_properties->is_cellular());
  ASSERT_TRUE(
      properties->type_properties->get_cellular()->allow_roaming->active_value);
}

TEST_F(CrosNetworkConfigTest,
       AllowTextMessagesWithSuppressTextMessagesFlagEnabled) {
  // When never set, allow_text_messages will be true.
  AssertCellularAllowTextMessages(kCellularGuid, /*expected_active_value=*/true,
                                  /*expected_policy_value=*/std::nullopt,
                                  mojom::PolicySource::kNone);

  // When text message state is set to false, the value will be updated to
  // false.
  auto config = mojom::ConfigProperties::New();
  auto cellular_config = mojom::CellularConfigProperties::New();
  auto new_text_message_state = mojom::TextMessagesAllowState::New();

  new_text_message_state->allow_text_messages = false;
  cellular_config->text_message_allow_state = std::move(new_text_message_state);
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));
  ASSERT_TRUE(SetProperties(kCellularGuid, std::move(config)));
  AssertCellularAllowTextMessages(
      kCellularGuid, /*expected_active_value=*/false,
      /*expected_policy_value=*/std::nullopt, mojom::PolicySource::kNone);

  // When text message state is undefined, this will not update the last saved
  // value of false.
  config = mojom::ConfigProperties::New();
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      mojom::CellularConfigProperties::New());
  ASSERT_TRUE(SetProperties(kCellularGuid, std::move(config)));
  AssertCellularAllowTextMessages(
      kCellularGuid, /*expected_active_value=*/false,
      /*expected_policy_value=*/std::nullopt, mojom::PolicySource::kNone);

  // When text message state is set to true, the value will be updated to true.
  config = mojom::ConfigProperties::New();
  cellular_config = mojom::CellularConfigProperties::New();
  new_text_message_state = mojom::TextMessagesAllowState::New();

  new_text_message_state->allow_text_messages = true;
  cellular_config->text_message_allow_state = std::move(new_text_message_state);
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      std::move(cellular_config));

  ASSERT_TRUE(SetProperties(kCellularGuid, std::move(config)));
  AssertCellularAllowTextMessages(kCellularGuid, /*expected_active_value=*/true,
                                  /*expected_policy_value=*/std::nullopt,
                                  mojom::PolicySource::kNone);

  // When text message state is undefined, this will not update the last saved
  // value of true.
  config = mojom::ConfigProperties::New();
  config->type_config = mojom::NetworkTypeConfigProperties::NewCellular(
      mojom::CellularConfigProperties::New());
  ASSERT_TRUE(SetProperties(kCellularGuid, std::move(config)));
  AssertCellularAllowTextMessages(kCellularGuid, /*expected_active_value=*/true,
                                  /*expected_policy_value=*/std::nullopt,
                                  mojom::PolicySource::kNone);
}

TEST_F(CrosNetworkConfigTest,
       AllowTextMessagesPolicyValueWithSuppressTextMessagesFlagEnabled) {
  base::Value::Dict global_config;

  // When the policy is explicitly Suppress, the managed boolean policy value
  // should return false and the policy source should be device enforced.
  global_config.Set(::onc::global_network_config::kAllowTextMessages,
                    ::onc::cellular::kTextMessagesSuppress);

  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();

  AssertCellularAllowTextMessages(kCellularGuid,
                                  /*expected_active_value=*/false,
                                  /*expected_policy_value=*/false,
                                  mojom::PolicySource::kDevicePolicyEnforced);

  // When the policy is explicitly Allow, the managed boolean policy value
  // should return true and the policy source should be device enforced.
  global_config.Set(::onc::global_network_config::kAllowTextMessages,
                    ::onc::cellular::kTextMessagesAllow);
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();

  AssertCellularAllowTextMessages(kCellularGuid,
                                  /*expected_active_value=*/true,
                                  /*expected_policy_value=*/true,
                                  mojom::PolicySource::kDevicePolicyEnforced);

  // When the policy is explicitly Unset, we default to the user set value.
  global_config.Set(::onc::global_network_config::kAllowTextMessages,
                    ::onc::cellular::kTextMessagesUnset);
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();

  AssertCellularAllowTextMessages(kCellularGuid,
                                  /*expected_active_value=*/true,
                                  /*expected_policy_value=*/std::nullopt,
                                  mojom::PolicySource::kNone);

  // When global network configuration is not set, we treat it as unset.
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(),
      /*global_network_config=*/base::Value::Dict());
  base::RunLoop().RunUntilIdle();

  AssertCellularAllowTextMessages(kCellularGuid,
                                  /*expected_active_value=*/true,
                                  /*expected_policy_value=*/std::nullopt,
                                  mojom::PolicySource::kNone);
}

TEST_F(CrosNetworkConfigTest, ConfigureNetwork) {
  // Note: shared = false requires a UserManager instance.
  bool shared = true;
  const std::string ssid = "new_wifi_ssid";
  // Configure a new wifi network.
  auto config = mojom::ConfigProperties::New();
  config->name = ssid;
  auto wifi = mojom::WiFiConfigProperties::New();
  wifi->ssid = ssid;
  wifi->hidden_ssid = mojom::HiddenSsidMode::kDisabled;
  config->type_config =
      mojom::NetworkTypeConfigProperties::NewWifi(std::move(wifi));
  std::string guid = ConfigureNetwork(std::move(config), shared);
  EXPECT_FALSE(guid.empty());

  // Verify the configuration.
  mojom::NetworkStatePropertiesPtr network = GetNetworkState(guid);
  ASSERT_TRUE(network);
  EXPECT_EQ(guid, network->guid);
  EXPECT_EQ(mojom::NetworkType::kWiFi, network->type);
  EXPECT_EQ(mojom::OncSource::kDevice, network->source);
  ASSERT_TRUE(network->type_state);
  ASSERT_TRUE(network->type_state->is_wifi());
  EXPECT_EQ(ssid, network->type_state->get_wifi()->ssid);
  ASSERT_FALSE(network->type_state->get_wifi()->hidden_ssid);
}

TEST_F(CrosNetworkConfigTest, ConfigureNetwork_AutomaticHiddenSSID) {
  const std::string ssid = "new_wifi_ssid";
  auto config = mojom::ConfigProperties::New();
  config->name = ssid;
  auto wifi = mojom::WiFiConfigProperties::New();
  wifi->ssid = ssid;
  wifi->hidden_ssid = mojom::HiddenSsidMode::kAutomatic;
  config->type_config =
      mojom::NetworkTypeConfigProperties::NewWifi(std::move(wifi));
  std::string guid = ConfigureNetwork(std::move(config), true);
  EXPECT_FALSE(guid.empty());

  // Verify the configuration.
  mojom::NetworkStatePropertiesPtr network = GetNetworkState(guid);
  ASSERT_TRUE(network);
  EXPECT_EQ(guid, network->guid);

  // For the purposes of this test, the wifi network is considered "in range"
  // and therefore the fake platform will not set the network to hidden.
  ASSERT_FALSE(network->type_state->get_wifi()->hidden_ssid);
}

TEST_F(CrosNetworkConfigTest, ConfigureNetworkExistingGuid) {
  // Note: shared = false requires a UserManager instance.
  bool shared = true;
  const std::string guid = "new_wifi_guid";
  const std::string ssid = "new_wifi_ssid";
  // Configure a new wifi network with an existing guid.
  auto config = mojom::ConfigProperties::New();
  config->guid = guid;
  config->name = ssid;
  auto wifi = mojom::WiFiConfigProperties::New();
  wifi->ssid = ssid;
  wifi->hidden_ssid = mojom::HiddenSsidMode::kEnabled;
  config->type_config =
      mojom::NetworkTypeConfigProperties::NewWifi(std::move(wifi));
  std::string config_guid = ConfigureNetwork(std::move(config), shared);
  // The new guid should be the same as the existing guid.
  EXPECT_EQ(config_guid, guid);

  mojom::NetworkStatePropertiesPtr network = GetNetworkState(guid);
  ASSERT_TRUE(network->type_state->get_wifi()->hidden_ssid);
}

TEST_F(CrosNetworkConfigTest, ForgetNetwork) {
  // Use a non visible configured network.
  const std::string kGUID = "wifi3_guid";

  // Verify the configuration exists.
  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kGUID);
  ASSERT_TRUE(properties);
  ASSERT_EQ(kGUID, properties->guid);

  // Forget the network and verify the configuration no longer exists.
  bool result = ForgetNetwork(kGUID);
  EXPECT_TRUE(result);
  properties = GetManagedProperties(kGUID);
  ASSERT_FALSE(properties);
}

TEST_F(CrosNetworkConfigTest, SetNetworkTypeEnabledState) {
  std::vector<mojom::DeviceStatePropertiesPtr> devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());
  EXPECT_EQ(mojom::NetworkType::kWiFi, devices[0]->type);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, devices[0]->device_state);

  // Disable WiFi
  bool succeeded = false;
  cros_network_config()->SetNetworkTypeEnabledState(
      mojom::NetworkType::kWiFi, false,
      base::BindOnce(
          [](bool* succeeded, bool success) { *succeeded = success; },
          &succeeded));
  // Wait for callback to complete; this test does not use mojo bindings.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(succeeded);
  devices = GetDeviceStateList();
  ASSERT_EQ(4u, devices.size());
  EXPECT_EQ(mojom::NetworkType::kWiFi, devices[0]->type);
  EXPECT_EQ(mojom::DeviceStateType::kDisabled, devices[0]->device_state);
}

TEST_F(CrosNetworkConfigTest, CellularInhibitState) {
  mojom::DeviceStatePropertiesPtr cellular =
      GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);

  std::unique_ptr<CellularInhibitor::InhibitLock> lock =
      InhibitCellularScanning(
          CellularInhibitor::InhibitReason::kInstallingProfile);
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_EQ(mojom::InhibitReason::kInstallingProfile, cellular->inhibit_reason);
}

TEST_F(CrosNetworkConfigTest, CellularInhibitState_Connecting) {
  const char kTestEuiccPath[] = "euicc_path";
  mojom::DeviceStatePropertiesPtr cellular =
      GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);

  // Set connect requested on cellular network.
  NetworkStateHandler* network_state_handler =
      NetworkHandler::Get()->network_state_handler();
  const NetworkState* network_state =
      network_state_handler->GetNetworkStateFromGuid(kCellularGuid);
  network_state_handler->SetNetworkConnectRequested(network_state->path(),
                                                    true);

  // Verify the inhibit state is not set when connecting if there are no EUICC.
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);

  // Verify the adding EUICC sets the inhibit reason correctly.
  helper()->hermes_manager_test()->AddEuicc(dbus::ObjectPath(kTestEuiccPath),
                                            "eid", /*is_active=*/true,
                                            /*physical_slot=*/0);
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular);
  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
  EXPECT_EQ(mojom::InhibitReason::kConnectingToProfile,
            cellular->inhibit_reason);
}

TEST_F(CrosNetworkConfigTest, SetCellularSimState) {
  // Assert initial state.
  mojom::DeviceStatePropertiesPtr cellular =
      GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular);
  ASSERT_FALSE(cellular->sim_absent);
  ASSERT_TRUE(cellular->sim_lock_status);
  ASSERT_TRUE(cellular->sim_lock_status->lock_enabled);
  ASSERT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);
  const int retries = FakeShillDeviceClient::kSimPinRetryCount;
  ASSERT_EQ(retries, cellular->sim_lock_status->retries_left);

  // Unlock the sim with the correct pin. |require_pin| should be ignored.
  EXPECT_TRUE(SetCellularSimState(FakeShillDeviceClient::kDefaultSimPin,
                                  /*new_pin=*/std::nullopt,
                                  /*require_pin=*/false));

  // Sim should be unlocked, locking should still be enabled.
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_TRUE(cellular->sim_lock_status->lock_type.empty());

  // Set |require_pin| to false (disable locking).
  EXPECT_TRUE(SetCellularSimState(FakeShillDeviceClient::kDefaultSimPin,
                                  /*new_pin=*/std::nullopt,
                                  /*require_pin=*/false));

  // Sim should be unlocked, locking should be disabled.
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_FALSE(cellular->sim_lock_status->lock_enabled);
  EXPECT_TRUE(cellular->sim_lock_status->lock_type.empty());

  // Set |require_pin| to true (enable locking).
  EXPECT_TRUE(SetCellularSimState(FakeShillDeviceClient::kDefaultSimPin,
                                  /*new_pin=*/std::nullopt,
                                  /*require_pin=*/true));

  // Sim should remain unlocked, locking should be enabled.
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_TRUE(cellular->sim_lock_status->lock_type.empty());

  // Lock the sim. (Can not be done via the mojo API).
  helper()->device_test()->SetSimLocked(kCellularDevicePath, true);
  base::RunLoop().RunUntilIdle();
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  ASSERT_TRUE(cellular->sim_lock_status->lock_enabled);
  ASSERT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);

  // Attempt to unlock the sim with an incorrect pin. Call should fail.
  EXPECT_FALSE(SetCellularSimState("incorrect pin", /*new_pin=*/std::nullopt,
                                   /*require_pin=*/false));

  // Ensure sim is still locked and retry count has decreased.
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockPin, cellular->sim_lock_status->lock_type);
  EXPECT_EQ(retries - 1, cellular->sim_lock_status->retries_left);

  // Additional attempts should set the sim to puk locked.
  for (int i = retries - 1; i > 0; --i) {
    SetCellularSimState("incorrect pin", /*new_pin=*/std::nullopt, false);
  }
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_enabled);
  EXPECT_EQ(shill::kSIMLockPuk, cellular->sim_lock_status->lock_type);

  // Attempt to unblock the sim with the incorrect puk. Call should fail.
  const std::string new_pin = "2222";
  EXPECT_FALSE(SetCellularSimState("incorrect puk", std::make_optional(new_pin),
                                   /*require_pin=*/false));

  // Attempt to unblock the sim with np pin. Call should fail.
  EXPECT_FALSE(SetCellularSimState(FakeShillDeviceClient::kSimPuk,
                                   /*new_pin=*/std::nullopt,
                                   /*require_pin=*/false));

  // Attempt to unlock the sim with the correct puk.
  EXPECT_TRUE(SetCellularSimState(FakeShillDeviceClient::kSimPuk,
                                  std::make_optional(new_pin),
                                  /*require_pin=*/false));

  // Sim should be unlocked
  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
  ASSERT_TRUE(cellular && cellular->sim_lock_status);
  EXPECT_TRUE(cellular->sim_lock_status->lock_type.empty());
}

TEST_F(CrosNetworkConfigTest, SelectCellularMobileNetwork) {
  // Create fake list of found networks.
  std::optional<base::Value> found_networks_list =
      base::JSONReader::Read(base::StringPrintf(
          R"([{"network_id": "network1", "technology": "GSM",
               "status": "current"},
              {"network_id": "network2", "technology": "GSM",
               "status": "available"}])"));
  helper()->device_test()->SetDeviceProperty(
      kCellularDevicePath, shill::kFoundNetworksProperty, *found_networks_list,
      /*notify_changed=*/true);

  // Assert initial state
  mojom::ManagedPropertiesPtr properties = GetManagedProperties(kCellularGuid);
  mojom::ManagedCellularProperties* cellular =
      properties->type_properties->get_cellular().get();
  ASSERT_TRUE(cellular);
  ASSERT_TRUE(cellular->found_networks);
  const std::vector<mojom::FoundNetworkPropertiesPtr>& found_networks1 =
      *(cellular->found_networks);
  ASSERT_EQ(2u, found_networks1.size());
  EXPECT_EQ("current", found_networks1[0]->status);
  EXPECT_EQ("available", found_networks1[1]->status);

  // Select "network2"
  EXPECT_TRUE(SelectCellularMobileNetwork(kCellularGuid, "network2"));
  properties = GetManagedProperties(kCellularGuid);
  cellular = properties->type_properties->get_cellular().get();
  ASSERT_TRUE(cellular);
  ASSERT_TRUE(cellular->found_networks);
  const std::vector<mojom::FoundNetworkPropertiesPtr>& found_networks2 =
      *(cellular->found_networks);
  ASSERT_EQ(2u, found_networks2.size());
  EXPECT_EQ("available", found_networks2[0]->status);
  EXPECT_EQ("current", found_networks2[1]->status);
}

TEST_F(CrosNetworkConfigTest, RequestNetworkScan) {
  // Observe device state list changes and track when the wifi scanning state
  // gets set to true. Note: In the test the scan will complete immediately and
  // the scanning state will get set back to false, so ignore that change.
  class ScanningObserver : public CrosNetworkConfigTestObserver {
   public:
    explicit ScanningObserver(CrosNetworkConfig* cros_network_config)
        : cros_network_config_(cros_network_config) {}
    void OnDeviceStateListChanged() override {
      cros_network_config_->GetDeviceStateList(base::BindOnce(
          [](bool* wifi_scanning,
             std::vector<mojom::DeviceStatePropertiesPtr> devices) {
            for (auto& device : devices) {
              if (device->type == mojom::NetworkType::kWiFi && device->scanning)
                *wifi_scanning = true;
            }
          },
          &wifi_scanning_));
    }
    raw_ptr<CrosNetworkConfig> cros_network_config_;
    bool wifi_scanning_ = false;
  };
  ScanningObserver observer(cros_network_config());
  cros_network_config()->AddObserver(observer.GenerateRemote());
  base::RunLoop().RunUntilIdle();

  cros_network_config()->RequestNetworkScan(mojom::NetworkType::kWiFi);
  base::RunLoop().RunUntilIdle();
  observer.FlushForTesting();
  EXPECT_TRUE(observer.wifi_scanning_);
}

TEST_F(CrosNetworkConfigTest, GetGlobalPolicy) {
  base::Value::Dict global_config;
  global_config.Set(
      ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
      true);
  global_config.Set(::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
                    false);
  base::Value::List blocked;
  blocked.Append("blocked_ssid1");
  blocked.Append("blocked_ssid2");
  global_config.Set(::onc::global_network_config::kBlockedHexSSIDs,
                    std::move(blocked));
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();
  mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
  ASSERT_TRUE(policy);
  EXPECT_TRUE(policy->allow_apn_modification);
  EXPECT_TRUE(policy->allow_cellular_sim_lock);
  EXPECT_FALSE(policy->allow_only_policy_cellular_networks);
  EXPECT_TRUE(policy->allow_only_policy_networks_to_autoconnect);
  EXPECT_FALSE(policy->allow_only_policy_wifi_networks_to_connect);
  EXPECT_FALSE(policy->allow_only_policy_wifi_networks_to_connect_if_available);
  EXPECT_FALSE(policy->dns_queries_monitored);
  EXPECT_FALSE(policy->report_xdr_events_enabled);
  ASSERT_EQ(2u, policy->blocked_hex_ssids.size());
  EXPECT_EQ("blocked_ssid1", policy->blocked_hex_ssids[0]);
  EXPECT_EQ("blocked_ssid2", policy->blocked_hex_ssids[1]);
  EXPECT_FALSE(policy->recommended_values_are_ephemeral);
  EXPECT_FALSE(policy->user_created_network_configurations_are_ephemeral);
  EXPECT_EQ(mojom::SuppressionType::kUnset, policy->allow_text_messages);
}

TEST_F(CrosNetworkConfigTest, GlobalPolicyApplied) {
  SetupObserver();
  EXPECT_EQ(0, observer()->GetPolicyAppliedCount(/*userhash=*/std::string()));

  base::Value::Dict global_config;
  global_config.Set(::onc::global_network_config::kAllowAPNModification, false);
  global_config.Set(::onc::global_network_config::kAllowCellularSimLock, false);
  global_config.Set(::onc::global_network_config::kAllowCellularHotspot, false);
  global_config.Set(
      ::onc::global_network_config::kAllowOnlyPolicyCellularNetworks, true);
  global_config.Set(::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
                    false);
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();
  mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
  ASSERT_TRUE(policy);
  EXPECT_TRUE(policy->allow_apn_modification);
  EXPECT_FALSE(policy->allow_cellular_sim_lock);
  EXPECT_FALSE(policy->allow_cellular_hotspot);
  EXPECT_TRUE(policy->allow_only_policy_cellular_networks);
  EXPECT_FALSE(policy->allow_only_policy_networks_to_autoconnect);
  EXPECT_FALSE(policy->allow_only_policy_wifi_networks_to_connect);
  EXPECT_FALSE(policy->allow_only_policy_wifi_networks_to_connect_if_available);
  EXPECT_FALSE(policy->dns_queries_monitored);
  EXPECT_FALSE(policy->report_xdr_events_enabled);
  EXPECT_FALSE(policy->recommended_values_are_ephemeral);
  EXPECT_FALSE(policy->user_created_network_configurations_are_ephemeral);
  EXPECT_EQ(mojom::SuppressionType::kUnset, policy->allow_text_messages);

  EXPECT_EQ(1, observer()->GetPolicyAppliedCount(/*userhash=*/std::string()));

  policy = GetGlobalPolicy();
  EXPECT_TRUE(policy->allow_apn_modification);

  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(/*enabled_features=*/
                                       {features::kApnRevamp,
                                        features::kAllowApnModificationPolicy},
                                       /*disabled_features=*/{});
  policy = GetGlobalPolicy();
  EXPECT_FALSE(policy->allow_apn_modification);
}

TEST_F(CrosNetworkConfigTest,
       GetGlobalPolicy_EphemeralNetworkPolicies_Disabled) {
  base::Value::Dict global_config;
  global_config.Set(
      ::onc::global_network_config::kRecommendedValuesAreEphemeral, true);
  global_config.Set(::onc::global_network_config::
                        kUserCreatedNetworkConfigurationsAreEphemeral,
                    true);
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();
  mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
  ASSERT_TRUE(policy);
  EXPECT_FALSE(policy->recommended_values_are_ephemeral);
  EXPECT_FALSE(policy->user_created_network_configurations_are_ephemeral);
}

TEST_F(CrosNetworkConfigTest,
       GetGlobalPolicy_EphemeralNetworkPolicies_Enabled) {
  policy_util::SetEphemeralNetworkPoliciesEnabled();

  base::Value::Dict global_config;
  global_config.Set(
      ::onc::global_network_config::kRecommendedValuesAreEphemeral, true);
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();
  {
    mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
    ASSERT_TRUE(policy);
    EXPECT_TRUE(policy->recommended_values_are_ephemeral);
    EXPECT_FALSE(policy->user_created_network_configurations_are_ephemeral);
  }

  global_config.Set(::onc::global_network_config::
                        kUserCreatedNetworkConfigurationsAreEphemeral,
                    true);
  managed_network_configuration_handler()->SetPolicy(
      ::onc::ONC_SOURCE_DEVICE_POLICY, /*userhash=*/std::string(),
      /*network_configs_onc=*/base::Value::List(), global_config);
  base::RunLoop().RunUntilIdle();
  {
    mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
    ASSERT_TRUE(policy);
    EXPECT_TRUE(policy->recommended_values_are_ephemeral);
    EXPECT_TRUE(policy->user_created_network_configurations_are_ephemeral);
  }
}

TEST_F(CrosNetworkConfigTest, StartConnect) {
  // wifi1 is already connected, StartConnect should fail.
  mojom::StartConnectResult result = StartConnect("wifi1_guid");
  EXPECT_EQ(mojom::StartConnectResult::kInvalidState, result);

  // wifi2 is not connected, StartConnect should succeed and connection_state
  // should change to connecting.
  mojom::NetworkStatePropertiesPtr network = GetNetworkState("wifi2_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            network->connection_state);
  result = StartConnect("wifi2_guid");
  EXPECT_EQ(mojom::StartConnectResult::kSuccess, result);
  network = GetNetworkState("wifi2_guid");
  EXPECT_EQ(mojom::ConnectionStateType::kConnecting, network->connection_state);
  // Wait for disconnect to complete.
  base::RunLoop().RunUntilIdle();
  network = GetNetworkState("wifi2_guid");
  EXPECT_EQ(mojom::ConnectionStateType::kOnline, network->connection_state);
}

TEST_F(CrosNetworkConfigTest, StartDisconnect) {
  // wifi1 is connected, StartDisconnect should succeed and connection_state
  // should change to disconnected.
  mojom::NetworkStatePropertiesPtr network = GetNetworkState("wifi1_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::ConnectionStateType::kConnected, network->connection_state);
  bool success = StartDisconnect("wifi1_guid");
  EXPECT_TRUE(success);
  // Wait for disconnect to complete.
  base::RunLoop().RunUntilIdle();
  network = GetNetworkState("wifi1_guid");
  EXPECT_EQ(mojom::ConnectionStateType::kNotConnected,
            network->connection_state);

  // wifi1 is now disconnected, StartDisconnect should fail.
  success = StartDisconnect("wifi1_guid");
  EXPECT_FALSE(success);
}

TEST_F(CrosNetworkConfigTest, VpnProviders) {
  SetupObserver();
  ASSERT_EQ(0, observer()->vpn_providers_changed());

  mojom::VpnProvider provider1(mojom::VpnType::kExtension, "provider1",
                               "provider_name1", "", base::Time());
  mojom::VpnProvider provider2(mojom::VpnType::kArc, "provider2",
                               "provider_name2", "app2", base::Time());
  std::vector<mojom::VpnProviderPtr> providers;
  providers.push_back(provider1.Clone());
  providers.push_back(provider2.Clone());
  cros_network_config()->SetVpnProviders(std::move(providers));

  std::vector<mojom::VpnProviderPtr> providers_received = GetVpnProviders();
  ASSERT_EQ(2u, providers_received.size());
  EXPECT_TRUE(providers_received[0]->Equals(provider1));
  EXPECT_TRUE(providers_received[1]->Equals(provider2));

  base::RunLoop().RunUntilIdle();  // Ensure observers run.
  ASSERT_EQ(1, observer()->vpn_providers_changed());
}

TEST_F(CrosNetworkConfigTest, NetworkCertificates) {
  SetupObserver();
  ASSERT_EQ(0, observer()->network_certificates_changed());

  std::vector<mojom::NetworkCertificatePtr> server_cas;
  std::vector<mojom::NetworkCertificatePtr> user_certs;
  GetNetworkCertificates(&server_cas, &user_certs);
  EXPECT_EQ(0u, server_cas.size());
  EXPECT_EQ(0u, user_certs.size());

  network_certificate_handler()->AddAuthorityCertificateForTest(
      "authority_cert");
  base::RunLoop().RunUntilIdle();  // Ensure observers run.
  ASSERT_EQ(1, observer()->network_certificates_changed());

  GetNetworkCertificates(&server_cas, &user_certs);
  EXPECT_EQ(1u, server_cas.size());
  EXPECT_EQ(0u, user_certs.size());
}

TEST_F(CrosNetworkConfigTest, NetworkListChanged) {
  SetupObserver();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, observer()->network_state_list_changed());

  // Add a wifi network.
  helper()->ConfigureService(
      R"({"GUID": "wifi3_guid", "Type": "wifi", "State": "ready"})");
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, observer()->network_state_list_changed());
}

TEST_F(CrosNetworkConfigTest, DeviceListChanged) {
  SetupObserver();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, observer()->device_state_list_changed());
  NetworkStateHandler* network_state_handler =
      NetworkHandler::Get()->network_state_handler();

  // Disable wifi
  NetworkHandler::Get()->technology_state_controller()->SetTechnologiesEnabled(
      NetworkTypePattern::WiFi(), false, network_handler::ErrorCallback());
  base::RunLoop().RunUntilIdle();
  // This will trigger three device list updates. First when wifi is in the
  // disabling state, next when it's actually disabled, and lastly when
  // Device::available_managed_network_path_ changes.
  EXPECT_EQ(3, observer()->device_state_list_changed());

  // Enable Tethering
  network_state_handler->SetTetherTechnologyState(
      NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(4, observer()->device_state_list_changed());

  // Tests that observers are notified of device state list change
  // when a tether scan begins for a device.
  network_state_handler->SetTetherScanState(true);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(5, observer()->device_state_list_changed());

  // Tests that observers are notified of device state list change
  // when a tether scan completes.
  network_state_handler->SetTetherScanState(false);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(6, observer()->device_state_list_changed());

  // Test that observers are notified of device state list change
  // when a cellular network connection state changes.
  const NetworkState* network_state =
      network_state_handler->GetNetworkStateFromGuid(kCellularGuid);
  network_state_handler->SetNetworkConnectRequested(network_state->path(),
                                                    /*connect_requested=*/true);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(7, observer()->device_state_list_changed());
}

TEST_F(CrosNetworkConfigTest, ActiveNetworksChanged) {
  SetupObserver();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, observer()->active_networks_changed());

  // Change a network state.
  helper()->SetServiceProperty(wifi1_path(), shill::kStateProperty,
                               base::Value(shill::kStateIdle));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, observer()->active_networks_changed());
}

TEST_F(CrosNetworkConfigTest, NetworkStateChanged) {
  SetupObserver();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, observer()->GetNetworkChangedCount("wifi1_guid"));
  EXPECT_EQ(0, observer()->GetNetworkChangedCount("wifi2_guid"));

  // Change a network state.
  helper()->SetServiceProperty(wifi1_path(), shill::kSignalStrengthProperty,
                               base::Value(10));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, observer()->GetNetworkChangedCount("wifi1_guid"));
  EXPECT_EQ(0, observer()->GetNetworkChangedCount("wifi2_guid"));
}

TEST_F(CrosNetworkConfigTest, PolicyEnforcedProxyMode) {
  // Proxies enforced by policy and/or extension are set in the kProxy
  // preference.
  user_prefs_.SetUserPref(proxy_config::prefs::kProxy,
                          ProxyConfigDictionary::CreateAutoDetect());

  mojom::NetworkStatePropertiesPtr network = GetNetworkState("wifi2_guid");
  ASSERT_TRUE(network);
  EXPECT_EQ(network->proxy_mode, mojom::ProxyMode::kAutoDetect);
}

TEST_F(CrosNetworkConfigTest, NetworkStateHasIccidAndEid) {
  const char kTestIccid[] = "iccid";
  const char kTestEid[] = "eid";

  // Add a fake eSIM network.
  SetupTestESimProfile(kTestEid, kTestIccid, "esim_service_path",
                       "test_profile_name", "test_profile_nickname");

  // Fetch the Cellular network's managed properties for the eSIM profile.
  std::string esim_guid = std::string("esim_guid") + kTestIccid;
  mojom::NetworkStatePropertiesPtr network = GetNetworkState(esim_guid);
  mojom::CellularStatePropertiesPtr& cellular =
      network->type_state->get_cellular();
  EXPECT_EQ(kTestIccid, cellular->iccid);
  EXPECT_EQ(kTestEid, cellular->eid);
}

TEST_F(CrosNetworkConfigTest, ESimManagedPropertiesNameComesFromHermes) {
  const char kTestProfileServicePath[] = "esim_service_path";
  const char kTestIccid[] = "iccid";
  const char kTestEid[] = "eid";
  const char kTestNameFromShill[] = "shill_network_name";
  const char kTestProfileName[] = "test_profile_name";
  const char kTestProfileNickname[] = "test_profile_nickname";

  // Add a fake eSIM with name kTestProfileName.
  SetupTestESimProfile(kTestEid, kTestIccid, kTestProfileServicePath,
                       kTestProfileName, kTestProfileNickname);

  // Change the network's name in Shill. Now, Hermes and Shill have different
  // names associated with the profile.
  helper()->SetServiceProperty(kTestProfileServicePath, shill::kNameProperty,
                               base::Value(kTestNameFromShill));
  base::RunLoop().RunUntilIdle();

  // Fetch the Cellular network's managed properties for the eSIM profile.
  std::string esim_guid = std::string("esim_guid") + kTestIccid;
  mojom::ManagedPropertiesPtr properties = GetManagedProperties(esim_guid);
  EXPECT_EQ(kTestProfileNickname, properties->name->active_value);
}

// Tests that the Passpoint identifier of a Wi-Fi network is reflected to its
// network state.
TEST_F(CrosNetworkConfigTest, NetworkStateHasPasspointId) {
  const char kWifiGuid[] = "wifi_pp_guid";
  const char kPasspointId[] = "passpoint_id";
  helper()->ConfigureService(base::StringPrintf(
      R"({"GUID": "%s", "Type": "wifi", "State": "idle",
          "Strength": 90, "AutoConnect": true, "Connectable": true,
          "Passpoint.ID": "%s"})",
      kWifiGuid, kPasspointId));
  mojom::NetworkStatePropertiesPtr network = GetNetworkState(kWifiGuid);
  ASSERT_TRUE(network);
  EXPECT_EQ(mojom::NetworkType::kWiFi, network->type);
  EXPECT_EQ(kPasspointId, network->type_state->get_wifi()->passpoint_id);
}

TEST_F(CrosNetworkConfigTest, GetAlwaysOnVpn) {
  mojom::AlwaysOnVpnPropertiesPtr properties;

  helper()->SetProfileProperty(helper()->ProfilePathUser(),
                               shill::kAlwaysOnVpnModeProperty,
                               base::Value("off"));
  helper()->SetProfileProperty(helper()->ProfilePathUser(),
                               shill::kAlwaysOnVpnServiceProperty,
                               base::Value(vpn_path()));
  properties = GetAlwaysOnVpn();
  EXPECT_EQ(mojom::AlwaysOnVpnMode::kOff, properties->mode);
  EXPECT_EQ("vpn_l2tp_guid", properties->service_guid);

  helper()->SetProfileProperty(helper()->ProfilePathUser(),
                               shill::kAlwaysOnVpnModeProperty,
                               base::Value("best-effort"));
  properties = GetAlwaysOnVpn();
  EXPECT_EQ(mojom::AlwaysOnVpnMode::kBestEffort, properties->mode);

  helper()->SetProfileProperty(helper()->ProfilePathUser(),
                               shill::kAlwaysOnVpnModeProperty,
                               base::Value("strict"));
  properties = GetAlwaysOnVpn();
  EXPECT_EQ(mojom::AlwaysOnVpnMode::kStrict, properties->mode);
}

TEST_F(CrosNetworkConfigTest, SetAlwaysOnVpn) {
  mojom::AlwaysOnVpnPropertiesPtr properties =
      mojom::AlwaysOnVpnProperties::New(mojom::AlwaysOnVpnMode::kBestEffort,
                                        "vpn_l2tp_guid");
  SetAlwaysOnVpn(std::move(properties));

  EXPECT_EQ("best-effort",
            helper()->GetProfileStringProperty(
                helper()->ProfilePathUser(), shill::kAlwaysOnVpnModeProperty));
  EXPECT_EQ(vpn_path(), helper()->GetProfileStringProperty(
                            helper()->ProfilePathUser(),
                            shill::kAlwaysOnVpnServiceProperty));

  properties = mojom::AlwaysOnVpnProperties::New(mojom::AlwaysOnVpnMode::kOff,
                                                 std::string());
  SetAlwaysOnVpn(std::move(properties));

  EXPECT_EQ("off",
            helper()->GetProfileStringProperty(
                helper()->ProfilePathUser(), shill::kAlwaysOnVpnModeProperty));
  EXPECT_EQ(vpn_path(), helper()->GetProfileStringProperty(
                            helper()->ProfilePathUser(),
                            shill::kAlwaysOnVpnServiceProperty));

  properties = mojom::AlwaysOnVpnProperties::New(mojom::AlwaysOnVpnMode::kOff,
                                                 "another_service");
  SetAlwaysOnVpn(std::move(properties));

  EXPECT_EQ("off",
            helper()->GetProfileStringProperty(
                helper()->ProfilePathUser(), shill::kAlwaysOnVpnModeProperty));
  EXPECT_EQ(vpn_path(), helper()->GetProfileStringProperty(
                            helper()->ProfilePathUser(),
                            shill::kAlwaysOnVpnServiceProperty));
}

TEST_F(CrosNetworkConfigTest, IsProhibitedFromConfiguringVpn) {
  arc::prefs::RegisterProfilePrefs(user_prefs_.registry());
  user_prefs_.registry()->RegisterBooleanPref(prefs::kVpnConfigAllowed, true);

  for (const std::string& package_name : {"", "package_name"}) {
      for (const bool vpn_configure_allowed : {true, false}) {
        SetArcAlwaysOnUserPrefs(package_name, vpn_configure_allowed);
        const std::string guid = ConfigureNetwork(
            CreateFakeVpnConfig("name", "host", mojom::VpnType::kArc),
            /*shared=*/true);
        if (package_name.empty() || vpn_configure_allowed) {
          EXPECT_FALSE(guid.empty());
          continue;
        }
        EXPECT_TRUE(guid.empty());
      }
  }
}

TEST_F(CrosNetworkConfigTest, RequestTrafficCountersWithIntegerType) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kTrafficCountersEnabled,
                            features::kTrafficCountersForWiFiTesting},
      /*disabled_features=*/{});
  traffic_counters::TrafficCountersHandler::InitializeForTesting();
  base::Value::List traffic_counters;

  base::Value::Dict chrome_dict;
  chrome_dict.Set("source", shill::kTrafficCounterSourceChrome);
  chrome_dict.Set("rx_bytes", 12);
  chrome_dict.Set("tx_bytes", 32);
  traffic_counters.Append(std::move(chrome_dict));

  base::Value::Dict user_dict;
  user_dict.Set("source", shill::kTrafficCounterSourceUser);
  user_dict.Set("rx_bytes", 90);
  user_dict.Set("tx_bytes", 87);
  traffic_counters.Append(std::move(user_dict));

  ASSERT_EQ(traffic_counters.size(), 2u);
  helper()->service_test()->SetFakeTrafficCounters(traffic_counters.Clone());

  RequestTrafficCountersAndCompareTrafficCounters(
      "wifi1_guid", traffic_counters, ComparisonType::INTEGER);
}

TEST_F(CrosNetworkConfigTest, RequestTrafficCountersWithDoubleType) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kTrafficCountersEnabled,
                            features::kTrafficCountersForWiFiTesting},
      /*disabled_features=*/{});
  traffic_counters::TrafficCountersHandler::InitializeForTesting();
  base::Value::List traffic_counters;

  base::Value::Dict chrome_dict;
  chrome_dict.Set("source", shill::kTrafficCounterSourceChrome);
  chrome_dict.Set("rx_bytes", 123456789987.0);
  chrome_dict.Set("tx_bytes", 3211234567898.0);
  traffic_counters.Append(std::move(chrome_dict));

  base::Value::Dict user_dict;
  user_dict.Set("source", shill::kTrafficCounterSourceUser);
  user_dict.Set("rx_bytes", 9000000000000000.0);
  user_dict.Set("tx_bytes", 8765432112345.0);
  traffic_counters.Append(std::move(user_dict));

  ASSERT_EQ(traffic_counters.size(), 2u);
  helper()->service_test()->SetFakeTrafficCounters(traffic_counters.Clone());

  RequestTrafficCountersAndCompareTrafficCounters(
      "wifi1_guid", traffic_counters, ComparisonType::DOUBLE);
}

TEST_F(CrosNetworkConfigTest, GetSupportedVpnTypes) {
  std::vector<std::string> result = GetSupportedVpnTypes();
  ASSERT_EQ(result.size(), 0u);

  helper()->manager_test()->SetManagerProperty(
      shill::kSupportedVPNTypesProperty, base::Value("l2tpipsec,openvpn"));
  result = GetSupportedVpnTypes();
  ASSERT_EQ(result.size(), 2u);

  helper()->manager_test()->SetShouldReturnNullProperties(true);
  result = GetSupportedVpnTypes();
  ASSERT_EQ(result.size(), 0u);
  helper()->manager_test()->SetShouldReturnNullProperties(false);
}

TEST_F(CrosNetworkConfigTest, SetResetDay) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kTrafficCountersEnabled,
                            features::kTrafficCountersForWiFiTesting},
      /*disabled_features=*/{});
  traffic_counters::TrafficCountersHandler::InitializeForTesting();
  SetTrafficCountersResetDayAndCompare("wifi1_guid",
                                       /*day=*/mojom::UInt32Value::New(32),
                                       /*expected_success=*/false,
                                       /*expected_reset_day=*/nullptr);
  base::Value expected_reset_day(2);
  SetTrafficCountersResetDayAndCompare("wifi1_guid",
                                       /*day=*/mojom::UInt32Value::New(2),
                                       /*expected_success=*/true,
                                       &expected_reset_day);
  // Auto reset prefs remains unchanged from last successful call.
  SetTrafficCountersResetDayAndCompare("wifi1_guid",
                                       /*day=*/mojom::UInt32Value::New(0),
                                       /*expected_success=*/false,
                                       &expected_reset_day);
}

// Make sure calling shutdown before cros_network_config destruction doesn't
// cause a crash.
TEST_F(CrosNetworkConfigTest, Shutdown) {
  SetupObserver();
  base::RunLoop().RunUntilIdle();

  NetworkHandler::Get()->network_state_handler()->Shutdown();
  NetworkHandler::Get()->managed_network_configuration_handler()->Shutdown();
}

}  // namespace ash::network_config