chromium/chromeos/ash/components/network/network_configuration_handler_unittest.cc

// Copyright 2012 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/components/network/network_configuration_handler.h"

#include <stddef.h>

#include <map>
#include <optional>
#include <set>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/network_configuration_observer.h"
#include "chromeos/ash/components/network/network_profile_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"
#include "chromeos/ash/components/network/shill_property_util.h"
#include "chromeos/ash/components/network/tether_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace ash {

namespace {

// Copies the result of GetProperties(). TODO: Use base::TestFuture.
void CopyProperties(bool* called,
                    std::string* service_path_out,
                    std::optional<base::Value::Dict>* result_out,
                    const std::string& service_path,
                    std::optional<base::Value::Dict> result) {
  *called = true;
  *service_path_out = service_path;
  *result_out = std::move(result);
}

// Copies service_path and guid returned for CreateShillConfiguration. TODO: Use
// base::TestFuture.
void CopyServiceResult(bool* called,
                       std::string* service_path_out,
                       std::string* guid_out,
                       const std::string& service_path,
                       const std::string& guid) {
  *called = true;
  *service_path_out = service_path;
  *guid_out = guid;
}

void ErrorCallback(const std::string& error_name) {
  ADD_FAILURE() << "Unexpected error: " << error_name;
}

void RecordError(std::string* error_name_ptr, const std::string& error_name) {
  *error_name_ptr = error_name;
}

class TestCallback {
 public:
  TestCallback() = default;
  void Run() { ++run_count_; }
  int run_count() const { return run_count_; }

 private:
  int run_count_ = 0;
};

class TestNetworkConfigurationObserver : public NetworkConfigurationObserver {
 public:
  TestNetworkConfigurationObserver() = default;

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

  // NetworkConfigurationObserver
  void OnConfigurationCreated(const std::string& service_path,
                              const std::string& guid) override {
    created_configurations_[service_path] = guid;
  }

  void OnBeforeConfigurationRemoved(const std::string& service_path,
                                    const std::string& guid) override {
    ASSERT_FALSE(base::Contains(before_remove_configurations_, service_path));
    before_remove_configurations_[service_path] = guid;
  }

  void OnConfigurationRemoved(const std::string& service_path,
                              const std::string& guid) override {
    ASSERT_FALSE(base::Contains(removed_configurations_, service_path));
    removed_configurations_[service_path] = guid;
  }

  void OnConfigurationModified(
      const std::string& service_path,
      const std::string& guid,
      const base::Value::Dict* set_properties) override {
    updated_configurations_[service_path] = guid;
  }

  bool HasCreatedConfiguration(const std::string& service_path) {
    return base::Contains(created_configurations_, service_path);
  }

  bool HasCalledBeforeRemoveConfiguration(const std::string& service_path) {
    return base::Contains(before_remove_configurations_, service_path);
  }

  bool HasRemovedConfiguration(const std::string& service_path) {
    return base::Contains(removed_configurations_, service_path);
  }

  bool HasUpdatedConfiguration(const std::string& service_path) {
    return base::Contains(updated_configurations_, service_path);
  }

 private:
  std::map<std::string, std::string> created_configurations_;
  std::map<std::string, std::string> before_remove_configurations_;
  std::map<std::string, std::string> removed_configurations_;
  std::map<std::string, std::string> updated_configurations_;
};

class TestNetworkStateHandlerObserver : public NetworkStateHandlerObserver {
 public:
  TestNetworkStateHandlerObserver() = default;

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

  // Returns the number of NetworkListChanged() call.
  size_t network_list_changed_count() const {
    return network_list_changed_count_;
  }

  // Returns the number of NetworkPropertiesUpdated() call for the
  // given |service_path|.
  int PropertyUpdatesForService(const std::string& service_path) const {
    auto iter = property_updates_.find(service_path);
    return iter == property_updates_.end() ? 0 : iter->second;
  }

  // NetworkStateHandlerObserver overrides:
  void NetworkListChanged() override { ++network_list_changed_count_; }
  void NetworkPropertiesUpdated(const NetworkState* network) override {
    property_updates_[network->path()]++;
  }

 private:
  size_t network_list_changed_count_ = 0;
  std::map<std::string, int> property_updates_;
};

}  // namespace

class NetworkConfigurationHandlerTest : public testing::Test {
 public:
  NetworkConfigurationHandlerTest() {
    shill_clients::InitializeFakes();

    network_state_handler_ = NetworkStateHandler::InitializeForTest();
    // Note: NetworkConfigurationHandler's contructor is private, so
    // std::make_unique cannot be used.
    network_configuration_handler_.reset(new NetworkConfigurationHandler());
    network_configuration_handler_->Init(network_state_handler_.get(),
                                         nullptr /* network_device_handler */);
    base::RunLoop().RunUntilIdle();
    network_state_handler_observer_ =
        std::make_unique<TestNetworkStateHandlerObserver>();
    network_state_handler_->AddObserver(network_state_handler_observer_.get());
  }

  ~NetworkConfigurationHandlerTest() override {
    network_state_handler_->Shutdown();
    network_state_handler_->RemoveObserver(
        network_state_handler_observer_.get());
    network_state_handler_observer_.reset();
    network_configuration_handler_.reset();
    network_state_handler_.reset();

    shill_clients::Shutdown();
  }

  void SuccessCallback(const std::string& callback_name) {
    success_callback_name_ = callback_name;
  }

  void GetPropertiesCallback(const std::string& service_path,
                             std::optional<base::Value::Dict> dictionary) {
    get_properties_path_ = service_path;
    if (dictionary)
      get_properties_ = std::move(*dictionary);
  }

  void ManagerGetPropertiesCallback(const std::string& success_callback_name,
                                    std::optional<base::Value::Dict> result) {
    if (result)
      success_callback_name_ = success_callback_name;
    manager_get_properties_ = std::move(result);
  }

  void CreateConfigurationCallback(const std::string& service_path,
                                   const std::string& guid) {
    create_service_path_ = service_path;
  }

  void CreateTestConfiguration(const std::string& service_path,
                               const std::string& type) {
    base::Value::Dict properties;
    shill_property_util::SetSSID(service_path, &properties);
    properties.Set(shill::kNameProperty, service_path);
    properties.Set(shill::kGuidProperty, service_path);
    properties.Set(shill::kTypeProperty, type);
    properties.Set(shill::kStateProperty, shill::kStateIdle);
    properties.Set(shill::kProfileProperty,
                   NetworkProfileHandler::GetSharedProfilePath());

    network_configuration_handler_->CreateShillConfiguration(
        std::move(properties),
        base::BindOnce(
            &NetworkConfigurationHandlerTest::CreateConfigurationCallback,
            base::Unretained(this)),
        base::BindOnce(&ErrorCallback));
    base::RunLoop().RunUntilIdle();
  }

  // Creates two profiles "profile1" and "profile2", and two services
  // "/service/1" and "/service/2", and ties for four combinations.
  // "/service/2"'s current profile is "profile2".
  void SetUpRemovableConfiguration() {
    // Create two profiles.
    GetShillProfileClient()->AddProfile("profile1", "abcde");
    GetShillProfileClient()->AddProfile("profile2", "vwxyz");

    // Create two services.
    GetShillServiceClient()->AddService(
        "/service/1", std::string() /* guid */, std::string() /* name */,
        "wifi", std::string() /* state */, true /* visible */);
    GetShillServiceClient()->AddService(
        "/service/2", std::string() /* guid */, std::string() /* name */,
        "wifi", std::string() /* state */, true /* visible */);

    // Register "/service/2" to "profile2".
    GetShillProfileClient()->AddService("profile2", "/service/2");

    // Tie profiles and services.
    const base::Value::Dict* service_properties_1 =
        GetShillServiceClient()->GetServiceProperties("/service/1");
    const base::Value::Dict* service_properties_2 =
        GetShillServiceClient()->GetServiceProperties("/service/2");
    ASSERT_TRUE(service_properties_1);
    ASSERT_TRUE(service_properties_2);
    GetShillProfileClient()->AddEntry("profile1", "/service/1",
                                      *service_properties_1);
    GetShillProfileClient()->AddEntry("profile1", "/service/2",
                                      *service_properties_2);
    GetShillProfileClient()->AddEntry("profile2", "/service/1",
                                      *service_properties_1);
    GetShillProfileClient()->AddEntry("profile2", "/service/2",
                                      *service_properties_2);
    base::RunLoop().RunUntilIdle();
  }

 protected:
  bool GetServiceStringProperty(const std::string& service_path,
                                const std::string& key,
                                std::string* result) {
    ShillServiceClient::TestInterface* service_test =
        ShillServiceClient::Get()->GetTestInterface();
    const base::Value::Dict* properties =
        service_test->GetServiceProperties(service_path);
    if (!properties)
      return false;
    const std::string* value = properties->FindString(key);
    if (!value)
      return false;
    *result = *value;
    return true;
  }

  bool GetReceivedStringProperty(const std::string& service_path,
                                 const std::string& key,
                                 std::string* result) {
    if (get_properties_path_ != service_path || !get_properties_.has_value()) {
      return false;
    }
    const std::string* value = get_properties_->FindString(key);
    if (!value) {
      return false;
    }
    *result = *value;
    return true;
  }

  bool GetReceivedStringManagerProperty(const std::string& key,
                                        std::string* result) {
    if (!manager_get_properties_) {
      return false;
    }
    const std::string* value = manager_get_properties_->FindString(key);
    if (!value) {
      return false;
    }
    *result = *value;
    return true;
  }

  ShillServiceClient::TestInterface* GetShillServiceClient() {
    return ShillServiceClient::Get()->GetTestInterface();
  }

  ShillProfileClient::TestInterface* GetShillProfileClient() {
    return ShillProfileClient::Get()->GetTestInterface();
  }

  std::unique_ptr<NetworkStateHandler> network_state_handler_;
  std::unique_ptr<NetworkConfigurationHandler> network_configuration_handler_;
  std::unique_ptr<TestNetworkStateHandlerObserver>
      network_state_handler_observer_;
  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
  std::string success_callback_name_;
  std::string get_properties_path_;
  std::optional<base::Value::Dict> get_properties_;
  std::optional<base::Value::Dict> manager_get_properties_;
  std::string create_service_path_;
};

TEST_F(NetworkConfigurationHandlerTest, GetProperties) {
  constexpr char kServicePath[] = "/service/1";
  constexpr char kNetworkName[] = "MyName";
  GetShillServiceClient()->AddService(
      kServicePath, std::string() /* guid */, kNetworkName, shill::kTypeWifi,
      std::string() /* state */, true /* visible */);

  bool success = false;
  std::string service_path;
  std::optional<base::Value::Dict> result;
  network_configuration_handler_->GetShillProperties(
      kServicePath,
      base::BindOnce(&CopyProperties, &success, &service_path, &result));
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(success);
  EXPECT_EQ(kServicePath, service_path);
  const std::string* ssid = result->FindString(shill::kSSIDProperty);
  ASSERT_TRUE(ssid);
  EXPECT_EQ(kNetworkName, *ssid);
}

TEST_F(NetworkConfigurationHandlerTest, GetProperties_TetherNetwork) {
  constexpr char kTetherGuid[] = "TetherGuid";
  constexpr char kTetherNetworkName[] = "TetherNetworkName";
  constexpr char kTetherNetworkCarrier[] = "TetherNetworkCarrier";
  constexpr int kBatteryPercentage = 100;
  constexpr int kSignalStrength = 100;
  constexpr bool kHasConnectedToHost = true;

  network_state_handler_->SetTetherTechnologyState(
      NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
  network_state_handler_->AddTetherNetworkState(
      kTetherGuid, kTetherNetworkName, kTetherNetworkCarrier,
      kBatteryPercentage, kSignalStrength, kHasConnectedToHost);

  bool success = false;
  std::string service_path;
  std::optional<base::Value::Dict> result;
  network_configuration_handler_->GetShillProperties(
      // Tether networks use service path and GUID interchangeably.
      kTetherGuid,
      base::BindOnce(&CopyProperties, &success, &service_path, &result));
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(success);
  const std::string* guid = result->FindString(shill::kGuidProperty);
  ASSERT_TRUE(guid);
  EXPECT_EQ(kTetherGuid, *guid);
  const std::string* name = result->FindString(shill::kNameProperty);
  ASSERT_TRUE(name);
  EXPECT_EQ(kTetherNetworkName, *name);
  std::optional<int> battery_percentage =
      result->FindInt(kTetherBatteryPercentage);
  ASSERT_TRUE(battery_percentage);
  EXPECT_EQ(kBatteryPercentage, *battery_percentage);
  const std::string* carrier = result->FindString(kTetherCarrier);
  ASSERT_TRUE(carrier);
  EXPECT_EQ(kTetherNetworkCarrier, *carrier);
  std::optional<bool> has_connected_to_host =
      result->FindBool(kTetherHasConnectedToHost);
  ASSERT_TRUE(has_connected_to_host);
  EXPECT_TRUE(*has_connected_to_host);
  std::optional<int> signal_strength = result->FindInt(kTetherSignalStrength);
  ASSERT_TRUE(signal_strength);
  EXPECT_EQ(kSignalStrength, *signal_strength);
}

TEST_F(NetworkConfigurationHandlerTest, SetProperties) {
  constexpr char kServicePath[] = "/service/1";
  constexpr char kNetworkName[] = "MyNetwork";

  GetShillServiceClient()->AddService(
      kServicePath, std::string() /* guid */, std::string() /* name */,
      shill::kTypeWifi, std::string() /* state */, true /* visible */);

  base::Value::Dict value;
  value.Set(shill::kSSIDProperty, kNetworkName);
  network_configuration_handler_->SetShillProperties(
      kServicePath, std::move(value), base::DoNothing(),
      base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  const base::Value::Dict* properties =
      GetShillServiceClient()->GetServiceProperties(kServicePath);
  ASSERT_TRUE(properties);
  const std::string* ssid = properties->FindString(shill::kSSIDProperty);
  ASSERT_TRUE(ssid);
  EXPECT_EQ(kNetworkName, *ssid);
}

TEST_F(NetworkConfigurationHandlerTest, ClearProperties) {
  constexpr char kServicePath[] = "/service/1";
  constexpr char kNetworkName[] = "MyNetwork";

  // Set up a value to be cleared.
  GetShillServiceClient()->AddService(
      kServicePath, std::string() /* guid */, kNetworkName, shill::kTypeWifi,
      std::string() /* state */, true /* visible */);

  // Now clear it.
  std::vector<std::string> names = {shill::kSSIDProperty};
  network_configuration_handler_->ClearShillProperties(
      kServicePath, names, base::DoNothing(), base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  const base::Value::Dict* properties =
      GetShillServiceClient()->GetServiceProperties(kServicePath);
  ASSERT_TRUE(properties);
  const std::string* ssid = properties->FindString(shill::kSSIDProperty);
  EXPECT_FALSE(ssid);
}

TEST_F(NetworkConfigurationHandlerTest, ClearProperties_Error) {
  constexpr char kServicePath[] = "/service/1";
  constexpr char kNetworkName[] = "MyNetwork";

  GetShillServiceClient()->AddService(
      kServicePath, std::string() /* guid */, kNetworkName, shill::kTypeWifi,
      std::string() /* state */, true /* visible */);

  // Now clear it. Even for unknown property removal (i.e. fail to clear it),
  // the whole ClearShillProperties() should succeed.
  std::vector<std::string> names = {"Unknown name"};
  network_configuration_handler_->ClearShillProperties(
      kServicePath, names, base::DoNothing(), base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();
}

TEST_F(NetworkConfigurationHandlerTest, CreateConfiguration) {
  constexpr char kGuid[] = "/service/2";
  constexpr char kNetworkName[] = "MyNetwork";

  base::Value::Dict value;
  shill_property_util::SetSSID(kNetworkName, &value);
  value.Set(shill::kTypeProperty, "wifi");
  value.Set(shill::kProfileProperty, "profile path");
  value.Set(shill::kGuidProperty, kGuid);

  bool success = false;
  std::string service_path;
  std::string guid;
  network_configuration_handler_->CreateShillConfiguration(
      std::move(value),
      base::BindOnce(&CopyServiceResult, &success, &service_path, &guid),
      base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(success);
  EXPECT_EQ(service_path,
            GetShillServiceClient()->FindServiceMatchingGUID(kGuid));
  EXPECT_EQ(guid, kGuid);
}

TEST_F(NetworkConfigurationHandlerTest, RemoveConfiguration) {
  ASSERT_NO_FATAL_FAILURE(SetUpRemovableConfiguration());

  TestCallback test_callback;
  network_configuration_handler_->RemoveConfiguration(
      "/service/2", /*remove_confirmer=*/std::nullopt,
      base::BindOnce(&TestCallback::Run, base::Unretained(&test_callback)),
      base::BindOnce(&ErrorCallback));

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, test_callback.run_count());

  std::vector<std::string> profiles;
  // "/service/1" should not be affected.
  GetShillProfileClient()->GetProfilePathsContainingService("/service/1",
                                                            &profiles);
  EXPECT_EQ(2u, profiles.size());
  profiles.clear();
  // "/service/2" should be removed from both profiles.
  GetShillProfileClient()->GetProfilePathsContainingService("/service/2",
                                                            &profiles);
  EXPECT_TRUE(profiles.empty());
}

TEST_F(NetworkConfigurationHandlerTest, RemoveConfigurationFromCurrentProfile) {
  ASSERT_NO_FATAL_FAILURE(SetUpRemovableConfiguration());

  TestCallback test_callback;
  network_configuration_handler_->RemoveConfigurationFromCurrentProfile(
      "/service/2",
      base::BindOnce(&TestCallback::Run, base::Unretained(&test_callback)),
      base::BindOnce(&ErrorCallback));

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, test_callback.run_count());

  std::vector<std::string> profiles;
  // "/service/1" should not be affected.
  GetShillProfileClient()->GetProfilePathsContainingService("/service/1",
                                                            &profiles);
  EXPECT_EQ(2u, profiles.size());
  profiles.clear();
  // "/service/2" should be removed only from the current profile.
  GetShillProfileClient()->GetProfilePathsContainingService("/service/2",
                                                            &profiles);
  EXPECT_EQ(1u, profiles.size());
}

TEST_F(NetworkConfigurationHandlerTest, RemoveConfigurationWithPredicate) {
  ASSERT_NO_FATAL_FAILURE(SetUpRemovableConfiguration());

  // Don't remove profile 1 entries.
  NetworkConfigurationHandler::RemoveConfirmer remove_confirmer =
      base::BindRepeating(
          [](const std::string& guid, const std::string& profile_path) {
            return profile_path != "profile1";
          });

  TestCallback test_callback1;
  network_configuration_handler_->RemoveConfiguration(
      "/service/1", remove_confirmer,
      base::BindOnce(&TestCallback::Run, base::Unretained(&test_callback1)),
      base::BindOnce(&ErrorCallback));

  TestCallback test_callback2;
  network_configuration_handler_->RemoveConfiguration(
      "/service/2", remove_confirmer,
      base::BindOnce(&TestCallback::Run, base::Unretained(&test_callback2)),
      base::BindOnce(&ErrorCallback));

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(1, test_callback1.run_count());
  EXPECT_EQ(1, test_callback2.run_count());

  std::vector<std::string> profilesForService1, profilesForService2;
  GetShillProfileClient()->GetProfilePathsContainingService(
      "/service/1", &profilesForService1);
  GetShillProfileClient()->GetProfilePathsContainingService(
      "/service/2", &profilesForService2);

  EXPECT_EQ(1u, profilesForService1.size());
  EXPECT_EQ("profile1", profilesForService1[0]);
  profilesForService1.clear();

  EXPECT_EQ(1u, profilesForService2.size());
  EXPECT_EQ("profile1", profilesForService2[0]);
  profilesForService2.clear();
}

TEST_F(NetworkConfigurationHandlerTest,
       RemoveNonExistentConfigurationFromCurrentProfile) {
  ASSERT_NO_FATAL_FAILURE(SetUpRemovableConfiguration());

  TestCallback test_callback;
  std::string error;
  network_configuration_handler_->RemoveConfigurationFromCurrentProfile(
      "/service/3",
      base::BindOnce(&TestCallback::Run, base::Unretained(&test_callback)),
      base::BindOnce(&RecordError, base::Unretained(&error)));

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, test_callback.run_count());
  // Should report an error for unknown configuration.
  EXPECT_EQ("NetworkNotConfigured", error);
}

TEST_F(NetworkConfigurationHandlerTest, StubSetAndClearProperties) {
  // TODO(stevenjb): Remove dependency on default Stub service.
  const std::string service_path("/service/wifi1");
  const std::string test_check_portal("auto");
  const std::string test_passphrase("test_passphrase");

  // Set Properties
  base::Value::Dict properties_to_set;
  properties_to_set.Set(shill::kCheckPortalProperty, test_check_portal);
  properties_to_set.Set(shill::kPassphraseProperty, test_passphrase);
  network_configuration_handler_->SetShillProperties(
      service_path, std::move(properties_to_set),
      base::BindOnce(&NetworkConfigurationHandlerTest::SuccessCallback,
                     base::Unretained(this), "SetProperties"),
      base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ("SetProperties", success_callback_name_);
  std::string check_portal, passphrase;
  EXPECT_TRUE(GetServiceStringProperty(
      service_path, shill::kCheckPortalProperty, &check_portal));
  EXPECT_TRUE(GetServiceStringProperty(service_path, shill::kPassphraseProperty,
                                       &passphrase));
  EXPECT_EQ(test_check_portal, check_portal);
  EXPECT_EQ(test_passphrase, passphrase);
  EXPECT_EQ(1, network_state_handler_observer_->PropertyUpdatesForService(
                   service_path));

  // Clear Properties
  std::vector<std::string> properties_to_clear;
  properties_to_clear.push_back(shill::kCheckPortalProperty);
  properties_to_clear.push_back(shill::kPassphraseProperty);
  network_configuration_handler_->ClearShillProperties(
      service_path, properties_to_clear,
      base::BindOnce(&NetworkConfigurationHandlerTest::SuccessCallback,
                     base::Unretained(this), "ClearProperties"),
      base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ("ClearProperties", success_callback_name_);
  EXPECT_FALSE(GetServiceStringProperty(
      service_path, shill::kCheckPortalProperty, &check_portal));
  EXPECT_FALSE(GetServiceStringProperty(
      service_path, shill::kPassphraseProperty, &passphrase));
  EXPECT_EQ(2, network_state_handler_observer_->PropertyUpdatesForService(
                   service_path));
}

TEST_F(NetworkConfigurationHandlerTest, StubGetNameFromWifiHex) {
  // TODO(stevenjb): Remove dependency on default Stub service.
  const std::string service_path("/service/wifi1");
  std::string wifi_hex = "5468697320697320484558205353494421";
  std::string expected_name = "This is HEX SSID!";

  // Set Properties
  base::Value::Dict properties_to_set;
  properties_to_set.Set(shill::kWifiHexSsid, wifi_hex);
  network_configuration_handler_->SetShillProperties(
      service_path, std::move(properties_to_set), base::DoNothing(),
      base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();
  std::string wifi_hex_result;
  EXPECT_TRUE(GetServiceStringProperty(service_path, shill::kWifiHexSsid,
                                       &wifi_hex_result));
  EXPECT_EQ(wifi_hex, wifi_hex_result);

  // Get Properties
  network_configuration_handler_->GetShillProperties(
      service_path,
      base::BindOnce(&NetworkConfigurationHandlerTest::GetPropertiesCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(service_path, get_properties_path_);
  std::string name_result;
  EXPECT_TRUE(GetReceivedStringProperty(service_path, shill::kNameProperty,
                                        &name_result));
  EXPECT_EQ(expected_name, name_result);
}

TEST_F(NetworkConfigurationHandlerTest, StubCreateConfiguration) {
  const std::string service_path("/service/test_wifi");
  CreateTestConfiguration(service_path, shill::kTypeWifi);

  EXPECT_FALSE(create_service_path_.empty());

  std::string guid;
  EXPECT_TRUE(GetServiceStringProperty(create_service_path_,
                                       shill::kGuidProperty, &guid));
  EXPECT_EQ(service_path, guid);

  std::string actual_profile;
  EXPECT_TRUE(GetServiceStringProperty(
      create_service_path_, shill::kProfileProperty, &actual_profile));
  EXPECT_EQ(NetworkProfileHandler::GetSharedProfilePath(), actual_profile);
}

TEST_F(NetworkConfigurationHandlerTest, NetworkConfigurationObserver_Added) {
  const std::string service_path("/service/test_wifi");

  auto network_configuration_observer =
      std::make_unique<TestNetworkConfigurationObserver>();
  network_configuration_handler_->AddObserver(
      network_configuration_observer.get());
  CreateTestConfiguration(service_path, shill::kTypeWifi);

  EXPECT_TRUE(network_configuration_observer->HasCreatedConfiguration(
      create_service_path_));

  network_configuration_handler_->RemoveObserver(
      network_configuration_observer.get());
}

TEST_F(NetworkConfigurationHandlerTest, NetworkConfigurationObserver_Removed) {
  const std::string service_path("/service/test_wifi");

  auto network_configuration_observer =
      std::make_unique<TestNetworkConfigurationObserver>();
  network_configuration_handler_->AddObserver(
      network_configuration_observer.get());
  CreateTestConfiguration(service_path, shill::kTypeWifi);

  EXPECT_FALSE(network_configuration_observer->HasRemovedConfiguration(
      create_service_path_));
  EXPECT_FALSE(
      network_configuration_observer->HasCalledBeforeRemoveConfiguration(
          create_service_path_));

  network_configuration_handler_->RemoveConfiguration(
      create_service_path_, /*remove_confirmer=*/std::nullopt,
      base::DoNothing(), base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(network_configuration_observer->HasRemovedConfiguration(
      create_service_path_));
  EXPECT_TRUE(
      network_configuration_observer->HasCalledBeforeRemoveConfiguration(
          create_service_path_));

  network_configuration_handler_->RemoveObserver(
      network_configuration_observer.get());
}

TEST_F(NetworkConfigurationHandlerTest, NetworkConfigurationObserver_Updated) {
  const std::string service_path("/service/test_wifi");

  auto network_configuration_observer =
      std::make_unique<TestNetworkConfigurationObserver>();
  network_configuration_handler_->AddObserver(
      network_configuration_observer.get());
  CreateTestConfiguration(service_path, shill::kTypeWifi);

  EXPECT_FALSE(
      network_configuration_observer->HasUpdatedConfiguration(service_path));

  base::Value::Dict properties;
  properties.Set(shill::kSecurityClassProperty, shill::kSecurityClassPsk);
  properties.Set(shill::kPassphraseProperty, "secret");

  network_configuration_handler_->SetShillProperties(
      create_service_path_, std::move(properties), base::DoNothing(),
      base::BindOnce(&ErrorCallback));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(network_configuration_observer->HasUpdatedConfiguration(
      create_service_path_));

  network_configuration_handler_->RemoveObserver(
      network_configuration_observer.get());
}

TEST_F(NetworkConfigurationHandlerTest, AlwaysOnVpn) {
  const std::string vpn_package = "com.android.vpn";

  network_configuration_handler_->SetManagerProperty(
      shill::kAlwaysOnVpnPackageProperty, base::Value(vpn_package));

  ShillManagerClient::Get()->GetProperties(
      BindOnce(&NetworkConfigurationHandlerTest::ManagerGetPropertiesCallback,
               base::Unretained(this), "ManagerGetProperties"));
  base::RunLoop().RunUntilIdle();
  std::string package_result;
  EXPECT_EQ("ManagerGetProperties", success_callback_name_);
  EXPECT_TRUE(GetReceivedStringManagerProperty(
      shill::kAlwaysOnVpnPackageProperty, &package_result));
  EXPECT_EQ(vpn_package, package_result);
}

}  // namespace ash