chromium/chromeos/ash/components/sync_wifi/synced_network_updater_impl_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/components/sync_wifi/synced_network_updater_impl.h"

#include <memory>
#include <optional>

#include "ash/public/cpp/network_config_service.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/dbus/shill/fake_shill_simulated_result.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/sync_wifi/fake_pending_network_configuration_tracker.h"
#include "chromeos/ash/components/sync_wifi/network_identifier.h"
#include "chromeos/ash/components/sync_wifi/network_test_helper.h"
#include "chromeos/ash/components/sync_wifi/pending_network_configuration_tracker_impl.h"
#include "chromeos/ash/components/sync_wifi/synced_network_metrics_logger.h"
#include "chromeos/ash/components/sync_wifi/test_data_generator.h"
#include "chromeos/ash/components/timer_factory/fake_timer_factory.h"
#include "chromeos/ash/services/network_config/cros_network_config.h"
#include "chromeos/ash/services/network_config/in_process_instance.h"
#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::network_config {
namespace mojom = ::chromeos::network_config::mojom;
}

namespace ash::sync_wifi {

namespace {

const char kFredSsid[] = "Fred";
const char kMangoSsid[] = "Mango";

// Because the identifier is created with `GenerateInvalidPskNetworkId`, this is
// invalid because it is an invalid hex string.
const char kInvalidSsid[] = "-423";

const NetworkState* FindLocalNetworkById(const NetworkIdentifier& id) {
  NetworkStateHandler::NetworkStateList network_list;
  NetworkHandler::Get()->network_state_handler()->GetNetworkListByType(
      NetworkTypePattern::WiFi(), /* configured_only= */ true,
      /* visible_only= */ false, /* limit= */ 0, &network_list);
  for (const NetworkState* network : network_list) {
    if (network->GetHexSsid() == id.hex_ssid() &&
        network->security_class() == id.security_type()) {
      return network;
    }
  }
  return nullptr;
}

}  // namespace

class SyncedNetworkUpdaterImplTest : public testing::Test {
 public:
  SyncedNetworkUpdaterImplTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::DEFAULT,
                          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    local_test_helper_ = std::make_unique<NetworkTestHelper>();
    ash::GetNetworkConfigService(
        remote_cros_network_config_.BindNewPipeAndPassReceiver());
  }

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

  ~SyncedNetworkUpdaterImplTest() override { local_test_helper_.reset(); }

  void SetUp() override {
    testing::Test::SetUp();
    local_test_helper_->SetUp();
    network_state_helper()->ResetDevicesAndServices();
    base::RunLoop().RunUntilIdle();

    auto tracker_unique_ptr =
        std::make_unique<FakePendingNetworkConfigurationTracker>();
    tracker_ = tracker_unique_ptr.get();
    timer_factory_ = std::make_unique<ash::timer_factory::FakeTimerFactory>();
    metrics_logger_ = std::make_unique<SyncedNetworkMetricsLogger>(
        /*network_state_handler=*/nullptr,
        /*network_connection_handler=*/nullptr);
    updater_ = std::make_unique<SyncedNetworkUpdaterImpl>(
        std::move(tracker_unique_ptr), remote_cros_network_config_.get(),
        timer_factory_.get(), metrics_logger_.get());
  }

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

  FakePendingNetworkConfigurationTracker* tracker() { return tracker_; }
  ash::timer_factory::FakeTimerFactory* timer_factory() {
    return timer_factory_.get();
  }
  SyncedNetworkUpdaterImpl* updater() { return updater_.get(); }
  NetworkStateTestHelper* network_state_helper() {
    return local_test_helper_->network_state_test_helper();
  }
  NetworkIdentifier fred_network_id() { return fred_network_id_; }
  NetworkIdentifier mango_network_id() { return mango_network_id_; }
  NetworkIdentifier invalid_network_id() { return invalid_network_id_; }

 private:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<NetworkTestHelper> local_test_helper_;
  std::unique_ptr<ash::timer_factory::FakeTimerFactory> timer_factory_;
  raw_ptr<FakePendingNetworkConfigurationTracker, DanglingUntriaged> tracker_;
  std::unique_ptr<SyncedNetworkMetricsLogger> metrics_logger_;
  std::unique_ptr<SyncedNetworkUpdaterImpl> updater_;
  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
      remote_cros_network_config_;

  NetworkIdentifier fred_network_id_ = GeneratePskNetworkId(kFredSsid);
  NetworkIdentifier mango_network_id_ = GeneratePskNetworkId(kMangoSsid);
  NetworkIdentifier invalid_network_id_ =
      GenerateInvalidPskNetworkId(kInvalidSsid);
};

TEST_F(SyncedNetworkUpdaterImplTest, TestAdd_OneNetwork) {
  base::HistogramTester histogram_tester;
  sync_pb::WifiConfigurationSpecifics specifics =
      GenerateTestWifiSpecifics(fred_network_id());
  NetworkIdentifier id = NetworkIdentifier::FromProto(specifics);
  updater()->AddOrUpdateNetwork(specifics);
  EXPECT_TRUE(tracker()->GetPendingUpdateById(id));
  base::RunLoop().RunUntilIdle();
  const NetworkState* network = FindLocalNetworkById(id);
  ASSERT_TRUE(network);
  EXPECT_FALSE(network->hidden_ssid());
  EXPECT_FALSE(tracker()->GetPendingUpdateById(id));
  EXPECT_TRUE(
      NetworkHandler::Get()->network_metadata_store()->GetIsConfiguredBySync(
          network->guid()));
  histogram_tester.ExpectBucketCount(kApplyResultHistogram, true, 1);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestUpdate_UnsetFields) {
  base::HistogramTester histogram_tester;
  sync_pb::WifiConfigurationSpecifics specifics =
      GenerateTestWifiSpecifics(fred_network_id());
  NetworkIdentifier id = NetworkIdentifier::FromProto(specifics);
  updater()->AddOrUpdateNetwork(specifics);
  EXPECT_TRUE(tracker()->GetPendingUpdateById(id));
  base::RunLoop().RunUntilIdle();
  network_config::mojom::ManagedPropertiesPtr network =
      GetManagedProperties(FindLocalNetworkById(id)->guid());
  EXPECT_TRUE(network->priority->active_value);
  EXPECT_TRUE(network->type_properties->get_wifi()->auto_connect->active_value);

  specifics.set_automatically_connect(
      sync_pb::WifiConfigurationSpecifics_AutomaticallyConnectOption::
          WifiConfigurationSpecifics_AutomaticallyConnectOption_AUTOMATICALLY_CONNECT_UNSPECIFIED);
  specifics.set_is_preferred(
      sync_pb::WifiConfigurationSpecifics_IsPreferredOption::
          WifiConfigurationSpecifics_IsPreferredOption_IS_PREFERRED_UNSPECIFIED);
  updater()->AddOrUpdateNetwork(specifics);
  EXPECT_TRUE(tracker()->GetPendingUpdateById(id));
  base::RunLoop().RunUntilIdle();
  network = GetManagedProperties(FindLocalNetworkById(id)->guid());
  // Verify that the previous values for priority and autoconnect were not
  // overwritten.
  EXPECT_TRUE(network->priority->active_value);
  EXPECT_TRUE(network->type_properties->get_wifi()->auto_connect->active_value);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestAdd_ThenRemove) {
  base::HistogramTester histogram_tester;
  EXPECT_FALSE(FindLocalNetworkById(fred_network_id()));
  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(fred_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(FindLocalNetworkById(fred_network_id()));
  updater()->RemoveNetwork(fred_network_id());
  PendingNetworkConfigurationUpdate* update =
      tracker()->GetPendingUpdateById(fred_network_id());
  EXPECT_TRUE(update);
  EXPECT_FALSE(update->specifics());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_FALSE(FindLocalNetworkById(fred_network_id()));
  histogram_tester.ExpectBucketCount(kApplyResultHistogram, true, 2);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestAdd_TwoNetworks) {
  base::HistogramTester histogram_tester;
  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(fred_network_id()));
  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(mango_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(mango_network_id()));
  base::RunLoop().RunUntilIdle();

  const NetworkState* fred_network = FindLocalNetworkById(fred_network_id());
  ASSERT_TRUE(fred_network);
  const NetworkState* mango_network = FindLocalNetworkById(mango_network_id());
  ASSERT_TRUE(mango_network);
  EXPECT_TRUE(
      NetworkHandler::Get()->network_metadata_store()->GetIsConfiguredBySync(
          fred_network->guid()));
  EXPECT_TRUE(
      NetworkHandler::Get()->network_metadata_store()->GetIsConfiguredBySync(
          mango_network->guid()));

  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_FALSE(tracker()->GetPendingUpdateById(mango_network_id()));
  histogram_tester.ExpectBucketCount(kApplyResultHistogram, true, 2);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestFailToAdd_Error) {
  base::HistogramTester histogram_tester;
  network_state_helper()->manager_test()->SetSimulateConfigurationResult(
      FakeShillSimulatedResult::kFailure);

  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(fred_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));

  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(FindLocalNetworkById(fred_network_id()));

  // The tracker should no longer be tracking the update because it reached the
  // max failed number of attempts.
  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));
  // Our test tracker holds on to the number of completed attempts after an
  // update has been removed, and that should be equal to kMaxRetries (3).
  EXPECT_EQ(3, tracker()->GetCompletedAttempts(fred_network_id()));

  histogram_tester.ExpectBucketCount(kApplyResultHistogram, false, 1);
  histogram_tester.ExpectBucketCount(
      kApplyFailureReasonHistogram, ApplyNetworkFailureReason::kFailedToAdd, 3);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestFailToAdd_Timeout) {
  base::HistogramTester histogram_tester;
  network_state_helper()->manager_test()->SetSimulateConfigurationResult(
      FakeShillSimulatedResult::kTimeout);

  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(fred_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));

  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_EQ(0, tracker()->GetCompletedAttempts(fred_network_id()));

  timer_factory()->FireAll();

  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_EQ(1, tracker()->GetCompletedAttempts(fred_network_id()));

  timer_factory()->FireAll();

  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_EQ(2, tracker()->GetCompletedAttempts(fred_network_id()));

  timer_factory()->FireAll();

  EXPECT_EQ(3, tracker()->GetCompletedAttempts(fred_network_id()));
  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));

  histogram_tester.ExpectBucketCount(kApplyResultHistogram, false, 1);
  histogram_tester.ExpectBucketCount(kApplyFailureReasonHistogram,
                                     ApplyNetworkFailureReason::kTimedout, 3);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestFailToAdd_Timeout_ThenSucceed) {
  base::HistogramTester histogram_tester;
  network_state_helper()->manager_test()->SetSimulateConfigurationResult(
      FakeShillSimulatedResult::kTimeout);

  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(fred_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));

  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_EQ(0, tracker()->GetCompletedAttempts(fred_network_id()));

  timer_factory()->FireAll();

  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_EQ(1, tracker()->GetCompletedAttempts(fred_network_id()));

  network_state_helper()->manager_test()->SetSimulateConfigurationResult(
      FakeShillSimulatedResult::kSuccess);

  timer_factory()->FireAll();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_TRUE(FindLocalNetworkById(fred_network_id()));

  histogram_tester.ExpectBucketCount(kApplyResultHistogram, false, 0);
  histogram_tester.ExpectBucketCount(kApplyResultHistogram, true, 1);
  histogram_tester.ExpectBucketCount(kApplyFailureReasonHistogram,
                                     ApplyNetworkFailureReason::kTimedout, 2);
}

TEST_F(SyncedNetworkUpdaterImplTest, TestFailToRemove) {
  base::HistogramTester histogram_tester;
  network_state_helper()->profile_test()->SetSimulateDeleteResult(
      FakeShillSimulatedResult::kFailure);
  updater()->AddOrUpdateNetwork(GenerateTestWifiSpecifics(fred_network_id()));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(FindLocalNetworkById(fred_network_id()));
  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));

  updater()->RemoveNetwork(fred_network_id());
  EXPECT_TRUE(tracker()->GetPendingUpdateById(fred_network_id()));

  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(FindLocalNetworkById(fred_network_id()));
  EXPECT_FALSE(tracker()->GetPendingUpdateById(fred_network_id()));
  EXPECT_EQ(3, tracker()->GetCompletedAttempts(fred_network_id()));

  histogram_tester.ExpectBucketCount(kApplyResultHistogram, false, 1);
  histogram_tester.ExpectBucketCount(kApplyFailureReasonHistogram,
                                     ApplyNetworkFailureReason::kFailedToRemove,
                                     3);
}

// Regression test for b/268408833
TEST_F(SyncedNetworkUpdaterImplTest, InvalidSsid) {
  base::HistogramTester histogram_tester;
  updater()->AddOrUpdateNetwork(
      GenerateTestWifiSpecifics(invalid_network_id()));

  EXPECT_EQ(0, tracker()->GetCompletedAttempts(invalid_network_id()));
  EXPECT_TRUE(tracker()->GetPendingUpdateById(invalid_network_id()));
  histogram_tester.ExpectBucketCount(kApplyGenerateLocalNetworkConfigHistogram,
                                     false, 1);
}

}  // namespace ash::sync_wifi