diff --git a/roborock/devices/device.py b/roborock/devices/device.py index d7908c74..509c7030 100644 --- a/roborock/devices/device.py +++ b/roborock/devices/device.py @@ -226,9 +226,11 @@ def diagnostic_data(self) -> dict[str, Any]: """Return diagnostics information about the device.""" extra: dict[str, Any] = {} if self.v1_properties: - extra["traits"] = redact_device_data(self.v1_properties.as_dict()) - return { - "device": redact_device_data(self.device_info.as_dict()), - "product": redact_device_data(self.product.as_dict()), - **extra, - } + extra["traits"] = self.v1_properties.as_dict() + return redact_device_data( + { + "device": self.device_info.as_dict(), + "product": self.product.as_dict(), + **extra, + } + ) diff --git a/roborock/devices/device_manager.py b/roborock/devices/device_manager.py index 9773923d..fb91ec00 100644 --- a/roborock/devices/device_manager.py +++ b/roborock/devices/device_manager.py @@ -4,7 +4,7 @@ import enum import logging from collections.abc import Callable, Mapping -from dataclasses import dataclass +from dataclasses import asdict, dataclass from typing import Any import aiohttp @@ -16,7 +16,7 @@ UserData, ) from roborock.devices.device import DeviceReadyCallback, RoborockDevice -from roborock.diagnostics import Diagnostics +from roborock.diagnostics import Diagnostics, redact_device_data from roborock.exceptions import RoborockException from roborock.map.map_parser import MapParserConfig from roborock.mqtt.roborock_session import create_lazy_mqtt_session @@ -76,6 +76,7 @@ def __init__( self._devices: dict[str, RoborockDevice] = {} self._mqtt_session = mqtt_session self._diagnostics = diagnostics + self._home_data: HomeData | None = None async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevice]: """Discover all devices for the logged-in user.""" @@ -91,9 +92,9 @@ async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevi raise _LOGGER.debug("Failed to fetch home data, using cached data: %s", ex) await self._cache.set(cache_data) - home_data = cache_data.home_data + self._home_data = cache_data.home_data - device_products = home_data.device_products + device_products = self._home_data.device_products _LOGGER.debug("Discovered %d devices", len(device_products)) # These are connected serially to avoid overwhelming the MQTT broker @@ -106,7 +107,7 @@ async def discover_devices(self, prefer_cache: bool = True) -> list[RoborockDevi if duid in self._devices: continue try: - new_device = self._device_creator(home_data, device, product) + new_device = self._device_creator(self._home_data, device, product) except UnsupportedDeviceError: _LOGGER.info("Skipping unsupported device %s %s", product.summary_info(), device.summary_info()) unsupported_devices_counter.increment(device.pv or "unknown") @@ -136,7 +137,11 @@ async def close(self) -> None: def diagnostic_data(self) -> Mapping[str, Any]: """Return diagnostics information about the device manager.""" - return self._diagnostics.as_dict() + return { + "home_data": redact_device_data(asdict(self._home_data) if self._home_data else {}), + "devices": [device.diagnostic_data() for device in self._devices.values()], + "diagnostics": self._diagnostics.as_dict(), + } @dataclass diff --git a/roborock/diagnostics.py b/roborock/diagnostics.py index b69d3ada..fb7ee199 100644 --- a/roborock/diagnostics.py +++ b/roborock/diagnostics.py @@ -101,30 +101,51 @@ def reset(self) -> None: "imageContent", "mapData", "rawApiResponse", + # Home data + "id", # We want to redact home_data.id but keep some other ids, see below + "name", + "productId", + "ipAddress", + "mac", + "wifiName", + "lat", + "long", +} +KEEP_KEYS = { + # Product information no unique per user + "product.id", + "product.schema.id", + "product.schema.name", + # Room ids are likely unique per user, but don't seem too sensitive and are + # useful for debugging + "rooms.id", } DEVICE_UID = "duid" REDACTED = "**REDACTED**" -def redact_device_data(data: T) -> T | dict[str, Any]: +def redact_device_data(data: T, path: str = "") -> T | dict[str, Any]: """Redact sensitive data in a dict.""" if not isinstance(data, (Mapping, list)): return data if isinstance(data, list): - return cast(T, [redact_device_data(item) for item in data]) + return cast(T, [redact_device_data(item, path) for item in data]) redacted = {**data} for key, value in redacted.items(): - if key in REDACT_KEYS: + curr_path = f"{path}.{key}" if path else key + if key in KEEP_KEYS or curr_path in KEEP_KEYS: + continue + if key in REDACT_KEYS or curr_path in REDACT_KEYS: redacted[key] = REDACTED elif key == DEVICE_UID and isinstance(value, str): redacted[key] = redact_device_uid(value) elif isinstance(value, dict): - redacted[key] = redact_device_data(value) + redacted[key] = redact_device_data(value, curr_path) elif isinstance(value, list): - redacted[key] = [redact_device_data(item) for item in value] + redacted[key] = [redact_device_data(item, curr_path) for item in value] return redacted diff --git a/tests/devices/__snapshots__/test_device_manager.ambr b/tests/devices/__snapshots__/test_device_manager.ambr new file mode 100644 index 00000000..591f1a06 --- /dev/null +++ b/tests/devices/__snapshots__/test_device_manager.ambr @@ -0,0 +1,651 @@ +# serializer version: 1 +# name: test_diagnostics_collection + dict({ + 'devices': list([ + dict({ + 'device': dict({ + 'activeTime': 1672364449, + 'deviceStatus': dict({ + '120': 0, + '121': 8, + '122': 100, + '123': 102, + '124': 203, + '125': 94, + '126': 90, + '127': 87, + '128': 0, + '133': 1, + }), + 'duid': '******bc123', + 'extra': '{"RRPhotoPrivacyVersion": "1"}', + 'featureSet': '2234201184108543', + 'fv': '02.56.02', + 'iconUrl': 'no_url', + 'localKey': '**REDACTED**', + 'name': '**REDACTED**', + 'newFeatureSet': '0000000000002041', + 'online': True, + 'productId': '**REDACTED**', + 'pv': '1.0', + 'roomId': 2362003, + 'share': False, + 'silentOtaSwitch': True, + 'sn': '**REDACTED**', + 'timeZoneId': 'America/Los_Angeles', + 'tuyaMigrated': False, + }), + 'product': dict({ + 'capability': 0, + 'category': 'robot.vacuum.cleaner', + 'code': 'a27', + 'id': 'product-id-s7-maxv', + 'model': 'roborock.vacuum.a27', + 'name': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'id': '101', + 'mode': 'rw', + 'name': 'rpc_request', + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'id': '102', + 'mode': 'rw', + 'name': 'rpc_response', + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'id': '120', + 'mode': 'ro', + 'name': '错误代码', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'id': '121', + 'mode': 'ro', + 'name': '设备状态', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'id': '122', + 'mode': 'ro', + 'name': '设备电量', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'id': '123', + 'mode': 'rw', + 'name': '清扫模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'id': '124', + 'mode': 'rw', + 'name': '拖地模式', + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'id': '125', + 'mode': 'rw', + 'name': '主刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'id': '126', + 'mode': 'rw', + 'name': '边刷寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'id': '127', + 'mode': 'rw', + 'name': '滤网寿命', + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'id': '128', + 'mode': 'ro', + 'name': '额外状态', + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'id': '130', + 'mode': 'ro', + 'name': '完成事件', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'id': '131', + 'mode': 'ro', + 'name': '电量不足任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'id': '132', + 'mode': 'ro', + 'name': '运动中任务取消', + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'id': '133', + 'mode': 'ro', + 'name': '充电状态', + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'id': '134', + 'mode': 'ro', + 'name': '烘干状态', + 'type': 'RAW', + }), + ]), + }), + 'traits': dict({ + 'device_features': dict({ + 'isActivateVideoChargingAndStandbySupported': False, + 'isAnalysisSupported': True, + 'isAnyStateTransitGotoSupported': False, + 'isAutoCollection2Supported': False, + 'isAutoDeliveryFieldInGlobalStatusSupported': False, + 'isAutoTearDownMopSupported': False, + 'isAvoidCollisionModeSupported': False, + 'isAvoidCollisionSupported': False, + 'isBackChargeAutoWashSupported': False, + 'isBackWashNewSmartSupported': False, + 'isCarefulSlowMopSupported': False, + 'isCarpetCustomCleanSupported': False, + 'isCarpetDeepCleanSupported': False, + 'isCarpetLongHairedExSupported': False, + 'isCarpetLongHairedSupported': False, + 'isCarpetPressureUseOriginParasSupported': False, + 'isCarpetShapeTypeSupported': False, + 'isCarpetShowOnMap': False, + 'isCarpetSupported': False, + 'isCes2022Supported': False, + 'isCleanCountSettingSupported': False, + 'isCleanDirectStatusSupported': False, + 'isCleanEfficiencySupported': False, + 'isCleanFluidDeliverySupported': False, + 'isCleanHistoryTimeLineSupported': False, + 'isCleanRouteDeepSlowPlusSupported': False, + 'isCleanRouteFastModeSupported': False, + 'isCleanRouteSettingSupported': True, + 'isCleanThenMopModeSupported': False, + 'isCleanTimeLineSupported': False, + 'isCollectDustCountShowSupported': False, + 'isCollectDustModeSupported': True, + 'isCornerCleanModeSupported': False, + 'isCornerMopStretchSupported': False, + 'isCtmWithRepeatSupported': False, + 'isCurrentMapRestoreEnabled': True, + 'isCustomCleanModeCountSupported': False, + 'isCustomModeSupported': True, + 'isCustomWaterBoxDistanceSupported': True, + 'isCustomizedCleanSupported': True, + 'isDetectWireCarpetSupported': False, + 'isDirtyObjectDetectSupported': False, + 'isDirtyReplenishCleanSupported': False, + 'isDryIntervalTimerSupported': False, + 'isDssBelievable': False, + 'isDualBandWiFiSupported': False, + 'isDustCollectionSettingSupported': False, + 'isDynamicallyAddCleanZonesSupported': False, + 'isDynamicallySkipCleanZoneSupported': False, + 'isEggDanceModeSupported': False, + 'isEggModeSupportedFromNewFeatures': False, + 'isExactCustomModeSupported': False, + 'isExhibitionFunctionSupported': False, + 'isFloorDirCleanAnyTimeSupported': False, + 'isFlowLedSettingSupported': False, + 'isFollowLowObsSupported': False, + 'isFullDuplesSwitchSupported': False, + 'isFwFilterObstacleSupported': False, + 'isGapDeepCleanSupported': False, + 'isGotoPureCleanPathSupported': False, + 'isHotWashTowelSupported': False, + 'isIdentifyRoomSupported': False, + 'isIgnoreUnknownMapObjectSupported': False, + 'isLdsLiftingSupported': False, + 'isLedStatusSwitchSupported': True, + 'isLeftWaterDrainSupported': False, + 'isLowAreaAccessSupported': False, + 'isMainBrushUpDownSupportedFromStr': False, + 'isMapBeautifyInternalDebugSupported': False, + 'isMapCarpetAddSupport': False, + 'isMapEraserSupported': False, + 'isMatterSupported': False, + 'isMaxPlusModeSupported': True, + 'isMaxZoneOpenedSupported': False, + 'isMechanicalArmModeSupported': False, + 'isMidwayBackToDockSupported': False, + 'isMinBattery15ToCleanTaskSupported': False, + 'isMopForbiddenSupported': True, + 'isMopPathSupported': False, + 'isMopShakeModuleSupported': True, + 'isMopShakeWaterMaxSupported': False, + 'isMultiFloorSupported': True, + 'isMultiMapSegmentTimerSupported': True, + 'isNewAiRecognitionSupported': False, + 'isNewDataForCleanHistory': False, + 'isNewDataForCleanHistoryDetail': False, + 'isNewEndpointSupported': False, + 'isNewRemoteViewSupported': True, + 'isNoNeedCarpetPressSetSupported': False, + 'isNonePureCleanMopWithMaxPlus': False, + 'isObjectDetectCheckSupported': False, + 'isOfflineMapSupported': False, + 'isOptimizeBatterySupported': False, + 'isOrderCleanSupported': True, + 'isOverSeaCtmSupported': False, + 'isPetSnapshotSupported': False, + 'isPetSuppliesDeepCleanSupported': False, + 'isProgramModeSupported': False, + 'isPumpingWaterSupported': False, + 'isPureCleanMopSupported': True, + 'isReSegmentSupported': True, + 'isRecordAllowed': False, + 'isRemoteSupported': True, + 'isRightBrushStretchSupported': False, + 'isRoomNameSupported': True, + 'isRpcRetrySupported': False, + 'isRubberBrushCarpetSupported': False, + 'isSetChildSupported': False, + 'isSettingCarpetFirstSupported': False, + 'isShakeMopSetSupported': False, + 'isShouldShowArmOverLoadSupported': False, + 'isShowCleanFinishReasonSupported': True, + 'isShowGeneralObstacleSupported': False, + 'isShowObstaclePhotoSupported': False, + 'isSideBrushLiftCarpetSupported': False, + 'isSmallSideMopSupported': False, + 'isSmartCleanModeSetSupported': False, + 'isSoakAndWashSupported': False, + 'isSoftCleanModeSupported': False, + 'isSrMapSupported': False, + 'isSuperDeepWashSupported': False, + 'isSupportApiAppStopGraspSupported': False, + 'isSupportBackupMap': True, + 'isSupportCleanEstimate': False, + 'isSupportCliffZone': False, + 'isSupportCustomCarpet': False, + 'isSupportCustomDnd': False, + 'isSupportCustomDoorSill': False, + 'isSupportCustomModeInCleaning': False, + 'isSupportFetchTimerSummary': True, + 'isSupportFloorDirection': False, + 'isSupportFloorEdit': False, + 'isSupportFurniture': False, + 'isSupportGetParticularStatusSupported': False, + 'isSupportIncrementalMap': False, + 'isSupportMainBrushUpDownSupported': False, + 'isSupportMopBackPwmSet': False, + 'isSupportQuickMapBuilder': True, + 'isSupportRemoteControlInCall': False, + 'isSupportRoomTag': False, + 'isSupportSetSwitchMapMode': False, + 'isSupportSetVolumeInCall': False, + 'isSupportSideBrushUpDownSupported': False, + 'isSupportSmartDoorSill': False, + 'isSupportSmartGlobalCleanWithCustomMode': False, + 'isSupportSmartScene': False, + 'isSupportStuckZone': False, + 'isSupportVoiceControlDebug': False, + 'isSupportWaterMode': True, + 'isSupportedDownloadTestVoice': False, + 'isSupportedDrying': False, + 'isSupportedValleyElectricity': False, + 'isSyncServerNameSupported': False, + 'isThreeDMappingInnerTestSupported': False, + 'isTidyupZonesSupported': False, + 'isTwoGearsNoCollisionSupported': False, + 'isTwoKeyRealTimeVideoSupported': False, + 'isTwoKeyRtvInChargingSupported': False, + 'isTypeIdentifySupported': False, + 'isUnsaveMapReasonSupported': True, + 'isUvcSterilizeSupported': False, + 'isVideoMonitorSupported': False, + 'isVideoPatrolSupported': False, + 'isVideoSettingSupported': False, + 'isVoiceControlLedSupported': False, + 'isVoiceControlSupported': False, + 'isWashThenChargeCmdSupported': False, + 'isWaterLeakCheckSupported': False, + 'isWaterSlideModeSupported': False, + 'isWaterUpDownDrainSupported': False, + 'isWifiManageSupported': False, + 'isWorkdayHolidaySupported': False, + }), + 'led_status': dict({ + 'status': 0, + }), + 'network_info': dict({ + 'ip': '**REDACTED**', + }), + 'status': dict({ + 'adbumperStatus': list([ + 0, + 0, + 0, + ]), + 'autoDustCollection': 1, + 'avoidCount': 19, + 'backType': -1, + 'battery': 100, + 'cameraStatus': 3457, + 'chargeStatus': 1, + 'cleanArea': 20965000, + 'cleanTime': 1176, + 'collisionAvoidStatus': 1, + 'debugMode': 0, + 'dndEnabled': 0, + 'dockErrorStatus': 0, + 'dockType': 3, + 'dustCollectionStatus': 0, + 'errorCode': 0, + 'fanPower': 102, + 'homeSecEnablePassword': 0, + 'homeSecStatus': 0, + 'inCleaning': 0, + 'inFreshState': 1, + 'inReturning': 0, + 'isExploring': 0, + 'isLocating': 0, + 'labStatus': 1, + 'lockStatus': 0, + 'mapPresent': 1, + 'mapStatus': 3, + 'mopForbiddenEnable': 1, + 'mopMode': 300, + 'msgSeq': 458, + 'msgVer': 2, + 'state': 8, + 'switchMapMode': 0, + 'unsaveMapFlag': 0, + 'unsaveMapReason': 0, + 'washPhase': 0, + 'washReady': 0, + 'waterBoxCarriageStatus': 1, + 'waterBoxMode': 203, + 'waterBoxStatus': 1, + 'waterShortageStatus': 0, + }), + }), + }), + ]), + 'diagnostics': dict({ + 'discover_devices': 1, + 'fetch_home_data': 1, + 'supported_devices': dict({ + '1.0': 1, + }), + }), + 'home_data': dict({ + 'devices': list([ + dict({ + 'active_time': 1672364449, + 'attribute': None, + 'cid': None, + 'create_time': None, + 'device_status': dict({ + '120': 0, + '121': 8, + '122': 100, + '123': 102, + '124': 203, + '125': 94, + '126': 90, + '127': 87, + '128': 0, + '133': 1, + }), + 'duid': '******bc123', + 'extra': '{"RRPhotoPrivacyVersion": "1"}', + 'f': None, + 'feature_set': '2234201184108543', + 'fv': '02.56.02', + 'icon_url': 'no_url', + 'lat': '**REDACTED**', + 'local_key': 'key123key123key1', + 'lon': None, + 'name': '**REDACTED**', + 'new_feature_set': '0000000000002041', + 'online': True, + 'product_id': 'product-id-s7-maxv', + 'pv': '1.0', + 'room_id': 2362003, + 'runtime_env': None, + 'setting': None, + 'share': False, + 'share_expired_time': None, + 'share_time': None, + 'share_type': None, + 'silent_ota_switch': True, + 'sn': '**REDACTED**', + 'time_zone_id': 'America/Los_Angeles', + 'tuya_migrated': False, + 'tuya_uuid': None, + }), + ]), + 'geo_name': None, + 'id': '**REDACTED**', + 'lat': '**REDACTED**', + 'lon': None, + 'name': '**REDACTED**', + 'products': list([ + dict({ + 'attribute': None, + 'capability': 0, + 'category': , + 'code': 'a27', + 'icon_url': None, + 'id': '**REDACTED**', + 'model': 'roborock.vacuum.a27', + 'name': '**REDACTED**', + 'schema': list([ + dict({ + 'code': 'rpc_request_code', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'rpc_response', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'error_code', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'state', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'battery', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'fan_power', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'water_box_mode', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"range": []}', + 'type': 'ENUM', + }), + dict({ + 'code': 'main_brush_life', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'side_brush_life', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'filter_life', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'rw', + 'name': '**REDACTED**', + 'product_property': None, + 'property': '{"max": 100, "min": 0, "step": 1, "unit": null, "scale": 1}', + 'type': 'VALUE', + }), + dict({ + 'code': 'additional_props', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'task_complete', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_low_power', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'task_cancel_in_motion', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'charge_status', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + dict({ + 'code': 'drying_status', + 'desc': None, + 'id': '**REDACTED**', + 'mode': 'ro', + 'name': '**REDACTED**', + 'product_property': None, + 'property': None, + 'type': 'RAW', + }), + ]), + }), + ]), + 'received_devices': list([ + ]), + 'rooms': list([ + dict({ + 'id': 2362048, + 'name': '**REDACTED**', + }), + dict({ + 'id': 2362044, + 'name': '**REDACTED**', + }), + dict({ + 'id': 2362041, + 'name': '**REDACTED**', + }), + ]), + }), + }) +# --- diff --git a/tests/devices/__snapshots__/test_v1_device.ambr b/tests/devices/__snapshots__/test_v1_device.ambr index 0a6fde4c..94f0c9ec 100644 --- a/tests/devices/__snapshots__/test_v1_device.ambr +++ b/tests/devices/__snapshots__/test_v1_device.ambr @@ -24,10 +24,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -42,7 +42,7 @@ 'code': 'a27', 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'schema': list([ dict({ 'code': 'rpc_request_code', @@ -450,10 +450,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -468,7 +468,7 @@ 'code': 'a27', 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'schema': list([ dict({ 'code': 'rpc_request_code', @@ -856,10 +856,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -874,7 +874,7 @@ 'code': 'a27', 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'schema': list([ dict({ 'code': 'rpc_request_code', @@ -1233,10 +1233,10 @@ 'fv': '02.56.02', 'iconUrl': 'no_url', 'localKey': '**REDACTED**', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'newFeatureSet': '0000000000002041', 'online': True, - 'productId': 'product-id-s7-maxv', + 'productId': '**REDACTED**', 'pv': '1.0', 'roomId': 2362003, 'share': False, @@ -1251,7 +1251,7 @@ 'code': 'a27', 'id': 'product-id-s7-maxv', 'model': 'roborock.vacuum.a27', - 'name': 'Roborock S7 MaxV', + 'name': '**REDACTED**', 'schema': list([ dict({ 'code': 'rpc_request_code', diff --git a/tests/devices/test_device_manager.py b/tests/devices/test_device_manager.py index c4758480..397ae2c5 100644 --- a/tests/devices/test_device_manager.py +++ b/tests/devices/test_device_manager.py @@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, Mock, patch import pytest +import syrupy from roborock.data import HomeData, UserData from roborock.devices.cache import InMemoryCache @@ -349,7 +350,7 @@ async def test_start_connect_unexpected_error(home_data: HomeData, channel_failu await create_device_manager(USER_PARAMS) -async def test_diagnostics_collection(home_data: HomeData) -> None: +async def test_diagnostics_collection(home_data: HomeData, snapshot: syrupy.SnapshotAssertion) -> None: """Test that diagnostics are collected correctly in the DeviceManager.""" device_manager = await create_device_manager(USER_PARAMS) devices = await device_manager.get_devices() @@ -357,8 +358,12 @@ async def test_diagnostics_collection(home_data: HomeData) -> None: diagnostics = device_manager.diagnostic_data() assert diagnostics is not None - assert diagnostics.get("discover_devices") == 1 - assert diagnostics.get("fetch_home_data") == 1 + diagnostics_data = diagnostics.get("diagnostics") + assert diagnostics_data + assert diagnostics_data.get("discover_devices") == 1 + assert diagnostics_data.get("fetch_home_data") == 1 + + assert snapshot == diagnostics await device_manager.close() @@ -410,6 +415,8 @@ async def test_unsupported_protocol_version() -> None: assert [device.duid for device in devices] == ["device-uid-1"] # Verify diagnostics - data = device_manager.diagnostic_data() - assert data.get("supported_devices") == {"1.0": 1} - assert data.get("unsupported_devices") == {"unknown-pv": 1} + diagnostics = device_manager.diagnostic_data() + diagnostics_data = diagnostics.get("diagnostics") + assert diagnostics_data + assert diagnostics_data.get("supported_devices") == {"1.0": 1} + assert diagnostics_data.get("unsupported_devices") == {"unknown-pv": 1}