Update Fitbit to avoid a KeyError when restingHeartRate is not present (#103872)

* Update Fitbit to avoid a KeyError when `restingHeartRate` is not present

* Explicitly handle none response values
This commit is contained in:
Allen Porter 2023-11-12 12:49:49 -08:00 committed by GitHub
parent efe33d815f
commit 64c9aa0cff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 1 deletions

View File

@ -135,6 +135,17 @@ def _water_unit(unit_system: FitbitUnitSystem) -> UnitOfVolume:
return UnitOfVolume.MILLILITERS
def _int_value_or_none(field: str) -> Callable[[dict[str, Any]], int | None]:
"""Value function that will parse the specified field if present."""
def convert(result: dict[str, Any]) -> int | None:
if (value := result["value"].get(field)) is not None:
return int(value)
return None
return convert
@dataclass
class FitbitSensorEntityDescription(SensorEntityDescription):
"""Describes Fitbit sensor entity."""
@ -207,7 +218,7 @@ FITBIT_RESOURCES_LIST: Final[tuple[FitbitSensorEntityDescription, ...]] = (
name="Resting Heart Rate",
native_unit_of_measurement="bpm",
icon="mdi:heart-pulse",
value_fn=lambda result: int(result["value"]["restingHeartRate"]),
value_fn=_int_value_or_none("restingHeartRate"),
scope=FitbitScope.HEART_RATE,
state_class=SensorStateClass.MEASUREMENT,
),

View File

@ -808,3 +808,60 @@ async def test_device_battery_level_reauth_required(
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"
@pytest.mark.parametrize(
("scopes", "response_data", "expected_state"),
[
(["heartrate"], {}, "unknown"),
(
["heartrate"],
{
"restingHeartRate": 120,
},
"120",
),
(
["heartrate"],
{
"restingHeartRate": 0,
},
"0",
),
],
ids=("missing", "valid", "zero"),
)
async def test_resting_heart_rate_responses(
hass: HomeAssistant,
setup_credentials: None,
integration_setup: Callable[[], Awaitable[bool]],
register_timeseries: Callable[[str, dict[str, Any]], None],
response_data: dict[str, Any],
expected_state: str,
) -> None:
"""Test resting heart rate sensor with various values from response."""
register_timeseries(
"activities/heart",
timeseries_response(
"activities-heart",
{
"customHeartRateZones": [],
"heartRateZones": [
{
"caloriesOut": 0,
"max": 220,
"min": 159,
"minutes": 0,
"name": "Peak",
},
],
**response_data,
},
),
)
assert await integration_setup()
state = hass.states.get("sensor.resting_heart_rate")
assert state
assert state.state == expected_state