Add more test coverage for fitbit sensors (#100776)

This commit is contained in:
Allen Porter 2023-09-23 15:38:53 -07:00 committed by GitHub
parent 451c085587
commit ae29ddee74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 530 additions and 20 deletions

View File

@ -104,13 +104,19 @@ async def mock_sensor_platform_setup(
@pytest.fixture(name="profile_id") @pytest.fixture(name="profile_id")
async def mock_profile_id() -> str: def mock_profile_id() -> str:
"""Fixture for the profile id returned from the API response.""" """Fixture for the profile id returned from the API response."""
return PROFILE_USER_ID return PROFILE_USER_ID
@pytest.fixture(name="profile_locale")
def mock_profile_locale() -> str:
"""Fixture to set the API response for the user profile."""
return "en_US"
@pytest.fixture(name="profile", autouse=True) @pytest.fixture(name="profile", autouse=True)
async def mock_profile(requests_mock: Mocker, profile_id: str) -> None: def mock_profile(requests_mock: Mocker, profile_id: str, profile_locale: str) -> None:
"""Fixture to setup fake requests made to Fitbit API during config flow.""" """Fixture to setup fake requests made to Fitbit API during config flow."""
requests_mock.register_uri( requests_mock.register_uri(
"GET", "GET",
@ -120,20 +126,20 @@ async def mock_profile(requests_mock: Mocker, profile_id: str) -> None:
"user": { "user": {
"encodedId": profile_id, "encodedId": profile_id,
"fullName": "My name", "fullName": "My name",
"locale": "en_US", "locale": profile_locale,
}, },
}, },
) )
@pytest.fixture(name="devices_response") @pytest.fixture(name="devices_response")
async def mock_device_response() -> list[dict[str, Any]]: def mock_device_response() -> list[dict[str, Any]]:
"""Return the list of devices.""" """Return the list of devices."""
return [] return []
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
async def mock_devices(requests_mock: Mocker, devices_response: dict[str, Any]) -> None: def mock_devices(requests_mock: Mocker, devices_response: dict[str, Any]) -> None:
"""Fixture to setup fake device responses.""" """Fixture to setup fake device responses."""
requests_mock.register_uri( requests_mock.register_uri(
"GET", "GET",
@ -151,7 +157,7 @@ def timeseries_response(resource: str, value: str) -> dict[str, Any]:
@pytest.fixture(name="register_timeseries") @pytest.fixture(name="register_timeseries")
async def mock_register_timeseries( def mock_register_timeseries(
requests_mock: Mocker, requests_mock: Mocker,
) -> Callable[[str, dict[str, Any]], None]: ) -> Callable[[str, dict[str, Any]], None]:
"""Fixture to setup fake timeseries API responses.""" """Fixture to setup fake timeseries API responses."""

View File

@ -0,0 +1,280 @@
# serializer version: 1
# name: test_sensors[monitored_resources0-sensor.activity_calories-activities/activityCalories-135]
tuple(
'135',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Activity Calories',
'icon': 'mdi:fire',
'unit_of_measurement': 'cal',
}),
'fitbit-api-user-id-1_activities/activityCalories',
)
# ---
# name: test_sensors[monitored_resources1-sensor.calories-activities/calories-139]
tuple(
'139',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Calories',
'icon': 'mdi:fire',
'unit_of_measurement': 'cal',
}),
'fitbit-api-user-id-1_activities/calories',
)
# ---
# name: test_sensors[monitored_resources10-sensor.steps-activities/steps-5600]
tuple(
'5600',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Steps',
'icon': 'mdi:walk',
'unit_of_measurement': 'steps',
}),
'fitbit-api-user-id-1_activities/steps',
)
# ---
# name: test_sensors[monitored_resources11-sensor.weight-body/weight-175]
tuple(
'175.0',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'weight',
'friendly_name': 'Weight',
'icon': 'mdi:human',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfMass.KILOGRAMS: 'kg'>,
}),
'fitbit-api-user-id-1_body/weight',
)
# ---
# name: test_sensors[monitored_resources12-sensor.body_fat-body/fat-18]
tuple(
'18.0',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Body Fat',
'icon': 'mdi:human',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'fitbit-api-user-id-1_body/fat',
)
# ---
# name: test_sensors[monitored_resources13-sensor.bmi-body/bmi-23.7]
tuple(
'23.7',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'BMI',
'icon': 'mdi:human',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': 'BMI',
}),
'fitbit-api-user-id-1_body/bmi',
)
# ---
# name: test_sensors[monitored_resources14-sensor.awakenings_count-sleep/awakeningsCount-7]
tuple(
'7',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Awakenings Count',
'icon': 'mdi:sleep',
'unit_of_measurement': 'times awaken',
}),
'fitbit-api-user-id-1_sleep/awakeningsCount',
)
# ---
# name: test_sensors[monitored_resources15-sensor.sleep_efficiency-sleep/efficiency-80]
tuple(
'80',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Sleep Efficiency',
'icon': 'mdi:sleep',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'fitbit-api-user-id-1_sleep/efficiency',
)
# ---
# name: test_sensors[monitored_resources16-sensor.minutes_after_wakeup-sleep/minutesAfterWakeup-17]
tuple(
'17',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Minutes After Wakeup',
'icon': 'mdi:sleep',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_sleep/minutesAfterWakeup',
)
# ---
# name: test_sensors[monitored_resources17-sensor.sleep_minutes_asleep-sleep/minutesAsleep-360]
tuple(
'360',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Sleep Minutes Asleep',
'icon': 'mdi:sleep',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_sleep/minutesAsleep',
)
# ---
# name: test_sensors[monitored_resources18-sensor.sleep_minutes_awake-sleep/minutesAwake-35]
tuple(
'35',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Sleep Minutes Awake',
'icon': 'mdi:sleep',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_sleep/minutesAwake',
)
# ---
# name: test_sensors[monitored_resources19-sensor.sleep_minutes_to_fall_asleep-sleep/minutesToFallAsleep-35]
tuple(
'35',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Sleep Minutes to Fall Asleep',
'icon': 'mdi:sleep',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_sleep/minutesToFallAsleep',
)
# ---
# name: test_sensors[monitored_resources2-sensor.distance-activities/distance-12.7]
tuple(
'12.70',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'distance',
'friendly_name': 'Distance',
'icon': 'mdi:map-marker',
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'fitbit-api-user-id-1_activities/distance',
)
# ---
# name: test_sensors[monitored_resources20-sensor.sleep_start_time-sleep/startTime-2020-01-27T00:17:30.000]
tuple(
'2020-01-27T00:17:30.000',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Sleep Start Time',
'icon': 'mdi:clock',
}),
'fitbit-api-user-id-1_sleep/startTime',
)
# ---
# name: test_sensors[monitored_resources21-sensor.sleep_time_in_bed-sleep/timeInBed-462]
tuple(
'462',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Sleep Time in Bed',
'icon': 'mdi:hotel',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_sleep/timeInBed',
)
# ---
# name: test_sensors[monitored_resources3-sensor.elevation-activities/elevation-7600.24]
tuple(
'7600.24',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'distance',
'friendly_name': 'Elevation',
'icon': 'mdi:walk',
'unit_of_measurement': <UnitOfLength.METERS: 'm'>,
}),
'fitbit-api-user-id-1_activities/elevation',
)
# ---
# name: test_sensors[monitored_resources4-sensor.floors-activities/floors-8]
tuple(
'8',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Floors',
'icon': 'mdi:walk',
'unit_of_measurement': 'floors',
}),
'fitbit-api-user-id-1_activities/floors',
)
# ---
# name: test_sensors[monitored_resources5-sensor.resting_heart_rate-activities/heart-api_value5]
tuple(
'76',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'friendly_name': 'Resting Heart Rate',
'icon': 'mdi:heart-pulse',
'unit_of_measurement': 'bpm',
}),
'fitbit-api-user-id-1_activities/heart',
)
# ---
# name: test_sensors[monitored_resources6-sensor.minutes_fairly_active-activities/minutesFairlyActive-35]
tuple(
'35',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Minutes Fairly Active',
'icon': 'mdi:walk',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_activities/minutesFairlyActive',
)
# ---
# name: test_sensors[monitored_resources7-sensor.minutes_lightly_active-activities/minutesLightlyActive-95]
tuple(
'95',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Minutes Lightly Active',
'icon': 'mdi:walk',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_activities/minutesLightlyActive',
)
# ---
# name: test_sensors[monitored_resources8-sensor.minutes_sedentary-activities/minutesSedentary-18]
tuple(
'18',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Minutes Sedentary',
'icon': 'mdi:seat-recline-normal',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_activities/minutesSedentary',
)
# ---
# name: test_sensors[monitored_resources9-sensor.minutes_very_active-activities/minutesVeryActive-20]
tuple(
'20',
ReadOnlyDict({
'attribution': 'Data provided by Fitbit.com',
'device_class': 'duration',
'friendly_name': 'Minutes Very Active',
'icon': 'mdi:run',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'fitbit-api-user-id-1_activities/minutesVeryActive',
)
# ---

View File

@ -5,10 +5,12 @@ from collections.abc import Awaitable, Callable
from typing import Any from typing import Any
import pytest import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import timeseries_response from .conftest import PROFILE_USER_ID, timeseries_response
DEVICE_RESPONSE_CHARGE_2 = { DEVICE_RESPONSE_CHARGE_2 = {
"battery": "Medium", "battery": "Medium",
@ -31,30 +33,169 @@ DEVICE_RESPONSE_ARIA_AIR = {
@pytest.mark.parametrize( @pytest.mark.parametrize(
"monitored_resources", (
[["activities/steps"]], "monitored_resources",
"entity_id",
"api_resource",
"api_value",
),
[
(
["activities/activityCalories"],
"sensor.activity_calories",
"activities/activityCalories",
"135",
),
(
["activities/calories"],
"sensor.calories",
"activities/calories",
"139",
),
(
["activities/distance"],
"sensor.distance",
"activities/distance",
"12.7",
),
(
["activities/elevation"],
"sensor.elevation",
"activities/elevation",
"7600.24",
),
(
["activities/floors"],
"sensor.floors",
"activities/floors",
"8",
),
(
["activities/heart"],
"sensor.resting_heart_rate",
"activities/heart",
{"restingHeartRate": 76},
),
(
["activities/minutesFairlyActive"],
"sensor.minutes_fairly_active",
"activities/minutesFairlyActive",
35,
),
(
["activities/minutesLightlyActive"],
"sensor.minutes_lightly_active",
"activities/minutesLightlyActive",
95,
),
(
["activities/minutesSedentary"],
"sensor.minutes_sedentary",
"activities/minutesSedentary",
18,
),
(
["activities/minutesVeryActive"],
"sensor.minutes_very_active",
"activities/minutesVeryActive",
20,
),
(
["activities/steps"],
"sensor.steps",
"activities/steps",
"5600",
),
(
["body/weight"],
"sensor.weight",
"body/weight",
"175",
),
(
["body/fat"],
"sensor.body_fat",
"body/fat",
"18",
),
(
["body/bmi"],
"sensor.bmi",
"body/bmi",
"23.7",
),
(
["sleep/awakeningsCount"],
"sensor.awakenings_count",
"sleep/awakeningsCount",
"7",
),
(
["sleep/efficiency"],
"sensor.sleep_efficiency",
"sleep/efficiency",
"80",
),
(
["sleep/minutesAfterWakeup"],
"sensor.minutes_after_wakeup",
"sleep/minutesAfterWakeup",
"17",
),
(
["sleep/minutesAsleep"],
"sensor.sleep_minutes_asleep",
"sleep/minutesAsleep",
"360",
),
(
["sleep/minutesAwake"],
"sensor.sleep_minutes_awake",
"sleep/minutesAwake",
"35",
),
(
["sleep/minutesToFallAsleep"],
"sensor.sleep_minutes_to_fall_asleep",
"sleep/minutesToFallAsleep",
"35",
),
(
["sleep/startTime"],
"sensor.sleep_start_time",
"sleep/startTime",
"2020-01-27T00:17:30.000",
),
(
["sleep/timeInBed"],
"sensor.sleep_time_in_bed",
"sleep/timeInBed",
"462",
),
],
) )
async def test_step_sensor( async def test_sensors(
hass: HomeAssistant, hass: HomeAssistant,
sensor_platform_setup: Callable[[], Awaitable[bool]], sensor_platform_setup: Callable[[], Awaitable[bool]],
register_timeseries: Callable[[str, dict[str, Any]], None], register_timeseries: Callable[[str, dict[str, Any]], None],
entity_registry: er.EntityRegistry,
entity_id: str,
api_resource: str,
api_value: str,
snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test battery level sensor.""" """Test sensors."""
register_timeseries( register_timeseries(
"activities/steps", timeseries_response("activities-steps", "5600") api_resource, timeseries_response(api_resource.replace("/", "-"), api_value)
) )
await sensor_platform_setup() await sensor_platform_setup()
state = hass.states.get("sensor.steps") state = hass.states.get(entity_id)
assert state assert state
assert state.state == "5600" entry = entity_registry.async_get(entity_id)
assert state.attributes == { assert entry
"attribution": "Data provided by Fitbit.com", assert (state.state, state.attributes, entry.unique_id) == snapshot
"friendly_name": "Steps",
"icon": "mdi:walk",
"unit_of_measurement": "steps",
}
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -64,6 +205,7 @@ async def test_step_sensor(
async def test_device_battery_level( async def test_device_battery_level(
hass: HomeAssistant, hass: HomeAssistant,
sensor_platform_setup: Callable[[], Awaitable[bool]], sensor_platform_setup: Callable[[], Awaitable[bool]],
entity_registry: er.EntityRegistry,
) -> None: ) -> None:
"""Test battery level sensor for devices.""" """Test battery level sensor for devices."""
@ -80,6 +222,10 @@ async def test_device_battery_level(
"type": "tracker", "type": "tracker",
} }
entry = entity_registry.async_get("sensor.charge_2_battery")
assert entry
assert entry.unique_id == f"{PROFILE_USER_ID}_devices/battery_816713257"
state = hass.states.get("sensor.aria_air_battery") state = hass.states.get("sensor.aria_air_battery")
assert state assert state
assert state.state == "High" assert state.state == "High"
@ -90,3 +236,81 @@ async def test_device_battery_level(
"model": "Aria Air", "model": "Aria Air",
"type": "scale", "type": "scale",
} }
entity_registry = er.async_get(hass)
entry = entity_registry.async_get("sensor.aria_air_battery")
assert entry
assert entry.unique_id == f"{PROFILE_USER_ID}_devices/battery_016713257"
@pytest.mark.parametrize(
("monitored_resources", "profile_locale", "expected_unit"),
[
(["body/weight"], "en_US", "kg"),
(["body/weight"], "en_GB", "st"),
(["body/weight"], "es_ES", "kg"),
],
)
async def test_profile_local(
hass: HomeAssistant,
sensor_platform_setup: Callable[[], Awaitable[bool]],
register_timeseries: Callable[[str, dict[str, Any]], None],
expected_unit: str,
) -> None:
"""Test the fitbit profile locale impact on unit of measure."""
register_timeseries("body/weight", timeseries_response("body-weight", "175"))
await sensor_platform_setup()
state = hass.states.get("sensor.weight")
assert state
assert state.attributes.get("unit_of_measurement") == expected_unit
@pytest.mark.parametrize(
("sensor_platform_config", "api_response", "expected_state"),
[
(
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
"17:05",
"5:05 PM",
),
(
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
"5:05",
"5:05 AM",
),
(
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
"00:05",
"12:05 AM",
),
(
{"clock_format": "24H", "monitored_resources": ["sleep/startTime"]},
"17:05",
"17:05",
),
(
{"clock_format": "12H", "monitored_resources": ["sleep/startTime"]},
"",
"-",
),
],
)
async def test_sleep_time_clock_format(
hass: HomeAssistant,
sensor_platform_setup: Callable[[], Awaitable[bool]],
register_timeseries: Callable[[str, dict[str, Any]], None],
api_response: str,
expected_state: str,
) -> None:
"""Test the clock format configuration."""
register_timeseries(
"sleep/startTime", timeseries_response("sleep-startTime", api_response)
)
await sensor_platform_setup()
state = hass.states.get("sensor.sleep_start_time")
assert state
assert state.state == expected_state