From 3f10233833b88579716ae87310c064f8fa222cd4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 23 Jun 2023 13:32:03 -0400 Subject: [PATCH] Add return value to conversation.process service (#94740) * Add return value to conversation.process service * Adjust for new API --- .../components/conversation/__init__.py | 18 ++- .../conversation/snapshots/test_init.ambr | 120 ++++++++++++++++++ tests/components/conversation/test_init.py | 28 +++- 3 files changed, 159 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index f0cd6cb504c..f3d883b1565 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -16,6 +16,7 @@ from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.config_entries import ConfigEntry from homeassistant.const import MATCH_ALL from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv, intent, singleton from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass @@ -154,12 +155,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if config_intents := config.get(DOMAIN, {}).get("intents"): hass.data[DATA_CONFIG] = config_intents - async def handle_process(service: core.ServiceCall) -> None: + async def handle_process(service: core.ServiceCall) -> core.ServiceResponse: """Parse text into commands.""" text = service.data[ATTR_TEXT] _LOGGER.debug("Processing: <%s>", text) try: - await async_converse( + result = await async_converse( hass=hass, text=text, conversation_id=None, @@ -168,7 +169,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: agent_id=service.data.get(ATTR_AGENT_ID), ) except intent.IntentHandleError as err: - _LOGGER.error("Error processing %s: %s", text, err) + raise HomeAssistantError(f"Error processing {text}: {err}") from err + + if service.return_response: + return result.as_dict() + + return None async def handle_reload(service: core.ServiceCall) -> None: """Reload intents.""" @@ -176,7 +182,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: await agent.async_reload(language=service.data.get(ATTR_LANGUAGE)) hass.services.async_register( - DOMAIN, SERVICE_PROCESS, handle_process, schema=SERVICE_PROCESS_SCHEMA + DOMAIN, + SERVICE_PROCESS, + handle_process, + schema=SERVICE_PROCESS_SCHEMA, + supports_response=core.SupportsResponse.OPTIONAL, ) hass.services.async_register( DOMAIN, SERVICE_RELOAD, handle_reload, schema=SERVICE_RELOAD_SCHEMA diff --git a/tests/components/conversation/snapshots/test_init.ambr b/tests/components/conversation/snapshots/test_init.ambr index 38a7ed92b52..f4325e2f291 100644 --- a/tests/components/conversation/snapshots/test_init.ambr +++ b/tests/components/conversation/snapshots/test_init.ambr @@ -222,6 +222,126 @@ ]), }) # --- +# name: test_turn_on_intent[turn kitchen on-None] + dict({ + 'conversation_id': None, + 'response': dict({ + 'card': dict({ + }), + 'data': dict({ + 'failed': list([ + ]), + 'success': list([ + dict({ + 'id': 'light.kitchen', + 'name': 'kitchen', + 'type': , + }), + ]), + 'targets': list([ + ]), + }), + 'language': 'en', + 'response_type': 'action_done', + 'speech': dict({ + 'plain': dict({ + 'extra_data': None, + 'speech': 'Turned on light', + }), + }), + }), + }) +# --- +# name: test_turn_on_intent[turn kitchen on-homeassistant] + dict({ + 'conversation_id': None, + 'response': dict({ + 'card': dict({ + }), + 'data': dict({ + 'failed': list([ + ]), + 'success': list([ + dict({ + 'id': 'light.kitchen', + 'name': 'kitchen', + 'type': , + }), + ]), + 'targets': list([ + ]), + }), + 'language': 'en', + 'response_type': 'action_done', + 'speech': dict({ + 'plain': dict({ + 'extra_data': None, + 'speech': 'Turned on light', + }), + }), + }), + }) +# --- +# name: test_turn_on_intent[turn on kitchen-None] + dict({ + 'conversation_id': None, + 'response': dict({ + 'card': dict({ + }), + 'data': dict({ + 'failed': list([ + ]), + 'success': list([ + dict({ + 'id': 'light.kitchen', + 'name': 'kitchen', + 'type': , + }), + ]), + 'targets': list([ + ]), + }), + 'language': 'en', + 'response_type': 'action_done', + 'speech': dict({ + 'plain': dict({ + 'extra_data': None, + 'speech': 'Turned on light', + }), + }), + }), + }) +# --- +# name: test_turn_on_intent[turn on kitchen-homeassistant] + dict({ + 'conversation_id': None, + 'response': dict({ + 'card': dict({ + }), + 'data': dict({ + 'failed': list([ + ]), + 'success': list([ + dict({ + 'id': 'light.kitchen', + 'name': 'kitchen', + 'type': , + }), + ]), + 'targets': list([ + ]), + }), + 'language': 'en', + 'response_type': 'action_done', + 'speech': dict({ + 'plain': dict({ + 'extra_data': None, + 'speech': 'Turned on light', + }), + }), + }), + }) +# --- # name: test_ws_get_agent_info dict({ 'attribution': dict({ diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 7c0cc54b91d..b55bd651b9e 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -12,6 +12,7 @@ from homeassistant.components.cover import SERVICE_OPEN_COVER from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.core import Context, HomeAssistant +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import ( area_registry as ar, device_registry as dr, @@ -873,7 +874,7 @@ async def test_http_processing_intent_conversion_not_expose_new( @pytest.mark.parametrize("agent_id", AGENT_ID_OPTIONS) @pytest.mark.parametrize("sentence", ("turn on kitchen", "turn kitchen on")) async def test_turn_on_intent( - hass: HomeAssistant, init_components, sentence, agent_id + hass: HomeAssistant, init_components, sentence, agent_id, snapshot ) -> None: """Test calling the turn on intent.""" hass.states.async_set("light.kitchen", "off") @@ -882,8 +883,13 @@ async def test_turn_on_intent( data = {conversation.ATTR_TEXT: sentence} if agent_id is not None: data[conversation.ATTR_AGENT_ID] = agent_id - await hass.services.async_call("conversation", "process", data) - await hass.async_block_till_done() + result = await hass.services.async_call( + "conversation", + "process", + data, + blocking=True, + return_response=True, + ) assert len(calls) == 1 call = calls[0] @@ -891,6 +897,22 @@ async def test_turn_on_intent( assert call.service == "turn_on" assert call.data == {"entity_id": ["light.kitchen"]} + assert result == snapshot + + +async def test_service_fails(hass: HomeAssistant, init_components) -> None: + """Test calling the turn on intent.""" + with pytest.raises(HomeAssistantError), patch( + "homeassistant.components.conversation.async_converse", + side_effect=intent.IntentHandleError, + ): + await hass.services.async_call( + "conversation", + "process", + {"text": "bla"}, + blocking=True, + ) + @pytest.mark.parametrize("sentence", ("turn off kitchen", "turn kitchen off")) async def test_turn_off_intent(hass: HomeAssistant, init_components, sentence) -> None: