Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,409 changes: 1,409 additions & 0 deletions tests/__snapshots__/test_supported_features.ambr

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions tests/devices/traits/v1/__snapshots__/test_device_features.ambr
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# serializer version: 1
# name: test_is_attribute_supported[home_data_device_a123.json]
dict({
'battery': True,
'charge_status': True,
'dry_status': True,
'error_code': True,
'fan_power': True,
'state': True,
'water_box_mode': True,
})
# ---
# name: test_is_attribute_supported[home_data_device_s5e.json]
dict({
'battery': True,
Expand All @@ -21,6 +32,17 @@
'water_box_mode': True,
})
# ---
# name: test_is_attribute_supported[home_data_device_saros.json]
dict({
'battery': True,
'charge_status': True,
'dry_status': True,
'error_code': True,
'fan_power': True,
'state': True,
'water_box_mode': True,
})
# ---
# name: test_is_attribute_supported[home_data_device_saros_10r.json]
dict({
'battery': True,
Expand All @@ -32,6 +54,13 @@
'water_box_mode': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_a123.json]
dict({
'filter_work_time': True,
'main_brush_work_time': True,
'side_brush_work_time': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_s5e.json]
dict({
'filter_work_time': True,
Expand All @@ -46,6 +75,13 @@
'side_brush_work_time': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_saros.json]
dict({
'filter_work_time': True,
'main_brush_work_time': True,
'side_brush_work_time': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_saros_10r.json]
dict({
'filter_work_time': True,
Expand Down
19 changes: 17 additions & 2 deletions tests/mock_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import pathlib
from typing import Any

from roborock.data.containers import HomeDataDevice, HomeDataProduct

# All data is based on a U.S. customer with a Roborock S7 MaxV Ultra
USER_EMAIL = "user@domain.com"

Expand Down Expand Up @@ -133,13 +135,26 @@
SS07_PRODUCT_DATA = PRODUCTS["home_data_product_ss07.json"]
A102_PRODUCT_DATA = PRODUCTS["home_data_product_a102.json"]
A114_PRODUCT_DATA = PRODUCTS["home_data_product_a114.json"]
A147_PRODUCT_DATA = PRODUCTS["home_data_product_a147.json"]

# Devices
S7_DEVICE_DATA = DEVICES["home_data_device_s7_maxv.json"]
S7_MAXV_DEVICE_DATA = DEVICES["home_data_device_s7_maxv.json"]
Q7_DEVICE_DATA = DEVICES["home_data_device_q7.json"]
Q10_DEVICE_DATA = DEVICES["home_data_device_q10.json"]
ZEO_ONE_DEVICE_DATA = DEVICES["home_data_device_zeo_one.json"]
SAROS_10R_DEVICE_DATA = DEVICES["home_data_device_saros_10r.json"]
SAROS_10_DEVICE_DATA = DEVICES["home_data_device_saros.json"]

# All testdata devices joined with their matching product (keyed by device filename).
# Devices whose productId has no corresponding product file are omitted.
_PRODUCTS_BY_ID: dict[str, HomeDataProduct] = {
p.id: p for p in (HomeDataProduct.from_dict(v) for v in PRODUCTS.values())
}
DEVICE_PRODUCT_PAIRS: dict[str, tuple[HomeDataDevice, HomeDataProduct]] = {
filename: (HomeDataDevice.from_dict(raw), product)
for filename, raw in DEVICES.items()
if (product := _PRODUCTS_BY_ID.get(HomeDataDevice.from_dict(raw).product_id)) is not None
}
Comment thread
allenporter marked this conversation as resolved.


HOME_DATA_RAW: dict[str, Any] = {
Expand All @@ -152,7 +167,7 @@
A27_PRODUCT_DATA,
],
"devices": [
S7_DEVICE_DATA,
S7_MAXV_DEVICE_DATA,
],
"receivedDevices": [],
"rooms": [
Expand Down
31 changes: 31 additions & 0 deletions tests/test_supported_features.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from dataclasses import asdict

import pytest
from syrupy import SnapshotAssertion

from roborock import SHORT_MODEL_TO_ENUM
from roborock.data.code_mappings import RoborockProductNickname
from roborock.device_features import DeviceFeatures
from tests import mock_data


def test_supported_features_qrevo_maxv():
Expand Down Expand Up @@ -73,3 +77,30 @@ def test_new_feature_str_missing():
assert not device_features.is_dust_collection_setting_supported
assert not device_features.is_hot_wash_towel_supported
assert not device_features.is_show_clean_finish_reason_supported


@pytest.mark.parametrize(
("device_filename"),
list(mock_data.DEVICE_PRODUCT_PAIRS.keys()),
Comment thread
allenporter marked this conversation as resolved.
)
def test_device_features_from_home_data(
device_filename: str,
snapshot: SnapshotAssertion,
) -> None:
"""Test DeviceFeatures constructed from real testdata devices and products.

For each paired device+product in testdata, construct DeviceFeatures from the
featureSet/newFeatureSet home data fields and assert the full feature dict
matches the snapshot. This catches regressions in feature-flag decoding
across all real device samples.
"""
device, product = mock_data.DEVICE_PRODUCT_PAIRS[device_filename]
device_features = DeviceFeatures.from_feature_flags(
new_feature_info=int(device.feature_set or "0"),
new_feature_info_str=device.new_feature_set or "",
feature_info=[],
product_nickname=product.product_nickname,
)
# Snapshot all boolean features as a sorted dict for stable output
feature_dict = {k: v for k, v in asdict(device_features).items() if isinstance(v, bool)}
Comment thread
allenporter marked this conversation as resolved.
assert feature_dict == snapshot
33 changes: 33 additions & 0 deletions tests/testdata/home_data_device_a123.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"duid": "device-id-a123",
"name": "Roborock PearlS Lite",
"localKey": "key123key123key1",
"productId": "product-id-a123",
"fv": "02.03.28",
"activeTime": 1742574271,
"timeZoneId": "America/Los_Angeles",
"iconUrl": "",
"share": false,
"online": true,
"pv": "1.0",
"tuyaMigrated": false,
"extra": "{}",
"sn": "A123SAMPLESNTEST",
"featureSet": "2247397454282751",
"newFeatureSet": "42BA8D587EDAFFFE",
"deviceStatus": {
"121": 8,
"122": 100,
"123": 110,
"124": 209,
"125": 0,
"126": 22,
"127": 0,
"128": 0,
"133": 1,
"120": 0,
"134": 0
},
"silentOtaSwitch": true,
"f": false
}
36 changes: 36 additions & 0 deletions tests/testdata/home_data_device_saros.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"duid": "device-id-saros-10",
"name": "Saros 10",
"localKey": "key123key123key1",
"productId": "product-saros-10",
"fv": "02.33.20",
"activeTime": 1672364449,
"timeZoneId": "Europe/Stockholm",
"iconUrl": "",
"share": false,
"online": true,
"pv": "1.0",
"tuyaMigrated": false,
"extra": "{}",
"sn": "device-saros-10-sn",
"featureSet": "4499195120484351",
"newFeatureSet": "0000000008EFBCDDDFFFAE7E7EFEFFFF",
"deviceStatus": {
"121": 8,
"122": 93,
"123": 102,
"124": 200,
"125": 100,
"126": 98,
"127": 100,
"128": 0,
"133": 1,
"135": 0,
"120": 0,
"134": 0
},
"silentOtaSwitch": false,
"f": false,
"createTime": 1766246477,
"cid": ""
}
136 changes: 136 additions & 0 deletions tests/testdata/home_data_product_a123.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"id": "product-id-a123",
"name": "Roborock PearlS Lite",
"model": "roborock.vacuum.a123",
"category": "robot.vacuum.cleaner",
"capability": 0,
"schema": [
{
"id": 101,
"name": "rpc_request",
"code": "rpc_request",
"mode": "rw",
"type": "RAW"
},
{
"id": 102,
"name": "rpc_response",
"code": "rpc_response",
"mode": "rw",
"type": "RAW"
},
{
"id": 120,
"name": "\u9519\u8bef\u4ee3\u7801",
"code": "error_code",
"mode": "ro",
"type": "ENUM",
"property": "{\"range\": [\"\"]}"
},
{
"id": 121,
"name": "\u8bbe\u5907\u72b6\u6001",
"code": "state",
"mode": "ro",
"type": "ENUM",
"property": "{\"range\": [\"\"]}"
},
{
"id": 122,
"name": "\u8bbe\u5907\u7535\u91cf",
"code": "battery",
"mode": "ro",
"type": "ENUM",
"property": "{\"range\": [\"\"]}"
},
{
"id": 123,
"name": "\u6e05\u626b\u6a21\u5f0f",
"code": "fan_power",
"mode": "rw",
"type": "ENUM",
"property": "{\"range\": [\"\"]}"
},
{
"id": 124,
"name": "\u62d6\u5730\u6a21\u5f0f",
"code": "water_box_mode",
"mode": "rw",
"type": "ENUM",
"property": "{\"range\": [\"\"]}"
},
{
"id": 125,
"name": "\u4e3b\u5237\u5bff\u547d",
"code": "main_brush_life",
"mode": "rw",
"type": "VALUE",
"property": "{\"max\": 100, \"min\": 0, \"step\": 1, \"unit\": \"null\", \"scale\": 1}"
},
{
"id": 126,
"name": "\u8fb9\u5237\u5bff\u547d",
"code": "side_brush_life",
"mode": "rw",
"type": "VALUE",
"property": "{\"max\": 100, \"min\": 0, \"step\": 1, \"unit\": \"null\", \"scale\": 1}"
},
{
"id": 127,
"name": "\u6ee4\u7f51\u5bff\u547d",
"code": "filter_life",
"mode": "rw",
"type": "VALUE",
"property": "{\"max\": 100, \"min\": 0, \"step\": 1, \"unit\": \"null\", \"scale\": 1}"
},
{
"id": 128,
"name": "\u989d\u5916\u72b6\u6001",
"code": "additional_props",
"mode": "ro",
"type": "RAW"
},
{
"id": 130,
"name": "\u5b8c\u6210\u4e8b\u4ef6",
"code": "task_complete",
"mode": "ro",
"type": "RAW"
},
{
"id": 131,
"name": "\u7535\u91cf\u4e0d\u8db3\u4efb\u52a1\u53d6\u6d88",
"code": "task_cancel_low_power",
"mode": "ro",
"type": "RAW"
},
{
"id": 132,
"name": "\u8fd0\u52a8\u4e2d\u4efb\u52a1\u53d6\u6d88",
"code": "task_cancel_in_motion",
"mode": "ro",
"type": "RAW"
},
{
"id": 133,
"name": "\u5145\u7535\u72b6\u6001",
"code": "charge_status",
"mode": "ro",
"type": "RAW"
},
{
"id": 134,
"name": "\u70d8\u5e72\u72b6\u6001",
"code": "drying_status",
"mode": "ro",
"type": "RAW"
},
{
"id": 135,
"name": "\u79bb\u7ebf\u539f\u56e0\u7ec6\u5206",
"code": "offline_status",
"mode": "ro",
"type": "RAW"
}
]
}
Loading
Loading