chromium/net/base/network_change_notifier_fuchsia_unittest.cc

// Copyright 2018 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_change_notifier_fuchsia.h"

#include <fuchsia/net/interfaces/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/auto_reset.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/threading/sequence_bound.h"
#include "base/threading/thread.h"
#include "net/base/ip_address.h"
#include "net/base/network_change_notifier.h"
#include "net/dns/dns_config_service.h"
#include "net/dns/system_dns_config_change_notifier.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace {

enum : uint32_t { kDefaultInterfaceId = 1, kSecondaryInterfaceId = 2 };

using IPv4Octets = std::array<uint8_t, 4>;
using IPv6Octets = std::array<uint8_t, 16>;

constexpr IPv4Octets kDefaultIPv4Address = {192, 168, 0, 2};
constexpr uint8_t kDefaultIPv4Prefix = 16;
constexpr IPv4Octets kSecondaryIPv4Address = {10, 0, 0, 1};
constexpr uint8_t kSecondaryIPv4Prefix = 8;

constexpr IPv6Octets kDefaultIPv6Address = {0x20, 0x01, 0x01};
constexpr uint8_t kDefaultIPv6Prefix = 16;
constexpr IPv6Octets kSecondaryIPv6Address = {0x20, 0x01, 0x02};
constexpr uint8_t kSecondaryIPv6Prefix = 16;

constexpr const char kDefaultInterfaceName[] = "net1";
constexpr const char kSecondaryInterfaceName[] = "net2";

fuchsia::net::IpAddress IpAddressFrom(IPv4Octets octets) {
  fuchsia::net::IpAddress output;
  output.ipv4().addr = octets;
  return output;
}

fuchsia::net::IpAddress IpAddressFrom(IPv6Octets octets) {
  fuchsia::net::IpAddress output;
  output.ipv6().addr = octets;
  return output;
}

template <typename T>
fuchsia::net::Subnet SubnetFrom(T octets, uint8_t prefix) {
  fuchsia::net::Subnet output;
  output.addr = IpAddressFrom(octets);
  output.prefix_len = prefix;
  return output;
}

template <typename T>
fuchsia::net::interfaces::Address InterfaceAddressFrom(T octets,
                                                       uint8_t prefix) {
  fuchsia::net::interfaces::Address addr;
  addr.set_addr(SubnetFrom(octets, prefix));
  return addr;
}

template <typename T>
std::vector<T> MakeSingleItemVec(T item) {
  std::vector<T> vec;
  vec.push_back(std::move(item));
  return vec;
}

fuchsia::net::interfaces::Properties DefaultInterfaceProperties(
    fuchsia::hardware::network::DeviceClass device_class =
        fuchsia::hardware::network::DeviceClass::ETHERNET) {
  // For most tests a live interface with an IPv4 address and ethernet class is
  // sufficient.
  fuchsia::net::interfaces::Properties interface;
  interface.set_id(kDefaultInterfaceId);
  interface.set_name(kDefaultInterfaceName);
  interface.set_online(true);
  interface.set_has_default_ipv4_route(true);
  interface.set_has_default_ipv6_route(true);
  interface.set_device_class(fuchsia::net::interfaces::DeviceClass::WithDevice(
      std::move(device_class)));
  interface.set_addresses(MakeSingleItemVec(
      InterfaceAddressFrom(kDefaultIPv4Address, kDefaultIPv4Prefix)));
  return interface;
}

fuchsia::net::interfaces::Properties SecondaryInterfaceProperties() {
  // For most tests a live interface with an IPv4 address and ethernet class is
  // sufficient.
  fuchsia::net::interfaces::Properties interface;
  interface.set_id(kSecondaryInterfaceId);
  interface.set_name(kSecondaryInterfaceName);
  interface.set_online(true);
  interface.set_has_default_ipv4_route(false);
  interface.set_has_default_ipv6_route(false);
  interface.set_device_class(fuchsia::net::interfaces::DeviceClass::WithDevice(
      fuchsia::hardware::network::DeviceClass::ETHERNET));
  interface.set_addresses(MakeSingleItemVec(
      InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
  return interface;
}

template <typename F>
fuchsia::net::interfaces::Event MakeChangeEvent(uint64_t interface_id, F fn) {
  fuchsia::net::interfaces::Properties props;
  props.set_id(interface_id);
  fn(&props);
  return fuchsia::net::interfaces::Event::WithChanged(std::move(props));
}

// Partial fake implementation of a fuchsia.net.interfaces/Watcher.
class FakeWatcher : public fuchsia::net::interfaces::testing::Watcher_TestBase {
 public:
  FakeWatcher() : binding_(this) {
    // Always create the watcher with an empty set of interfaces.
    // Callers can override the initial set of events with SetInitial.
    pending_.push(fuchsia::net::interfaces::Event::WithIdle(
        fuchsia::net::interfaces::Empty{}));
  }
  FakeWatcher(const FakeWatcher&) = delete;
  FakeWatcher& operator=(const FakeWatcher&) = delete;
  ~FakeWatcher() override = default;

  void Bind(fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> request) {
    CHECK_EQ(ZX_OK, binding_.Bind(std::move(request)));
  }

  void Unbind() { binding_.Unbind(); }

  void PushEvent(fuchsia::net::interfaces::Event event) {
    if (pending_callback_) {
      pending_callback_(std::move(event));
      pending_callback_ = nullptr;
    } else {
      pending_.push(std::move(event));
    }
  }

  void SetInitial(std::vector<fuchsia::net::interfaces::Properties> props) {
    // Discard any pending events.
    pending_ = std::queue<fuchsia::net::interfaces::Event>();
    for (auto& prop : props) {
      pending_.push(
          fuchsia::net::interfaces::Event::WithExisting(std::move(prop)));
    }
    pending_.push(fuchsia::net::interfaces::Event::WithIdle(
        fuchsia::net::interfaces::Empty{}));
    // We should not have a pending callback already when setting initial state.
    CHECK(!pending_callback_);
  }

 private:
  void Watch(WatchCallback callback) override {
    ASSERT_FALSE(pending_callback_);
    if (pending_.empty()) {
      pending_callback_ = std::move(callback);
    } else {
      callback(std::move(pending_.front()));
      pending_.pop();
    }
  }

  void NotImplemented_(const std::string& name) override {
    LOG(FATAL) << "Unimplemented function called: " << name;
  }

  std::queue<fuchsia::net::interfaces::Event> pending_;
  fidl::Binding<fuchsia::net::interfaces::Watcher> binding_;
  WatchCallback pending_callback_ = nullptr;
};

class FakeWatcherAsync {
 public:
  FakeWatcherAsync() {
    base::Thread::Options options(base::MessagePumpType::IO, 0);
    CHECK(thread_.StartWithOptions(std::move(options)));
    watcher_ = base::SequenceBound<FakeWatcher>(thread_.task_runner());
  }
  FakeWatcherAsync(const FakeWatcherAsync&) = delete;
  FakeWatcherAsync& operator=(const FakeWatcherAsync&) = delete;
  ~FakeWatcherAsync() = default;

  void Bind(fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> request) {
    watcher_.AsyncCall(&FakeWatcher::Bind).WithArgs(std::move(request));
  }

  void Unbind() { watcher_.AsyncCall(&FakeWatcher::Unbind); }

  // Asynchronously push an event to the watcher.
  void PushEvent(fuchsia::net::interfaces::Event event) {
    watcher_.AsyncCall(&FakeWatcher::PushEvent).WithArgs(std::move(event));
  }

  // Asynchronously push an initial set of interfaces to the watcher.
  void SetInitial(std::vector<fuchsia::net::interfaces::Properties> props) {
    watcher_.AsyncCall(&FakeWatcher::SetInitial).WithArgs(std::move(props));
  }

  // Asynchronously push an initial single intface to the watcher.
  void SetInitial(fuchsia::net::interfaces::Properties prop) {
    SetInitial(MakeSingleItemVec(std::move(prop)));
  }

  // Ensures that any PushEvent() or SetInitial() calls have
  // been processed.
  void FlushThread() { thread_.FlushForTesting(); }

 private:
  base::Thread thread_{"Watcher Thread"};
  base::SequenceBound<FakeWatcher> watcher_;
};

template <class T>
class ResultReceiver {
 public:
  ~ResultReceiver() { EXPECT_EQ(entries_.size(), 0u); }
  bool RunAndExpectEntries(std::vector<T> expected_entries) {
    if (entries_.size() < expected_entries.size()) {
      base::RunLoop loop;
      base::AutoReset<size_t> size(&expected_count_, expected_entries.size());
      base::AutoReset<base::OnceClosure> quit(&quit_loop_, loop.QuitClosure());
      loop.Run();
    }
    return expected_entries == std::exchange(entries_, {});
  }
  void AddEntry(T entry) {
    entries_.push_back(entry);
    if (quit_loop_ && entries_.size() >= expected_count_)
      std::move(quit_loop_).Run();
  }

 protected:
  size_t expected_count_ = 0u;
  std::vector<T> entries_;
  base::OnceClosure quit_loop_;
};

// Accumulates the list of ConnectionTypes notified via OnConnectionTypeChanged.
class FakeConnectionTypeObserver final
    : public NetworkChangeNotifier::ConnectionTypeObserver {
 public:
  FakeConnectionTypeObserver() {
    NetworkChangeNotifier::AddConnectionTypeObserver(this);
  }
  ~FakeConnectionTypeObserver() override {
    NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
  }

  bool RunAndExpectConnectionTypes(
      std::vector<NetworkChangeNotifier::ConnectionType> sequence) {
    return receiver_.RunAndExpectEntries(sequence);
  }

  // ConnectionTypeObserver implementation.
  void OnConnectionTypeChanged(
      NetworkChangeNotifier::ConnectionType type) override {
    receiver_.AddEntry(type);
  }

 protected:
  ResultReceiver<NetworkChangeNotifier::ConnectionType> receiver_;
};

// Accumulates the list of ConnectionTypes notified via OnConnectionTypeChanged.
class FakeNetworkChangeObserver final
    : public NetworkChangeNotifier::NetworkChangeObserver {
 public:
  FakeNetworkChangeObserver() {
    NetworkChangeNotifier::AddNetworkChangeObserver(this);
  }
  ~FakeNetworkChangeObserver() override {
    NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
  }

  bool RunAndExpectNetworkChanges(
      std::vector<NetworkChangeNotifier::ConnectionType> sequence) {
    return receiver_.RunAndExpectEntries(sequence);
  }

  // NetworkChangeObserver implementation.
  void OnNetworkChanged(NetworkChangeNotifier::ConnectionType type) override {
    receiver_.AddEntry(type);
  }

 protected:
  ResultReceiver<NetworkChangeNotifier::ConnectionType> receiver_;
};

// Accumulates the list of ConnectionTypes notified via OnConnectionTypeChanged.
class FakeIPAddressObserver final
    : public NetworkChangeNotifier::IPAddressObserver {
 public:
  FakeIPAddressObserver() { NetworkChangeNotifier::AddIPAddressObserver(this); }
  ~FakeIPAddressObserver() override {
    NetworkChangeNotifier::RemoveIPAddressObserver(this);
    EXPECT_EQ(ip_change_count_, 0u);
  }

  size_t ip_change_count() const { return ip_change_count_; }

  bool RunAndExpectCallCount(size_t expected_count) {
    if (ip_change_count_ < expected_count) {
      base::RunLoop loop;
      base::AutoReset<size_t> expectation(&expected_count_, expected_count);
      base::AutoReset<base::OnceClosure> quit(&quit_loop_, loop.QuitClosure());
      loop.Run();
    }
    return std::exchange(ip_change_count_, 0u) == expected_count;
  }

  // IPAddressObserver implementation.
  void OnIPAddressChanged() override {
    ip_change_count_++;
    if (quit_loop_ && ip_change_count_ >= expected_count_)
      std::move(quit_loop_).Run();
  }

 protected:
  size_t expected_count_ = 0u;
  size_t ip_change_count_ = 0u;
  base::OnceClosure quit_loop_;
};

}  // namespace

class NetworkChangeNotifierFuchsiaTest : public testing::Test {
 public:
  NetworkChangeNotifierFuchsiaTest() = default;
  NetworkChangeNotifierFuchsiaTest(const NetworkChangeNotifierFuchsiaTest&) =
      delete;
  NetworkChangeNotifierFuchsiaTest& operator=(
      const NetworkChangeNotifierFuchsiaTest&) = delete;
  ~NetworkChangeNotifierFuchsiaTest() override = default;

  // Creates a NetworkChangeNotifier that binds to |watcher_|.
  // |observer_| is registered last, so that tests need only express
  // expectations on changes they make themselves.
  void CreateNotifier(bool require_wlan = false,
                      bool disconnect_watcher = false) {
    // Ensure that internal state is up-to-date before the
    // notifier queries it.
    watcher_.FlushThread();

    fidl::InterfaceHandle<fuchsia::net::interfaces::Watcher> watcher;
    fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> watcher_request =
        watcher.NewRequest();
    if (disconnect_watcher) {
      // Reset the InterfaceRequest to close the `watcher` channel.
      watcher_request = {};
    } else {
      watcher_.Bind(std::move(watcher_request));
    }

    // Use a noop DNS notifier.
    dns_config_notifier_ = std::make_unique<SystemDnsConfigChangeNotifier>(
        nullptr /* task_runner */, nullptr /* dns_config_service */);
    notifier_ = base::WrapUnique(new NetworkChangeNotifierFuchsia(
        std::move(watcher), require_wlan, dns_config_notifier_.get()));

    type_observer_ = std::make_unique<FakeConnectionTypeObserver>();
    ip_observer_ = std::make_unique<FakeIPAddressObserver>();
  }

  void TearDown() override {
    // Spin the loops to catch any unintended notifications.
    watcher_.FlushThread();
    base::RunLoop().RunUntilIdle();
  }

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};

  FakeWatcherAsync watcher_;

  // Allows us to allocate our own NetworkChangeNotifier for unit testing.
  NetworkChangeNotifier::DisableForTest disable_for_test_;
  std::unique_ptr<SystemDnsConfigChangeNotifier> dns_config_notifier_;
  std::unique_ptr<NetworkChangeNotifierFuchsia> notifier_;

  std::unique_ptr<FakeConnectionTypeObserver> type_observer_;
  std::unique_ptr<FakeIPAddressObserver> ip_observer_;
};

TEST_F(NetworkChangeNotifierFuchsiaTest, ConnectFail_BeforeGetWatcher) {
  // CreateNotifier will pass an already-disconnected Watcher handle to the
  // new NetworkChangeNotifier, which will cause the process to exit during
  // construction.
  EXPECT_EXIT(
      CreateNotifier(/*require_wlan=*/false, /*disconnect_watcher=*/true),
      testing::ExitedWithCode(1), "");
}

TEST_F(NetworkChangeNotifierFuchsiaTest, ConnectFail_AfterGetWatcher) {
  CreateNotifier();

  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
            notifier_->GetCurrentConnectionType());

  // Disconnect the Watcher protocol in-use by the NetworkChangeNotifier.
  watcher_.Unbind();
  watcher_.FlushThread();

  // Spin the loop to process the disconnection, which should terminate the
  // test process.
  EXPECT_EXIT(base::RunLoop().RunUntilIdle(), testing::ExitedWithCode(1), "");

  // Teardown the notifier here to ensure it doesn't observe further events.
  notifier_ = nullptr;
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InitialState) {
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
            notifier_->GetCurrentConnectionType());
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfacesChangeDuringConstruction) {
  // Set a live interface with an IP address.
  watcher_.SetInitial(DefaultInterfaceProperties(
      fuchsia::hardware::network::DeviceClass::WLAN));

  // Inject an interfaces change event so that the notifier will receive it
  // immediately after the initial state.
  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        props->set_addresses(MakeSingleItemVec(
            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
      }));

  // Create the Notifier, which should process the initial network state before
  // returning, but not the change event, yet.
  CreateNotifier();
  EXPECT_EQ(ip_observer_->ip_change_count(), 0u);

  // Now spin the loop to allow the change event to be processed, triggering a
  // call to the |ip_observer_|.
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, NotifyNetworkChangeOnInitialIPChange) {
  // Set a live interface with an IP address and create the notifier.
  watcher_.SetInitial(DefaultInterfaceProperties(
      fuchsia::hardware::network::DeviceClass::WLAN));
  CreateNotifier();

  // Add the NetworkChangeNotifier, and change the IP address. This should
  // trigger a network change notification.
  FakeNetworkChangeObserver network_change_observer;

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        props->set_addresses(MakeSingleItemVec(
            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
      }));

  EXPECT_TRUE(network_change_observer.RunAndExpectNetworkChanges(
      {NetworkChangeNotifier::CONNECTION_NONE,
       NetworkChangeNotifier::CONNECTION_WIFI}));
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, NoChange) {
  // Set a live interface with an IP address and create the notifier.
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());
  // Push an event with no side-effects.
  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, NoChangeV6) {
  auto initial = DefaultInterfaceProperties();
  initial.set_addresses(MakeSingleItemVec(
      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix)));
  watcher_.SetInitial(std::move(initial));
  CreateNotifier();
  // Push an event with no side-effects.
  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, MultiInterfaceNoChange) {
  std::vector<fuchsia::net::interfaces::Properties> props;
  props.push_back(DefaultInterfaceProperties());
  props.push_back(SecondaryInterfaceProperties());
  watcher_.SetInitial(std::move(props));
  CreateNotifier();
  // Push an event with no side-effects.
  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, MultiV6IPNoChange) {
  auto props = DefaultInterfaceProperties();
  props.mutable_addresses()->push_back(
      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix));
  props.mutable_addresses()->push_back(
      InterfaceAddressFrom(kSecondaryIPv6Address, kSecondaryIPv6Prefix));

  watcher_.SetInitial(std::move(props));
  CreateNotifier();

  // Push an event with no side-effects.
  watcher_.PushEvent(MakeChangeEvent(kDefaultInterfaceId, [](auto*) {}));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, IpChange) {
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        props->set_addresses(MakeSingleItemVec(
            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix)));
      }));

  // Expect a single OnIPAddressChanged() notification.
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, IpChangeV6) {
  auto props = DefaultInterfaceProperties();
  props.set_addresses(MakeSingleItemVec(
      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix)));
  watcher_.SetInitial(std::move(props));
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        props->set_addresses(MakeSingleItemVec(
            InterfaceAddressFrom(kSecondaryIPv6Address, kSecondaryIPv6Prefix)));
      }));

  // Expect a single OnIPAddressChanged() notification.
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, MultiV6IPChanged) {
  auto props = DefaultInterfaceProperties();
  props.mutable_addresses()->push_back(
      InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix));

  watcher_.SetInitial(std::move(props));
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        std::vector<fuchsia::net::interfaces::Address> addrs;
        addrs.push_back(
            InterfaceAddressFrom(kSecondaryIPv4Address, kSecondaryIPv4Prefix));
        addrs.push_back(
            InterfaceAddressFrom(kSecondaryIPv6Address, kSecondaryIPv6Prefix));
        props->set_addresses(std::move(addrs));
      }));

  // Expect a single OnIPAddressChanged() notification.
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, Ipv6AdditionalIpChange) {
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        // Add the initial default address + a new IPv6 one. Address changes are
        // always sent as the entire new list of addresses.
        props->mutable_addresses()->push_back(
            InterfaceAddressFrom(kDefaultIPv4Address, kDefaultIPv4Prefix));
        props->mutable_addresses()->push_back(
            InterfaceAddressFrom(kDefaultIPv6Address, kDefaultIPv6Prefix));
      }));

  // Expect a single OnIPAddressChanged() notification.
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceDown) {
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        props->set_online(false);
      }));

  EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
      {NetworkChangeNotifier::ConnectionType::CONNECTION_NONE}));
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceUp) {
  auto props = DefaultInterfaceProperties();
  props.set_online(false);
  watcher_.SetInitial(std::move(props));
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(MakeChangeEvent(
      kDefaultInterfaceId, [](fuchsia::net::interfaces::Properties* props) {
        props->set_online(true);
      }));

  EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
      {NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET}));
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceDeleted) {
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(
      fuchsia::net::interfaces::Event::WithRemoved(kDefaultInterfaceId));

  EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
      {NetworkChangeNotifier::ConnectionType::CONNECTION_NONE}));
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, InterfaceAdded) {
  // Initial interface list is intentionally left empty.
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
            notifier_->GetCurrentConnectionType());

  watcher_.PushEvent(
      fuchsia::net::interfaces::Event::WithAdded(DefaultInterfaceProperties(
          fuchsia::hardware::network::DeviceClass::WLAN)));

  EXPECT_TRUE(type_observer_->RunAndExpectConnectionTypes(
      {NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI}));
  EXPECT_TRUE(ip_observer_->RunAndExpectCallCount(1));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, SecondaryInterfaceAddedNoop) {
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier();

  watcher_.PushEvent(fuchsia::net::interfaces::Event::WithAdded(
      SecondaryInterfaceProperties()));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, SecondaryInterfaceDeletedNoop) {
  std::vector<fuchsia::net::interfaces::Properties> interfaces;
  interfaces.push_back(DefaultInterfaceProperties());
  interfaces.push_back(SecondaryInterfaceProperties());

  watcher_.SetInitial(std::move(interfaces));
  CreateNotifier();

  watcher_.PushEvent(
      fuchsia::net::interfaces::Event::WithRemoved(kSecondaryInterfaceId));
}

TEST_F(NetworkChangeNotifierFuchsiaTest, FoundWiFi) {
  watcher_.SetInitial(DefaultInterfaceProperties(
      fuchsia::hardware::network::DeviceClass::WLAN));
  CreateNotifier();
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
            notifier_->GetCurrentConnectionType());
}

TEST_F(NetworkChangeNotifierFuchsiaTest, FindsInterfaceWithRequiredWlan) {
  watcher_.SetInitial(DefaultInterfaceProperties(
      fuchsia::hardware::network::DeviceClass::WLAN));
  CreateNotifier(/*require_wlan=*/true);
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI,
            notifier_->GetCurrentConnectionType());
}

TEST_F(NetworkChangeNotifierFuchsiaTest, IgnoresNonWlanInterface) {
  watcher_.SetInitial(DefaultInterfaceProperties());
  CreateNotifier(/*require_wlan=*/true);
  EXPECT_EQ(NetworkChangeNotifier::ConnectionType::CONNECTION_NONE,
            notifier_->GetCurrentConnectionType());
}

}  // namespace net