mcbluetooth/tests/test_dbus_client.py
Ryan Malloy 013cd0eb2f Initial implementation of mcbluetooth MCP server
Comprehensive BlueZ MCP server exposing Linux Bluetooth stack to LLMs:

- Adapter management (list, power, discoverable, pairable)
- Device discovery and management (scan, pair, connect, trust)
- Audio profiles with PipeWire/PulseAudio integration (A2DP/HFP switching)
- BLE/GATT support (services, characteristics, read/write/notify)

Built on:
- FastMCP 2.14.4 for MCP protocol
- dbus-fast 4.0.0 for async BlueZ D-Bus communication
- pulsectl-asyncio 1.2.2 for audio control
2026-02-02 02:03:49 -07:00

94 lines
2.8 KiB
Python

"""Tests for the BlueZ D-Bus client."""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from mcbluetooth.dbus_client import (
BlueZClient,
AdapterInfo,
DeviceInfo,
address_to_path,
path_to_address,
)
class TestPathConversion:
"""Test address/path conversion utilities."""
def test_address_to_path(self):
assert address_to_path("hci0", "AA:BB:CC:DD:EE:FF") == "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF"
assert address_to_path("hci1", "11:22:33:44:55:66") == "/org/bluez/hci1/dev_11_22_33_44_55_66"
def test_address_to_path_lowercase(self):
# Should uppercase the address
assert address_to_path("hci0", "aa:bb:cc:dd:ee:ff") == "/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF"
def test_path_to_address(self):
assert path_to_address("/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF") == "AA:BB:CC:DD:EE:FF"
assert path_to_address("/org/bluez/hci1/dev_11_22_33_44_55_66") == "11:22:33:44:55:66"
def test_path_to_address_invalid(self):
assert path_to_address("/org/bluez/hci0") == ""
assert path_to_address("/invalid/path") == ""
class TestAdapterInfo:
"""Test AdapterInfo dataclass."""
def test_adapter_info_creation(self):
info = AdapterInfo(
path="/org/bluez/hci0",
name="hci0",
address="AA:BB:CC:DD:EE:FF",
alias="My Adapter",
powered=True,
discoverable=False,
discoverable_timeout=180,
pairable=True,
pairable_timeout=0,
discovering=False,
)
assert info.name == "hci0"
assert info.powered is True
assert info.discoverable is False
class TestDeviceInfo:
"""Test DeviceInfo dataclass."""
def test_device_info_creation(self):
info = DeviceInfo(
path="/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF",
adapter="hci0",
address="AA:BB:CC:DD:EE:FF",
name="Test Device",
alias="My Device",
paired=True,
bonded=True,
trusted=True,
blocked=False,
connected=True,
rssi=-50,
tx_power=4,
)
assert info.address == "AA:BB:CC:DD:EE:FF"
assert info.paired is True
assert info.connected is True
assert info.rssi == -50
class TestBlueZClient:
"""Test BlueZClient methods with mocked D-Bus."""
@pytest.fixture
def client(self):
return BlueZClient()
@pytest.mark.asyncio
async def test_client_not_connected_initially(self, client):
assert client._bus is None
assert client._object_manager is None
# Additional tests would require more extensive mocking of dbus-fast
# which is better done as integration tests with a real BlueZ stack