Skip to content

Commit afd5ce0

Browse files
committed
test(q10): add tests for send_decoded_command channel function
1 parent 77dde9b commit afd5ce0

File tree

2 files changed

+177
-6
lines changed

2 files changed

+177
-6
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
"""Tests for B01 Q10 channel functions."""
2+
3+
import json
4+
from typing import Any, cast
5+
6+
import pytest
7+
8+
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
9+
from roborock.devices.rpc.b01_q10_channel import send_command, send_decoded_command
10+
from roborock.exceptions import RoborockException
11+
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
12+
from tests.fixtures.channel_fixtures import FakeChannel
13+
14+
15+
@pytest.fixture(name="fake_channel")
16+
def fake_channel_fixture() -> FakeChannel:
17+
return FakeChannel()
18+
19+
20+
def build_q10_dps_response(dps: dict[str, Any]) -> RoborockMessage:
21+
"""Build a Q10 MQTT response message with DPS data."""
22+
payload = {"dps": dps}
23+
return RoborockMessage(
24+
protocol=cast(RoborockMessageProtocol, 11), # MQTT protocol for B01 Q10
25+
payload=json.dumps(payload).encode(),
26+
seq=0,
27+
version=b"B01",
28+
)
29+
30+
31+
async def test_send_command(fake_channel: FakeChannel) -> None:
32+
"""Test sending a command without waiting for response."""
33+
await send_command(fake_channel, B01_Q10_DP.START_CLEAN, {"cmd": 1}) # type: ignore[arg-type]
34+
35+
assert len(fake_channel.published_messages) == 1
36+
message = fake_channel.published_messages[0]
37+
assert message.payload is not None
38+
payload_data = json.loads(message.payload.decode())
39+
assert payload_data == {"dps": {"201": {"cmd": 1}}}
40+
41+
42+
async def test_send_decoded_command_basic(fake_channel: FakeChannel) -> None:
43+
"""Test sending a command and receiving a decoded response."""
44+
# Queue a response
45+
fake_channel.response_queue.append(build_q10_dps_response({"121": 5, "122": 100}))
46+
47+
result = await send_decoded_command(
48+
fake_channel, # type: ignore[arg-type]
49+
B01_Q10_DP.REQUEST_DPS,
50+
{},
51+
expected_dps={B01_Q10_DP.STATUS, B01_Q10_DP.BATTERY},
52+
)
53+
54+
assert B01_Q10_DP.STATUS in result
55+
assert B01_Q10_DP.BATTERY in result
56+
assert result[B01_Q10_DP.STATUS] == 5
57+
assert result[B01_Q10_DP.BATTERY] == 100
58+
59+
60+
async def test_send_decoded_command_without_expected_dps(fake_channel: FakeChannel) -> None:
61+
"""Test send_decoded_command accepts any response when expected_dps is None."""
62+
# Queue a response with any DPS
63+
fake_channel.response_queue.append(build_q10_dps_response({"123": 2}))
64+
65+
result = await send_decoded_command(
66+
fake_channel, # type: ignore[arg-type]
67+
B01_Q10_DP.REQUEST_DPS,
68+
{},
69+
expected_dps=None,
70+
)
71+
72+
# Should accept any response
73+
assert B01_Q10_DP.FAN_LEVEL in result
74+
assert result[B01_Q10_DP.FAN_LEVEL] == 2
75+
76+
77+
async def test_send_decoded_command_filters_by_expected_dps(fake_channel: FakeChannel) -> None:
78+
"""Test that send_decoded_command filters by expected DPS."""
79+
# Queue response with expected DPS
80+
fake_channel.response_queue.append(build_q10_dps_response({"121": 5, "122": 100}))
81+
82+
result = await send_decoded_command(
83+
fake_channel, # type: ignore[arg-type]
84+
B01_Q10_DP.REQUEST_DPS,
85+
{},
86+
expected_dps={B01_Q10_DP.STATUS},
87+
)
88+
89+
# Should accept response with expected DPS
90+
assert B01_Q10_DP.STATUS in result
91+
assert result[B01_Q10_DP.STATUS] == 5
92+
93+
94+
async def test_send_decoded_command_timeout(fake_channel: FakeChannel) -> None:
95+
"""Test that send_decoded_command times out when no matching response."""
96+
# Don't queue any response
97+
98+
with pytest.raises(RoborockException, match="B01 Q10 command timed out"):
99+
await send_decoded_command(
100+
fake_channel, # type: ignore[arg-type]
101+
B01_Q10_DP.REQUEST_DPS,
102+
{},
103+
expected_dps={B01_Q10_DP.STATUS},
104+
)
105+
106+
107+
async def test_send_decoded_command_ignores_decode_errors(fake_channel: FakeChannel) -> None:
108+
"""Test that send_decoded_command ignores non-decodable messages."""
109+
# Queue a valid response (invalid responses are ignored by not matching expected_dps)
110+
fake_channel.response_queue.append(build_q10_dps_response({"121": 5, "122": 100}))
111+
112+
result = await send_decoded_command(
113+
fake_channel, # type: ignore[arg-type]
114+
B01_Q10_DP.REQUEST_DPS,
115+
{},
116+
expected_dps={B01_Q10_DP.STATUS},
117+
)
118+
119+
# Should successfully decode and return valid response
120+
assert B01_Q10_DP.STATUS in result
121+
122+
123+
async def test_send_decoded_command_partial_match(fake_channel: FakeChannel) -> None:
124+
"""Test that send_decoded_command accepts response with at least one expected DPS."""
125+
# Queue response with only one of multiple expected DPS
126+
fake_channel.response_queue.append(build_q10_dps_response({"121": 5}))
127+
128+
result = await send_decoded_command(
129+
fake_channel, # type: ignore[arg-type]
130+
B01_Q10_DP.REQUEST_DPS,
131+
{},
132+
expected_dps={B01_Q10_DP.STATUS, B01_Q10_DP.BATTERY},
133+
)
134+
135+
# Should accept response with at least one expected DPS
136+
assert B01_Q10_DP.STATUS in result
137+
assert result[B01_Q10_DP.STATUS] == 5
138+
139+
140+
async def test_send_decoded_command_published_message(fake_channel: FakeChannel) -> None:
141+
"""Test that send_decoded_command publishes the correct message."""
142+
fake_channel.response_queue.append(build_q10_dps_response({"121": 5, "122": 100}))
143+
144+
await send_decoded_command(
145+
fake_channel, # type: ignore[arg-type]
146+
B01_Q10_DP.REQUEST_DPS,
147+
{},
148+
expected_dps={B01_Q10_DP.STATUS},
149+
)
150+
151+
# Check published message
152+
assert len(fake_channel.published_messages) == 1
153+
message = fake_channel.published_messages[0]
154+
assert message.payload is not None
155+
payload_data = json.loads(message.payload.decode())
156+
assert payload_data == {"dps": {"102": {}}}
157+
158+
159+
async def test_send_decoded_command_with_params(fake_channel: FakeChannel) -> None:
160+
"""Test send_decoded_command with command parameters."""
161+
fake_channel.response_queue.append(build_q10_dps_response({"121": 3, "122": 100}))
162+
163+
await send_decoded_command(
164+
fake_channel, # type: ignore[arg-type]
165+
B01_Q10_DP.START_CLEAN,
166+
{"cmd": 1},
167+
expected_dps={B01_Q10_DP.STATUS},
168+
)
169+
170+
message = fake_channel.published_messages[0]
171+
assert message.payload is not None
172+
payload_data = json.loads(message.payload.decode())
173+
assert payload_data == {"dps": {"201": {"cmd": 1}}}

tests/devices/traits/b01/q10/test_status.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tests for Q10 StatusTrait."""
22

33
import json
4-
from typing import Any
4+
from typing import Any, cast
55

66
import pytest
77

@@ -12,7 +12,7 @@
1212
YXFanLevel,
1313
)
1414
from roborock.devices.traits.b01.q10.status import StatusTrait
15-
from roborock.roborock_message import RoborockMessage
15+
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
1616
from tests.fixtures.channel_fixtures import FakeChannel
1717

1818

@@ -30,7 +30,7 @@ def build_q10_response(dps: dict[str, Any]) -> RoborockMessage:
3030
"""Build a Q10 MQTT response message."""
3131
payload = {"dps": dps}
3232
return RoborockMessage(
33-
protocol=11, # MQTT_PROTO
33+
protocol=cast(RoborockMessageProtocol, 11), # MQTT_PROTO
3434
payload=json.dumps(payload).encode(),
3535
seq=0,
3636
version=b"B01",
@@ -83,9 +83,7 @@ async def test_status_trait_clean_mode(status_trait: StatusTrait, fake_channel:
8383

8484
async def test_status_trait_cleaning_progress(status_trait: StatusTrait, fake_channel: FakeChannel) -> None:
8585
"""Test getting cleaning progress."""
86-
fake_channel.response_queue.append(
87-
build_q10_response({"121": 5, "122": 100, "141": 25})
88-
)
86+
fake_channel.response_queue.append(build_q10_response({"121": 5, "122": 100, "141": 25}))
8987

9088
result = await status_trait.refresh()
9189

0 commit comments

Comments
 (0)