diff --git a/homeassistant/components/hr_energy_qube/__init__.py b/homeassistant/components/hr_energy_qube/__init__.py index 7e74035e23ab..9d0ca46e0de2 100644 --- a/homeassistant/components/hr_energy_qube/__init__.py +++ b/homeassistant/components/hr_energy_qube/__init__.py @@ -1,57 +1,23 @@ """The Qube Heat Pump integration.""" -from dataclasses import dataclass - from python_qube_heatpump import QubeClient from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from .const import PLATFORMS from .coordinator import QubeCoordinator - -@dataclass -class QubeData: - """Runtime data for Qube Heat Pump.""" - - coordinator: QubeCoordinator - client: QubeClient - sw_version: str | None - - -type QubeConfigEntry = ConfigEntry[QubeData] +type QubeConfigEntry = ConfigEntry[QubeCoordinator] async def async_setup_entry(hass: HomeAssistant, entry: QubeConfigEntry) -> bool: """Set up Qube Heat Pump from a config entry.""" client = QubeClient(entry.data[CONF_HOST], entry.data[CONF_PORT]) - - # Connect and read software version for device info - sw_version: str | None = None - try: - connected = await client.connect() - if not connected: - await client.close() - raise ConfigEntryNotReady( - f"Unable to connect to Qube heat pump at {entry.data[CONF_HOST]}" - ) - sw_version = await client.async_get_software_version() - except (OSError, TimeoutError) as err: - await client.close() - raise ConfigEntryNotReady( - f"Unable to connect to Qube heat pump at {entry.data[CONF_HOST]}" - ) from err - coordinator = QubeCoordinator(hass, client, entry) - entry.runtime_data = QubeData( - coordinator=coordinator, - client=client, - sw_version=sw_version, - ) + entry.runtime_data = coordinator await coordinator.async_config_entry_first_refresh() await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/homeassistant/components/hr_energy_qube/binary_sensor.py b/homeassistant/components/hr_energy_qube/binary_sensor.py index ef24d0d69b80..a200f5e07139 100644 --- a/homeassistant/components/hr_energy_qube/binary_sensor.py +++ b/homeassistant/components/hr_energy_qube/binary_sensor.py @@ -260,7 +260,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube binary sensors.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities( QubeBinarySensor(coordinator, entry, description) diff --git a/homeassistant/components/hr_energy_qube/config_flow.py b/homeassistant/components/hr_energy_qube/config_flow.py index e73b5179d108..845351e225fd 100644 --- a/homeassistant/components/hr_energy_qube/config_flow.py +++ b/homeassistant/components/hr_energy_qube/config_flow.py @@ -38,7 +38,7 @@ class QubeConfigFlow(ConfigFlow, domain=DOMAIN): version = await client.async_get_software_version() if version is None: errors["base"] = "not_qube_device" - except OSError, TimeoutError: + except OSError: errors["base"] = "cannot_connect" finally: await client.close() diff --git a/homeassistant/components/hr_energy_qube/coordinator.py b/homeassistant/components/hr_energy_qube/coordinator.py index ef08cb8f0aed..37e695dc7a4b 100644 --- a/homeassistant/components/hr_energy_qube/coordinator.py +++ b/homeassistant/components/hr_energy_qube/coordinator.py @@ -31,6 +31,8 @@ class QubeData: class QubeCoordinator(DataUpdateCoordinator[QubeData]): """Qube Heat Pump data coordinator.""" + sw_version: str | None = None + def __init__( self, hass: HomeAssistant, client: QubeClient, entry: ConfigEntry ) -> None: @@ -44,6 +46,23 @@ class QubeCoordinator(DataUpdateCoordinator[QubeData]): config_entry=entry, ) + @override + async def _async_setup(self) -> None: + """Connect to the device and read its software version.""" + try: + connected = await self.client.connect() + if not connected: + await self.client.close() + raise UpdateFailed( + f"Unable to connect to Qube heat pump at {self.client.host}" + ) + self.sw_version = await self.client.async_get_software_version() + except OSError as err: + await self.client.close() + raise UpdateFailed( + f"Unable to connect to Qube heat pump at {self.client.host}" + ) from err + @override async def _async_update_data(self) -> QubeData: """Fetch data from the device.""" @@ -51,7 +70,7 @@ class QubeCoordinator(DataUpdateCoordinator[QubeData]): state = await self.client.get_all_data() switches = await self.client.read_all_switches() sg_ready_mode = await self.client.get_sg_ready_mode() - except (ConnectionError, TimeoutError, OSError) as exc: + except OSError as exc: raise UpdateFailed( f"Error communicating with Qube heat pump: {exc}" ) from exc diff --git a/homeassistant/components/hr_energy_qube/entity.py b/homeassistant/components/hr_energy_qube/entity.py index 100df9db859d..d4eb17bc3fb0 100644 --- a/homeassistant/components/hr_energy_qube/entity.py +++ b/homeassistant/components/hr_energy_qube/entity.py @@ -28,5 +28,5 @@ class QubeEntity(CoordinatorEntity[QubeCoordinator]): identifiers={(DOMAIN, entry.entry_id)}, manufacturer="Qube", model="Heat Pump", - sw_version=entry.runtime_data.sw_version, + sw_version=coordinator.sw_version, ) diff --git a/homeassistant/components/hr_energy_qube/quality_scale.yaml b/homeassistant/components/hr_energy_qube/quality_scale.yaml index 6e344d1657ea..9652c324fe89 100644 --- a/homeassistant/components/hr_energy_qube/quality_scale.yaml +++ b/homeassistant/components/hr_energy_qube/quality_scale.yaml @@ -42,7 +42,7 @@ rules: docs-installation-parameters: done entity-unavailable: done integration-owner: done - log-when-unavailable: todo + log-when-unavailable: done parallel-updates: done reauthentication-flow: status: exempt @@ -64,9 +64,9 @@ rules: dynamic-devices: status: exempt comment: Single device per config entry. - entity-category: todo + entity-category: done entity-device-class: done - entity-disabled-by-default: todo + entity-disabled-by-default: done entity-translations: done exception-translations: todo icon-translations: todo diff --git a/homeassistant/components/hr_energy_qube/select.py b/homeassistant/components/hr_energy_qube/select.py index 2d43d6c04487..16bc20b4c0a1 100644 --- a/homeassistant/components/hr_energy_qube/select.py +++ b/homeassistant/components/hr_energy_qube/select.py @@ -23,7 +23,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube select entities.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities([QubeSGReadySelect(coordinator, entry)]) @@ -59,7 +59,7 @@ class QubeSGReadySelect(QubeEntity, SelectEntity): """Set the SG Ready mode.""" try: success = await self.coordinator.client.set_sg_ready_mode(option) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_command_failed", diff --git a/homeassistant/components/hr_energy_qube/sensor.py b/homeassistant/components/hr_energy_qube/sensor.py index 787bcbca37d9..51ccdc39ad92 100644 --- a/homeassistant/components/hr_energy_qube/sensor.py +++ b/homeassistant/components/hr_energy_qube/sensor.py @@ -4,6 +4,8 @@ from collections.abc import Callable from dataclasses import dataclass from typing import TYPE_CHECKING, override +from python_qube_heatpump import STATUS_CODE_MAP + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -31,21 +33,18 @@ if TYPE_CHECKING: from . import QubeConfigEntry from .coordinator import QubeCoordinator -# Status code to state mapping +# Status code to state mapping, derived from the library's status code map. STATUS_MAP: dict[int, str] = { - 1: "standby", - 2: "alarm", - 6: "keyboard_off", - 8: "compressor_startup", - 9: "compressor_shutdown", - 14: "standby", - 15: "cooling", - 16: "heating", - 17: "start_fail", - 18: "standby", - 22: "heating_dhw", + code: status.value for code, status in STATUS_CODE_MAP.items() } +# Options list for the status sensor: unique status strings from STATUS_MAP, +# in first-seen order. StatusCode also defines ANTI_LEGIONELLA and UNKNOWN, +# but those are not part of STATUS_CODE_MAP (only surfaced via the library's +# resolve_status() helper, which this integration does not use), so they are +# excluded here automatically rather than needing an explicit filter. +STATUS_OPTIONS: list[str] = list(dict.fromkeys(STATUS_MAP.values())) + @dataclass(frozen=True, kw_only=True) class QubeSensorEntityDescription(SensorEntityDescription): @@ -226,17 +225,7 @@ SENSOR_TYPES: tuple[QubeSensorEntityDescription, ...] = ( key="status_heatpump", translation_key="status_heatpump", device_class=SensorDeviceClass.ENUM, - options=[ - "standby", - "alarm", - "keyboard_off", - "compressor_startup", - "compressor_shutdown", - "cooling", - "heating", - "start_fail", - "heating_dhw", - ], + options=STATUS_OPTIONS, value_fn=_status_value, ), ) @@ -248,7 +237,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube sensors.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities( QubeSensor(coordinator, entry, description) for description in SENSOR_TYPES diff --git a/homeassistant/components/hr_energy_qube/switch.py b/homeassistant/components/hr_energy_qube/switch.py index 0b0b205b9ff8..1109b5cd8a22 100644 --- a/homeassistant/components/hr_energy_qube/switch.py +++ b/homeassistant/components/hr_energy_qube/switch.py @@ -55,7 +55,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube switches.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities( QubeSwitch(coordinator, entry, description) for description in SWITCH_TYPES @@ -98,7 +98,7 @@ class QubeSwitch(QubeEntity, SwitchEntity): register_key = self.entity_description.register_key try: success = await self.coordinator.client.write_switch(register_key, value) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_command_failed", diff --git a/homeassistant/components/hr_energy_qube/water_heater.py b/homeassistant/components/hr_energy_qube/water_heater.py index 19f5e208fb94..ab14b20eff9c 100644 --- a/homeassistant/components/hr_energy_qube/water_heater.py +++ b/homeassistant/components/hr_energy_qube/water_heater.py @@ -3,6 +3,7 @@ from typing import Any, override from homeassistant.components.water_heater import ( + ATTR_TEMPERATURE, STATE_HEAT_PUMP, STATE_PERFORMANCE, WaterHeaterEntity, @@ -34,7 +35,7 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up the Qube water heater.""" - coordinator = entry.runtime_data.coordinator + coordinator = entry.runtime_data async_add_entities([QubeWaterHeater(coordinator, entry)]) @@ -86,14 +87,12 @@ class QubeWaterHeater(QubeEntity, WaterHeaterEntity): @override async def async_set_temperature(self, **kwargs: Any) -> None: """Set the target DHW temperature.""" - temperature = kwargs.get("temperature") - if temperature is None: - return + temperature = kwargs[ATTR_TEMPERATURE] try: success = await self.coordinator.client.write_setpoint( DHW_SETPOINT_KEY, temperature ) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="set_temperature_failed", @@ -111,7 +110,7 @@ class QubeWaterHeater(QubeEntity, WaterHeaterEntity): boost = operation_mode == STATE_PERFORMANCE try: success = await self.coordinator.client.write_switch(DHW_BOOST_KEY, boost) - except (ConnectionError, TimeoutError, OSError) as err: + except OSError as err: raise HomeAssistantError( translation_domain=DOMAIN, translation_key="switch_command_failed", diff --git a/tests/components/hr_energy_qube/test_binary_sensor.py b/tests/components/hr_energy_qube/test_binary_sensor.py index f7ef3737f16a..438d282b4795 100644 --- a/tests/components/hr_energy_qube/test_binary_sensor.py +++ b/tests/components/hr_energy_qube/test_binary_sensor.py @@ -1,19 +1,17 @@ """Tests for the Qube Heat Pump binary sensor platform.""" -from datetime import timedelta -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform @pytest.mark.usefixtures("entity_registry_enabled_by_default") @@ -32,41 +30,3 @@ async def test_entities( await setup_integration(hass, mock_config_entry) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - - -@pytest.mark.parametrize( - ("side_effect", "return_value"), - [ - (ConnectionError("Connection lost"), None), - (None, None), - ], -) -async def test_binary_sensor_unavailable_on_coordinator_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, -) -> None: - """Test binary sensors become unavailable when coordinator fails.""" - await setup_integration(hass, mock_config_entry) - - # Verify binary sensors are available after setup - states = hass.states.async_all("binary_sensor") - assert len(states) > 0 - assert all(s.state != STATE_UNAVAILABLE for s in states) - - # Make the next fetch fail - mock_qube_client.get_all_data = AsyncMock( - side_effect=side_effect, return_value=return_value - ) - - # Skip time to trigger coordinator refresh - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - # All binary sensors should be unavailable - states = hass.states.async_all("binary_sensor") - assert all(s.state == STATE_UNAVAILABLE for s in states) diff --git a/tests/components/hr_energy_qube/test_init.py b/tests/components/hr_energy_qube/test_init.py index 24f6653e34ce..07946826c6a1 100644 --- a/tests/components/hr_energy_qube/test_init.py +++ b/tests/components/hr_energy_qube/test_init.py @@ -2,6 +2,8 @@ from unittest.mock import MagicMock +import pytest + from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant @@ -25,3 +27,29 @@ async def test_setup_and_unload_entry( assert mock_config_entry.state is ConfigEntryState.NOT_LOADED mock_qube_client.close.assert_called_once() + + +@pytest.mark.parametrize( + ("connect_result", "connect_error"), + [ + (False, None), + (None, OSError("Connection refused")), + ], +) +async def test_setup_entry_connection_failure( + hass: HomeAssistant, + mock_qube_client: MagicMock, + mock_config_entry: MockConfigEntry, + connect_result: bool | None, + connect_error: Exception | None, +) -> None: + """Test setup failure when the device cannot be reached.""" + if connect_error is not None: + mock_qube_client.connect.side_effect = connect_error + else: + mock_qube_client.connect.return_value = connect_result + + await setup_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + mock_qube_client.close.assert_called_once() diff --git a/tests/components/hr_energy_qube/test_select.py b/tests/components/hr_energy_qube/test_select.py index 915505b8ab26..7a9ebeddb75c 100644 --- a/tests/components/hr_energy_qube/test_select.py +++ b/tests/components/hr_energy_qube/test_select.py @@ -1,9 +1,7 @@ """Tests for the Qube Heat Pump select platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -12,29 +10,16 @@ from homeassistant.components.select import ( DOMAIN as SELECT_DOMAIN, SERVICE_SELECT_OPTION, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform +from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform - -async def _setup_select( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, -) -> str: - """Set up the select platform and return the entity_id.""" - with patch("homeassistant.components.hr_energy_qube.PLATFORMS", [Platform.SELECT]): - await setup_integration(hass, mock_config_entry) - entity_registry = er.async_get(hass) - entries = er.async_entries_for_config_entry( - entity_registry, mock_config_entry.entry_id - ) - assert len(entries) == 1 - return entries[0].entity_id +ENTITY_ID = "select.qube_heat_pump_smart_grid_ready_mode" async def test_entities( @@ -59,84 +44,42 @@ async def test_select_option( option: str, ) -> None: """Test selecting an SG Ready mode.""" - entity_id = await _setup_select(hass, mock_config_entry) + await setup_integration(hass, mock_config_entry) await hass.services.async_call( SELECT_DOMAIN, SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: option}, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_OPTION: option}, blocking=True, ) mock_qube_client.set_sg_ready_mode.assert_awaited_once_with(option) -async def test_select_option_connection_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test select raises HomeAssistantError on connection error.""" - entity_id = await _setup_select(hass, mock_config_entry) - - mock_qube_client.set_sg_ready_mode = AsyncMock(side_effect=ConnectionError) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "plus"}, - blocking=True, - ) - - -async def test_select_option_write_failure( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test select raises HomeAssistantError when write fails.""" - entity_id = await _setup_select(hass, mock_config_entry) - - mock_qube_client.set_sg_ready_mode = AsyncMock(return_value=False) - with pytest.raises(HomeAssistantError): - await hass.services.async_call( - SELECT_DOMAIN, - SERVICE_SELECT_OPTION, - {ATTR_ENTITY_ID: entity_id, ATTR_OPTION: "plus"}, - blocking=True, - ) - - @pytest.mark.parametrize( ("side_effect", "return_value"), [ - (ConnectionError("Connection lost"), None), - (None, None), + (ConnectionError, None), + (None, False), ], ) -async def test_select_unavailable_on_coordinator_error( +async def test_select_option_error( hass: HomeAssistant, mock_qube_client: MagicMock, mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, + side_effect: type[Exception] | None, + return_value: bool | None, ) -> None: - """Test select becomes unavailable when coordinator fails.""" - entity_id = await _setup_select(hass, mock_config_entry) + """Test select raises HomeAssistantError on connection error or write failure.""" + await setup_integration(hass, mock_config_entry) - state = hass.states.get(entity_id) - assert state is not None - assert state.state != STATE_UNAVAILABLE - - mock_qube_client.get_all_data = AsyncMock( + mock_qube_client.set_sg_ready_mode = AsyncMock( side_effect=side_effect, return_value=return_value ) - - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get(entity_id) - assert state is not None - assert state.state == STATE_UNAVAILABLE + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_OPTION: "plus"}, + blocking=True, + ) diff --git a/tests/components/hr_energy_qube/test_switch.py b/tests/components/hr_energy_qube/test_switch.py index 60fa5f9f5224..7da553670283 100644 --- a/tests/components/hr_energy_qube/test_switch.py +++ b/tests/components/hr_energy_qube/test_switch.py @@ -1,19 +1,15 @@ """Tests for the Qube Heat Pump switch platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.components.hr_energy_qube.const import DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_UNAVAILABLE, Platform, ) from homeassistant.core import HomeAssistant @@ -22,19 +18,9 @@ from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform -HEATING_DEMAND_KEY = "heating_demand" - - -def _get_entity_id( - entity_registry: er.EntityRegistry, entry: MockConfigEntry, key: str -) -> str: - """Look up entity_id by key and config entry.""" - unique_id = f"{entry.entry_id}-{key}" - entity_id = entity_registry.async_get_entity_id(SWITCH_DOMAIN, DOMAIN, unique_id) - assert entity_id is not None - return entity_id +ENTITY_ID = "switch.qube_heat_pump_heating_demand" async def test_entities( @@ -63,7 +49,6 @@ async def test_entities( ) async def test_turn_on_off( hass: HomeAssistant, - entity_registry: er.EntityRegistry, mock_qube_client: MagicMock, mock_config_entry: MockConfigEntry, service: str, @@ -72,11 +57,10 @@ async def test_turn_on_off( """Test turning a switch on and off.""" await setup_integration(hass, mock_config_entry) - entity_id = _get_entity_id(entity_registry, mock_config_entry, HEATING_DEMAND_KEY) await hass.services.async_call( SWITCH_DOMAIN, service, - {ATTR_ENTITY_ID: entity_id}, + {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True, ) @@ -94,7 +78,6 @@ async def test_turn_on_off( ) async def test_turn_on_error( hass: HomeAssistant, - entity_registry: er.EntityRegistry, mock_qube_client: MagicMock, mock_config_entry: MockConfigEntry, side_effect: type[Exception] | None, @@ -106,49 +89,10 @@ async def test_turn_on_error( mock_qube_client.write_switch = AsyncMock( side_effect=side_effect, return_value=return_value ) - entity_id = _get_entity_id(entity_registry, mock_config_entry, HEATING_DEMAND_KEY) with pytest.raises(HomeAssistantError): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: entity_id}, + {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True, ) - - -@pytest.mark.parametrize( - ("side_effect", "return_value"), - [ - (ConnectionError("Connection lost"), None), - (None, None), - ], -) -async def test_switch_unavailable_on_coordinator_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, -) -> None: - """Test switches become unavailable when coordinator fails.""" - await setup_integration(hass, mock_config_entry) - - # Verify switches are available after setup - states = hass.states.async_all("switch") - assert len(states) > 0 - assert all(s.state != STATE_UNAVAILABLE for s in states) - - # Make the next fetch fail - mock_qube_client.get_all_data = AsyncMock( - side_effect=side_effect, return_value=return_value - ) - - # Skip time to trigger coordinator refresh - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - # All switches should be unavailable - states = hass.states.async_all("switch") - assert all(s.state == STATE_UNAVAILABLE for s in states) diff --git a/tests/components/hr_energy_qube/test_water_heater.py b/tests/components/hr_energy_qube/test_water_heater.py index 71bb65265105..6519ed4baa91 100644 --- a/tests/components/hr_energy_qube/test_water_heater.py +++ b/tests/components/hr_energy_qube/test_water_heater.py @@ -1,9 +1,7 @@ """Tests for the Qube Heat Pump water heater platform.""" -from datetime import timedelta from unittest.mock import AsyncMock, MagicMock, patch -from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion @@ -16,14 +14,14 @@ from homeassistant.components.water_heater import ( STATE_HEAT_PUMP, STATE_PERFORMANCE, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform +from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration -from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform +from tests.common import MockConfigEntry, snapshot_platform ENTITY_ID = "water_heater.qube_heat_pump_domestic_hot_water" @@ -124,38 +122,3 @@ async def test_current_operation_boost( state = hass.states.get(ENTITY_ID) assert state is not None assert state.attributes["operation_mode"] == STATE_PERFORMANCE - - -@pytest.mark.parametrize( - ("side_effect", "return_value"), - [ - (ConnectionError("Connection lost"), None), - (None, None), - ], -) -async def test_water_heater_unavailable_on_coordinator_error( - hass: HomeAssistant, - mock_qube_client: MagicMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, - side_effect: Exception | None, - return_value: None, -) -> None: - """Test water heater becomes unavailable when coordinator fails.""" - await setup_integration(hass, mock_config_entry) - - state = hass.states.get(ENTITY_ID) - assert state is not None - assert state.state != STATE_UNAVAILABLE - - mock_qube_client.get_all_data = AsyncMock( - side_effect=side_effect, return_value=return_value - ) - - freezer.tick(timedelta(seconds=31)) - async_fire_time_changed(hass) - await hass.async_block_till_done() - - state = hass.states.get(ENTITY_ID) - assert state is not None - assert state.state == STATE_UNAVAILABLE