chromium/net/base/network_cost_change_notifier_win_unittest.cc

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

#include "net/base/network_cost_change_notifier_win.h"

#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/test/scoped_os_info_override_win.h"
#include "base/win/windows_version.h"
#include "net/base/network_change_notifier.h"
#include "net/test/test_connection_cost_observer.h"
#include "net/test/test_with_task_environment.h"
#include "net/test/win/fake_network_cost_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

class NetworkCostChangeNotifierWinTest : public TestWithTaskEnvironment {
 public:
  void SetUp() override {
    if (base::win::GetVersion() <
        NetworkCostChangeNotifierWin::kSupportedOsVersion) {
      GTEST_SKIP();
    }
  }

 protected:
  FakeNetworkCostManagerEnvironment fake_network_cost_manager_environment_;
};

TEST_F(NetworkCostChangeNotifierWinTest, InitialCostUnknown) {
  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNKNOWN);

  TestConnectionCostObserver cost_change_observer;
  auto cost_change_callback =
      base::BindRepeating(&TestConnectionCostObserver::OnConnectionCostChanged,
                          base::Unretained(&cost_change_observer));

  base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
      NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

  // Wait for `NetworkCostChangeNotifierWin` to finish initializing.
  cost_change_observer.WaitForConnectionCostChanged();

  // `NetworkCostChangeNotifierWin` must report an unknown cost after
  // initializing.
  EXPECT_EQ(cost_change_observer.cost_changed_calls(), 1u);
  EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
            NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNKNOWN);
}

TEST_F(NetworkCostChangeNotifierWinTest, InitialCostKnown) {
  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

  TestConnectionCostObserver cost_change_observer;
  auto cost_change_callback =
      base::BindRepeating(&TestConnectionCostObserver::OnConnectionCostChanged,
                          base::Unretained(&cost_change_observer));

  base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
      NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

  // Initializing changes the cost from unknown to unmetered.
  cost_change_observer.WaitForConnectionCostChanged();

  ASSERT_EQ(cost_change_observer.cost_changed_calls(), 1u);
  EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
            NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);
}

TEST_F(NetworkCostChangeNotifierWinTest, MultipleCostChangedEvents) {
  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

  TestConnectionCostObserver cost_change_observer;
  auto cost_change_callback =
      base::BindRepeating(&TestConnectionCostObserver::OnConnectionCostChanged,
                          base::Unretained(&cost_change_observer));

  base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
      NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

  // Initializing changes the cost from unknown to unmetered.
  cost_change_observer.WaitForConnectionCostChanged();

  ASSERT_EQ(cost_change_observer.cost_changed_calls(), 1u);
  EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
            NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED);

  // The simulated event changes the cost from unmetered to metered.
  cost_change_observer.WaitForConnectionCostChanged();

  ASSERT_EQ(cost_change_observer.cost_changed_calls(), 2u);
  EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
            NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED);

  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNKNOWN);

  // The simulated event changes the cost from metered to unknown.
  cost_change_observer.WaitForConnectionCostChanged();

  ASSERT_EQ(cost_change_observer.cost_changed_calls(), 3u);
  EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
            NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNKNOWN);
}

TEST_F(NetworkCostChangeNotifierWinTest, DuplicateEvents) {
  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

  TestConnectionCostObserver cost_change_observer;
  auto cost_change_callback =
      base::BindRepeating(&TestConnectionCostObserver::OnConnectionCostChanged,
                          base::Unretained(&cost_change_observer));

  base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
      NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

  // Initializing changes the cost from unknown to unmetered.
  cost_change_observer.WaitForConnectionCostChanged();
  ASSERT_EQ(cost_change_observer.cost_changed_calls(), 1u);

  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

  cost_change_observer.WaitForConnectionCostChanged();

  // Changing from unmetered to unmetered must dispatch a cost changed event.
  ASSERT_EQ(cost_change_observer.cost_changed_calls(), 2u);
  EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
            NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);
}

TEST_F(NetworkCostChangeNotifierWinTest, ShutdownImmediately) {
  TestConnectionCostObserver cost_change_observer;
  auto cost_change_callback =
      base::BindRepeating(&TestConnectionCostObserver::OnConnectionCostChanged,
                          base::Unretained(&cost_change_observer));

  base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
      NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

  // Shutting down immediately must not crash.
  cost_change_notifier.Reset();

  // Wait for `NetworkCostChangeNotifierWin` to finish initializing and shutting
  // down.
  RunUntilIdle();

  // `NetworkCostChangeNotifierWin` reports a connection change after
  // initializing.
  EXPECT_EQ(cost_change_observer.cost_changed_calls(), 1u);

  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_METERED);

  // Wait for `NetworkCostChangeNotifierWin` to handle the cost changed event.
  RunUntilIdle();

  // After shutdown, cost changed events must have no effect.
  EXPECT_EQ(cost_change_observer.cost_changed_calls(), 1u);
}

TEST_F(NetworkCostChangeNotifierWinTest, ErrorHandling) {
  // Simulate the failure of each OS API while initializing
  // `NetworkCostChangeNotifierWin`.
  constexpr const NetworkCostManagerStatus kErrorList[] = {
      NetworkCostManagerStatus::kErrorCoCreateInstanceFailed,
      NetworkCostManagerStatus::kErrorQueryInterfaceFailed,
      NetworkCostManagerStatus::kErrorFindConnectionPointFailed,
      NetworkCostManagerStatus::kErrorAdviseFailed,
      NetworkCostManagerStatus::kErrorGetCostFailed,
  };
  for (auto error : kErrorList) {
    fake_network_cost_manager_environment_.SetCost(
        NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

    fake_network_cost_manager_environment_.SimulateError(error);

    TestConnectionCostObserver cost_change_observer;
    auto cost_change_callback = base::BindRepeating(
        &TestConnectionCostObserver::OnConnectionCostChanged,
        base::Unretained(&cost_change_observer));

    base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
        NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

    if (error == NetworkCostManagerStatus::kErrorGetCostFailed) {
      // `NetworkCostChangeNotifierWin` must report an unknown cost after
      // `INetworkCostManager::GetCost()` fails.
      cost_change_observer.WaitForConnectionCostChanged();

      EXPECT_EQ(cost_change_observer.cost_changed_calls(), 1u);
      EXPECT_EQ(cost_change_observer.last_cost_changed_input(),
                NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNKNOWN);
    } else {
      // Wait for `NetworkCostChangeNotifierWin` to finish initializing.
      RunUntilIdle();

      // `NetworkCostChangeNotifierWin` must NOT report a changed cost after
      // failing to initialize.
      EXPECT_EQ(cost_change_observer.cost_changed_calls(), 0u);
    }
  }
}

TEST_F(NetworkCostChangeNotifierWinTest, UnsupportedOS) {
  base::test::ScopedOSInfoOverride os_override(
      base::test::ScopedOSInfoOverride::Type::kWinServer2016);

  fake_network_cost_manager_environment_.SetCost(
      NetworkChangeNotifier::ConnectionCost::CONNECTION_COST_UNMETERED);

  TestConnectionCostObserver cost_change_observer;
  auto cost_change_callback =
      base::BindRepeating(&TestConnectionCostObserver::OnConnectionCostChanged,
                          base::Unretained(&cost_change_observer));

  base::SequenceBound<NetworkCostChangeNotifierWin> cost_change_notifier =
      NetworkCostChangeNotifierWin::CreateInstance(cost_change_callback);

  // Wait for `NetworkCostChangeNotifierWin` to finish initializing.
  RunUntilIdle();

  // `NetworkCostChangeNotifierWin` must NOT report a changed cost for
  // unsupported OSes.
  EXPECT_EQ(cost_change_observer.cost_changed_calls(), 0u);
}

}  // namespace net