mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Add action exception handling to Actron Air (#160579)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
@@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
|
||||
from .entity import ActronAirAcEntity, ActronAirZoneEntity
|
||||
from .entity import ActronAirAcEntity, ActronAirZoneEntity, handle_actron_api_errors
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -136,16 +136,19 @@ class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
|
||||
"""Return the target temperature."""
|
||||
return self._status.user_aircon_settings.temperature_setpoint_cool_c
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set a new fan mode."""
|
||||
api_fan_mode = FAN_MODE_MAPPING_HA_TO_ACTRONAIR.get(fan_mode)
|
||||
await self._status.user_aircon_settings.set_fan_mode(api_fan_mode)
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the HVAC mode."""
|
||||
ac_mode = HVAC_MODE_MAPPING_HA_TO_ACTRONAIR.get(hvac_mode)
|
||||
await self._status.ac_system.set_system_mode(ac_mode)
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the temperature."""
|
||||
temp = kwargs.get(ATTR_TEMPERATURE)
|
||||
@@ -209,11 +212,13 @@ class ActronZoneClimate(ActronAirZoneEntity, ActronAirClimateEntity):
|
||||
"""Return the target temperature."""
|
||||
return self._zone.temperature_setpoint_cool_c
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the HVAC mode."""
|
||||
is_enabled = hvac_mode != HVACMode.OFF
|
||||
await self._zone.enable(is_enabled)
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set the temperature."""
|
||||
await self._zone.set_temperature(temperature=kwargs.get(ATTR_TEMPERATURE))
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
"""Base entity classes for Actron Air integration."""
|
||||
|
||||
from actron_neo_api import ActronAirZone
|
||||
from collections.abc import Callable, Coroutine
|
||||
from functools import wraps
|
||||
from typing import Any, Concatenate
|
||||
|
||||
from actron_neo_api import ActronAirAPIError, ActronAirZone
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@@ -9,6 +14,26 @@ from .const import DOMAIN
|
||||
from .coordinator import ActronAirSystemCoordinator
|
||||
|
||||
|
||||
def handle_actron_api_errors[_EntityT: ActronAirEntity, **_P](
|
||||
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
|
||||
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
|
||||
"""Decorate Actron Air API calls to handle ActronAirAPIError exceptions."""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
|
||||
"""Wrap API calls with exception handling."""
|
||||
try:
|
||||
await func(self, *args, **kwargs)
|
||||
except ActronAirAPIError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="api_error",
|
||||
translation_placeholders={"error": str(err)},
|
||||
) from err
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class ActronAirEntity(CoordinatorEntity[ActronAirSystemCoordinator]):
|
||||
"""Base class for Actron Air entities."""
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ rules:
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
action-exceptions: todo
|
||||
action-exceptions: done
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"api_error": {
|
||||
"message": "Failed to communicate with Actron Air device: {error}"
|
||||
},
|
||||
"auth_error": {
|
||||
"message": "Authentication failed, please reauthenticate"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
|
||||
from .entity import ActronAirAcEntity
|
||||
from .entity import ActronAirAcEntity, handle_actron_api_errors
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -105,10 +105,12 @@ class ActronAirSwitch(ActronAirAcEntity, SwitchEntity):
|
||||
"""Return true if the switch is on."""
|
||||
return self.entity_description.is_on_fn(self.coordinator)
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
await self.entity_description.set_fn(self.coordinator, True)
|
||||
|
||||
@handle_actron_api_errors
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
await self.entity_description.set_fn(self.coordinator, False)
|
||||
|
||||
@@ -7,7 +7,10 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.actron_air.const import DOMAIN
|
||||
from homeassistant.const import CONF_API_TOKEN
|
||||
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -62,11 +65,14 @@ def mock_actron_api() -> Generator[AsyncMock]:
|
||||
api.state_manager = MagicMock()
|
||||
status = api.state_manager.get_status.return_value
|
||||
status.master_info.live_temp_c = 22.0
|
||||
status.master_info.live_humidity_pc = 50.0
|
||||
status.ac_system.system_name = "Test System"
|
||||
status.ac_system.serial_number = "123456"
|
||||
status.ac_system.master_wc_model = "Test Model"
|
||||
status.ac_system.master_wc_firmware_version = "1.0.0"
|
||||
status.ac_system.set_system_mode = AsyncMock()
|
||||
status.remote_zone_info = []
|
||||
status.zones = {}
|
||||
status.min_temp = 16
|
||||
status.max_temp = 30
|
||||
status.aircon_system.mode = "OFF"
|
||||
@@ -75,18 +81,27 @@ def mock_actron_api() -> Generator[AsyncMock]:
|
||||
status.room_temp = 25
|
||||
status.is_on = False
|
||||
|
||||
# Mock user_aircon_settings for the switch platform
|
||||
# Mock user_aircon_settings for the switch and climate platforms
|
||||
settings = status.user_aircon_settings
|
||||
settings.away_mode = False
|
||||
settings.continuous_fan_enabled = False
|
||||
settings.quiet_mode_enabled = False
|
||||
settings.turbo_enabled = False
|
||||
settings.turbo_supported = True
|
||||
settings.is_on = False
|
||||
settings.mode = "COOL"
|
||||
settings.base_fan_mode = "LOW"
|
||||
settings.temperature_setpoint_cool_c = 24.0
|
||||
|
||||
settings.set_away_mode = AsyncMock()
|
||||
settings.set_continuous_mode = AsyncMock()
|
||||
settings.set_quiet_mode = AsyncMock()
|
||||
settings.set_turbo_mode = AsyncMock()
|
||||
settings.set_temperature = AsyncMock()
|
||||
settings.set_fan_mode = AsyncMock()
|
||||
|
||||
# Mock ac_system methods for climate platform
|
||||
status.ac_system.set_system_mode = AsyncMock()
|
||||
|
||||
yield api
|
||||
|
||||
@@ -102,6 +117,26 @@ def mock_config_entry() -> MockConfigEntry:
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_zone() -> MagicMock:
|
||||
"""Return a mocked zone."""
|
||||
zone = MagicMock()
|
||||
zone.exists = True
|
||||
zone.zone_id = 1
|
||||
zone.zone_name = "Test Zone"
|
||||
zone.title = "Living Room"
|
||||
zone.live_temp_c = 22.0
|
||||
zone.temperature_setpoint_cool_c = 24.0
|
||||
zone.is_active = True
|
||||
zone.hvac_mode = "COOL"
|
||||
zone.humidity = 50.0
|
||||
zone.min_temp = 16
|
||||
zone.max_temp = 30
|
||||
zone.set_temperature = AsyncMock()
|
||||
zone.enable = AsyncMock()
|
||||
return zone
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Mock async_setup_entry."""
|
||||
@@ -109,3 +144,19 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"homeassistant.components.actron_air.async_setup_entry", return_value=True
|
||||
) as mock_setup:
|
||||
yield mock_setup
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration_with_zone(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_actron_api: AsyncMock,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Set up the Actron Air integration with zone for testing."""
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.remote_zone_info = [mock_zone]
|
||||
status.zones = {1: mock_zone}
|
||||
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
158
tests/components/actron_air/snapshots/test_climate.ambr
Normal file
158
tests/components/actron_air/snapshots/test_climate.ambr
Normal file
@@ -0,0 +1,158 @@
|
||||
# serializer version: 1
|
||||
# name: test_climate_entities[climate.living_room-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 16,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.living_room',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'actron_air',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'translation_key': None,
|
||||
'unique_id': '123456_zone_1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_climate_entities[climate.living_room-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 50.0,
|
||||
'current_temperature': 22.0,
|
||||
'friendly_name': 'Living Room',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 16,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 24.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.living_room',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'cool',
|
||||
})
|
||||
# ---
|
||||
# name: test_climate_entities[climate.test_system-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 16,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.test_system',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'actron_air',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 393>,
|
||||
'translation_key': None,
|
||||
'unique_id': '123456',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_climate_entities[climate.test_system-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 50.0,
|
||||
'current_temperature': 22.0,
|
||||
'fan_mode': 'low',
|
||||
'fan_modes': list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Test System',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 16,
|
||||
'supported_features': <ClimateEntityFeature: 393>,
|
||||
'temperature': 24.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.test_system',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
260
tests/components/actron_air/test_climate.py
Normal file
260
tests/components/actron_air/test_climate.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""Tests for the Actron Air climate platform."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from actron_neo_api import ActronAirAPIError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_HVAC_MODE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, 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, snapshot_platform
|
||||
|
||||
|
||||
async def test_climate_entities(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test climate entities."""
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.remote_zone_info = [mock_zone]
|
||||
status.zones = {1: mock_zone}
|
||||
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_system_set_temperature(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting temperature for system climate entity."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: "climate.test_system", ATTR_TEMPERATURE: 22.5},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
status.user_aircon_settings.set_temperature.assert_awaited_once_with(
|
||||
temperature=22.5
|
||||
)
|
||||
|
||||
|
||||
async def test_system_set_temperature_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test API error when setting temperature for system climate entity."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.user_aircon_settings.set_temperature.side_effect = ActronAirAPIError(
|
||||
"Test error"
|
||||
)
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Test error"):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: "climate.test_system", ATTR_TEMPERATURE: 22.5},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_system_set_fan_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting fan mode for system climate entity."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.test_system", ATTR_FAN_MODE: "low"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
status.user_aircon_settings.set_fan_mode.assert_awaited_once_with("LOW")
|
||||
|
||||
|
||||
async def test_system_set_fan_mode_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test API error when setting fan mode for system climate entity."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.user_aircon_settings.set_fan_mode.side_effect = ActronAirAPIError(
|
||||
"Test error"
|
||||
)
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Test error"):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.test_system", ATTR_FAN_MODE: "high"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_system_set_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting HVAC mode for system climate entity."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.test_system", ATTR_HVAC_MODE: HVACMode.COOL},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
status.ac_system.set_system_mode.assert_awaited_once_with("COOL")
|
||||
|
||||
|
||||
async def test_system_set_hvac_mode_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test API error when setting HVAC mode for system climate entity."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
status.ac_system.set_system_mode.side_effect = ActronAirAPIError("Test error")
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Test error"):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.test_system", ATTR_HVAC_MODE: HVACMode.HEAT},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_zone_set_temperature(
|
||||
hass: HomeAssistant,
|
||||
init_integration_with_zone: None,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test setting temperature for zone climate entity."""
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: "climate.living_room", ATTR_TEMPERATURE: 23.0},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_zone.set_temperature.assert_awaited_once_with(temperature=23.0)
|
||||
|
||||
|
||||
async def test_zone_set_temperature_api_error(
|
||||
hass: HomeAssistant,
|
||||
init_integration_with_zone: None,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test API error when setting temperature for zone climate entity."""
|
||||
mock_zone.set_temperature.side_effect = ActronAirAPIError("Test error")
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Test error"):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: "climate.living_room", ATTR_TEMPERATURE: 23.0},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_zone_set_hvac_mode_on(
|
||||
hass: HomeAssistant,
|
||||
init_integration_with_zone: None,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test setting HVAC mode to on for zone climate entity."""
|
||||
mock_zone.is_active = False
|
||||
mock_zone.hvac_mode = "OFF"
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVACMode.COOL},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_zone.enable.assert_awaited_once_with(True)
|
||||
|
||||
|
||||
async def test_zone_set_hvac_mode_off(
|
||||
hass: HomeAssistant,
|
||||
init_integration_with_zone: None,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test setting HVAC mode to off for zone climate entity."""
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVACMode.OFF},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_zone.enable.assert_awaited_once_with(False)
|
||||
|
||||
|
||||
async def test_zone_set_hvac_mode_api_error(
|
||||
hass: HomeAssistant,
|
||||
init_integration_with_zone: None,
|
||||
mock_zone: MagicMock,
|
||||
) -> None:
|
||||
"""Test API error when setting HVAC mode for zone climate entity."""
|
||||
mock_zone.enable.side_effect = ActronAirAPIError("Test error")
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Test error"):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: "climate.living_room", ATTR_HVAC_MODE: HVACMode.OFF},
|
||||
blocking=True,
|
||||
)
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from actron_neo_api import ActronAirAPIError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -12,6 +13,7 @@ from homeassistant.components.switch import (
|
||||
)
|
||||
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
|
||||
@@ -88,3 +90,37 @@ async def test_turbo_mode_not_supported(
|
||||
entity_id = "switch.test_system_turbo_mode"
|
||||
assert not hass.states.get(entity_id)
|
||||
assert not entity_registry.async_get(entity_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "method", "service"),
|
||||
[
|
||||
("switch.test_system_away_mode", "set_away_mode", SERVICE_TURN_ON),
|
||||
("switch.test_system_continuous_fan", "set_continuous_mode", SERVICE_TURN_OFF),
|
||||
("switch.test_system_quiet_mode", "set_quiet_mode", SERVICE_TURN_ON),
|
||||
("switch.test_system_turbo_mode", "set_turbo_mode", SERVICE_TURN_OFF),
|
||||
],
|
||||
)
|
||||
async def test_switch_api_error(
|
||||
hass: HomeAssistant,
|
||||
mock_actron_api: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
method: str,
|
||||
service: str,
|
||||
) -> None:
|
||||
"""Test API error handling when toggling switches."""
|
||||
with patch("homeassistant.components.actron_air.PLATFORMS", [Platform.SWITCH]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
status = mock_actron_api.state_manager.get_status.return_value
|
||||
mock_method = getattr(status.user_aircon_settings, method)
|
||||
mock_method.side_effect = ActronAirAPIError("Test error")
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Test error"):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
service,
|
||||
{ATTR_ENTITY_ID: [entity_id]},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user