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
94 lines
2.8 KiB
Python
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
|