diff --git a/homeassistant/components/intent/timers.py b/homeassistant/components/intent/timers.py index 9b4524cf9b0c..a8ec6b70f5e4 100644 --- a/homeassistant/components/intent/timers.py +++ b/homeassistant/components/intent/timers.py @@ -457,20 +457,8 @@ class TimerManager: timer.finish() if timer.conversation_command: - from homeassistant.components.conversation import ( # noqa: PLC0415 - async_converse, - ) - self.hass.async_create_background_task( - async_converse( - self.hass, - timer.conversation_command, - conversation_id=None, - context=Context(), - language=timer.language, - agent_id=timer.conversation_agent_id, - device_id=timer.device_id, - ), + self._async_run_conversation_command(timer), "timer assist command", ) elif timer.device_id in self.handlers: @@ -483,6 +471,33 @@ class TimerManager: timer.device_id, ) + async def _async_run_conversation_command(self, timer: TimerInfo) -> None: + """Run the delayed command of a finished timer.""" + from homeassistant.components.conversation import ( # noqa: PLC0415 + async_converse, + ) + + assert timer.conversation_command is not None + + result = await async_converse( + self.hass, + timer.conversation_command, + conversation_id=None, + context=Context(), + language=timer.language, + agent_id=timer.conversation_agent_id, + device_id=timer.device_id, + ) + + # Nothing is listening to the response, so an error is only visible here. + if result.response.response_type is intent.IntentResponseType.ERROR: + _LOGGER.warning( + "Delayed command failed: command=%s, code=%s, response=%s", + timer.conversation_command, + result.response.error_code, + result.response.speech.get("plain", {}).get("speech", ""), + ) + def is_timer_device(self, device_id: str) -> bool: """Return True if device has been registered to handle timer events.""" return device_id in self.handlers diff --git a/tests/components/intent/test_timers.py b/tests/components/intent/test_timers.py index ea3d60a5be1b..3f2ce7e7c0d8 100644 --- a/tests/components/intent/test_timers.py +++ b/tests/components/intent/test_timers.py @@ -1,6 +1,8 @@ """Tests for intent timers.""" import asyncio +from collections.abc import Callable +import logging from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -32,6 +34,9 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry +DELAYED_COMMAND = "turn on the lights" +DELAYED_COMMAND_ERROR = "Sorry, I am not aware of any device called lights" + @pytest.fixture async def init_components(hass: HomeAssistant) -> None: @@ -1526,6 +1531,75 @@ async def test_start_timer_with_conversation_command( assert mock_converse.call_args.args[1] == test_command +def _delayed_command_acted() -> intent.IntentResponse: + """Return the response of a delayed command that acted.""" + return intent.IntentResponse(language="en") + + +def _delayed_command_failed() -> intent.IntentResponse: + """Return the response of a delayed command that could not act.""" + response = intent.IntentResponse(language="en") + response.async_set_error( + intent.IntentResponseErrorCode.NO_VALID_TARGETS, DELAYED_COMMAND_ERROR + ) + return response + + +@pytest.mark.usefixtures("init_components") +@pytest.mark.parametrize( + ("make_response", "expected_warnings"), + [ + pytest.param(_delayed_command_acted, [], id="command_acted"), + pytest.param( + _delayed_command_failed, + [ + f"Delayed command failed: command={DELAYED_COMMAND}," + f" code=no_valid_targets, response={DELAYED_COMMAND_ERROR}" + ], + id="command_failed", + ), + ], +) +async def test_start_timer_conversation_command_result_logged( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + make_response: Callable[[], intent.IntentResponse], + expected_warnings: list[str], +) -> None: + """Test that a delayed command which could not act is logged. + + Nothing listens to the response of a delayed command, so an error is + otherwise invisible. + """ + with patch( + "homeassistant.components.conversation.async_converse", + return_value=conversation.ConversationResult(response=make_response()), + ): + result = await intent.async_handle( + hass, + "test", + intent.INTENT_START_TIMER, + { + "seconds": {"value": 0}, + "conversation_command": {"value": DELAYED_COMMAND}, + }, + device_id="test_device", + conversation_agent_id="test_agent", + ) + + assert result.response_type is intent.IntentResponseType.ACTION_DONE + + # The delayed command runs in a background task + await hass.async_block_till_done(wait_background_tasks=True) + + assert [ + record.getMessage() + for record in caplog.records + if record.name == "homeassistant.components.intent.timers" + and record.levelno == logging.WARNING + ] == expected_warnings + + async def test_start_timer_with_sentence_trigger_validation( hass: HomeAssistant, init_components ) -> None: