Update fitbit device fetch to use a data update coordinator (#101619)
* Add a fitbit device update coordinator * Remove unnecessary debug output * Update comments * Update fitbit coordinator exception handling and test coverage * Handle reauth failures in other sensors * Fix scope changes after rebase.
This commit is contained in:
parent
6d1876394e
commit
d78ee96e2a
8 changed files with 389 additions and 132 deletions
|
@ -4,6 +4,7 @@ from collections.abc import Awaitable, Callable
|
|||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
from requests_mock.mocker import Mocker
|
||||
|
||||
from homeassistant.components.fitbit.const import (
|
||||
CONF_CLIENT_ID,
|
||||
|
@ -16,6 +17,7 @@ from homeassistant.core import HomeAssistant
|
|||
from .conftest import (
|
||||
CLIENT_ID,
|
||||
CLIENT_SECRET,
|
||||
DEVICES_API_URL,
|
||||
FAKE_ACCESS_TOKEN,
|
||||
FAKE_REFRESH_TOKEN,
|
||||
SERVER_ACCESS_TOKEN,
|
||||
|
@ -125,3 +127,50 @@ async def test_token_requires_reauth(
|
|||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["step_id"] == "reauth_confirm"
|
||||
|
||||
|
||||
async def test_device_update_coordinator_failure(
|
||||
hass: HomeAssistant,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
setup_credentials: None,
|
||||
requests_mock: Mocker,
|
||||
) -> None:
|
||||
"""Test case where the device update coordinator fails on the first request."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
DEVICES_API_URL,
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
assert not await integration_setup()
|
||||
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_device_update_coordinator_reauth(
|
||||
hass: HomeAssistant,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
setup_credentials: None,
|
||||
requests_mock: Mocker,
|
||||
) -> None:
|
||||
"""Test case where the device update coordinator fails on the first request."""
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
DEVICES_API_URL,
|
||||
status_code=HTTPStatus.UNAUTHORIZED,
|
||||
json={
|
||||
"errors": [{"errorType": "invalid_grant"}],
|
||||
},
|
||||
)
|
||||
|
||||
assert not await integration_setup()
|
||||
assert config_entry.state == ConfigEntryState.SETUP_ERROR
|
||||
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["step_id"] == "reauth_confirm"
|
||||
|
|
|
@ -27,6 +27,8 @@ from .conftest import (
|
|||
timeseries_response,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
DEVICE_RESPONSE_CHARGE_2 = {
|
||||
"battery": "Medium",
|
||||
"batteryLevel": 60,
|
||||
|
@ -577,6 +579,43 @@ async def test_sensor_update_failed(
|
|||
assert state
|
||||
assert state.state == "unavailable"
|
||||
|
||||
# Verify the config entry is in a normal state (no reauth required)
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert not flows
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("scopes"),
|
||||
[(["heartrate"])],
|
||||
)
|
||||
async def test_sensor_update_failed_requires_reauth(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
requests_mock: Mocker,
|
||||
) -> None:
|
||||
"""Test a sensor update request requires reauth."""
|
||||
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
TIMESERIES_API_URL_FORMAT.format(resource="activities/heart"),
|
||||
status_code=HTTPStatus.UNAUTHORIZED,
|
||||
json={
|
||||
"errors": [{"errorType": "invalid_grant"}],
|
||||
},
|
||||
)
|
||||
|
||||
assert await integration_setup()
|
||||
|
||||
state = hass.states.get("sensor.resting_heart_rate")
|
||||
assert state
|
||||
assert state.state == "unavailable"
|
||||
|
||||
# Verify that reauth is required
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["step_id"] == "reauth_confirm"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("scopes", "mock_devices"),
|
||||
|
@ -594,11 +633,6 @@ async def test_device_battery_level_update_failed(
|
|||
"GET",
|
||||
DEVICES_API_URL,
|
||||
[
|
||||
{
|
||||
"status_code": HTTPStatus.OK,
|
||||
"json": [DEVICE_RESPONSE_CHARGE_2],
|
||||
},
|
||||
# A second spurious update request on startup
|
||||
{
|
||||
"status_code": HTTPStatus.OK,
|
||||
"json": [DEVICE_RESPONSE_CHARGE_2],
|
||||
|
@ -626,7 +660,63 @@ async def test_device_battery_level_update_failed(
|
|||
|
||||
# Request an update for the entity which will fail
|
||||
await async_update_entity(hass, "sensor.charge_2_battery")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.charge_2_battery")
|
||||
assert state
|
||||
assert state.state == "unavailable"
|
||||
|
||||
# Verify the config entry is in a normal state (no reauth required)
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert not flows
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("scopes", "mock_devices"),
|
||||
[(["settings"], None)],
|
||||
)
|
||||
async def test_device_battery_level_reauth_required(
|
||||
hass: HomeAssistant,
|
||||
setup_credentials: None,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
requests_mock: Mocker,
|
||||
) -> None:
|
||||
"""Test API failure requires reauth."""
|
||||
|
||||
requests_mock.register_uri(
|
||||
"GET",
|
||||
DEVICES_API_URL,
|
||||
[
|
||||
{
|
||||
"status_code": HTTPStatus.OK,
|
||||
"json": [DEVICE_RESPONSE_CHARGE_2],
|
||||
},
|
||||
# Fail when requesting an update
|
||||
{
|
||||
"status_code": HTTPStatus.UNAUTHORIZED,
|
||||
"json": {
|
||||
"errors": [{"errorType": "invalid_grant"}],
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
assert await integration_setup()
|
||||
|
||||
state = hass.states.get("sensor.charge_2_battery")
|
||||
assert state
|
||||
assert state.state == "Medium"
|
||||
|
||||
# Request an update for the entity which will fail
|
||||
await async_update_entity(hass, "sensor.charge_2_battery")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.charge_2_battery")
|
||||
assert state
|
||||
assert state.state == "unavailable"
|
||||
|
||||
# Verify that reauth is required
|
||||
flows = hass.config_entries.flow.async_progress()
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["step_id"] == "reauth_confirm"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue