diff --git a/homeassistant/components/litterrobot/coordinator.py b/homeassistant/components/litterrobot/coordinator.py index 0076eae007c..ba40602cee1 100644 --- a/homeassistant/components/litterrobot/coordinator.py +++ b/homeassistant/components/litterrobot/coordinator.py @@ -46,11 +46,18 @@ class LitterRobotDataUpdateCoordinator(DataUpdateCoordinator[None]): async def _async_update_data(self) -> None: """Update all device states from the Litter-Robot API.""" - await self.account.refresh_robots() - await self.account.load_pets() - for pet in self.account.pets: - # Need to fetch weight history for `get_visits_since` - await pet.fetch_weight_history() + try: + await self.account.refresh_robots() + await self.account.load_pets() + for pet in self.account.pets: + # Need to fetch weight history for `get_visits_since` + await pet.fetch_weight_history() + except LitterRobotLoginException as ex: + raise ConfigEntryAuthFailed("Invalid credentials") from ex + except LitterRobotException as ex: + raise UpdateFailed( + f"Unable to fetch data from the Whisker API: {ex}" + ) from ex async def _async_setup(self) -> None: """Set up the coordinator.""" diff --git a/homeassistant/components/litterrobot/quality_scale.yaml b/homeassistant/components/litterrobot/quality_scale.yaml index 172e3ce1f27..8c135d69b0d 100644 --- a/homeassistant/components/litterrobot/quality_scale.yaml +++ b/homeassistant/components/litterrobot/quality_scale.yaml @@ -29,9 +29,9 @@ rules: status: done comment: No options to configure docs-installation-parameters: done - entity-unavailable: todo + entity-unavailable: done integration-owner: done - log-when-unavailable: todo + log-when-unavailable: done parallel-updates: done reauthentication-flow: done test-coverage: diff --git a/tests/components/litterrobot/test_coordinator.py b/tests/components/litterrobot/test_coordinator.py new file mode 100644 index 00000000000..2ff7fee4d9d --- /dev/null +++ b/tests/components/litterrobot/test_coordinator.py @@ -0,0 +1,81 @@ +"""Tests for the Litter-Robot coordinator.""" + +from unittest.mock import MagicMock + +from freezegun.api import FrozenDateTimeFactory +from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException + +from homeassistant.components.litterrobot.const import DOMAIN +from homeassistant.components.litterrobot.coordinator import UPDATE_INTERVAL +from homeassistant.components.vacuum import DOMAIN as VACUUM_DOMAIN +from homeassistant.config_entries import SOURCE_REAUTH +from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant + +from .common import VACUUM_ENTITY_ID +from .conftest import setup_integration + +from tests.common import async_fire_time_changed + + +async def test_coordinator_update_error( + hass: HomeAssistant, + mock_account: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Test entities become unavailable when coordinator update fails.""" + await setup_integration(hass, mock_account, VACUUM_DOMAIN) + + assert (state := hass.states.get(VACUUM_ENTITY_ID)) + assert state.state != STATE_UNAVAILABLE + + # Simulate an API error during update + mock_account.refresh_robots.side_effect = LitterRobotException("Unable to connect") + freezer.tick(UPDATE_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert (state := hass.states.get(VACUUM_ENTITY_ID)) + assert state.state == STATE_UNAVAILABLE + + # Recover + mock_account.refresh_robots.side_effect = None + freezer.tick(UPDATE_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert (state := hass.states.get(VACUUM_ENTITY_ID)) + assert state.state != STATE_UNAVAILABLE + + +async def test_coordinator_update_auth_error( + hass: HomeAssistant, + mock_account: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Test reauthentication flow is triggered on login error during update.""" + entry = await setup_integration(hass, mock_account, VACUUM_DOMAIN) + + assert (state := hass.states.get(VACUUM_ENTITY_ID)) + assert state.state != STATE_UNAVAILABLE + + # Simulate an authentication error during update + mock_account.refresh_robots.side_effect = LitterRobotLoginException( + "Invalid credentials" + ) + freezer.tick(UPDATE_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert (state := hass.states.get(VACUUM_ENTITY_ID)) + assert state.state == STATE_UNAVAILABLE + + # Ensure a reauthentication flow was triggered + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + + flow = flows[0] + assert flow["step_id"] == "reauth_confirm" + assert flow["handler"] == DOMAIN + assert flow["context"].get("source") == SOURCE_REAUTH + assert flow["context"].get("entry_id") == entry.entry_id