Skip to content

Commit f477a3b

Browse files
committed
feat: Fix tests reverted by co-pilot
1 parent e841c35 commit f477a3b

File tree

2 files changed

+59
-60
lines changed

2 files changed

+59
-60
lines changed

roborock/protocols/v1_protocol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def decode_rpc_response(message: RoborockMessage) -> dict[str, Any]:
120120
raise RoborockException("Invalid V1 message format: missing payload")
121121
try:
122122
payload = json.loads(message.payload.decode())
123-
except json.JSONDecodeError as e:
123+
except (json.JSONDecodeError, TypeError) as e:
124124
raise RoborockException(f"Invalid V1 message payload: {e} for {message.payload!r}") from e
125125

126126
_LOGGER.debug("Decoded V1 message payload: %s", payload)

tests/devices/test_v1_channel.py

Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66

77
import json
88
import logging
9-
from dataclasses import dataclass
10-
from unittest.mock import AsyncMock, Mock, patch
9+
from unittest.mock import AsyncMock, Mock
1110

1211
import pytest
1312

14-
from roborock.containers import NetworkInfo, RoborockBase, UserData, S5MaxStatus, RoborockStateCode
13+
from roborock.containers import NetworkInfo, RoborockStateCode, S5MaxStatus, UserData
1514
from roborock.devices.local_channel import LocalChannel, LocalSession
1615
from roborock.devices.mqtt_channel import MqttChannel
1716
from roborock.devices.v1_channel import V1Channel
@@ -37,7 +36,9 @@
3736
)
3837
TEST_RESPONSE = RoborockMessage(
3938
protocol=RoborockMessageProtocol.RPC_RESPONSE,
40-
payload=json.dumps({"dps": {"102": json.dumps({"id": 12345, "result": {"state": RoborockStateCode.cleaning}})}}).encode(),
39+
payload=json.dumps(
40+
{"dps": {"102": json.dumps({"id": 12345, "result": {"state": RoborockStateCode.cleaning}})}}
41+
).encode(),
4142
)
4243
TEST_NETWORK_INFO_RESPONSE = RoborockMessage(
4344
protocol=RoborockMessageProtocol.RPC_RESPONSE,
@@ -62,6 +63,19 @@ def setup_mock_mqtt_channel() -> Mock:
6263
return mock_mqtt
6364

6465

66+
@pytest.fixture(name="mqtt_responses", autouse=True)
67+
def setup_mqtt_responses(mock_mqtt_channel: Mock) -> list[RoborockMessage]:
68+
"""Fixture to provide a list of mock MQTT responses."""
69+
70+
responses: list[RoborockMessage] = [TEST_NETWORK_INFO_RESPONSE]
71+
72+
def send_command(*args) -> RoborockMessage:
73+
return responses.pop(0)
74+
75+
mock_mqtt_channel.send_command.side_effect = send_command
76+
return responses
77+
78+
6579
@pytest.fixture(name="mock_local_channel")
6680
def setup_mock_local_channel() -> Mock:
6781
"""Mock Local channel for testing."""
@@ -148,9 +162,8 @@ async def test_v1_channel_subscribe_both_connections_success(
148162
mock_local_channel.subscribe.return_value = local_unsub
149163

150164
# Mock network info retrieval
151-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
152-
callback = Mock()
153-
unsub = await v1_channel.subscribe(callback)
165+
callback = Mock()
166+
unsub = await v1_channel.subscribe(callback)
154167

155168
# Verify both connections established
156169
mock_mqtt_channel.subscribe.assert_called_once()
@@ -189,8 +202,7 @@ async def test_v1_channel_local_connection_warning_logged(
189202
mock_mqtt_channel.subscribe.return_value = Mock()
190203
mock_local_channel.connect.side_effect = RoborockException("Local connection failed")
191204

192-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
193-
await v1_channel.subscribe(Mock())
205+
await v1_channel.subscribe(Mock())
194206

195207
assert "Could not establish local connection for device abc123" in warning_caplog.text
196208
assert "Local connection failed" in warning_caplog.text
@@ -205,16 +217,12 @@ async def test_v1_channel_send_decoded_command_local_preferred(
205217
mock_local_channel: Mock,
206218
) -> None:
207219
"""Test command sending prefers local connection when available."""
208-
# Setup: both connections available
209-
mock_mqtt_channel.subscribe.return_value = Mock()
210-
mock_local_channel.subscribe.return_value = Mock()
211-
mock_local_channel.send_command.return_value = TEST_RESPONSE
212-
213220
# Establish connections
214-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
215-
await v1_channel.subscribe(Mock())
221+
await v1_channel.subscribe(Mock())
222+
mock_mqtt_channel.send_command.reset_mock(return_value=False)
216223

217224
# Send command
225+
mock_local_channel.send_command.return_value = TEST_RESPONSE
218226
result = await v1_channel.send_decoded_command(
219227
RoborockCommand.CHANGE_SOUND_VOLUME,
220228
response_type=S5MaxStatus,
@@ -230,21 +238,19 @@ async def test_v1_channel_send_decoded_command_fallback_to_mqtt(
230238
v1_channel: V1Channel,
231239
mock_mqtt_channel: Mock,
232240
mock_local_channel: Mock,
241+
mqtt_responses: list[RoborockMessage],
233242
) -> None:
234243
"""Test command sending falls back to MQTT when local fails."""
235-
# Setup: both connections available initially
236-
mock_mqtt_channel.subscribe.return_value = Mock()
237-
mock_local_channel.subscribe.return_value = Mock()
238-
mock_mqtt_channel.send_command.return_value = TEST_RESPONSE
239244

240245
# Establish connections
241-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
242-
await v1_channel.subscribe(Mock())
246+
await v1_channel.subscribe(Mock())
247+
mock_mqtt_channel.send_command.reset_mock(return_value=False)
243248

244249
# Local command fails
245250
mock_local_channel.send_command.side_effect = RoborockException("Local failed")
246251

247252
# Send command
253+
mqtt_responses.append(TEST_RESPONSE)
248254
result = await v1_channel.send_decoded_command(
249255
RoborockCommand.CHANGE_SOUND_VOLUME,
250256
response_type=S5MaxStatus,
@@ -260,22 +266,19 @@ async def test_v1_channel_send_decoded_command_mqtt_only(
260266
v1_channel: V1Channel,
261267
mock_mqtt_channel: Mock,
262268
mock_local_channel: Mock,
269+
mqtt_responses: list[RoborockMessage],
263270
) -> None:
264271
"""Test command sending works with MQTT only."""
265272
# Setup: only MQTT connection
266-
mock_mqtt_channel.subscribe.return_value = Mock()
273+
# mock_mqtt_channel.subscribe.return_value = Mock()
267274
mock_local_channel.connect.side_effect = RoborockException("No local")
268275

269-
responses = [TEST_NETWORK_INFO_RESPONSE, TEST_RESPONSE]
270-
271-
def send_command(*args) -> RoborockMessage:
272-
return responses.pop(0)
273-
274-
mock_mqtt_channel.send_command.side_effect = send_command
275-
276276
await v1_channel.subscribe(Mock())
277+
mock_mqtt_channel.send_command.assert_called_once() # network info
278+
mock_mqtt_channel.send_command.reset_mock(return_value=False)
277279

278280
# Send command
281+
mqtt_responses.append(TEST_RESPONSE)
279282
result = await v1_channel.send_decoded_command(
280283
RoborockCommand.CHANGE_SOUND_VOLUME,
281284
response_type=S5MaxStatus,
@@ -293,17 +296,13 @@ async def test_v1_channel_send_decoded_command_with_params(
293296
mock_local_channel: Mock,
294297
) -> None:
295298
"""Test command sending with parameters."""
296-
# Setup: local connection only
297-
mock_mqtt_channel.subscribe.return_value = Mock()
298-
mock_local_channel.subscribe.return_value = Mock()
299-
mock_local_channel.send_command.return_value = TEST_RESPONSE
300299

301-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
302-
await v1_channel.subscribe(Mock())
300+
await v1_channel.subscribe(Mock())
303301

304302
# Send command with params
303+
mock_local_channel.send_command.return_value = TEST_RESPONSE
305304
test_params = {"volume": 80}
306-
result = await v1_channel.send_decoded_command(
305+
await v1_channel.send_decoded_command(
307306
RoborockCommand.CHANGE_SOUND_VOLUME,
308307
response_type=S5MaxStatus,
309308
params=test_params,
@@ -312,11 +311,17 @@ async def test_v1_channel_send_decoded_command_with_params(
312311
# Verify command was sent with correct params
313312
mock_local_channel.send_command.assert_called_once()
314313
call_args = mock_local_channel.send_command.call_args
315-
assert call_args[1]["params"] == test_params
316-
assert result.state == RoborockStateCode.cleaning
317-
318-
319-
# V1Channel message handling tests
314+
sent_message = call_args[0][0]
315+
assert sent_message
316+
assert isinstance(sent_message, RoborockMessage)
317+
assert sent_message.payload
318+
payload = sent_message.payload.decode()
319+
json_data = json.loads(payload)
320+
assert "dps" in json_data
321+
assert "101" in json_data["dps"]
322+
decoded_payload = json.loads(json_data["dps"]["101"])
323+
assert decoded_payload["method"] == "change_sound_volume"
324+
assert decoded_payload["params"] == {"volume": 80}
320325

321326

322327
async def test_v1_channel_subscription_receives_mqtt_messages(
@@ -332,8 +337,7 @@ async def test_v1_channel_subscription_receives_mqtt_messages(
332337
mock_local_channel.connect.side_effect = RoborockException("Local failed")
333338

334339
# Subscribe
335-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
336-
await v1_channel.subscribe(callback)
340+
await v1_channel.subscribe(callback)
337341

338342
# Get the MQTT callback that was registered
339343
mqtt_callback = mock_mqtt_channel.subscribe.call_args[0][0]
@@ -359,8 +363,7 @@ async def test_v1_channel_subscription_receives_local_messages(
359363
mock_local_channel.subscribe.return_value = Mock()
360364

361365
# Subscribe
362-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
363-
await v1_channel.subscribe(callback)
366+
await v1_channel.subscribe(callback)
364367

365368
# Get the local callback that was registered
366369
local_callback = mock_local_channel.subscribe.call_args[0][0]
@@ -476,13 +479,11 @@ async def test_v1_channel_connection_state_properties(v1_channel: V1Channel) ->
476479
assert not v1_channel.is_local_connected
477480

478481

479-
# V1Channel integration tests
480-
481-
482482
async def test_v1_channel_full_subscribe_and_command_flow(
483483
mock_mqtt_channel: Mock,
484484
mock_local_session: Mock,
485485
mock_local_channel: Mock,
486+
mqtt_responses: list[RoborockMessage],
486487
) -> None:
487488
"""Test the complete flow from subscription to command execution."""
488489
# Setup: successful connections and responses
@@ -501,9 +502,9 @@ async def test_v1_channel_full_subscribe_and_command_flow(
501502
)
502503

503504
# Mock network info for local connection
504-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
505-
callback = Mock()
506-
unsub = await v1_channel.subscribe(callback)
505+
callback = Mock()
506+
unsub = await v1_channel.subscribe(callback)
507+
mock_mqtt_channel.send_command.reset_mock(return_value=False)
507508

508509
# Verify both connections established
509510
assert v1_channel.is_mqtt_connected
@@ -536,31 +537,29 @@ async def test_v1_channel_graceful_degradation_local_to_mqtt(
536537
mock_mqtt_channel: Mock,
537538
mock_local_session: Mock,
538539
mock_local_channel: Mock,
540+
mqtt_responses: list[RoborockMessage],
539541
) -> None:
540542
"""Test graceful degradation from local to MQTT during operation."""
541-
# Setup: both connections succeed initially
542-
mock_mqtt_channel.subscribe.return_value = Mock()
543-
mock_local_channel.subscribe.return_value = Mock()
544-
545543
v1_channel = V1Channel(
546544
device_uid=TEST_DEVICE_UID,
547545
security_data=TEST_SECURITY_DATA,
548546
mqtt_channel=mock_mqtt_channel,
549547
local_session=mock_local_session,
550548
)
551549

552-
with patch.object(v1_channel, "_get_networking_info", return_value=TEST_NETWORKING_INFO):
553-
await v1_channel.subscribe(Mock())
550+
await v1_channel.subscribe(Mock())
551+
mock_mqtt_channel.send_command.reset_mock(return_value=False)
554552

555553
# First command: local works
556554
mock_local_channel.send_command.return_value = TEST_RESPONSE
557555
result1 = await v1_channel.send_decoded_command(RoborockCommand.GET_STATUS, response_type=S5MaxStatus)
558556
assert result1.state == RoborockStateCode.cleaning
559557
mock_local_channel.send_command.assert_called_once()
558+
mock_mqtt_channel.send_command.assert_not_called()
560559

561560
# Second command: local fails, falls back to MQTT
562561
mock_local_channel.send_command.side_effect = RoborockException("Local failed")
563-
mock_mqtt_channel.send_command.return_value = TEST_RESPONSE
562+
mqtt_responses.append(TEST_RESPONSE)
564563
result2 = await v1_channel.send_decoded_command(RoborockCommand.GET_STATUS, response_type=S5MaxStatus)
565564
assert result2.state == RoborockStateCode.cleaning
566565

0 commit comments

Comments
 (0)