Trigger SleepIQ reauth flow on authentication failure during updates (#179021)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Bharat Mediratta
2026-08-14 14:39:47 +02:00
committed by GitHub
co-authored by Joostlek
parent 4cbec3d9a5
commit cba75939ed
2 changed files with 88 additions and 2 deletions
@@ -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
+80 -2
View File
@@ -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."""