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
from dataclasses import dataclass
from datetime import timedelta
from http import HTTPStatus
import logging
from typing import override
@@ -11,6 +12,7 @@ from asyncsleepiq import AsyncSleepIQ, SleepIQAPIException, SleepIQTimeoutExcept
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
_LOGGER = logging.getLogger(__name__)
@@ -54,6 +56,8 @@ class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[None]):
except SleepIQTimeoutException as err:
raise UpdateFailed(f"Timed out fetching SleepIQ data: {err}") from 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
@@ -87,6 +91,8 @@ class SleepIQPauseUpdateCoordinator(DataUpdateCoordinator[None]):
except SleepIQTimeoutException as err:
raise UpdateFailed(f"Timed out fetching SleepIQ pause data: {err}") from 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
@@ -125,6 +131,8 @@ class SleepIQSleepDataCoordinator(DataUpdateCoordinator[None]):
except SleepIQTimeoutException as err:
raise UpdateFailed(f"Timed out fetching SleepIQ sleep data: {err}") from 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
+80 -2
View File
@@ -1,14 +1,25 @@
"""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 (
SleepIQAPIException,
SleepIQLoginException,
SleepIQTimeoutException,
)
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.sleepiq.const import DOMAIN, IS_IN_BED, SLEEP_NUMBER
from homeassistant.components.sleepiq.coordinator import UPDATE_INTERVAL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.components.sleepiq.coordinator import (
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.core import HomeAssistant
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)
@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:
"""Test migration of sensor unique IDs."""