Catch exceptions in service calls by buttons/switches in pyLoad integration (#120701)

* Catch exceptions in service calls by buttons/switches

* changes

* more changes

* update tests
pull/120782/head
Mr. Bubbles 2024-06-28 20:11:03 +02:00 committed by GitHub
parent c029c534d6
commit 4fb0621027
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 7 deletions

View File

@ -7,13 +7,15 @@ from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from pyloadapi.api import PyLoadAPI
from pyloadapi.api import CannotConnect, InvalidAuth, PyLoadAPI
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import PyLoadConfigEntry
from .const import DOMAIN
from .entity import BasePyLoadEntity
@ -80,4 +82,15 @@ class PyLoadBinarySensor(BasePyLoadEntity, ButtonEntity):
async def async_press(self) -> None:
"""Handle the button press."""
await self.entity_description.press_fn(self.coordinator.pyload)
try:
await self.entity_description.press_fn(self.coordinator.pyload)
except CannotConnect as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
) from e
except InvalidAuth as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_auth_exception",
) from e

View File

@ -98,6 +98,12 @@
},
"setup_authentication_exception": {
"message": "Authentication failed for {username}, verify your login credentials"
},
"service_call_exception": {
"message": "Unable to send command to pyLoad due to a connection error, try again later"
},
"service_call_auth_exception": {
"message": "Unable to send command to pyLoad due to an authentication error, try again later"
}
},
"issues": {

View File

@ -7,7 +7,7 @@ from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from pyloadapi.api import PyLoadAPI
from pyloadapi.api import CannotConnect, InvalidAuth, PyLoadAPI
from homeassistant.components.switch import (
SwitchDeviceClass,
@ -15,9 +15,11 @@ from homeassistant.components.switch import (
SwitchEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import PyLoadConfigEntry
from .const import DOMAIN
from .coordinator import PyLoadData
from .entity import BasePyLoadEntity
@ -90,15 +92,51 @@ class PyLoadSwitchEntity(BasePyLoadEntity, SwitchEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
await self.entity_description.turn_on_fn(self.coordinator.pyload)
try:
await self.entity_description.turn_on_fn(self.coordinator.pyload)
except CannotConnect as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
) from e
except InvalidAuth as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_auth_exception",
) from e
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity on."""
await self.entity_description.turn_off_fn(self.coordinator.pyload)
try:
await self.entity_description.turn_off_fn(self.coordinator.pyload)
except CannotConnect as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
) from e
except InvalidAuth as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_auth_exception",
) from e
await self.coordinator.async_refresh()
async def async_toggle(self, **kwargs: Any) -> None:
"""Toggle the entity."""
await self.entity_description.toggle_fn(self.coordinator.pyload)
try:
await self.entity_description.toggle_fn(self.coordinator.pyload)
except CannotConnect as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
) from e
except InvalidAuth as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_auth_exception",
) from e
await self.coordinator.async_refresh()

View File

@ -3,6 +3,7 @@
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, call, patch
from pyloadapi import CannotConnect, InvalidAuth
import pytest
from syrupy.assertion import SnapshotAssertion
@ -11,6 +12,7 @@ from homeassistant.components.pyload.button import PyLoadButtonEntity
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@ -78,6 +80,43 @@ async def test_button_press(
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)
await hass.async_block_till_done()
assert API_CALL[entity_entry.translation_key] in mock_pyloadapi.method_calls
mock_pyloadapi.reset_mock()
@pytest.mark.parametrize(
("side_effect"),
[CannotConnect, InvalidAuth],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_button_press_errors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pyloadapi: AsyncMock,
entity_registry: er.EntityRegistry,
side_effect: Exception,
) -> None:
"""Test button press method."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entity_entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
mock_pyloadapi.stop_all_downloads.side_effect = side_effect
mock_pyloadapi.restart_failed.side_effect = side_effect
mock_pyloadapi.delete_finished.side_effect = side_effect
mock_pyloadapi.restart.side_effect = side_effect
for entity_entry in entity_entries:
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)

View File

@ -3,6 +3,7 @@
from collections.abc import AsyncGenerator
from unittest.mock import AsyncMock, call, patch
from pyloadapi import CannotConnect, InvalidAuth
import pytest
from syrupy.assertion import SnapshotAssertion
@ -16,6 +17,7 @@ from homeassistant.components.switch import (
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, snapshot_platform
@ -102,3 +104,49 @@ async def test_turn_on_off(
in mock_pyloadapi.method_calls
)
mock_pyloadapi.reset_mock()
@pytest.mark.parametrize(
("service_call"),
[
SERVICE_TURN_ON,
SERVICE_TURN_OFF,
SERVICE_TOGGLE,
],
)
@pytest.mark.parametrize(
("side_effect"),
[CannotConnect, InvalidAuth],
)
async def test_turn_on_off_errors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_pyloadapi: AsyncMock,
service_call: str,
entity_registry: er.EntityRegistry,
side_effect: Exception,
) -> None:
"""Test switch turn on/off, toggle method."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
entity_entries = er.async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
mock_pyloadapi.unpause.side_effect = side_effect
mock_pyloadapi.pause.side_effect = side_effect
mock_pyloadapi.toggle_pause.side_effect = side_effect
mock_pyloadapi.toggle_reconnect.side_effect = side_effect
for entity_entry in entity_entries:
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
SWITCH_DOMAIN,
service_call,
{ATTR_ENTITY_ID: entity_entry.entity_id},
blocking=True,
)