chromium/chrome/browser/ash/nearby/nearby_process_manager_impl_unittest.cc

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

#include "chrome/browser/ash/nearby/nearby_process_manager_impl.h"

#include <memory>
#include <utility>

#include "ash/public/cpp/network_config_service.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "chrome/browser/ash/nearby/bluetooth_adapter_manager.h"
#include "chrome/browser/ash/nearby/nearby_dependencies_provider.h"
#include "chrome/browser/ash/nearby/nearby_process_manager_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/services/sharing/nearby/test_support/fake_adapter.h"
#include "chrome/services/sharing/nearby/test_support/fake_nearby_presence_credential_storage.h"
#include "chrome/services/sharing/nearby/test_support/mock_webrtc_dependencies.h"
#include "chromeos/ash/services/nearby/public/cpp/fake_firewall_hole_factory.h"
#include "chromeos/ash/services/nearby/public/cpp/fake_mdns_manager.h"
#include "chromeos/ash/services/nearby/public/cpp/fake_nearby_presence.h"
#include "chromeos/ash/services/nearby/public/cpp/fake_tcp_socket_factory.h"
#include "chromeos/ash/services/nearby/public/cpp/mock_nearby_connections.h"
#include "chromeos/ash/services/nearby/public/cpp/mock_nearby_sharing_decoder.h"
#include "chromeos/ash/services/nearby/public/cpp/mock_quick_start_decoder.h"
#include "chromeos/ash/services/nearby/public/mojom/firewall_hole.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/mdns.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_connections.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_decoder.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_presence.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/sharing.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/tcp_socket_factory.mojom.h"
#include "chromeos/ash/services/nearby/public/mojom/webrtc.mojom.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 "components/account_id/account_id.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

// Noop implementation for tests.
class FakeWifiDirectManager
    : public ash::wifi_direct::mojom::WifiDirectManager {
  // ash::wifi_direct::mojom::WifiDirectManager
  void CreateWifiDirectGroup(
      ash::wifi_direct::mojom::WifiCredentialsPtr credentials,
      CreateWifiDirectGroupCallback callback) override {
    // Noop
  }
  void ConnectToWifiDirectGroup(
      ash::wifi_direct::mojom::WifiCredentialsPtr credentials,
      std::optional<uint32_t> frequency,
      ConnectToWifiDirectGroupCallback callback) override {
    // Noop
  }
  void GetWifiP2PCapabilities(
      GetWifiP2PCapabilitiesCallback callback) override {
    // Noop
  }
};

}  // namespace

namespace ash {
namespace nearby {
namespace {

class FakeSharingMojoService : public sharing::mojom::Sharing {
 public:
  FakeSharingMojoService() = default;
  ~FakeSharingMojoService() override = default;

  bool AreMocksSet() const { return mock_connections_ && mock_decoder_; }

  mojo::PendingRemote<sharing::mojom::Sharing> BindSharingService() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void Reset() {
    receiver_.reset();
    mock_connections_.reset();
    mock_decoder_.reset();
    mock_quick_start_decoder_.reset();
  }

 private:
  // mojom::Sharing:
  void Connect(
      sharing::mojom::NearbyDependenciesPtr deps,
      mojo::PendingReceiver<NearbyConnectionsMojom> connections_receiver,
      mojo::PendingReceiver<NearbyPresenceMojom> presence_receiver,
      mojo::PendingReceiver<sharing::mojom::NearbySharingDecoder>
          decoder_receiver,
      mojo::PendingReceiver<ash::quick_start::mojom::QuickStartDecoder>
          quick_start_decoder_receiver) override {
    EXPECT_FALSE(mock_connections_);
    EXPECT_FALSE(fake_presence_);
    EXPECT_FALSE(mock_decoder_);
    EXPECT_FALSE(mock_quick_start_decoder_);

    mock_connections_ = std::make_unique<MockNearbyConnections>();
    mock_connections_->BindInterface(std::move(connections_receiver));

    fake_presence_ = std::make_unique<presence::FakeNearbyPresence>();
    fake_presence_->BindInterface(std::move(presence_receiver));

    mock_decoder_ = std::make_unique<MockNearbySharingDecoder>();
    mock_decoder_->BindInterface(std::move(decoder_receiver));

    mock_quick_start_decoder_ = std::make_unique<MockQuickStartDecoder>();
    mock_quick_start_decoder_->BindInterface(
        std::move(quick_start_decoder_receiver));
  }

  void ShutDown(ShutDownCallback callback) override {
    mock_connections_.reset();
    fake_presence_.reset();
    mock_decoder_.reset();
    mock_quick_start_decoder_.reset();
    std::move(callback).Run();
  }

  std::unique_ptr<MockNearbyConnections> mock_connections_;
  std::unique_ptr<presence::FakeNearbyPresence> fake_presence_;
  std::unique_ptr<MockNearbySharingDecoder> mock_decoder_;
  std::unique_ptr<MockQuickStartDecoder> mock_quick_start_decoder_;
  mojo::Receiver<sharing::mojom::Sharing> receiver_{this};
};

}  // namespace

class NearbyProcessManagerImplTest : public testing::Test {
 public:
  class FakeNearbyDependenciesProvider : public NearbyDependenciesProvider {
   public:
    FakeNearbyDependenciesProvider() = default;
    ~FakeNearbyDependenciesProvider() override = default;

    // NearbyDependenciesProvider:
    sharing::mojom::NearbyDependenciesPtr GetDependencies() override {
      fake_adapter_ = std::make_unique<bluetooth::FakeAdapter>();
      fake_nearby_presence_credential_storage_ =
          std::make_unique<presence::FakeNearbyPresenceCredentialStorage>();
      webrtc_dependencies_ =
          std::make_unique<sharing::MockWebRtcDependencies>();

      // Set up CrosNetworkConfig mojo service.
      mojo::PendingRemote<chromeos::network_config::mojom::CrosNetworkConfig>
          cros_network_config_remote;
      ash::GetNetworkConfigService(
          cros_network_config_remote.InitWithNewPipeAndPassReceiver());

      // Set up firewall hole factory mojo service.
      mojo::PendingRemote<sharing::mojom::FirewallHoleFactory>
          firewall_hole_factory_remote;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<ash::nearby::FakeFirewallHoleFactory>(),
          firewall_hole_factory_remote.InitWithNewPipeAndPassReceiver());

      // Set up TCP socket factory mojo service.
      mojo::PendingRemote<sharing::mojom::TcpSocketFactory>
          tcp_socket_factory_remote;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<ash::nearby::FakeTcpSocketFactory>(
              /*default_local_addr=*/net::IPEndPoint(
                  net::IPAddress(192, 168, 86, 75), 44444)),
          tcp_socket_factory_remote.InitWithNewPipeAndPassReceiver());

      // Set up Mdns Manager mojo service.
      mojo::PendingRemote<sharing::mojom::MdnsManager> mdns_manager_remote;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<ash::nearby::FakeMdnsManager>(),
          mdns_manager_remote.InitWithNewPipeAndPassReceiver());

      // Set up fake WiFiDirect mojo services.
      mojo::PendingRemote<ash::wifi_direct::mojom::WifiDirectManager>
          wifi_direct_manager_remote;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<FakeWifiDirectManager>(),
          wifi_direct_manager_remote.InitWithNewPipeAndPassReceiver());
      mojo::PendingRemote<::sharing::mojom::FirewallHoleFactory>
          wifi_direct_firewall_hole_factory_remote;
      mojo::MakeSelfOwnedReceiver(
          std::make_unique<ash::nearby::FakeFirewallHoleFactory>(),
          wifi_direct_firewall_hole_factory_remote
              .InitWithNewPipeAndPassReceiver());

      return sharing::mojom::NearbyDependencies::New(
          fake_adapter_->adapter_.BindNewPipeAndPassRemote(),
          sharing::mojom::WebRtcDependencies::New(
              webrtc_dependencies_->socket_manager_.BindNewPipeAndPassRemote(),
              webrtc_dependencies_->mdns_responder_factory_
                  .BindNewPipeAndPassRemote(),
              webrtc_dependencies_->ice_config_fetcher_
                  .BindNewPipeAndPassRemote(),
              webrtc_dependencies_->messenger_.BindNewPipeAndPassRemote()),
          sharing::mojom::WifiLanDependencies::New(
              std::move(cros_network_config_remote),
              std::move(firewall_hole_factory_remote),
              std::move(tcp_socket_factory_remote),
              std::move(mdns_manager_remote)),
          ::sharing::mojom::WifiDirectDependencies::New(
              std::move(wifi_direct_manager_remote),
              std::move(wifi_direct_firewall_hole_factory_remote)),
          fake_nearby_presence_credential_storage_->receiver()
              .BindNewPipeAndPassRemote(),
          ::nearby::api::LogMessage::Severity::kInfo);
    }

    void PrepareForShutdown() override { prepare_for_shutdown_count_++; }

    int prepare_for_shutdown_count() { return prepare_for_shutdown_count_; }

   private:
    network_config::CrosNetworkConfigTestHelper
        cros_network_config_test_helper_;
    std::unique_ptr<bluetooth::FakeAdapter> fake_adapter_;
    std::unique_ptr<presence::FakeNearbyPresenceCredentialStorage>
        fake_nearby_presence_credential_storage_;
    std::unique_ptr<sharing::MockWebRtcDependencies> webrtc_dependencies_;
    int prepare_for_shutdown_count_ = 0;
  };

  NearbyProcessManagerImplTest() = default;
  ~NearbyProcessManagerImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
    mock_timer_ = mock_timer.get();

    nearby_process_manager_ = base::WrapUnique(new NearbyProcessManagerImpl(
        &fake_deps_provider_, std::move(mock_timer),
        base::BindRepeating(&FakeSharingMojoService::BindSharingService,
                            base::Unretained(&fake_sharing_mojo_service_))));
  }

  std::unique_ptr<NearbyProcessManager::NearbyProcessReference>
  CreateReference() {
    auto reference = nearby_process_manager_->GetNearbyProcessReference(
        base::BindOnce(&NearbyProcessManagerImplTest::OnProcessStopped,
                       base::Unretained(this)));
    GetImpl()->sharing_.FlushForTesting();
    return reference;
  }

  FakeSharingMojoService* fake_sharing_mojo_service() {
    return &fake_sharing_mojo_service_;
  }

  size_t num_process_stopped_calls() const {
    return num_process_stopped_calls_;
  }

  FakeNearbyDependenciesProvider* fake_deps_provider() {
    return &fake_deps_provider_;
  }

  void VerifyBound(
      const NearbyProcessManager::NearbyProcessReference* reference) {
    EXPECT_TRUE(GetImpl()->sharing_.is_bound());
    EXPECT_TRUE(reference->GetNearbyConnections().is_bound());
    EXPECT_TRUE(reference->GetNearbyPresence().is_bound());
    EXPECT_TRUE(reference->GetNearbySharingDecoder().is_bound());
    EXPECT_TRUE(reference->GetQuickStartDecoder().is_bound());
    EXPECT_TRUE(fake_sharing_mojo_service_.AreMocksSet());
  }

  void VerifyNotBound() {
    EXPECT_FALSE(GetImpl()->sharing_.is_bound());
    EXPECT_FALSE(fake_sharing_mojo_service_.AreMocksSet());
  }

  bool IsTimerRunning() const { return mock_timer_->IsRunning(); }

  void FireTimer() { mock_timer_->Fire(); }

 private:
  NearbyProcessManagerImpl* GetImpl() {
    return static_cast<NearbyProcessManagerImpl*>(
        nearby_process_manager_.get());
  }

  void OnProcessStopped(NearbyProcessManager::NearbyProcessShutdownReason) {
    ++num_process_stopped_calls_;
  }

  const base::test::TaskEnvironment task_environment_;
  size_t num_process_stopped_calls_ = 0u;

  FakeSharingMojoService fake_sharing_mojo_service_;
  FakeNearbyDependenciesProvider fake_deps_provider_;

  std::unique_ptr<NearbyProcessManager> nearby_process_manager_;

  raw_ptr<base::MockOneShotTimer> mock_timer_ = nullptr;
};

TEST_F(NearbyProcessManagerImplTest, StartAndStop) {
  std::unique_ptr<NearbyProcessManager::NearbyProcessReference> reference =
      CreateReference();
  VerifyBound(reference.get());

  reference.reset();
  FireTimer();
  base::RunLoop().RunUntilIdle();
  VerifyNotBound();
  EXPECT_EQ(0u, num_process_stopped_calls());
  EXPECT_EQ(1, fake_deps_provider()->prepare_for_shutdown_count());

  // Reset, then repeat process to verify it still works with multiple tries.
  fake_sharing_mojo_service()->Reset();

  reference = CreateReference();
  VerifyBound(reference.get());

  reference.reset();
  FireTimer();
  base::RunLoop().RunUntilIdle();
  VerifyNotBound();
  EXPECT_EQ(0u, num_process_stopped_calls());
  EXPECT_EQ(2, fake_deps_provider()->prepare_for_shutdown_count());
}

TEST_F(NearbyProcessManagerImplTest, MultipleReferences) {
  std::unique_ptr<NearbyProcessManager::NearbyProcessReference> reference1 =
      CreateReference();
  std::unique_ptr<NearbyProcessManager::NearbyProcessReference> reference2 =
      CreateReference();
  VerifyBound(reference1.get());
  VerifyBound(reference2.get());

  // Deleting one reference should still keep the other reference bound.
  reference1.reset();
  EXPECT_FALSE(IsTimerRunning());
  base::RunLoop().RunUntilIdle();
  VerifyBound(reference2.get());

  reference2.reset();
  FireTimer();
  base::RunLoop().RunUntilIdle();
  VerifyNotBound();
  EXPECT_EQ(0u, num_process_stopped_calls());
  EXPECT_EQ(1, fake_deps_provider()->prepare_for_shutdown_count());
}

TEST_F(NearbyProcessManagerImplTest, ProcessStopped) {
  std::unique_ptr<NearbyProcessManager::NearbyProcessReference> reference =
      CreateReference();
  VerifyBound(reference.get());

  // Reset, then wait for the disconnection to propagate.
  fake_sharing_mojo_service()->Reset();
  base::RunLoop().RunUntilIdle();

  VerifyNotBound();
  EXPECT_EQ(1u, num_process_stopped_calls());
  EXPECT_FALSE(IsTimerRunning());
}

TEST_F(NearbyProcessManagerImplTest,
       NewReferenceObtainedWhileWaitingToShutDown) {
  std::unique_ptr<NearbyProcessManager::NearbyProcessReference> reference =
      CreateReference();
  VerifyBound(reference.get());

  // Delete the reference; the timer should be running so that the process is
  // shut down after the cleanup timeout.
  reference.reset();
  EXPECT_TRUE(IsTimerRunning());

  // Obtain a new reference; the timer should have stopped.
  reference = CreateReference();
  VerifyBound(reference.get());
  EXPECT_FALSE(IsTimerRunning());

  // Delete the reference and let the timer fire to shut down the process.
  reference.reset();
  FireTimer();
  base::RunLoop().RunUntilIdle();
  VerifyNotBound();
  EXPECT_EQ(0u, num_process_stopped_calls());
}

}  // namespace nearby
}  // namespace ash