mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Trigger SleepIQ reauth flow on authentication failure during updates (#179021)
Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
co-authored by
Joostlek
parent
4cbec3d9a5
commit
cba75939ed
@@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ from asyncsleepiq import AsyncSleepIQ, SleepIQAPIException, SleepIQTimeoutExcept
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_USERNAME
|
from homeassistant.const import CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -54,6 +56,8 @@ class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
except SleepIQTimeoutException as err:
|
except SleepIQTimeoutException as err:
|
||||||
raise UpdateFailed(f"Timed out fetching SleepIQ data: {err}") from err
|
raise UpdateFailed(f"Timed out fetching SleepIQ data: {err}") from err
|
||||||
except SleepIQAPIException as err:
|
except SleepIQAPIException as err:
|
||||||
|
if err.code == HTTPStatus.UNAUTHORIZED:
|
||||||
|
raise ConfigEntryAuthFailed from err
|
||||||
raise UpdateFailed(f"Failed to fetch SleepIQ data: {err}") from err
|
raise UpdateFailed(f"Failed to fetch SleepIQ data: {err}") from err
|
||||||
|
|
||||||
|
|
||||||
@@ -87,6 +91,8 @@ class SleepIQPauseUpdateCoordinator(DataUpdateCoordinator[None]):
|
|||||||
except SleepIQTimeoutException as err:
|
except SleepIQTimeoutException as err:
|
||||||
raise UpdateFailed(f"Timed out fetching SleepIQ pause data: {err}") from err
|
raise UpdateFailed(f"Timed out fetching SleepIQ pause data: {err}") from err
|
||||||
except SleepIQAPIException as err:
|
except SleepIQAPIException as err:
|
||||||
|
if err.code == HTTPStatus.UNAUTHORIZED:
|
||||||
|
raise ConfigEntryAuthFailed from err
|
||||||
raise UpdateFailed(f"Failed to fetch SleepIQ pause data: {err}") from err
|
raise UpdateFailed(f"Failed to fetch SleepIQ pause data: {err}") from err
|
||||||
|
|
||||||
|
|
||||||
@@ -125,6 +131,8 @@ class SleepIQSleepDataCoordinator(DataUpdateCoordinator[None]):
|
|||||||
except SleepIQTimeoutException as err:
|
except SleepIQTimeoutException as err:
|
||||||
raise UpdateFailed(f"Timed out fetching SleepIQ sleep data: {err}") from err
|
raise UpdateFailed(f"Timed out fetching SleepIQ sleep data: {err}") from err
|
||||||
except SleepIQAPIException as err:
|
except SleepIQAPIException as err:
|
||||||
|
if err.code == HTTPStatus.UNAUTHORIZED:
|
||||||
|
raise ConfigEntryAuthFailed from err
|
||||||
raise UpdateFailed(f"Failed to fetch SleepIQ sleep data: {err}") from err
|
raise UpdateFailed(f"Failed to fetch SleepIQ sleep data: {err}") from err
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
"""Tests for the SleepIQ integration."""
|
"""Tests for the SleepIQ integration."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from datetime import timedelta
|
||||||
|
from http import HTTPStatus
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from asyncsleepiq import (
|
from asyncsleepiq import (
|
||||||
SleepIQAPIException,
|
SleepIQAPIException,
|
||||||
SleepIQLoginException,
|
SleepIQLoginException,
|
||||||
SleepIQTimeoutException,
|
SleepIQTimeoutException,
|
||||||
)
|
)
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.sleepiq.const import DOMAIN, IS_IN_BED, SLEEP_NUMBER
|
from homeassistant.components.sleepiq.const import DOMAIN, IS_IN_BED, SLEEP_NUMBER
|
||||||
from homeassistant.components.sleepiq.coordinator import UPDATE_INTERVAL
|
from homeassistant.components.sleepiq.coordinator import (
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
LONGER_UPDATE_INTERVAL,
|
||||||
|
SLEEP_DATA_UPDATE_INTERVAL,
|
||||||
|
UPDATE_INTERVAL,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import CONF_USERNAME, PRESSURE
|
from homeassistant.const import CONF_USERNAME, PRESSURE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
@@ -89,6 +100,73 @@ async def test_api_timeout(hass: HomeAssistant, mock_asyncsleepiq) -> None:
|
|||||||
assert not await hass.config_entries.async_setup(entry.entry_id)
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("get_fetch_mock", "platform", "interval"),
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
lambda client, bed: client.fetch_bed_statuses,
|
||||||
|
"sensor",
|
||||||
|
UPDATE_INTERVAL,
|
||||||
|
id="bed_status",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
lambda client, bed: bed.fetch_pause_mode,
|
||||||
|
"switch",
|
||||||
|
LONGER_UPDATE_INTERVAL,
|
||||||
|
id="pause_mode",
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
lambda client, bed: bed.sleepers[0].fetch_sleep_data,
|
||||||
|
"sensor",
|
||||||
|
SLEEP_DATA_UPDATE_INTERVAL,
|
||||||
|
id="sleep_data",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_update_auth_error_starts_reauth_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_asyncsleepiq: MagicMock,
|
||||||
|
mock_bed: MagicMock,
|
||||||
|
get_fetch_mock: Callable[[MagicMock, MagicMock], MagicMock],
|
||||||
|
platform: str,
|
||||||
|
interval: timedelta,
|
||||||
|
) -> None:
|
||||||
|
"""Test an authentication failure during an update starts the reauth flow."""
|
||||||
|
entry = await setup_platform(hass, platform)
|
||||||
|
|
||||||
|
get_fetch_mock(mock_asyncsleepiq, mock_bed).side_effect = SleepIQAPIException(
|
||||||
|
HTTPStatus.UNAUTHORIZED, "unauthorized"
|
||||||
|
)
|
||||||
|
freezer.tick(interval)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
|
||||||
|
assert len(flows) == 1
|
||||||
|
assert flows[0]["context"]["source"] == SOURCE_REAUTH
|
||||||
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_api_error_does_not_start_reauth_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_asyncsleepiq: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test a non-authentication API error during an update does not start reauth."""
|
||||||
|
await setup_platform(hass, "sensor")
|
||||||
|
|
||||||
|
mock_asyncsleepiq.fetch_bed_statuses.side_effect = SleepIQAPIException(
|
||||||
|
HTTPStatus.INTERNAL_SERVER_ERROR, "server error"
|
||||||
|
)
|
||||||
|
freezer.tick(UPDATE_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_asyncsleepiq.fetch_bed_statuses.call_count == 2
|
||||||
|
assert not hass.config_entries.flow.async_progress_by_handler(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
async def test_unique_id_migration(hass: HomeAssistant, mock_asyncsleepiq) -> None:
|
async def test_unique_id_migration(hass: HomeAssistant, mock_asyncsleepiq) -> None:
|
||||||
"""Test migration of sensor unique IDs."""
|
"""Test migration of sensor unique IDs."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user