From 2522c6d697833d39fc2ab537aa1e94949c7edfe1 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 27 Apr 2023 17:10:29 +0200 Subject: [PATCH] Add WS command cloud/alexa/entities/get (#92121) * Add WS command cloud/alexa/entities/get * Fix bugs, add test --- .../components/cloud/alexa_config.py | 6 +- homeassistant/components/cloud/http_api.py | 42 +++++++++++++ tests/components/cloud/test_http_api.py | 61 +++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index 7acfbbb7f6b..9c691ebed55 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -94,7 +94,7 @@ SUPPORTED_SENSOR_DEVICE_CLASSES = { } -def _supported_legacy(hass: HomeAssistant, entity_id: str) -> bool: +def entity_supported(hass: HomeAssistant, entity_id: str) -> bool: """Return if the entity is supported. This is called when migrating from legacy config format to avoid exposing @@ -249,12 +249,12 @@ class CloudAlexaConfig(alexa_config.AbstractConfig): # Backwards compat if (default_expose := self._prefs.alexa_default_expose) is None: - return not auxiliary_entity and _supported_legacy(self.hass, entity_id) + return not auxiliary_entity and entity_supported(self.hass, entity_id) return ( not auxiliary_entity and split_entity_id(entity_id)[0] in default_expose - and _supported_legacy(self.hass, entity_id) + and entity_supported(self.hass, entity_id) ) def should_expose(self, entity_id): diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 8d6c4e65e3c..8bc31f7b862 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -29,6 +29,7 @@ from homeassistant.helpers import entity_registry as er from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util.location import async_detect_location_info +from .alexa_config import entity_supported as entity_supported_by_alexa from .const import ( DOMAIN, PREF_ALEXA_REPORT_STATE, @@ -73,6 +74,7 @@ async def async_setup(hass): websocket_api.async_register_command(hass, google_assistant_list) websocket_api.async_register_command(hass, google_assistant_update) + websocket_api.async_register_command(hass, alexa_get) websocket_api.async_register_command(hass, alexa_list) websocket_api.async_register_command(hass, alexa_sync) @@ -668,6 +670,46 @@ async def google_assistant_update( connection.send_result(msg["id"]) +@websocket_api.require_admin +@_require_cloud_login +@websocket_api.websocket_command( + { + "type": "cloud/alexa/entities/get", + "entity_id": str, + } +) +@websocket_api.async_response +@_ws_handle_cloud_errors +async def alexa_get( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], +) -> None: + """Get data for a single alexa entity.""" + entity_registry = er.async_get(hass) + entity_id: str = msg["entity_id"] + + if not entity_registry.async_is_registered(entity_id): + connection.send_error( + msg["id"], + websocket_api.const.ERR_NOT_FOUND, + f"{entity_id} not in the entity registry", + ) + return + + if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES or not entity_supported_by_alexa( + hass, entity_id + ): + connection.send_error( + msg["id"], + websocket_api.const.ERR_NOT_SUPPORTED, + f"{entity_id} not supported by Alexa", + ) + return + + connection.send_result(msg["id"]) + + @websocket_api.require_admin @_require_cloud_login @websocket_api.websocket_command({"type": "cloud/alexa/entities"}) diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 351caff883f..ff4b9be4d3f 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -938,6 +938,67 @@ async def test_list_alexa_entities( } +async def test_get_alexa_entity( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + hass_ws_client: WebSocketGenerator, + setup_api, + mock_cloud_login, +) -> None: + """Test that we can get an Alexa entity.""" + client = await hass_ws_client(hass) + + # Test getting an unknown entity + await client.send_json_auto_id( + {"type": "cloud/alexa/entities/get", "entity_id": "light.kitchen"} + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"] == { + "code": "not_found", + "message": "light.kitchen not in the entity registry", + } + + # Test getting a blocked entity + entity_registry.async_get_or_create( + "group", "test", "unique", suggested_object_id="all_locks" + ) + hass.states.async_set("group.all_locks", "bla") + await client.send_json_auto_id( + {"type": "cloud/alexa/entities/get", "entity_id": "group.all_locks"} + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"] == { + "code": "not_supported", + "message": "group.all_locks not supported by Alexa", + } + + entity_registry.async_get_or_create( + "light", "test", "unique", suggested_object_id="kitchen" + ) + entity_registry.async_get_or_create( + "water_heater", "test", "unique", suggested_object_id="basement" + ) + + await client.send_json_auto_id( + {"type": "cloud/alexa/entities/get", "entity_id": "light.kitchen"} + ) + response = await client.receive_json() + assert response["success"] + assert response["result"] is None + + await client.send_json_auto_id( + {"type": "cloud/alexa/entities/get", "entity_id": "water_heater.basement"} + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"] == { + "code": "not_supported", + "message": "water_heater.basement not supported by Alexa", + } + + async def test_update_alexa_entity( hass: HomeAssistant, entity_registry: er.EntityRegistry,