chromium/device/bluetooth/bluetooth_adapter_mac_unittest.mm

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

#include "device/bluetooth/bluetooth_adapter_mac.h"

#import <IOBluetooth/IOBluetooth.h>

#include <memory>

#include "base/memory/raw_ptr.h"
#import "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "device/bluetooth/bluetooth_classic_device_mac.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#import "device/bluetooth/test/test_bluetooth_adapter_observer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {

namespace {

class MockBluetoothClassicDeviceMac : public BluetoothClassicDeviceMac {
 public:
  MockBluetoothClassicDeviceMac(BluetoothAdapterMac* adapter,
                                IOBluetoothDevice* device)
      : BluetoothClassicDeviceMac(adapter, device) {}
  MOCK_METHOD(UUIDSet, GetUUIDs, (), (const, override));
  MOCK_METHOD(std::string, GetAddress, (), (const override));
};

}  // namespace

using ::testing::Return;

class BluetoothAdapterMacTest : public testing::Test {
 public:
  BluetoothAdapterMacTest()
      : ui_task_runner_(new base::TestSimpleTaskRunner()),
        adapter_(new BluetoothAdapterMac()),
        adapter_mac_(static_cast<BluetoothAdapterMac*>(adapter_.get())),
        observer_(adapter_) {
    adapter_mac_->InitForTest(ui_task_runner_);
  }

  void TearDown() override { task_environment_.RunUntilIdle(); }

  // Helper methods for setup and access to BluetoothAdapterMacTest's
  // members.
  void PollAdapter() { adapter_mac_->PollAdapter(); }

  void SetHostControllerPowerFunction(bool powered) {
    adapter_mac_->SetHostControllerStateFunctionForTesting(
        base::BindLambdaForTesting([powered] {
          BluetoothAdapterMac::HostControllerState state;
          state.classic_powered = powered;
          return state;
        }));
  }

  std::unique_ptr<MockBluetoothClassicDeviceMac> CreateClassicDevice(
      const std::string& device_address,
      BluetoothDevice::UUIDSet uuids) {
    auto device =
        std::make_unique<testing::NiceMock<MockBluetoothClassicDeviceMac>>(
            adapter_mac_, /*device=*/nil);
    ON_CALL(*device, GetUUIDs).WillByDefault(Return(uuids));
    ON_CALL(*device, GetAddress).WillByDefault(Return(device_address));
    return device;
  }

  void AddClassicDevice(const std::string& device_address,
                        BluetoothDevice::UUIDSet uuids) {
    auto device = CreateClassicDevice(device_address, uuids);
    adapter_mac_->ClassicDeviceAdded(std::move(device));
  }

  void ClassicDeviceConnected(const std::string& device_address,
                              BluetoothDevice::UUIDSet uuids) {
    auto device = CreateClassicDevice(device_address, uuids);
    adapter_mac_->DeviceConnected(std::move(device));
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_;
  scoped_refptr<BluetoothAdapter> adapter_;
  raw_ptr<BluetoothAdapterMac> adapter_mac_;
  TestBluetoothAdapterObserver observer_;
};

// TODO(https://crbug.com/331653043): Re-enable when passing on macOS 14 bots.
TEST_F(BluetoothAdapterMacTest, DISABLED_Poll) {
  PollAdapter();
  EXPECT_TRUE(ui_task_runner_->HasPendingTask());
}

// TODO(https://crbug.com/331653043): Re-enable when passing on macOS 14 bots.
TEST_F(BluetoothAdapterMacTest, DISABLED_PollAndChangePower) {
  // By default the adapter is powered off, check that this expectation matches
  // reality.
  EXPECT_FALSE(adapter_mac_->IsPowered());
  EXPECT_EQ(0, observer_.powered_changed_count());

  SetHostControllerPowerFunction(true);
  PollAdapter();
  EXPECT_TRUE(ui_task_runner_->HasPendingTask());
  ui_task_runner_->RunPendingTasks();
  EXPECT_EQ(1, observer_.powered_changed_count());
  EXPECT_TRUE(observer_.last_powered());
  EXPECT_TRUE(adapter_mac_->IsPowered());

  SetHostControllerPowerFunction(false);
  PollAdapter();
  EXPECT_TRUE(ui_task_runner_->HasPendingTask());
  ui_task_runner_->RunPendingTasks();
  EXPECT_EQ(2, observer_.powered_changed_count());
  EXPECT_FALSE(observer_.last_powered());
  EXPECT_FALSE(adapter_mac_->IsPowered());
}

TEST_F(BluetoothAdapterMacTest, ClassicDeviceAddedAndChanged) {
  // Simulate a paired Bluetooth Classic device with one service UUID.
  std::string device_address = "AA:BB:CC:DD:EE:FF";
  BluetoothDevice::UUIDSet uuids;
  uuids.insert(BluetoothUUID("110b"));
  AddClassicDevice(device_address, uuids);
  EXPECT_EQ(1, observer_.device_added_count());
  EXPECT_EQ(0, observer_.device_changed_count());
  EXPECT_EQ(observer_.last_device_address(), device_address);
  observer_.Reset();

  // Adding the same device again does not notify observers.
  AddClassicDevice(device_address, uuids);
  EXPECT_EQ(0, observer_.device_added_count());
  EXPECT_EQ(0, observer_.device_changed_count());
  observer_.Reset();

  // Update the device by adding a second service UUID.
  uuids.insert(BluetoothUUID("110c"));
  AddClassicDevice(device_address, uuids);
  EXPECT_EQ(0, observer_.device_added_count());
  EXPECT_EQ(1, observer_.device_changed_count());
  EXPECT_EQ(observer_.last_device_address(), device_address);
}

TEST_F(BluetoothAdapterMacTest, DeviceConnected) {
  // Simulate a paired Bluetooth Classic device with one service UUID.
  std::string device_address = "AA:BB:CC:DD:EE:FF";
  BluetoothDevice::UUIDSet uuids;
  uuids.insert(BluetoothUUID("110b"));

  // Device connected when device is unknown to the adapter.
  ClassicDeviceConnected(device_address, uuids);
  EXPECT_EQ(1, observer_.device_added_count());
  EXPECT_EQ(0, observer_.device_changed_count());
  EXPECT_EQ(observer_.last_device_address(), device_address);
  observer_.Reset();

  // Device connected when device is known to the adapter.
  ClassicDeviceConnected(device_address, uuids);
  EXPECT_EQ(0, observer_.device_added_count());
  EXPECT_EQ(1, observer_.device_changed_count());
}

}  // namespace device