|
1 | 1 | """Tests for the DeviceManager class.""" |
2 | 2 |
|
| 3 | +import datetime |
| 4 | +import asyncio |
3 | 5 | from collections.abc import Generator, Iterator |
4 | 6 | from unittest.mock import AsyncMock, Mock, patch |
5 | 7 |
|
@@ -34,6 +36,25 @@ def channel_fixture() -> Generator[Mock, None, None]: |
34 | 36 | yield mock_channel |
35 | 37 |
|
36 | 38 |
|
| 39 | +@pytest.fixture(autouse=True) |
| 40 | +def mock_sleep() -> Generator[Mock, None, None]: |
| 41 | + """Mock asyncio.sleep in device module to speed up tests.""" |
| 42 | + sleep_time = datetime.timedelta(seconds=0.001) |
| 43 | + with patch("roborock.devices.device.MIN_BACKOFF_INTERVAL", sleep_time), patch("roborock.devices.device.MAX_BACKOFF_INTERVAL", sleep_time): |
| 44 | + yield |
| 45 | + |
| 46 | + |
| 47 | +@pytest.fixture(name="channel_failure") |
| 48 | +def channel_failure_fixture() -> Generator[Mock, None, None]: |
| 49 | + """Fixture that makes channel subscribe fail.""" |
| 50 | + with patch("roborock.devices.device_manager.create_v1_channel") as mock_channel: |
| 51 | + mock_channel.return_value.subscribe = AsyncMock( |
| 52 | + side_effect=RoborockException("Connection failed") |
| 53 | + ) |
| 54 | + mock_channel.return_value.is_connected = False |
| 55 | + yield mock_channel |
| 56 | + |
| 57 | + |
37 | 58 | @pytest.fixture(name="home_data_no_devices") |
38 | 59 | def home_data_no_devices_fixture() -> Iterator[HomeData]: |
39 | 60 | """Mock home data API that returns no devices.""" |
@@ -127,3 +148,43 @@ async def mock_home_data_with_counter(*args, **kwargs) -> HomeData: |
127 | 148 | assert len(devices2) == 1 |
128 | 149 |
|
129 | 150 | await device_manager.close() |
| 151 | + |
| 152 | + |
| 153 | +async def test_start_connect_failure(home_data: HomeData, channel_failure: Mock, mock_sleep: Mock) -> None: |
| 154 | + """Test that start_connect retries when connection fails.""" |
| 155 | + device_manager = await create_device_manager(USER_PARAMS) |
| 156 | + devices = await device_manager.get_devices() |
| 157 | + |
| 158 | + # Wait for the device to attempt to connect |
| 159 | + attempts = 0 |
| 160 | + subscribe_mock = channel_failure.return_value.subscribe |
| 161 | + while subscribe_mock.call_count < 1: |
| 162 | + await asyncio.sleep(0.01) |
| 163 | + attempts += 1 |
| 164 | + assert attempts < 10, "Device did not connect after multiple attempts" |
| 165 | + |
| 166 | + # Device should exist but not be connected |
| 167 | + assert len(devices) == 1 |
| 168 | + assert not devices[0].is_connected |
| 169 | + |
| 170 | + # Verify retry attempts |
| 171 | + assert channel_failure.return_value.subscribe.call_count >= 1 |
| 172 | + |
| 173 | + # Reset the mock channel so that it succeeds on the next attempt |
| 174 | + mock_unsub = Mock() |
| 175 | + subscribe_mock = AsyncMock() |
| 176 | + subscribe_mock.return_value = mock_unsub |
| 177 | + channel_failure.return_value.subscribe = subscribe_mock |
| 178 | + channel_failure.return_value.is_connected = True |
| 179 | + |
| 180 | + # Wait for the device to attempt to connect again |
| 181 | + attempts = 0 |
| 182 | + while subscribe_mock.call_count < 1: |
| 183 | + await asyncio.sleep(0.01) |
| 184 | + attempts += 1 |
| 185 | + assert attempts < 10, "Device did not connect after multiple attempts" |
| 186 | + |
| 187 | + assert devices[0].is_connected |
| 188 | + |
| 189 | + await device_manager.close() |
| 190 | + assert mock_unsub.call_count == 1 |
0 commit comments