Skip to content

Commit 9a382f6

Browse files
committed
chore: Add end to end tests for Q10 devices
Moves the mock API responses to json files to make them easier to collect for new device types and modify in tests.
1 parent dd4421f commit 9a382f6

13 files changed

+959
-577
lines changed

tests/data/test_containers.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
HOME_DATA_RAW,
2020
K_VALUE,
2121
LOCAL_KEY,
22-
PRODUCT_ID,
2322
USER_DATA,
2423
)
2524

@@ -180,7 +179,7 @@ def test_home_data():
180179
assert hd.lat is None
181180
assert hd.geo_name is None
182181
product = hd.products[0]
183-
assert product.id == PRODUCT_ID
182+
assert product.id == "product-id-s7-maxv"
184183
assert product.name == "Roborock S7 MaxV"
185184
assert product.code == "a27"
186185
assert product.model == "roborock.vacuum.a27"
@@ -205,7 +204,7 @@ def test_home_data():
205204
assert device.runtime_env is None
206205
assert device.time_zone_id == "America/Los_Angeles"
207206
assert device.icon_url == "no_url"
208-
assert device.product_id == "product-id-123"
207+
assert device.product_id == "product-id-s7-maxv"
209208
assert device.lon is None
210209
assert device.lat is None
211210
assert not device.share

tests/devices/__snapshots__/test_file_cache.ambr

Lines changed: 9 additions & 9 deletions
Large diffs are not rendered by default.

tests/devices/__snapshots__/test_v1_device.ambr

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
'name': 'Roborock S7 MaxV',
2828
'newFeatureSet': '0000000000002041',
2929
'online': True,
30-
'productId': 'product-id-123',
30+
'productId': 'product-id-s7-maxv',
3131
'pv': '1.0',
3232
'roomId': 2362003,
3333
'share': False,
@@ -40,7 +40,7 @@
4040
'capability': 0,
4141
'category': 'robot.vacuum.cleaner',
4242
'code': 'a27',
43-
'id': 'product-id-123',
43+
'id': 'product-id-s7-maxv',
4444
'model': 'roborock.vacuum.a27',
4545
'name': 'Roborock S7 MaxV',
4646
'schema': list([
@@ -453,7 +453,7 @@
453453
'name': 'Roborock S7 MaxV',
454454
'newFeatureSet': '0000000000002041',
455455
'online': True,
456-
'productId': 'product-id-123',
456+
'productId': 'product-id-s7-maxv',
457457
'pv': '1.0',
458458
'roomId': 2362003,
459459
'share': False,
@@ -466,7 +466,7 @@
466466
'capability': 0,
467467
'category': 'robot.vacuum.cleaner',
468468
'code': 'a27',
469-
'id': 'product-id-123',
469+
'id': 'product-id-s7-maxv',
470470
'model': 'roborock.vacuum.a27',
471471
'name': 'Roborock S7 MaxV',
472472
'schema': list([
@@ -859,7 +859,7 @@
859859
'name': 'Roborock S7 MaxV',
860860
'newFeatureSet': '0000000000002041',
861861
'online': True,
862-
'productId': 'product-id-123',
862+
'productId': 'product-id-s7-maxv',
863863
'pv': '1.0',
864864
'roomId': 2362003,
865865
'share': False,
@@ -872,7 +872,7 @@
872872
'capability': 0,
873873
'category': 'robot.vacuum.cleaner',
874874
'code': 'a27',
875-
'id': 'product-id-123',
875+
'id': 'product-id-s7-maxv',
876876
'model': 'roborock.vacuum.a27',
877877
'name': 'Roborock S7 MaxV',
878878
'schema': list([
@@ -1236,7 +1236,7 @@
12361236
'name': 'Roborock S7 MaxV',
12371237
'newFeatureSet': '0000000000002041',
12381238
'online': True,
1239-
'productId': 'product-id-123',
1239+
'productId': 'product-id-s7-maxv',
12401240
'pv': '1.0',
12411241
'roomId': 2362003,
12421242
'share': False,
@@ -1249,7 +1249,7 @@
12491249
'capability': 0,
12501250
'category': 'robot.vacuum.cleaner',
12511251
'code': 'a27',
1252-
'id': 'product-id-123',
1252+
'id': 'product-id-s7-maxv',
12531253
'model': 'roborock.vacuum.a27',
12541254
'name': 'Roborock S7 MaxV',
12551255
'schema': list([

tests/e2e/__snapshots__/test_device_manager.ambr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,28 @@
209209
00000410 a0 9b 5e 72 8d 3e 57 69 0b 7c 21 80 2f a4 d5 12 |..^r.>Wi.|!./...|
210210
00000420 99 be 49 6e f3 0b 57 e5 a8 1e 88 b6 7b 48 |..In..W.....{H|
211211
# ---
212+
# name: test_q10_device[home_data0]
213+
[mqtt >]
214+
00000000 10 29 00 04 4d 51 54 54 05 c2 00 3c 00 00 00 00 |.)..MQTT...<....|
215+
00000010 08 31 39 36 34 38 66 39 34 00 10 32 33 34 36 37 |.19648f94..23467|
216+
00000020 38 65 61 38 35 34 66 31 39 39 65 |8ea854f199e|
217+
[mqtt <]
218+
00000000 20 09 02 00 06 22 00 0a 21 00 14 | ...."..!..|
219+
[mqtt >]
220+
00000000 82 2e 00 01 00 00 28 72 72 2f 6d 2f 6f 2f 75 73 |......(rr/m/o/us|
221+
00000010 65 72 31 32 33 2f 31 39 36 34 38 66 39 34 2f 64 |er123/19648f94/d|
222+
00000020 65 76 69 63 65 2d 69 64 2d 64 65 66 34 35 36 00 |evice-id-def456.|
223+
[mqtt <]
224+
00000000 90 04 00 01 00 00 |......|
225+
[mqtt >]
226+
00000000 30 62 00 28 72 72 2f 6d 2f 69 2f 75 73 65 72 31 |0b.(rr/m/i/user1|
227+
00000010 32 33 2f 31 39 36 34 38 66 39 34 2f 64 65 76 69 |23/19648f94/devi|
228+
00000020 63 65 2d 69 64 2d 64 65 66 34 35 36 00 42 30 31 |ce-id-def456.B01|
229+
00000030 00 00 23 82 00 00 23 83 68 a6 a2 23 00 65 00 20 |..#...#.h..#.e. |
230+
00000040 31 38 71 36 ad 3b 7d 9d 50 0b b6 f0 be 74 5d b9 |18q6.;}.P....t].|
231+
00000050 7e 75 e3 ca e4 bc 42 34 f6 a5 2e ef c7 de 0c 10 |~u....B4........|
232+
00000060 62 f0 6c f5 |b.l.|
233+
# ---
212234
# name: test_v1_device
213235
[mqtt >]
214236
00000000 10 29 00 04 4d 51 54 54 05 c2 00 3c 00 00 00 00 |.)..MQTT...<....|

tests/e2e/test_device_manager.py

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import pytest
1717
import syrupy
1818

19+
from roborock.data.b01_q10.b01_q10_code_mappings import B01_Q10_DP
1920
from roborock.data.containers import UserData
2021
from roborock.devices.cache import Cache, InMemoryCache
2122
from roborock.devices.device_manager import DeviceManager, UserParams, create_device_manager
@@ -25,7 +26,7 @@
2526
from roborock.web_api import RoborockApiClient
2627
from tests import mock_data, mqtt_packet
2728
from tests.fixtures.logging import CapturedRequestLog
28-
from tests.mock_data import LOCAL_KEY
29+
from tests.mock_data import HOME_DATA_RAW, LOCAL_KEY
2930

3031
TEST_USERNAME = "user@example.com"
3132
TEST_CODE = 1234
@@ -42,6 +43,18 @@
4243
"bssid": "aa:bb:cc:dd:ee:ff",
4344
"rssi": -50,
4445
}
46+
# For tests that want to skip the web API login flow
47+
TEST_USER_PARAMS = UserParams(
48+
username=TEST_USERNAME,
49+
user_data=UserData.from_dict(mock_data.USER_DATA),
50+
base_url=mock_data.BASE_URL,
51+
)
52+
MQTT_DEFAULT_RESPONSES: list[bytes] = [
53+
# MQTT connection response
54+
mqtt_packet.gen_connack(rc=0, flags=2),
55+
# ACK the request to subscribe to the topic
56+
mqtt_packet.gen_suback(mid=1),
57+
]
4558

4659

4760
@pytest.fixture(autouse=True)
@@ -161,10 +174,7 @@ async def test_v1_device(
161174
# Prepare MQTT requests
162175
response_builder = ResponseBuilder()
163176
mqtt_responses: list[bytes] = [
164-
# MQTT connection response
165-
mqtt_packet.gen_connack(rc=0, flags=2),
166-
# ACK the request to subscribe to the topic
167-
mqtt_packet.gen_suback(mid=1),
177+
*MQTT_DEFAULT_RESPONSES,
168178
# ACK the GET_NETWORK_INFO call. id is deterministic based on deterministic_message_fixtures
169179
mqtt_packet.gen_publish(
170180
TEST_TOPIC, mid=2, payload=response_builder.build_rpc(data={"id": 9090, "result": NETWORK_INFO})
@@ -219,10 +229,7 @@ async def test_v1_device(
219229
await device_manager.close()
220230

221231
mqtt_responses = [
222-
# MQTT connection response
223-
mqtt_packet.gen_connack(rc=0, flags=2),
224-
# ACK the request to subscribe to the topic
225-
mqtt_packet.gen_suback(mid=1),
232+
*MQTT_DEFAULT_RESPONSES,
226233
# No network info call this time since it should be cached
227234
]
228235
for response in mqtt_responses:
@@ -282,10 +289,7 @@ async def test_l01_device(
282289
# Prepare MQTT requests
283290
mqtt_response_builder = ResponseBuilder()
284291
mqtt_responses: list[bytes] = [
285-
# MQTT connection response
286-
mqtt_packet.gen_connack(rc=0, flags=2),
287-
# ACK the request to subscribe to the topic
288-
mqtt_packet.gen_suback(mid=1),
292+
*MQTT_DEFAULT_RESPONSES,
289293
# ACK the GET_NETWORK_INFO call. id is deterministic based on deterministic_message_fixtures
290294
mqtt_packet.gen_publish(
291295
TEST_TOPIC, mid=2, payload=mqtt_response_builder.build_rpc(data={"id": 9090, "result": NETWORK_INFO})
@@ -316,12 +320,7 @@ async def test_l01_device(
316320
local_response_queue.put_nowait(payload)
317321

318322
# Create the device manager
319-
user_params = UserParams(
320-
username=TEST_USERNAME,
321-
user_data=UserData.from_dict(mock_data.USER_DATA),
322-
base_url=mock_data.BASE_URL,
323-
)
324-
device_manager = await device_manager_factory(user_params)
323+
device_manager = await device_manager_factory(TEST_USER_PARAMS)
325324

326325
# The mocked Home Data API returns a single v1 device
327326
devices = await device_manager.get_devices()
@@ -345,3 +344,51 @@ async def test_l01_device(
345344
assert not device.v1_properties.device_features.is_matter_supported
346345

347346
assert snapshot == log
347+
348+
349+
@pytest.mark.parametrize(
350+
"home_data",
351+
[
352+
(
353+
{
354+
**HOME_DATA_RAW,
355+
"devices": [mock_data.Q10_DEVICE_DATA],
356+
"products": [mock_data.SS07_PRODUCT_DATA],
357+
}
358+
)
359+
],
360+
)
361+
async def test_q10_device(
362+
mock_rest: Any,
363+
push_mqtt_response: Callable[[bytes], None],
364+
log: CapturedRequestLog,
365+
device_manager_factory: Callable[[UserParams], Awaitable[DeviceManager]],
366+
home_data: dict[str, Any],
367+
snapshot: syrupy.SnapshotAssertion,
368+
) -> None:
369+
"""Test the device manager end to end flow with a B01 Q10 device."""
370+
# Prepare MQTT requests
371+
for response in MQTT_DEFAULT_RESPONSES:
372+
push_mqtt_response(response)
373+
374+
# Create the device manager
375+
device_manager = await device_manager_factory(TEST_USER_PARAMS)
376+
377+
# The mocked Home Data API returns a single v1 device
378+
devices = await device_manager.get_devices()
379+
assert len(devices) == 1
380+
device = devices[0]
381+
assert device.duid == "device-id-def456"
382+
assert device.name == "Roborock Q10 S5+"
383+
assert device.is_connected
384+
assert not device.is_local_connected # Q10 does not support local connections
385+
386+
# Send a command. We don't block any response, but just use this to verify
387+
# against the golden byte stream snapshot.
388+
assert device.b01_q10_properties
389+
command = device.b01_q10_properties.command
390+
await command.send(B01_Q10_DP.REQUETDPS, params={})
391+
392+
# In the future here we can verify receiving requests from the device
393+
394+
assert snapshot == log

tests/fixtures/web_api_fixtures.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ def skip_rate_limit() -> Generator[None, None, None]:
1919
yield
2020

2121

22+
@pytest.fixture(name="home_data")
23+
def home_data_fixture() -> dict[str, Any]:
24+
"""Fixture to provide HomeData instance for tests."""
25+
return HOME_DATA_RAW
26+
27+
2228
@pytest.fixture(name="mock_rest")
23-
def mock_rest_fixture(skip_rate_limit: Any) -> aioresponses:
29+
def mock_rest_fixture(skip_rate_limit: Any, home_data: dict[str, Any]) -> aioresponses:
2430
"""Mock all rest endpoints so they won't hit real endpoints"""
2531
with aioresponses() as mocked:
2632
# Match the base URL and allow any query params
@@ -60,12 +66,12 @@ def mock_rest_fixture(skip_rate_limit: Any) -> aioresponses:
6066
mocked.get(
6167
re.compile(r"https://api-.*\.roborock\.com/v2/user/homes*"),
6268
status=200,
63-
payload={"api": None, "code": 200, "result": HOME_DATA_RAW, "status": "ok", "success": True},
69+
payload={"api": None, "code": 200, "result": home_data, "status": "ok", "success": True},
6470
)
6571
mocked.get(
6672
re.compile(r"https://api-.*\.roborock\.com/v3/user/homes*"),
6773
status=200,
68-
payload={"api": None, "code": 200, "result": HOME_DATA_RAW, "status": "ok", "success": True},
74+
payload={"api": None, "code": 200, "result": home_data, "status": "ok", "success": True},
6975
)
7076
mocked.post(
7177
re.compile(r"https://api-.*\.roborock\.com/nc/prepare"),

0 commit comments

Comments
 (0)