Add exception translation for entity action not supported (#131956)

pull/132011/head
Jan Bouwhuis 2024-12-01 16:53:06 +01:00 committed by GitHub
parent c55a4e9584
commit 3aae9b629f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 98 additions and 31 deletions

View File

@ -224,6 +224,9 @@
"service_not_found": {
"message": "Action {domain}.{service} not found."
},
"service_not_supported": {
"message": "Entity {entity_id} does not support action {domain}.{service}."
},
"service_does_not_support_response": {
"message": "An action which does not return responses can't be called with {return_response}."
},

View File

@ -270,6 +270,25 @@ class ServiceNotFound(ServiceValidationError):
self.generate_message = True
class ServiceNotSupported(ServiceValidationError):
"""Raised when an entity action is not supported."""
def __init__(self, domain: str, service: str, entity_id: str) -> None:
"""Initialize ServiceNotSupported exception."""
super().__init__(
translation_domain="homeassistant",
translation_key="service_not_supported",
translation_placeholders={
"domain": domain,
"service": service,
"entity_id": entity_id,
},
)
self.domain = domain
self.service = service
self.generate_message = True
class MaxLengthExceeded(HomeAssistantError):
"""Raised when a property value has exceeded the max character length."""

View File

@ -42,6 +42,7 @@ from homeassistant.core import (
)
from homeassistant.exceptions import (
HomeAssistantError,
ServiceNotSupported,
TemplateError,
Unauthorized,
UnknownUser,
@ -986,9 +987,7 @@ async def entity_service_call(
):
# If entity explicitly referenced, raise an error
if referenced is not None and entity.entity_id in referenced.referenced:
raise HomeAssistantError(
f"Entity {entity.entity_id} does not support this service."
)
raise ServiceNotSupported(call.domain, call.service, entity.entity_id)
continue

View File

@ -20,8 +20,9 @@ from homeassistant.const import (
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import ServiceNotSupported
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from .mocks import (
@ -453,8 +454,9 @@ async def test_open_throws_hass_service_not_supported_error(
hass: HomeAssistant,
) -> None:
"""Test open throws correct error on entity does not support this service error."""
await async_setup_component(hass, "homeassistant", {})
mocked_lock_detail = await _mock_operative_august_lock_detail(hass)
await _create_august_with_devices(hass, [mocked_lock_detail])
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
with pytest.raises(HomeAssistantError, match="does not support this service"):
with pytest.raises(ServiceNotSupported, match="does not support action"):
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)

View File

@ -14,7 +14,8 @@ import voluptuous as vol
from homeassistant.components.calendar import DOMAIN, SERVICE_GET_EVENTS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceNotSupported
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from .conftest import MockCalendarEntity, MockConfigEntry
@ -214,8 +215,12 @@ async def test_unsupported_websocket(
async def test_unsupported_create_event_service(hass: HomeAssistant) -> None:
"""Test unsupported service call."""
with pytest.raises(HomeAssistantError, match="does not support this service"):
await async_setup_component(hass, "homeassistant", {})
with pytest.raises(
ServiceNotSupported,
match="Entity calendar.calendar_1 does not "
"support action calendar.create_event",
):
await hass.services.async_call(
DOMAIN,
"create_event",

View File

@ -20,7 +20,8 @@ from homeassistant.components.google.const import CONF_CALENDAR_ACCESS
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_OFF
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceNotSupported
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import UTC, utcnow
from .conftest import (
@ -593,7 +594,7 @@ async def test_unsupported_create_event(
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test create event service call is unsupported for virtual calendars."""
await async_setup_component(hass, "homeassistant", {})
mock_calendars_list({"items": [test_api_calendar]})
mock_events_list({})
assert await component_setup()
@ -601,8 +602,12 @@ async def test_unsupported_create_event(
start_datetime = datetime.datetime.now(tz=zoneinfo.ZoneInfo("America/Regina"))
delta = datetime.timedelta(days=3, hours=3)
end_datetime = start_datetime + delta
entity_id = "calendar.backyard_light"
with pytest.raises(HomeAssistantError, match="does not support this service"):
with pytest.raises(
ServiceNotSupported,
match=f"Entity {entity_id} does not support action google.create_event",
):
await hass.services.async_call(
DOMAIN,
"create_event",
@ -613,7 +618,7 @@ async def test_unsupported_create_event(
"summary": TEST_EVENT_SUMMARY,
"description": TEST_EVENT_DESCRIPTION,
},
target={"entity_id": "calendar.backyard_light"},
target={"entity_id": entity_id},
blocking=True,
)

View File

@ -8,8 +8,10 @@ import pytest
from syrupy import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, HomeAssistantError
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceNotSupported
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from .common import (
set_node_attribute,
@ -35,6 +37,8 @@ async def test_vacuum_actions(
matter_node: MatterNode,
) -> None:
"""Test vacuum entity actions."""
# Fetch translations
await async_setup_component(hass, "homeassistant", {})
entity_id = "vacuum.mock_vacuum"
state = hass.states.get(entity_id)
assert state
@ -96,8 +100,8 @@ async def test_vacuum_actions(
# test stop action
# stop command is not supported by the vacuum fixture
with pytest.raises(
HomeAssistantError,
match="Entity vacuum.mock_vacuum does not support this service.",
ServiceNotSupported,
match="Entity vacuum.mock_vacuum does not support action vacuum.stop",
):
await hass.services.async_call(
"vacuum",

View File

@ -76,7 +76,8 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import ServiceNotSupported
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from . import async_wait_config_entry_reload, setup_samsungtv_entry
@ -1021,8 +1022,9 @@ async def test_turn_on_wol(hass: HomeAssistant) -> None:
async def test_turn_on_without_turnon(hass: HomeAssistant, remote: Mock) -> None:
"""Test turn on."""
await async_setup_component(hass, "homeassistant", {})
await setup_samsungtv_entry(hass, MOCK_CONFIG)
with pytest.raises(HomeAssistantError, match="does not support this service"):
with pytest.raises(ServiceNotSupported, match="does not support action"):
await hass.services.async_call(
MP_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True
)

View File

@ -24,8 +24,9 @@ from homeassistant.components.lock import (
from homeassistant.components.webhook import async_generate_url
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceNotSupported
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.setup import async_setup_component
from .conftest import WEBHOOK_ID
@ -113,6 +114,8 @@ async def test_lock_without_pullspring(
snapshot: SnapshotAssertion,
) -> None:
"""Test the tedee lock without pullspring."""
# Fetch translations
await async_setup_component(hass, "homeassistant", {})
mock_tedee.lock.return_value = None
mock_tedee.unlock.return_value = None
mock_tedee.open.return_value = None
@ -131,8 +134,8 @@ async def test_lock_without_pullspring(
assert device == snapshot
with pytest.raises(
HomeAssistantError,
match="Entity lock.lock_2c3d does not support this service.",
ServiceNotSupported,
match=f"Entity lock.lock_2c3d does not support action {LOCK_DOMAIN}.{SERVICE_OPEN}",
):
await hass.services.async_call(
LOCK_DOMAIN,

View File

@ -24,8 +24,13 @@ from homeassistant.components.climate import (
from homeassistant.components.tesla_fleet.coordinator import VEHICLE_INTERVAL
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.exceptions import (
HomeAssistantError,
ServiceNotSupported,
ServiceValidationError,
)
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from . import assert_entities, setup_platform
from .const import (
@ -391,6 +396,7 @@ async def test_climate_noscope(
snapshot: SnapshotAssertion,
) -> None:
"""Tests with no command scopes."""
await async_setup_component(hass, "homeassistant", {})
await setup_platform(hass, readonly_config_entry, [Platform.CLIMATE])
entity_id = "climate.test_climate"
@ -405,8 +411,9 @@ async def test_climate_noscope(
)
with pytest.raises(
HomeAssistantError,
match="Entity climate.test_climate does not support this service.",
ServiceNotSupported,
match="Entity climate.test_climate does not "
"support action climate.set_temperature",
):
await hass.services.async_call(
CLIMATE_DOMAIN,

View File

@ -27,7 +27,11 @@ from homeassistant.components.todo import (
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.exceptions import (
HomeAssistantError,
ServiceNotSupported,
ServiceValidationError,
)
from homeassistant.helpers import intent
from homeassistant.setup import async_setup_component
@ -941,14 +945,15 @@ async def test_unsupported_service(
payload: dict[str, Any] | None,
) -> None:
"""Test a To-do list that does not support features."""
# Fetch translations
await async_setup_component(hass, "homeassistant", "")
entity1 = TodoListEntity()
entity1.entity_id = "todo.entity1"
await create_mock_platform(hass, [entity1])
with pytest.raises(
HomeAssistantError,
match="does not support this service",
ServiceNotSupported,
match=f"Entity todo.entity1 does not support action {DOMAIN}.{service_name}",
):
await hass.services.async_call(
DOMAIN,

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import ServiceNotSupported
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.util.dt as dt_util
@ -29,6 +29,7 @@ from .mocks import (
_mock_lock_from_fixture,
_mock_lock_with_unlatch,
_mock_operative_yale_lock_detail,
async_setup_component,
)
from tests.common import async_fire_time_changed
@ -418,8 +419,14 @@ async def test_open_throws_hass_service_not_supported_error(
hass: HomeAssistant,
) -> None:
"""Test open throws correct error on entity does not support this service error."""
# Fetch translations
await async_setup_component(hass, "homeassistant", {})
mocked_lock_detail = await _mock_operative_yale_lock_detail(hass)
await _create_yale_with_devices(hass, [mocked_lock_detail])
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
with pytest.raises(HomeAssistantError, match="does not support this service"):
entity_id = "lock.a6697750d607098bae8d6baa11ef8063_name"
data = {ATTR_ENTITY_ID: entity_id}
with pytest.raises(
ServiceNotSupported,
match=f"Entity {entity_id} does not support action {LOCK_DOMAIN}.{SERVICE_OPEN}",
):
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)

View File

@ -1274,6 +1274,8 @@ async def test_register_with_mixed_case(hass: HomeAssistant) -> None:
async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -> None:
"""Test service calls invoked only if entity has required features."""
# Set up homeassistant component to fetch the translations
await async_setup_component(hass, "homeassistant", {})
test_service_mock = AsyncMock(return_value=None)
await service.entity_service_call(
hass,
@ -1293,7 +1295,11 @@ async def test_call_with_required_features(hass: HomeAssistant, mock_entities) -
# Test we raise if we target entity ID that does not support the service
test_service_mock.reset_mock()
with pytest.raises(exceptions.HomeAssistantError):
with pytest.raises(
exceptions.ServiceNotSupported,
match="Entity light.living_room does not "
"support action test_domain.test_service",
):
await service.entity_service_call(
hass,
mock_entities,