Skip to content

Commit 0fac328

Browse files
allenporterLash-L
andauthored
chore: Add test coverage of end to end trait parsing from raw responses (#482)
* chore: Add test coverage of end to end trait parsin from raw responses * fix: docstring * chore: fix lint errors --------- Co-authored-by: Luke Lashley <conway220@gmail.com>
1 parent 949a076 commit 0fac328

File tree

2 files changed

+77
-17
lines changed

2 files changed

+77
-17
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# serializer version: 1
2+
# name: test_device_trait_command_parsing[payload0-status-StatusTrait-get_status]
3+
S7MaxVStatus(msg_ver=2, msg_seq=515, state=<RoborockStateCode.charging: 8>, battery=100, clean_time=5405, clean_area=91287500, square_meter_clean_area=91.3, error_code=<RoborockErrorCode.none: 0>, map_present=1, in_cleaning=<RoborockInCleaning.complete: 0>, in_returning=0, in_fresh_state=1, lab_status=1, water_box_status=0, back_type=None, wash_phase=None, wash_ready=None, fan_power=<RoborockFanSpeedS7MaxV.custom: 106>, dnd_enabled=1, map_status=3, is_locating=0, lock_status=0, water_box_mode=<RoborockMopIntensityS7.custom: 204>, water_box_carriage_status=0, mop_forbidden_enable=0, camera_status=None, is_exploring=None, home_sec_status=None, home_sec_enable_password=None, adbumper_status=None, water_shortage_status=None, dock_type=None, dust_collection_status=None, auto_dust_collection=None, avoid_count=None, mop_mode=None, debug_mode=None, collision_avoid_status=None, switch_map_mode=None, dock_error_status=None, charge_status=None, unsave_map_reason=4, unsave_map_flag=0, wash_status=None, distance_off=0, in_warmup=None, dry_status=None, rdt=None, clean_percent=None, rss=None, dss=None, common_status=None, corner_clean_mode=None, error_code_name='none', state_name='charging', water_box_mode_name='custom', fan_power_options=['off', 'quiet', 'balanced', 'turbo', 'max', 'custom', 'max_plus'], fan_power_name='custom', mop_mode_name=None)
4+
# ---
5+
# name: test_device_trait_command_parsing[payload1-do_not_disturb-DoNotDisturbTrait-get_dnd_timer]
6+
list([
7+
dict({
8+
'enabled': 1,
9+
'end_hour': 8,
10+
'end_minute': 0,
11+
'start_hour': 22,
12+
'start_minute': 0,
13+
}),
14+
])
15+
# ---
16+
# name: test_device_trait_command_parsing[payload2-clean_summary-CleanSummaryTrait-get_clean_summary]
17+
CleanSummary(clean_time=1442559, clean_area=24258125000, square_meter_clean_area=24258.1, clean_count=296, dust_collection_count=None, records=[1756848207, 1754930385, 1753203976, 1752183435, 1747427370, 1746204046, 1745601543, 1744387080, 1743528522, 1742489154, 1741022299, 1740433682, 1739902516, 1738875106, 1738864366, 1738620067, 1736873889, 1736197544, 1736121269, 1734458038], last_clean_t=None)
18+
# ---
19+
# name: test_device_trait_command_parsing[payload3-sound_volume-SoundVolumeTrait-get_volume]
20+
90
21+
# ---

tests/devices/test_v1_device.py

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
"""Tests for the Device class."""
22

3+
import pathlib
4+
from collections.abc import Awaitable, Callable
35
from unittest.mock import AsyncMock, Mock
46

57
import pytest
8+
from syrupy import SnapshotAssertion
69

710
from roborock.containers import HomeData, S7MaxVStatus, UserData
811
from roborock.devices.device import RoborockDevice
12+
from roborock.devices.traits.clean_summary import CleanSummaryTrait
13+
from roborock.devices.traits.dnd import DoNotDisturbTrait
14+
from roborock.devices.traits.sound_volume import SoundVolumeTrait
915
from roborock.devices.traits.status import StatusTrait
1016
from roborock.devices.traits.trait import Trait
17+
from roborock.devices.v1_rpc_channel import decode_rpc_response
18+
from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
1119

1220
from .. import mock_data
1321

1422
USER_DATA = UserData.from_dict(mock_data.USER_DATA)
1523
HOME_DATA = HomeData.from_dict(mock_data.HOME_DATA_RAW)
1624
STATUS = S7MaxVStatus.from_dict(mock_data.STATUS)
1725

26+
TESTDATA = pathlib.Path("tests/protocols/testdata/v1_protocol/")
27+
1828

1929
@pytest.fixture(autouse=True, name="channel")
2030
def device_channel_fixture() -> AsyncMock:
@@ -45,7 +55,10 @@ def traits_fixture(rpc_channel: AsyncMock) -> list[Trait]:
4555
StatusTrait(
4656
product_info=HOME_DATA.products[0],
4757
rpc_channel=rpc_channel,
48-
)
58+
),
59+
CleanSummaryTrait(rpc_channel=rpc_channel),
60+
DoNotDisturbTrait(rpc_channel=rpc_channel),
61+
SoundVolumeTrait(rpc_channel=rpc_channel),
4962
]
5063

5164

@@ -70,19 +83,45 @@ async def test_device_connection(device: RoborockDevice, channel: AsyncMock) ->
7083
assert unsub.called
7184

7285

73-
async def test_device_get_status_command(device: RoborockDevice, rpc_channel: AsyncMock) -> None:
74-
"""Test the device get_status command."""
75-
# Mock response for get_status command
76-
rpc_channel.send_command.return_value = [STATUS.as_dict()]
77-
78-
# Test get_status and verify the command was sent
79-
status_api = device.traits["status"]
80-
assert isinstance(status_api, StatusTrait)
81-
assert status_api is not None
82-
status = await status_api.get_status()
83-
assert rpc_channel.send_command.called
84-
85-
# Verify the result
86-
assert status is not None
87-
assert status.battery == 100
88-
assert status.state == 8
86+
@pytest.fixture(name="setup_rpc_channel")
87+
def setup_rpc_channel_fixture(rpc_channel: AsyncMock, payload: pathlib.Path) -> AsyncMock:
88+
"""Fixture to set up the RPC channel for the tests."""
89+
# The values other than the payload are arbitrary
90+
message = RoborockMessage(
91+
protocol=RoborockMessageProtocol.GENERAL_RESPONSE,
92+
payload=payload.read_bytes(),
93+
seq=12750,
94+
version=b"1.0",
95+
random=97431,
96+
timestamp=1652547161,
97+
)
98+
response_message = decode_rpc_response(message)
99+
rpc_channel.send_command.return_value = response_message.data
100+
return rpc_channel
101+
102+
103+
@pytest.mark.parametrize(
104+
("payload", "trait_name", "trait_type", "trait_method"),
105+
[
106+
(TESTDATA / "get_status.json", "status", StatusTrait, StatusTrait.get_status),
107+
(TESTDATA / "get_dnd.json", "do_not_disturb", DoNotDisturbTrait, DoNotDisturbTrait.get_dnd_timer),
108+
(TESTDATA / "get_clean_summary.json", "clean_summary", CleanSummaryTrait, CleanSummaryTrait.get_clean_summary),
109+
(TESTDATA / "get_volume.json", "sound_volume", SoundVolumeTrait, SoundVolumeTrait.get_volume),
110+
],
111+
)
112+
async def test_device_trait_command_parsing(
113+
device: RoborockDevice,
114+
setup_rpc_channel: AsyncMock,
115+
snapshot: SnapshotAssertion,
116+
trait_name: str,
117+
trait_type: type[Trait],
118+
trait_method: Callable[..., Awaitable[object]],
119+
) -> None:
120+
"""Test the device trait command."""
121+
trait = device.traits[trait_name]
122+
assert trait
123+
assert isinstance(trait, trait_type)
124+
result = await trait_method(trait)
125+
assert setup_rpc_channel.send_command.called
126+
127+
assert result == snapshot

0 commit comments

Comments
 (0)