From a62ffeaa9983f5ce67242328ea8a3b4c8c054e5f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 9 Sep 2023 19:11:28 +0200 Subject: [PATCH] Decouple Withings sensor tests from yaml (#99691) * Decouple Withings sensor tests from yaml * Fix feedback * Add pytest fixture * Update tests/components/withings/test_sensor.py Co-authored-by: G Johansson * Update snapshots * Update snapshots --------- Co-authored-by: G Johansson --- tests/components/withings/__init__.py | 84 ++ tests/components/withings/conftest.py | 345 ++++- .../withings/snapshots/test_sensor.ambr | 1254 +++++++++++++++++ tests/components/withings/test_sensor.py | 355 +---- 4 files changed, 1727 insertions(+), 311 deletions(-) create mode 100644 tests/components/withings/snapshots/test_sensor.ambr diff --git a/tests/components/withings/__init__.py b/tests/components/withings/__init__.py index c1caac222a5..e148c1a2c84 100644 --- a/tests/components/withings/__init__.py +++ b/tests/components/withings/__init__.py @@ -1 +1,85 @@ """Tests for the withings component.""" +from collections.abc import Iterable +from typing import Any, Optional +from urllib.parse import urlparse + +import arrow +from withings_api import DateType +from withings_api.common import ( + GetSleepSummaryField, + MeasureGetMeasGroupCategory, + MeasureGetMeasResponse, + MeasureType, + SleepGetSummaryResponse, + UserGetDeviceResponse, +) + +from homeassistant.components.webhook import async_generate_url +from homeassistant.core import HomeAssistant + +from .common import ProfileConfig, WebhookResponse + + +async def call_webhook( + hass: HomeAssistant, webhook_id: str, data: dict[str, Any], client +) -> WebhookResponse: + """Call the webhook.""" + webhook_url = async_generate_url(hass, webhook_id) + + resp = await client.post( + urlparse(webhook_url).path, + data=data, + ) + + # Wait for remaining tasks to complete. + await hass.async_block_till_done() + + data: dict[str, Any] = await resp.json() + resp.close() + + return WebhookResponse(message=data["message"], message_code=data["code"]) + + +class MockWithings: + """Mock object for Withings.""" + + def __init__(self, user_profile: ProfileConfig): + """Initialize mock.""" + self.api_response_user_get_device = user_profile.api_response_user_get_device + self.api_response_measure_get_meas = user_profile.api_response_measure_get_meas + self.api_response_sleep_get_summary = ( + user_profile.api_response_sleep_get_summary + ) + + def user_get_device(self) -> UserGetDeviceResponse: + """Get devices.""" + if isinstance(self.api_response_user_get_device, Exception): + raise self.api_response_user_get_device + return self.api_response_user_get_device + + def measure_get_meas( + self, + meastype: MeasureType | None = None, + category: MeasureGetMeasGroupCategory | None = None, + startdate: DateType | None = None, + enddate: DateType | None = None, + offset: int | None = None, + lastupdate: DateType | None = None, + ) -> MeasureGetMeasResponse: + """Get measurements.""" + if isinstance(self.api_response_measure_get_meas, Exception): + raise self.api_response_measure_get_meas + return self.api_response_measure_get_meas + + def sleep_get_summary( + self, + data_fields: Iterable[GetSleepSummaryField], + startdateymd: Optional[DateType] = arrow.utcnow(), + enddateymd: Optional[DateType] = arrow.utcnow(), + offset: Optional[int] = None, + lastupdate: Optional[DateType] = arrow.utcnow(), + ) -> SleepGetSummaryResponse: + """Get sleep.""" + if isinstance(self.api_response_sleep_get_summary, Exception): + raise self.api_response_sleep_get_summary + return self.api_response_sleep_get_summary diff --git a/tests/components/withings/conftest.py b/tests/components/withings/conftest.py index 887a9b8179b..510fc980dc7 100644 --- a/tests/components/withings/conftest.py +++ b/tests/components/withings/conftest.py @@ -1,15 +1,276 @@ """Fixtures for tests.""" - +from collections.abc import Awaitable, Callable, Coroutine +import time +from typing import Any from unittest.mock import patch +import arrow import pytest +from withings_api.common import ( + GetSleepSummaryData, + GetSleepSummarySerie, + MeasureGetMeasGroup, + MeasureGetMeasGroupAttrib, + MeasureGetMeasGroupCategory, + MeasureGetMeasMeasure, + MeasureGetMeasResponse, + MeasureType, + SleepGetSummaryResponse, + SleepModel, +) +from homeassistant.components.application_credentials import ( + ClientCredential, + async_import_client_credential, +) +from homeassistant.components.withings.const import DOMAIN +from homeassistant.config import async_process_ha_core_config from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component +from homeassistant.util import dt as dt_util -from .common import ComponentFactory +from . import MockWithings +from .common import ComponentFactory, new_profile_config +from tests.common import MockConfigEntry from tests.test_util.aiohttp import AiohttpClientMocker +ComponentSetup = Callable[[], Awaitable[MockWithings]] + +CLIENT_ID = "1234" +CLIENT_SECRET = "5678" +SCOPES = [ + "user.info", + "user.metrics", + "user.activity", + "user.sleepevents", +] +TITLE = "henk" +WEBHOOK_ID = "55a7335ea8dee830eed4ef8f84cda8f6d80b83af0847dc74032e86120bffed5e" + +PERSON0 = new_profile_config( + profile="12345", + user_id=12345, + api_response_measure_get_meas=MeasureGetMeasResponse( + measuregrps=( + MeasureGetMeasGroup( + attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER, + category=MeasureGetMeasGroupCategory.REAL, + created=arrow.utcnow().shift(hours=-1), + date=arrow.utcnow().shift(hours=-1), + deviceid="DEV_ID", + grpid=1, + measures=( + MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=70), + MeasureGetMeasMeasure( + type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=5 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_FREE_MASS, unit=0, value=60 + ), + MeasureGetMeasMeasure( + type=MeasureType.MUSCLE_MASS, unit=0, value=50 + ), + MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=10), + MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=2), + MeasureGetMeasMeasure( + type=MeasureType.TEMPERATURE, unit=0, value=40 + ), + MeasureGetMeasMeasure( + type=MeasureType.BODY_TEMPERATURE, unit=0, value=40 + ), + MeasureGetMeasMeasure( + type=MeasureType.SKIN_TEMPERATURE, unit=0, value=20 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_RATIO, unit=-3, value=70 + ), + MeasureGetMeasMeasure( + type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=70 + ), + MeasureGetMeasMeasure( + type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=100 + ), + MeasureGetMeasMeasure( + type=MeasureType.HEART_RATE, unit=0, value=60 + ), + MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=95), + MeasureGetMeasMeasure( + type=MeasureType.HYDRATION, unit=-2, value=95 + ), + MeasureGetMeasMeasure( + type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=100 + ), + ), + ), + MeasureGetMeasGroup( + attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER, + category=MeasureGetMeasGroupCategory.REAL, + created=arrow.utcnow().shift(hours=-2), + date=arrow.utcnow().shift(hours=-2), + deviceid="DEV_ID", + grpid=1, + measures=( + MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=71), + MeasureGetMeasMeasure( + type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=51 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_FREE_MASS, unit=0, value=61 + ), + MeasureGetMeasMeasure( + type=MeasureType.MUSCLE_MASS, unit=0, value=51 + ), + MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=11), + MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=21), + MeasureGetMeasMeasure( + type=MeasureType.TEMPERATURE, unit=0, value=41 + ), + MeasureGetMeasMeasure( + type=MeasureType.BODY_TEMPERATURE, unit=0, value=41 + ), + MeasureGetMeasMeasure( + type=MeasureType.SKIN_TEMPERATURE, unit=0, value=21 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_RATIO, unit=-3, value=71 + ), + MeasureGetMeasMeasure( + type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=71 + ), + MeasureGetMeasMeasure( + type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=101 + ), + MeasureGetMeasMeasure( + type=MeasureType.HEART_RATE, unit=0, value=61 + ), + MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=96), + MeasureGetMeasMeasure( + type=MeasureType.HYDRATION, unit=-2, value=96 + ), + MeasureGetMeasMeasure( + type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=101 + ), + ), + ), + MeasureGetMeasGroup( + attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER_AMBIGUOUS, + category=MeasureGetMeasGroupCategory.REAL, + created=arrow.utcnow(), + date=arrow.utcnow(), + deviceid="DEV_ID", + grpid=1, + measures=( + MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=71), + MeasureGetMeasMeasure( + type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=4 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_FREE_MASS, unit=0, value=40 + ), + MeasureGetMeasMeasure( + type=MeasureType.MUSCLE_MASS, unit=0, value=51 + ), + MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=11), + MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=201), + MeasureGetMeasMeasure( + type=MeasureType.TEMPERATURE, unit=0, value=41 + ), + MeasureGetMeasMeasure( + type=MeasureType.BODY_TEMPERATURE, unit=0, value=34 + ), + MeasureGetMeasMeasure( + type=MeasureType.SKIN_TEMPERATURE, unit=0, value=21 + ), + MeasureGetMeasMeasure( + type=MeasureType.FAT_RATIO, unit=-3, value=71 + ), + MeasureGetMeasMeasure( + type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=71 + ), + MeasureGetMeasMeasure( + type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=101 + ), + MeasureGetMeasMeasure( + type=MeasureType.HEART_RATE, unit=0, value=61 + ), + MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=98), + MeasureGetMeasMeasure( + type=MeasureType.HYDRATION, unit=-2, value=96 + ), + MeasureGetMeasMeasure( + type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=102 + ), + ), + ), + ), + more=False, + timezone=dt_util.UTC, + updatetime=arrow.get("2019-08-01"), + offset=0, + ), + api_response_sleep_get_summary=SleepGetSummaryResponse( + more=False, + offset=0, + series=( + GetSleepSummarySerie( + timezone=dt_util.UTC, + model=SleepModel.SLEEP_MONITOR, + startdate=arrow.get("2019-02-01"), + enddate=arrow.get("2019-02-01"), + date=arrow.get("2019-02-01"), + modified=arrow.get(12345), + data=GetSleepSummaryData( + breathing_disturbances_intensity=110, + deepsleepduration=111, + durationtosleep=112, + durationtowakeup=113, + hr_average=114, + hr_max=115, + hr_min=116, + lightsleepduration=117, + remsleepduration=118, + rr_average=119, + rr_max=120, + rr_min=121, + sleep_score=122, + snoring=123, + snoringepisodecount=124, + wakeupcount=125, + wakeupduration=126, + ), + ), + GetSleepSummarySerie( + timezone=dt_util.UTC, + model=SleepModel.SLEEP_MONITOR, + startdate=arrow.get("2019-02-01"), + enddate=arrow.get("2019-02-01"), + date=arrow.get("2019-02-01"), + modified=arrow.get(12345), + data=GetSleepSummaryData( + breathing_disturbances_intensity=210, + deepsleepduration=211, + durationtosleep=212, + durationtowakeup=213, + hr_average=214, + hr_max=215, + hr_min=216, + lightsleepduration=217, + remsleepduration=218, + rr_average=219, + rr_max=220, + rr_min=221, + sleep_score=222, + snoring=223, + snoringepisodecount=224, + wakeupcount=225, + wakeupduration=226, + ), + ), + ), + ), +) + @pytest.fixture def component_factory( @@ -25,3 +286,83 @@ def component_factory( yield ComponentFactory( hass, api_class_mock, hass_client_no_auth, aioclient_mock ) + + +@pytest.fixture(name="scopes") +def mock_scopes() -> list[str]: + """Fixture to set the scopes present in the OAuth token.""" + return SCOPES + + +@pytest.fixture(autouse=True) +async def setup_credentials(hass: HomeAssistant) -> None: + """Fixture to setup credentials.""" + assert await async_setup_component(hass, "application_credentials", {}) + await async_import_client_credential( + hass, + DOMAIN, + ClientCredential(CLIENT_ID, CLIENT_SECRET), + DOMAIN, + ) + + +@pytest.fixture(name="expires_at") +def mock_expires_at() -> int: + """Fixture to set the oauth token expiration time.""" + return time.time() + 3600 + + +@pytest.fixture(name="config_entry") +def mock_config_entry(expires_at: int, scopes: list[str]) -> MockConfigEntry: + """Create Withings entry in Home Assistant.""" + return MockConfigEntry( + domain=DOMAIN, + title=TITLE, + unique_id="12345", + data={ + "auth_implementation": DOMAIN, + "token": { + "status": 0, + "userid": "12345", + "access_token": "mock-access-token", + "refresh_token": "mock-refresh-token", + "expires_at": expires_at, + "scope": ",".join(scopes), + }, + "profile": TITLE, + "use_webhook": True, + "webhook_id": WEBHOOK_ID, + }, + ) + + +@pytest.fixture(name="setup_integration") +async def mock_setup_integration( + hass: HomeAssistant, config_entry: MockConfigEntry +) -> Callable[[], Coroutine[Any, Any, MockWithings]]: + """Fixture for setting up the component.""" + config_entry.add_to_hass(hass) + + assert await async_setup_component(hass, "application_credentials", {}) + await async_import_client_credential( + hass, + DOMAIN, + ClientCredential(CLIENT_ID, CLIENT_SECRET), + DOMAIN, + ) + await async_process_ha_core_config( + hass, + {"internal_url": "http://example.local:8123"}, + ) + + async def func() -> MockWithings: + mock = MockWithings(PERSON0) + with patch( + "homeassistant.components.withings.common.ConfigEntryWithingsApi", + return_value=mock, + ): + assert await async_setup_component(hass, DOMAIN, {}) + await hass.async_block_till_done() + return mock + + return func diff --git a/tests/components/withings/snapshots/test_sensor.ambr b/tests/components/withings/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..6aa9e5b3784 --- /dev/null +++ b/tests/components/withings/snapshots/test_sensor.ambr @@ -0,0 +1,1254 @@ +# serializer version: 1 +# name: test_all_entities + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'weight', + 'friendly_name': 'henk Weight', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_weight', + 'last_changed': , + 'last_updated': , + 'state': '70.0', + }) +# --- +# name: test_all_entities.1 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'weight', + 'friendly_name': 'henk Fat mass', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_fat_mass', + 'last_changed': , + 'last_updated': , + 'state': '5.0', + }) +# --- +# name: test_all_entities.10 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Diastolic blood pressure', + 'state_class': , + 'unit_of_measurement': 'mmhg', + }), + 'context': , + 'entity_id': 'sensor.henk_diastolic_blood_pressure', + 'last_changed': , + 'last_updated': , + 'state': '70.0', + }) +# --- +# name: test_all_entities.11 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Systolic blood pressure', + 'state_class': , + 'unit_of_measurement': 'mmhg', + }), + 'context': , + 'entity_id': 'sensor.henk_systolic_blood_pressure', + 'last_changed': , + 'last_updated': , + 'state': '100.0', + }) +# --- +# name: test_all_entities.12 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Heart pulse', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.henk_heart_pulse', + 'last_changed': , + 'last_updated': , + 'state': '60.0', + }) +# --- +# name: test_all_entities.13 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk SpO2', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.henk_spo2', + 'last_changed': , + 'last_updated': , + 'state': '0.95', + }) +# --- +# name: test_all_entities.14 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'weight', + 'friendly_name': 'henk Hydration', + 'icon': 'mdi:water', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_hydration', + 'last_changed': , + 'last_updated': , + 'state': '0.95', + }) +# --- +# name: test_all_entities.15 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'speed', + 'friendly_name': 'henk Pulse wave velocity', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_pulse_wave_velocity', + 'last_changed': , + 'last_updated': , + 'state': '100.0', + }) +# --- +# name: test_all_entities.16 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Breathing disturbances intensity', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.henk_breathing_disturbances_intensity', + 'last_changed': , + 'last_updated': , + 'state': '160.0', + }) +# --- +# name: test_all_entities.17 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'henk Deep sleep', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_deep_sleep', + 'last_changed': , + 'last_updated': , + 'state': '322', + }) +# --- +# name: test_all_entities.18 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'henk Time to sleep', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_time_to_sleep', + 'last_changed': , + 'last_updated': , + 'state': '162.0', + }) +# --- +# name: test_all_entities.19 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'henk Time to wakeup', + 'icon': 'mdi:sleep-off', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_time_to_wakeup', + 'last_changed': , + 'last_updated': , + 'state': '163.0', + }) +# --- +# name: test_all_entities.2 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'weight', + 'friendly_name': 'henk Fat free mass', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_fat_free_mass', + 'last_changed': , + 'last_updated': , + 'state': '60.0', + }) +# --- +# name: test_all_entities.20 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Average heart rate', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.henk_average_heart_rate', + 'last_changed': , + 'last_updated': , + 'state': '164.0', + }) +# --- +# name: test_all_entities.21 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Fat mass', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.henk_fat_mass_2', + 'last_changed': , + 'last_updated': , + 'state': '165.0', + }) +# --- +# name: test_all_entities.22 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Maximum heart rate', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.henk_maximum_heart_rate', + 'last_changed': , + 'last_updated': , + 'state': '166.0', + }) +# --- +# name: test_all_entities.23 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'henk Light sleep', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_light_sleep', + 'last_changed': , + 'last_updated': , + 'state': '334', + }) +# --- +# name: test_all_entities.24 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'henk REM sleep', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_rem_sleep', + 'last_changed': , + 'last_updated': , + 'state': '336', + }) +# --- +# name: test_all_entities.25 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Average respiratory rate', + 'state_class': , + 'unit_of_measurement': 'br/min', + }), + 'context': , + 'entity_id': 'sensor.henk_average_respiratory_rate', + 'last_changed': , + 'last_updated': , + 'state': '169.0', + }) +# --- +# name: test_all_entities.26 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Maximum respiratory rate', + 'state_class': , + 'unit_of_measurement': 'br/min', + }), + 'context': , + 'entity_id': 'sensor.henk_maximum_respiratory_rate', + 'last_changed': , + 'last_updated': , + 'state': '170.0', + }) +# --- +# name: test_all_entities.27 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Minimum respiratory rate', + 'state_class': , + 'unit_of_measurement': 'br/min', + }), + 'context': , + 'entity_id': 'sensor.henk_minimum_respiratory_rate', + 'last_changed': , + 'last_updated': , + 'state': '171.0', + }) +# --- +# name: test_all_entities.28 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Sleep score', + 'icon': 'mdi:medal', + 'state_class': , + 'unit_of_measurement': 'points', + }), + 'context': , + 'entity_id': 'sensor.henk_sleep_score', + 'last_changed': , + 'last_updated': , + 'state': '222', + }) +# --- +# name: test_all_entities.29 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Snoring', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.henk_snoring', + 'last_changed': , + 'last_updated': , + 'state': '173.0', + }) +# --- +# name: test_all_entities.3 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'weight', + 'friendly_name': 'henk Muscle mass', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_muscle_mass', + 'last_changed': , + 'last_updated': , + 'state': '50.0', + }) +# --- +# name: test_all_entities.30 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Snoring episode count', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.henk_snoring_episode_count', + 'last_changed': , + 'last_updated': , + 'state': '348', + }) +# --- +# name: test_all_entities.31 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Wakeup count', + 'icon': 'mdi:sleep-off', + 'state_class': , + 'unit_of_measurement': 'times', + }), + 'context': , + 'entity_id': 'sensor.henk_wakeup_count', + 'last_changed': , + 'last_updated': , + 'state': '350', + }) +# --- +# name: test_all_entities.32 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'henk Wakeup time', + 'icon': 'mdi:sleep-off', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_wakeup_time', + 'last_changed': , + 'last_updated': , + 'state': '176.0', + }) +# --- +# name: test_all_entities.33 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_breathing_disturbances_intensity henk', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_breathing_disturbances_intensity_henk', + 'last_changed': , + 'last_updated': , + 'state': '160.0', + }) +# --- +# name: test_all_entities.34 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_deep_duration_seconds_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:sleep', + 'original_name': 'Withings sleep_deep_duration_seconds henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_deep_duration_seconds', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities.35 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Withings sleep_deep_duration_seconds henk', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_deep_duration_seconds_henk', + 'last_changed': , + 'last_updated': , + 'state': '322', + }) +# --- +# name: test_all_entities.36 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_tosleep_duration_seconds_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:sleep', + 'original_name': 'Withings sleep_tosleep_duration_seconds henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_tosleep_duration_seconds', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities.37 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Withings sleep_tosleep_duration_seconds henk', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_tosleep_duration_seconds_henk', + 'last_changed': , + 'last_updated': , + 'state': '162.0', + }) +# --- +# name: test_all_entities.38 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_towakeup_duration_seconds_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:sleep-off', + 'original_name': 'Withings sleep_towakeup_duration_seconds henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_towakeup_duration_seconds', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities.39 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Withings sleep_towakeup_duration_seconds henk', + 'icon': 'mdi:sleep-off', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_towakeup_duration_seconds_henk', + 'last_changed': , + 'last_updated': , + 'state': '163.0', + }) +# --- +# name: test_all_entities.4 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'weight', + 'friendly_name': 'henk Bone mass', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_bone_mass', + 'last_changed': , + 'last_updated': , + 'state': '10.0', + }) +# --- +# name: test_all_entities.40 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_heart_rate_average_bpm_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:heart-pulse', + 'original_name': 'Withings sleep_heart_rate_average_bpm henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_heart_rate_average_bpm', + 'unit_of_measurement': 'bpm', + }) +# --- +# name: test_all_entities.41 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_heart_rate_average_bpm henk', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_heart_rate_average_bpm_henk', + 'last_changed': , + 'last_updated': , + 'state': '164.0', + }) +# --- +# name: test_all_entities.42 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_heart_rate_max_bpm_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:heart-pulse', + 'original_name': 'Withings sleep_heart_rate_max_bpm henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_heart_rate_max_bpm', + 'unit_of_measurement': 'bpm', + }) +# --- +# name: test_all_entities.43 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_heart_rate_max_bpm henk', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_heart_rate_max_bpm_henk', + 'last_changed': , + 'last_updated': , + 'state': '165.0', + }) +# --- +# name: test_all_entities.44 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_heart_rate_min_bpm_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:heart-pulse', + 'original_name': 'Withings sleep_heart_rate_min_bpm henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_heart_rate_min_bpm', + 'unit_of_measurement': 'bpm', + }) +# --- +# name: test_all_entities.45 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_heart_rate_min_bpm henk', + 'icon': 'mdi:heart-pulse', + 'state_class': , + 'unit_of_measurement': 'bpm', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_heart_rate_min_bpm_henk', + 'last_changed': , + 'last_updated': , + 'state': '166.0', + }) +# --- +# name: test_all_entities.46 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_light_duration_seconds_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:sleep', + 'original_name': 'Withings sleep_light_duration_seconds henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_light_duration_seconds', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities.47 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Withings sleep_light_duration_seconds henk', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_light_duration_seconds_henk', + 'last_changed': , + 'last_updated': , + 'state': '334', + }) +# --- +# name: test_all_entities.48 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_rem_duration_seconds_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:sleep', + 'original_name': 'Withings sleep_rem_duration_seconds henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_rem_duration_seconds', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities.49 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Withings sleep_rem_duration_seconds henk', + 'icon': 'mdi:sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_rem_duration_seconds_henk', + 'last_changed': , + 'last_updated': , + 'state': '336', + }) +# --- +# name: test_all_entities.5 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'distance', + 'friendly_name': 'henk Height', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_height', + 'last_changed': , + 'last_updated': , + 'state': '2.0', + }) +# --- +# name: test_all_entities.50 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_respiratory_average_bpm_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Withings sleep_respiratory_average_bpm henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_respiratory_average_bpm', + 'unit_of_measurement': 'br/min', + }) +# --- +# name: test_all_entities.51 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_respiratory_average_bpm henk', + 'state_class': , + 'unit_of_measurement': 'br/min', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_respiratory_average_bpm_henk', + 'last_changed': , + 'last_updated': , + 'state': '169.0', + }) +# --- +# name: test_all_entities.52 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_respiratory_max_bpm_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Withings sleep_respiratory_max_bpm henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_respiratory_max_bpm', + 'unit_of_measurement': 'br/min', + }) +# --- +# name: test_all_entities.53 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_respiratory_max_bpm henk', + 'state_class': , + 'unit_of_measurement': 'br/min', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_respiratory_max_bpm_henk', + 'last_changed': , + 'last_updated': , + 'state': '170.0', + }) +# --- +# name: test_all_entities.54 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_respiratory_min_bpm_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Withings sleep_respiratory_min_bpm henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_respiratory_min_bpm', + 'unit_of_measurement': 'br/min', + }) +# --- +# name: test_all_entities.55 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_respiratory_min_bpm henk', + 'state_class': , + 'unit_of_measurement': 'br/min', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_respiratory_min_bpm_henk', + 'last_changed': , + 'last_updated': , + 'state': '171.0', + }) +# --- +# name: test_all_entities.56 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_score_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:medal', + 'original_name': 'Withings sleep_score henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_score', + 'unit_of_measurement': 'points', + }) +# --- +# name: test_all_entities.57 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_score henk', + 'icon': 'mdi:medal', + 'state_class': , + 'unit_of_measurement': 'points', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_score_henk', + 'last_changed': , + 'last_updated': , + 'state': '222', + }) +# --- +# name: test_all_entities.58 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_snoring_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Withings sleep_snoring henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_snoring', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities.59 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_snoring henk', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_snoring_henk', + 'last_changed': , + 'last_updated': , + 'state': '173.0', + }) +# --- +# name: test_all_entities.6 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'henk Temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_temperature', + 'last_changed': , + 'last_updated': , + 'state': '40.0', + }) +# --- +# name: test_all_entities.60 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_snoring_eposode_count_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Withings sleep_snoring_eposode_count henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_snoring_eposode_count', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities.61 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_snoring_eposode_count henk', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_snoring_eposode_count_henk', + 'last_changed': , + 'last_updated': , + 'state': '348', + }) +# --- +# name: test_all_entities.62 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_wakeup_count_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': 'mdi:sleep-off', + 'original_name': 'Withings sleep_wakeup_count henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_wakeup_count', + 'unit_of_measurement': 'times', + }) +# --- +# name: test_all_entities.63 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Withings sleep_wakeup_count henk', + 'icon': 'mdi:sleep-off', + 'state_class': , + 'unit_of_measurement': 'times', + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_wakeup_count_henk', + 'last_changed': , + 'last_updated': , + 'state': '350', + }) +# --- +# name: test_all_entities.64 + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.withings_sleep_wakeup_duration_seconds_henk', + 'has_entity_name': False, + 'hidden_by': None, + 'icon': None, + 'id': , + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': 'mdi:sleep-off', + 'original_name': 'Withings sleep_wakeup_duration_seconds henk', + 'platform': 'withings', + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'withings_12345_sleep_wakeup_duration_seconds', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities.65 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'Withings sleep_wakeup_duration_seconds henk', + 'icon': 'mdi:sleep-off', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.withings_sleep_wakeup_duration_seconds_henk', + 'last_changed': , + 'last_updated': , + 'state': '176.0', + }) +# --- +# name: test_all_entities.7 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'henk Body temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_body_temperature', + 'last_changed': , + 'last_updated': , + 'state': '40.0', + }) +# --- +# name: test_all_entities.8 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'henk Skin temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.henk_skin_temperature', + 'last_changed': , + 'last_updated': , + 'state': '20.0', + }) +# --- +# name: test_all_entities.9 + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'henk Fat ratio', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.henk_fat_ratio', + 'last_changed': , + 'last_updated': , + 'state': '0.07', + }) +# --- diff --git a/tests/components/withings/test_sensor.py b/tests/components/withings/test_sensor.py index 6c4bb867f75..07fcb8fedaa 100644 --- a/tests/components/withings/test_sensor.py +++ b/tests/components/withings/test_sensor.py @@ -2,20 +2,9 @@ from typing import Any from unittest.mock import patch -import arrow -from withings_api.common import ( - GetSleepSummaryData, - GetSleepSummarySerie, - MeasureGetMeasGroup, - MeasureGetMeasGroupAttrib, - MeasureGetMeasGroupCategory, - MeasureGetMeasMeasure, - MeasureGetMeasResponse, - MeasureType, - NotifyAppli, - SleepGetSummaryResponse, - SleepModel, -) +import pytest +from syrupy import SnapshotAssertion +from withings_api.common import NotifyAppli from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.withings.common import WithingsEntityDescription @@ -24,236 +13,17 @@ from homeassistant.components.withings.sensor import SENSORS from homeassistant.core import HomeAssistant, State from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_registry import EntityRegistry -from homeassistant.util import dt as dt_util -from .common import ComponentFactory, async_get_entity_id, new_profile_config +from . import MockWithings, call_webhook +from .common import async_get_entity_id +from .conftest import PERSON0, WEBHOOK_ID, ComponentSetup + +from tests.typing import ClientSessionGenerator WITHINGS_MEASUREMENTS_MAP: dict[Measurement, WithingsEntityDescription] = { attr.measurement: attr for attr in SENSORS } -PERSON0 = new_profile_config( - "person0", - 0, - api_response_measure_get_meas=MeasureGetMeasResponse( - measuregrps=( - MeasureGetMeasGroup( - attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER, - category=MeasureGetMeasGroupCategory.REAL, - created=arrow.utcnow().shift(hours=-1), - date=arrow.utcnow().shift(hours=-1), - deviceid="DEV_ID", - grpid=1, - measures=( - MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=70), - MeasureGetMeasMeasure( - type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=5 - ), - MeasureGetMeasMeasure( - type=MeasureType.FAT_FREE_MASS, unit=0, value=60 - ), - MeasureGetMeasMeasure( - type=MeasureType.MUSCLE_MASS, unit=0, value=50 - ), - MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=10), - MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=2), - MeasureGetMeasMeasure( - type=MeasureType.TEMPERATURE, unit=0, value=40 - ), - MeasureGetMeasMeasure( - type=MeasureType.BODY_TEMPERATURE, unit=0, value=40 - ), - MeasureGetMeasMeasure( - type=MeasureType.SKIN_TEMPERATURE, unit=0, value=20 - ), - MeasureGetMeasMeasure( - type=MeasureType.FAT_RATIO, unit=-3, value=70 - ), - MeasureGetMeasMeasure( - type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=70 - ), - MeasureGetMeasMeasure( - type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=100 - ), - MeasureGetMeasMeasure( - type=MeasureType.HEART_RATE, unit=0, value=60 - ), - MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=95), - MeasureGetMeasMeasure( - type=MeasureType.HYDRATION, unit=-2, value=95 - ), - MeasureGetMeasMeasure( - type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=100 - ), - ), - ), - MeasureGetMeasGroup( - attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER, - category=MeasureGetMeasGroupCategory.REAL, - created=arrow.utcnow().shift(hours=-2), - date=arrow.utcnow().shift(hours=-2), - deviceid="DEV_ID", - grpid=1, - measures=( - MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=71), - MeasureGetMeasMeasure( - type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=51 - ), - MeasureGetMeasMeasure( - type=MeasureType.FAT_FREE_MASS, unit=0, value=61 - ), - MeasureGetMeasMeasure( - type=MeasureType.MUSCLE_MASS, unit=0, value=51 - ), - MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=11), - MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=21), - MeasureGetMeasMeasure( - type=MeasureType.TEMPERATURE, unit=0, value=41 - ), - MeasureGetMeasMeasure( - type=MeasureType.BODY_TEMPERATURE, unit=0, value=41 - ), - MeasureGetMeasMeasure( - type=MeasureType.SKIN_TEMPERATURE, unit=0, value=21 - ), - MeasureGetMeasMeasure( - type=MeasureType.FAT_RATIO, unit=-3, value=71 - ), - MeasureGetMeasMeasure( - type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=71 - ), - MeasureGetMeasMeasure( - type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=101 - ), - MeasureGetMeasMeasure( - type=MeasureType.HEART_RATE, unit=0, value=61 - ), - MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=96), - MeasureGetMeasMeasure( - type=MeasureType.HYDRATION, unit=-2, value=96 - ), - MeasureGetMeasMeasure( - type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=101 - ), - ), - ), - MeasureGetMeasGroup( - attrib=MeasureGetMeasGroupAttrib.DEVICE_ENTRY_FOR_USER_AMBIGUOUS, - category=MeasureGetMeasGroupCategory.REAL, - created=arrow.utcnow(), - date=arrow.utcnow(), - deviceid="DEV_ID", - grpid=1, - measures=( - MeasureGetMeasMeasure(type=MeasureType.WEIGHT, unit=0, value=71), - MeasureGetMeasMeasure( - type=MeasureType.FAT_MASS_WEIGHT, unit=0, value=4 - ), - MeasureGetMeasMeasure( - type=MeasureType.FAT_FREE_MASS, unit=0, value=40 - ), - MeasureGetMeasMeasure( - type=MeasureType.MUSCLE_MASS, unit=0, value=51 - ), - MeasureGetMeasMeasure(type=MeasureType.BONE_MASS, unit=0, value=11), - MeasureGetMeasMeasure(type=MeasureType.HEIGHT, unit=0, value=201), - MeasureGetMeasMeasure( - type=MeasureType.TEMPERATURE, unit=0, value=41 - ), - MeasureGetMeasMeasure( - type=MeasureType.BODY_TEMPERATURE, unit=0, value=34 - ), - MeasureGetMeasMeasure( - type=MeasureType.SKIN_TEMPERATURE, unit=0, value=21 - ), - MeasureGetMeasMeasure( - type=MeasureType.FAT_RATIO, unit=-3, value=71 - ), - MeasureGetMeasMeasure( - type=MeasureType.DIASTOLIC_BLOOD_PRESSURE, unit=0, value=71 - ), - MeasureGetMeasMeasure( - type=MeasureType.SYSTOLIC_BLOOD_PRESSURE, unit=0, value=101 - ), - MeasureGetMeasMeasure( - type=MeasureType.HEART_RATE, unit=0, value=61 - ), - MeasureGetMeasMeasure(type=MeasureType.SP02, unit=-2, value=98), - MeasureGetMeasMeasure( - type=MeasureType.HYDRATION, unit=-2, value=96 - ), - MeasureGetMeasMeasure( - type=MeasureType.PULSE_WAVE_VELOCITY, unit=0, value=102 - ), - ), - ), - ), - more=False, - timezone=dt_util.UTC, - updatetime=arrow.get("2019-08-01"), - offset=0, - ), - api_response_sleep_get_summary=SleepGetSummaryResponse( - more=False, - offset=0, - series=( - GetSleepSummarySerie( - timezone=dt_util.UTC, - model=SleepModel.SLEEP_MONITOR, - startdate=arrow.get("2019-02-01"), - enddate=arrow.get("2019-02-01"), - date=arrow.get("2019-02-01"), - modified=arrow.get(12345), - data=GetSleepSummaryData( - breathing_disturbances_intensity=110, - deepsleepduration=111, - durationtosleep=112, - durationtowakeup=113, - hr_average=114, - hr_max=115, - hr_min=116, - lightsleepduration=117, - remsleepduration=118, - rr_average=119, - rr_max=120, - rr_min=121, - sleep_score=122, - snoring=123, - snoringepisodecount=124, - wakeupcount=125, - wakeupduration=126, - ), - ), - GetSleepSummarySerie( - timezone=dt_util.UTC, - model=SleepModel.SLEEP_MONITOR, - startdate=arrow.get("2019-02-01"), - enddate=arrow.get("2019-02-01"), - date=arrow.get("2019-02-01"), - modified=arrow.get(12345), - data=GetSleepSummaryData( - breathing_disturbances_intensity=210, - deepsleepduration=211, - durationtosleep=212, - durationtowakeup=213, - hr_average=214, - hr_max=215, - hr_min=216, - lightsleepduration=217, - remsleepduration=218, - rr_average=219, - rr_max=220, - rr_min=221, - sleep_score=222, - snoring=223, - snoringepisodecount=224, - wakeupcount=225, - wakeupduration=226, - ), - ), - ), - ), -) EXPECTED_DATA = ( (PERSON0, Measurement.WEIGHT_KG, 70.0), @@ -304,79 +74,22 @@ def async_assert_state_equals( ) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_sensor_default_enabled_entities( hass: HomeAssistant, - component_factory: ComponentFactory, - current_request_with_host: None, + setup_integration: ComponentSetup, + hass_client_no_auth: ClientSessionGenerator, ) -> None: """Test entities enabled by default.""" + await setup_integration() entity_registry: EntityRegistry = er.async_get(hass) - await component_factory.configure_component(profile_configs=(PERSON0,)) - - # Assert entities should not exist yet. - for attribute in SENSORS: - assert not await async_get_entity_id( - hass, attribute, PERSON0.user_id, SENSOR_DOMAIN - ) - - # person 0 - await component_factory.setup_profile(PERSON0.user_id) - - # Assert entities should exist. - for attribute in SENSORS: - entity_id = await async_get_entity_id( - hass, attribute, PERSON0.user_id, SENSOR_DOMAIN - ) - assert entity_id - assert entity_registry.async_is_registered(entity_id) - - resp = await component_factory.call_webhook(PERSON0.user_id, NotifyAppli.SLEEP) - assert resp.message_code == 0 - - resp = await component_factory.call_webhook(PERSON0.user_id, NotifyAppli.WEIGHT) - assert resp.message_code == 0 - - for person, measurement, expected in EXPECTED_DATA: - attribute = WITHINGS_MEASUREMENTS_MAP[measurement] - entity_id = await async_get_entity_id( - hass, attribute, person.user_id, SENSOR_DOMAIN - ) - state_obj = hass.states.get(entity_id) - - if attribute.entity_registry_enabled_default: - async_assert_state_equals(entity_id, state_obj, expected, attribute) - else: - assert state_obj is None - - # Unload - await component_factory.unload(PERSON0) - - -async def test_all_entities( - hass: HomeAssistant, - component_factory: ComponentFactory, - current_request_with_host: None, -) -> None: - """Test all entities.""" - entity_registry: EntityRegistry = er.async_get(hass) - + mock = MockWithings(PERSON0) with patch( - "homeassistant.components.withings.sensor.BaseWithingsSensor.entity_registry_enabled_default" - ) as enabled_by_default_mock: - enabled_by_default_mock.return_value = True - - await component_factory.configure_component(profile_configs=(PERSON0,)) - - # Assert entities should not exist yet. - for attribute in SENSORS: - assert not await async_get_entity_id( - hass, attribute, PERSON0.user_id, SENSOR_DOMAIN - ) - - # person 0 - await component_factory.setup_profile(PERSON0.user_id) - + "homeassistant.components.withings.common.ConfigEntryWithingsApi", + return_value=mock, + ): + client = await hass_client_no_auth() # Assert entities should exist. for attribute in SENSORS: entity_id = await async_get_entity_id( @@ -384,11 +97,21 @@ async def test_all_entities( ) assert entity_id assert entity_registry.async_is_registered(entity_id) - - resp = await component_factory.call_webhook(PERSON0.user_id, NotifyAppli.SLEEP) + resp = await call_webhook( + hass, + WEBHOOK_ID, + {"userid": PERSON0.user_id, "appli": NotifyAppli.SLEEP}, + client, + ) + assert resp.message_code == 0 + resp = await call_webhook( + hass, + WEBHOOK_ID, + {"userid": PERSON0.user_id, "appli": NotifyAppli.WEIGHT}, + client, + ) assert resp.message_code == 0 - resp = await component_factory.call_webhook(PERSON0.user_id, NotifyAppli.WEIGHT) assert resp.message_code == 0 for person, measurement, expected in EXPECTED_DATA: @@ -400,5 +123,19 @@ async def test_all_entities( async_assert_state_equals(entity_id, state_obj, expected, attribute) - # Unload - await component_factory.unload(PERSON0) + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_all_entities( + hass: HomeAssistant, setup_integration: ComponentSetup, snapshot: SnapshotAssertion +) -> None: + """Test all entities.""" + await setup_integration() + + mock = MockWithings(PERSON0) + with patch( + "homeassistant.components.withings.common.ConfigEntryWithingsApi", + return_value=mock, + ): + for sensor in SENSORS: + entity_id = await async_get_entity_id(hass, sensor, 12345, SENSOR_DOMAIN) + assert hass.states.get(entity_id) == snapshot