diff --git a/nervous_sensors/connection_manager.py b/nervous_sensors/connection_manager.py index cbea96f..08ac130 100644 --- a/nervous_sensors/connection_manager.py +++ b/nervous_sensors/connection_manager.py @@ -92,8 +92,11 @@ async def manage_connection(self, sensor): async def manage_battery_level(self): while not self._stop_event.is_set(): - await asyncio.sleep(120) - self.print_battery_level() + try: + await asyncio.wait_for(self._stop_event.wait(), timeout=120) + self.print_battery_level() + except TimeoutError: + self.print_battery_level() # Sensors parallel actions diff --git a/nervous_sensors/nervous_sensor.py b/nervous_sensors/nervous_sensor.py index 50cb91f..7ce18a9 100644 --- a/nervous_sensors/nervous_sensor.py +++ b/nervous_sensors/nervous_sensor.py @@ -2,7 +2,7 @@ from datetime import datetime from bleak import BleakClient, BleakScanner -from bleak.exc import BleakDeviceNotFoundError, BleakError +from bleak.exc import BleakError from .cli_utils import RESET, get_color from .codec import time_encode @@ -100,7 +100,7 @@ async def stop_notifications(self) -> bool: await self._client.stop_notify("00002a19-0000-1000-8000-00805f9b34fb") await self._client.stop_notify("6e400003-b5a3-f393-e0a9-e50e24dcca9e") return True - except (BleakError, KeyError, AttributeError, ValueError): + except: return False async def connect(self): @@ -132,7 +132,7 @@ async def connect(self): self._client = None - except BleakDeviceNotFoundError: + except: if connection_was_established: self._connection_manager.on_sensor_disconnect(self) else: diff --git a/poetry.lock b/poetry.lock index 4156566..35ec77f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -903,13 +903,13 @@ pyobjc-framework-Cocoa = ">=10.3.1" [[package]] name = "pytest" -version = "8.3.1" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.1-py3-none-any.whl", hash = "sha256:e9600ccf4f563976e2c99fa02c7624ab938296551f280835ee6516df8bc4ae8c"}, - {file = "pytest-8.3.1.tar.gz", hash = "sha256:7e8e5c5abd6e93cb1cc151f23e57adc31fcf8cfd2a3ff2da63e23f732de35db6"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -923,6 +923,24 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.8" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1408,4 +1426,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "7b3547e9c3458d3477ca1f7cf61bf2fca14cab67ea2a9573453bad9dc0a39c03" +content-hash = "13a46ffcfe47ff500bae67ae768b1cf6c96ac1e46bc08fa5ba5463774e386354" diff --git a/pyproject.toml b/pyproject.toml index c67609e..92687fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ optional = true [tool.poetry.group.dev.dependencies] pytest = "^8.2.2" +pytest-asyncio = "^0.23.8" pre-commit = "^3.7.1" [tool.setuptools.packages.find] @@ -44,6 +45,7 @@ lint.select = ["I", "E", "F", "C"] [tool.ruff.lint.per-file-ignores] "nervous_sensors/pb2.py" = ["F821", "E501"] +"nervous_sensors/nervous_sensor.py" = ["E722"] [build-system] requires = ["poetry-core"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nervous_sensors/tests/test_1.py b/tests/test_1.py similarity index 100% rename from nervous_sensors/tests/test_1.py rename to tests/test_1.py diff --git a/tests/test_connection_manager.py b/tests/test_connection_manager.py new file mode 100644 index 0000000..5a0b5f0 --- /dev/null +++ b/tests/test_connection_manager.py @@ -0,0 +1,48 @@ +import asyncio +from unittest.mock import Mock + +import pytest + +from nervous_sensors.connection_manager import ConnectionManager +from nervous_sensors.nervous_sensor import NervousSensor + + +@pytest.fixture +def mock_nervous_sensor1(): + mock_sensor = Mock(spec=NervousSensor) + mock_sensor.is_connected.return_value = False + return mock_sensor + + +@pytest.fixture +def mock_nervous_sensor2(): + mock_sensor = Mock(spec=NervousSensor) + mock_sensor.is_connected.return_value = False + return mock_sensor + + +@pytest.fixture +def mock_nervous_sensor3(): + mock_sensor = Mock(spec=NervousSensor) + mock_sensor.is_connected.return_value = False + return mock_sensor + + +@pytest.mark.asyncio +async def test_start_connection(mock_nervous_sensor1, mock_nervous_sensor2, mock_nervous_sensor3): + manager = ConnectionManager(sensor_names=[], gui=False, folder=False, lsl=False, parallel_connection_authorized=3) + sensors = [mock_nervous_sensor1, mock_nervous_sensor2, mock_nervous_sensor3] + manager._sensors = sensors + + try: + await asyncio.wait_for(manager.start(), timeout=1) + except asyncio.TimeoutError: + pass + + for mock_sensor in sensors: + assert mock_sensor.connect.call_count >= 1 + + +@pytest.mark.asyncio +async def test_stop_all_notifications(): + assert True diff --git a/tests/test_nervous_sensor.py b/tests/test_nervous_sensor.py new file mode 100644 index 0000000..1a441a2 --- /dev/null +++ b/tests/test_nervous_sensor.py @@ -0,0 +1,66 @@ +import asyncio +from asyncio import TaskGroup +from unittest.mock import Mock + +import pytest +from bleak import BleakClient, BleakScanner, BLEDevice + +from nervous_sensors.connection_manager import ConnectionManager +from nervous_sensors.nervous_sensor import NervousSensor + + +@pytest.fixture +def mock_bleak_client(): + return Mock(spec=BleakClient) + + +@pytest.fixture +def mock_bleak_scanner(): + return Mock(spec=BleakScanner) + + +@pytest.fixture +def mock_bleak_device(): + return Mock(spec=BLEDevice) + + +@pytest.fixture +def mock_connection_manager(): + return Mock(spec=ConnectionManager) + + +@pytest.mark.asyncio +async def tes_connect(mock_connection_manager, mock_bleak_client, mock_bleak_scanner, mock_bleak_device): + sensor = NervousSensor("ECG73BA", "2023-03-01 12:00:00", 10, mock_connection_manager) + sensor._client = mock_bleak_client + sensor._scanner = mock_bleak_scanner + + mock_bleak_scanner.find_device_by_name.return_value = mock_bleak_device + mock_bleak_client.connect.return_value = True + + async def disconnection_event(): + await asyncio.sleep(1) + await sensor.disconnect() + + async with TaskGroup() as tg: + tg.create_task(sensor.connect()) + tg.create_task(disconnection_event()) + + mock_connection_manager.on_sensor_connect.assert_called_once_with(sensor) + mock_connection_manager.on_sensor_fail_to_connect.assert_not_called() + mock_connection_manager.on_sensor_disconnect.assert_called_once_with(sensor) + + +@pytest.mark.asyncio +async def tes_connect_fail(mock_connection_manager, mock_bleak_client, mock_bleak_scanner): + sensor = NervousSensor("ECG73BA", "2023-03-01 12:00:00", 10, mock_connection_manager) + sensor._client = mock_bleak_client + sensor._scanner = mock_bleak_scanner + + mock_bleak_scanner.find_device_by_name.return_value = None + mock_bleak_client.connect.return_value = False + await sensor.connect() + + mock_connection_manager.on_sensor_connect.assert_not_called() + mock_connection_manager.on_sensor_fail_to_connect.assert_called_once_with(sensor) + mock_connection_manager.on_sensor_disconnect.assert_not_called()