Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,585 changes: 1,585 additions & 0 deletions tests/__snapshots__/test_supported_features.ambr

Large diffs are not rendered by default.

54 changes: 54 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,26 @@
# serializer version: 1
# name: test_is_attribute_supported[home_data_device_pearls.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_q5_max.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 +43,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 +65,20 @@
'water_box_mode': True,
})
# ---
# name: test_is_consumable_field_supported[home_data_device_pearls.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_q5_max.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 +93,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
28 changes: 24 additions & 4 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 @@ -124,8 +126,12 @@

TESTDATA = pathlib.Path("tests/testdata")

PRODUCTS = {file.name: json.load(file.open(encoding="utf-8")) for file in TESTDATA.glob("home_data_product_*.json")}
DEVICES = {file.name: json.load(file.open(encoding="utf-8")) for file in TESTDATA.glob("home_data_device_*.json")}
PRODUCTS = {
file.name: json.load(file.open(encoding="utf-8")) for file in sorted(TESTDATA.glob("home_data_product_*.json"))
}
DEVICES = {
file.name: json.load(file.open(encoding="utf-8")) for file in sorted(TESTDATA.glob("home_data_device_*.json"))
}

# Products
A27_PRODUCT_DATA = PRODUCTS["home_data_product_a27.json"]
Expand All @@ -135,12 +141,26 @@
A114_PRODUCT_DATA = PRODUCTS["home_data_product_a114.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"]

# 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())
}
_DEVICES_BY_FILENAME: dict[str, HomeDataDevice] = {
filename: HomeDataDevice.from_dict(device_data) for filename, device_data in DEVICES.items()
}
DEVICE_PRODUCT_PAIRS: dict[str, tuple[HomeDataDevice, HomeDataProduct]] = {
filename: (device, product)
for filename, device in _DEVICES_BY_FILENAME.items()
if (product := _PRODUCTS_BY_ID.get(device.product_id)) is not None
}


HOME_DATA_RAW: dict[str, Any] = {
"id": 123456,
Expand All @@ -152,7 +172,7 @@
A27_PRODUCT_DATA,
],
"devices": [
S7_DEVICE_DATA,
S7_MAXV_DEVICE_DATA,
],
"receivedDevices": [],
"rooms": [
Expand Down
30 changes: 30 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,29 @@ 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()),
)
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,
)
feature_dict = {k: v for k, v in asdict(device_features).items() if isinstance(v, bool)}
assert feature_dict == snapshot
33 changes: 33 additions & 0 deletions tests/testdata/home_data_device_pearls.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
}
32 changes: 32 additions & 0 deletions tests/testdata/home_data_device_q5_max.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"duid": "device-id-a125",
"name": "Roborock Q5 Max+",
"localKey": "key123key123key1",
"productId": "product-id-a125",
"fv": "02.01.24",
"activeTime": 1765325043,
"timeZoneId": "America/Chicago",
"iconUrl": "",
"share": false,
"online": true,
"pv": "1.0",
"tuyaMigrated": false,
"extra": "{}",
"sn": "A125SAMPLESNTEST",
"featureSet": "2097860516935167",
"newFeatureSet": "00000040385AFFF6",
"deviceStatus": {
"121": 8,
"122": 100,
"123": 106,
"125": 11,
"126": 0,
"127": 0,
"128": 0,
"133": 1,
"120": 0,
"124": 204
},
"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": ""
}
Loading
Loading