chromium/ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl_unittest.cc

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

#include "ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner_impl.h"

#include <memory>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/quick_pair/common/constants.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/pair_failure.h"
#include "ash/quick_pair/repository/fake_fast_pair_repository.h"
#include "ash/quick_pair/scanning/fast_pair/fake_fast_pair_scanner.h"
#include "ash/quick_pair/scanning/fast_pair/fast_pair_discoverable_scanner.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/services/quick_pair/fast_pair_data_parser.h"
#include "chromeos/ash/services/quick_pair/mock_quick_pair_process_manager.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process_manager.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process_manager_impl.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/floss/floss_features.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

constexpr char kValidModelId[] = "718c17";
const std::string kAddress = "test_address";

class FakeQuickPairProcessManager
    : public ash::quick_pair::QuickPairProcessManager {
 public:
  FakeQuickPairProcessManager(
      base::test::SingleThreadTaskEnvironment* task_environment)
      : task_environment_(task_environment) {
    data_parser_ = std::make_unique<ash::quick_pair::FastPairDataParser>(
        fast_pair_data_parser_.InitWithNewPipeAndPassReceiver());

    data_parser_remote_.Bind(std::move(fast_pair_data_parser_),
                             task_environment_->GetMainThreadTaskRunner());
  }

  ~FakeQuickPairProcessManager() override = default;

  std::unique_ptr<ProcessReference> GetProcessReference(
      ProcessStoppedCallback on_process_stopped_callback) override {
    on_process_stopped_callback_ = std::move(on_process_stopped_callback);

    if (process_stopped_) {
      std::move(on_process_stopped_callback_)
          .Run(
              ash::quick_pair::QuickPairProcessManager::ShutdownReason::kCrash);
    }

    return std::make_unique<
        ash::quick_pair::QuickPairProcessManagerImpl::ProcessReferenceImpl>(
        data_parser_remote_, base::DoNothing());
  }

  void SetProcessStopped(bool process_stopped) {
    process_stopped_ = process_stopped;
  }

 private:
  bool process_stopped_ = false;
  mojo::SharedRemote<ash::quick_pair::mojom::FastPairDataParser>
      data_parser_remote_;
  mojo::PendingRemote<ash::quick_pair::mojom::FastPairDataParser>
      fast_pair_data_parser_;
  std::unique_ptr<ash::quick_pair::FastPairDataParser> data_parser_;
  raw_ptr<base::test::SingleThreadTaskEnvironment> task_environment_;
  ProcessStoppedCallback on_process_stopped_callback_;
};

}  // namespace

namespace ash {
namespace quick_pair {

class FastPairDiscoverableScannerImplTest : public testing::Test {
 public:
  void SetUp() override {
    NetworkHandler::Initialize();
    repository_ = std::make_unique<FakeFastPairRepository>();

    nearby::fastpair::Device metadata;
    metadata.set_trigger_distance(2);
    metadata.set_device_type(
        nearby::fastpair::DeviceType::TRUE_WIRELESS_HEADPHONES);

    nearby::fastpair::Status* status = metadata.mutable_status();
    status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);

    repository_->SetFakeMetadata(kValidModelId, metadata);

    scanner_ = base::MakeRefCounted<FakeFastPairScanner>();

    adapter_ =
        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();

    process_manager_ =
        std::make_unique<FakeQuickPairProcessManager>(&task_environment_);
    quick_pair_process::SetProcessManager(process_manager_.get());
    fake_process_manager_ =
        static_cast<FakeQuickPairProcessManager*>(process_manager_.get());

    discoverable_scanner_ = std::make_unique<FastPairDiscoverableScannerImpl>(
        scanner_, adapter_, found_device_callback_.Get(),
        lost_device_callback_.Get());
  }

  void TearDown() override {
    process_manager_.reset();
    testing::Test::TearDown();
    discoverable_scanner_.reset();
    NetworkHandler::Shutdown();
  }

  MockQuickPairProcessManager* mock_process_manager() {
    return static_cast<MockQuickPairProcessManager*>(process_manager_.get());
  }

 protected:
  device::BluetoothDevice* GetDevice(const std::string& hex_model_id,
                                     bool is_paired = false) {
    auto device = std::make_unique<device::MockBluetoothDevice>(
        adapter_.get(), 0, "test_name", kAddress, /*paired=*/is_paired,
        /*connected=*/false);

    if (!hex_model_id.empty()) {
      std::vector<uint8_t> model_id_bytes;
      base::HexStringToBytes(hex_model_id, &model_id_bytes);
      device->SetServiceDataForUUID(kFastPairBluetoothUuid, model_id_bytes);
    }

    device::BluetoothDevice* device_ptr = device.get();

    adapter_->AddMockDevice(std::move(device));
    ON_CALL(*adapter_, GetDevice(kAddress))
        .WillByDefault(testing::Return(device_ptr));

    return device_ptr;
  }

  raw_ptr<FakeQuickPairProcessManager, DanglingUntriaged> fake_process_manager_;
  base::test::SingleThreadTaskEnvironment task_environment_;
  NetworkStateTestHelper helper_{/*use_default_devices_and_services=*/true};
  scoped_refptr<FakeFastPairScanner> scanner_;
  std::unique_ptr<FakeFastPairRepository> repository_;
  std::unique_ptr<FastPairDiscoverableScannerImpl> discoverable_scanner_;
  std::unique_ptr<FastPairDiscoverableScanner> discoverable_scanner2_;
  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
  std::unique_ptr<QuickPairProcessManager> process_manager_;
  base::MockCallback<DeviceCallback> found_device_callback_;
  base::MockCallback<DeviceCallback> lost_device_callback_;
};

TEST_F(FastPairDiscoverableScannerImplTest,
       UtilityProcessStopped_FailedAllRetryAttempts) {
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  fake_process_manager_->SetProcessStopped(true);
  scanner_->NotifyDeviceFound(device);
}

TEST_F(FastPairDiscoverableScannerImplTest, UtilityProcessStopped_DeviceLost) {
  auto device = std::make_unique<device::MockBluetoothDevice>(
      adapter_.get(), 0, "test_name", kAddress, /*paired=*/false,
      /*connected=*/false);
  device->SetServiceDataForUUID(kFastPairBluetoothUuid, {1, 2, 3});

  device::BluetoothDevice* device_ptr = device.get();

  adapter_->AddMockDevice(std::move(device));
  ON_CALL(*adapter_, GetDevice(kAddress))
      .WillByDefault(testing::Return(nullptr));

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  fake_process_manager_->SetProcessStopped(true);
  scanner_->NotifyDeviceFound(device_ptr);
}

TEST_F(FastPairDiscoverableScannerImplTest, ValidModelId_FactoryCreate) {
  discoverable_scanner_.reset();
  std::unique_ptr<FastPairDiscoverableScanner>
      discoverable_scanner_from_factory =
          FastPairDiscoverableScannerImpl::Factory::Create(
              scanner_, adapter_, found_device_callback_.Get(),
              lost_device_callback_.Get());

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, NoServiceData) {
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  std::unique_ptr<device::BluetoothDevice> device =
      base::WrapUnique(static_cast<device::BluetoothDevice*>(
          new testing::NiceMock<device::MockBluetoothDevice>(
              adapter_.get(), 0, "test_name", "test_address",
              /*paired=*/false,
              /*connected=*/false)));

  scanner_->NotifyDeviceFound(device.get());
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, NoModelIdDataInRepository) {
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  auto device = std::make_unique<device::MockBluetoothDevice>(
      adapter_.get(), 0, "test_name", kAddress, /*paired=*/false,
      /*connected=*/false);
  device->SetServiceDataForUUID(kFastPairBluetoothUuid, {1, 2, 3});
  device::BluetoothDevice* device_ptr = device.get();

  adapter_->AddMockDevice(std::move(device));
  ON_CALL(*adapter_, GetDevice(kAddress))
      .WillByDefault(testing::Return(device_ptr));

  scanner_->NotifyDeviceFound(device_ptr);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, NoMetadata) {
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice("");
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, ValidModelId) {
  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, WrongDeviceType) {
  nearby::fastpair::Device metadata;
  metadata.set_trigger_distance(2);
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_device_type(nearby::fastpair::DeviceType::AUTOMOTIVE);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, UnspecifiedNotificationType) {
  // Set metadata to mimic a device that doesn't specify the notification type,
  // interaction type, or device type. Since we aren't sure what this device is,
  // we'll show the notification to be safe.
  nearby::fastpair::Device metadata;
  metadata.set_trigger_distance(2);
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_device_type(
      nearby::fastpair::DeviceType::DEVICE_TYPE_UNSPECIFIED);
  metadata.set_notification_type(
      nearby::fastpair::NotificationType::NOTIFICATION_TYPE_UNSPECIFIED);
  metadata.set_interaction_type(
      nearby::fastpair::InteractionType::INTERACTION_TYPE_UNKNOWN);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, V1NotificationType) {
  // Set metadata to mimic a V1 device which advertises with no device
  // type, interaction type of notification, and a notification type of
  // FAST_PAIR_ONE.
  nearby::fastpair::Device metadata;
  metadata.set_trigger_distance(2);
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_device_type(
      nearby::fastpair::DeviceType::DEVICE_TYPE_UNSPECIFIED);
  metadata.set_notification_type(
      nearby::fastpair::NotificationType::FAST_PAIR_ONE);
  metadata.set_interaction_type(
      nearby::fastpair::InteractionType::NOTIFICATION);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, V2NotificationType) {
  // Set metadata to mimic a V2 device which advertises with a device
  // type of TRUE_WIRELESS_HEADPHONES, interaction type of notification, and a
  // notification type of FAST_PAIR.
  nearby::fastpair::Device metadata;
  metadata.set_trigger_distance(2);
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_device_type(
      nearby::fastpair::DeviceType::TRUE_WIRELESS_HEADPHONES);
  metadata.set_notification_type(nearby::fastpair::NotificationType::FAST_PAIR);
  metadata.set_interaction_type(
      nearby::fastpair::InteractionType::NOTIFICATION);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, WrongNotificationType) {
  // Set metadata to mimic a Fitbit wearable which advertises with no
  // device type, interaction type of notification, and a notification type of
  // APP_LAUNCH.
  nearby::fastpair::Device metadata;
  metadata.set_trigger_distance(2);
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_device_type(
      nearby::fastpair::DeviceType::DEVICE_TYPE_UNSPECIFIED);
  metadata.set_notification_type(
      nearby::fastpair::NotificationType::APP_LAUNCH);
  metadata.set_interaction_type(
      nearby::fastpair::InteractionType::NOTIFICATION);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, WrongInteractionType) {
  // Set metadata to mimic a Smart Setup advertisement which advertises with
  // no device type, interaction type of AUTO_LAUNCH, and a notification type of
  // FAST_PAIR_ONE.
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_trigger_distance(2);
  metadata.set_device_type(
      nearby::fastpair::DeviceType::DEVICE_TYPE_UNSPECIFIED);
  metadata.set_notification_type(
      nearby::fastpair::NotificationType::FAST_PAIR_ONE);
  metadata.set_interaction_type(nearby::fastpair::InteractionType::AUTO_LAUNCH);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, MouseDisallowedWhenHIDDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairHID});
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_trigger_distance(2);
  metadata.set_device_type(nearby::fastpair::DeviceType::MOUSE);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, MouseAllowedWhenHIDEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairHID,
                            floss::features::kFlossEnabled},
      /*disabled_features=*/{});
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_trigger_distance(2);
  metadata.set_device_type(nearby::fastpair::DeviceType::MOUSE);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       InputDeviceDisallowedWhenKeyboardsDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kFastPairKeyboards});
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_trigger_distance(2);
  metadata.set_device_type(nearby::fastpair::DeviceType::INPUT_DEVICE);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       InputDeviceAllowedWhenKeyboardsEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairKeyboards,
                            floss::features::kFlossEnabled},
      /*disabled_features=*/{});
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_trigger_distance(2);
  metadata.set_device_type(nearby::fastpair::DeviceType::INPUT_DEVICE);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, PublishedStatusTypeShown) {
  // Set metadata to mimic a device with a published Status Type.
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_trigger_distance(2);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  // A DeviceFound notification should be displayed.
  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, UnpublishedStatusTypeHidden) {
  // Set metadata to mimic a device that doesn't specify the status type,
  // which is what a "Debug device" will look like.
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::TYPE_UNSPECIFIED);
  metadata.set_trigger_distance(2);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  // No notification should be displayed.
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       UnpublishedStatusTypeShownWithDebugMetadataFlag) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kFastPairDebugMetadata},
      /*disabled_features=*/{});

  // Set metadata to mimic a device that has submitted to the Nearby console and
  // is awaiting publication.
  nearby::fastpair::Device metadata;
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::SUBMITTED);
  metadata.set_trigger_distance(2);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  // The notification should be displayed since Debug Metadata flag is on.
  EXPECT_CALL(found_device_callback_, Run).Times(1);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, DeviceLost) {
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceFound(device);
  scanner_->NotifyDeviceLost(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, NearbyShareModelId) {
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice("fc128e");
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, InvokesLostCallbackAfterFound_v1) {
  device::BluetoothDevice* device = GetDevice(kValidModelId);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(lost_device_callback_, Run).Times(1);
  scanner_->NotifyDeviceLost(device);

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       InvokesFoundCallback_AfterNetworkAvailable) {
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  repository_->set_is_network_connected(false);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  repository_->set_is_network_connected(true);
  discoverable_scanner_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       NoFoundCallback_AfterNetworkUnavailable) {
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  repository_->set_is_network_connected(false);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  repository_->set_is_network_connected(false);
  discoverable_scanner_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       NoFoundCallback_AfterDeviceLostAndNetworkAvailable) {
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  repository_->set_is_network_connected(false);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();

  scanner_->NotifyDeviceLost(device);
  repository_->set_is_network_connected(true);
  discoverable_scanner_->DefaultNetworkChanged(
      helper_.network_state_handler()->DefaultNetwork());
  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest, InvokesLostCallbackAfterFound_v2) {
  nearby::fastpair::Device metadata;
  metadata.set_trigger_distance(2);
  nearby::fastpair::Status* status = metadata.mutable_status();
  status->set_status_type(nearby::fastpair::StatusType::PUBLISHED);
  metadata.set_device_type(
      nearby::fastpair::DeviceType::TRUE_WIRELESS_HEADPHONES);
  auto* key_pair = new ::nearby::fastpair::AntiSpoofingKeyPair();
  key_pair->set_public_key("test_public_key");
  metadata.set_allocated_anti_spoofing_key_pair(key_pair);
  repository_->SetFakeMetadata(kValidModelId, metadata);

  device::BluetoothDevice* device = GetDevice(kValidModelId);

  EXPECT_CALL(found_device_callback_, Run).Times(1);
  scanner_->NotifyDeviceFound(device);

  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(lost_device_callback_, Run).Times(1);
  scanner_->NotifyDeviceLost(device);

  base::RunLoop().RunUntilIdle();
}

// TODO(b/242100708): This test is misleading since we don't actually
// catch the cases where a paired device is discovered by this scanner.
// Update/remove this test once this bug is fixed.
TEST_F(FastPairDiscoverableScannerImplTest, AlreadyPaired) {
  device::BluetoothDevice* device =
      GetDevice(kValidModelId, /*is_paired=*/true);

  EXPECT_CALL(found_device_callback_, Run).Times(0);
  scanner_->NotifyDeviceFound(device);
  base::RunLoop().RunUntilIdle();

  EXPECT_CALL(lost_device_callback_, Run).Times(0);
  scanner_->NotifyDeviceLost(device);

  base::RunLoop().RunUntilIdle();
}

TEST_F(FastPairDiscoverableScannerImplTest,
       DoesntInvokeLostCallbackIfDidntInvokeFound) {
  EXPECT_CALL(found_device_callback_, Run).Times(0);
  EXPECT_CALL(lost_device_callback_, Run).Times(0);
  device::BluetoothDevice* device = GetDevice(kValidModelId);
  scanner_->NotifyDeviceLost(device);
  base::RunLoop().RunUntilIdle();
}

}  // namespace quick_pair
}  // namespace ash