Bump intents and add tests for new error messages (#118317)

* Add new error keys

* Bump intents and test new error messages

* Fix response text
This commit is contained in:
Michael Hansen 2024-05-28 11:24:24 -05:00 committed by GitHub
parent 05fc7cfbde
commit 106cb4cfb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 166 additions and 8 deletions

View File

@ -358,7 +358,7 @@ class DefaultAgent(ConversationEntity):
except intent.MatchFailedError as match_error:
# Intent was valid, but no entities matched the constraints.
error_response_type, error_response_args = _get_match_error_response(
match_error
self.hass, match_error
)
return _make_error_result(
language,
@ -1037,6 +1037,7 @@ def _get_unmatched_response(result: RecognizeResult) -> tuple[ErrorKey, dict[str
def _get_match_error_response(
hass: core.HomeAssistant,
match_error: intent.MatchFailedError,
) -> tuple[ErrorKey, dict[str, Any]]:
"""Return key and template arguments for error when target matching fails."""
@ -1103,6 +1104,23 @@ def _get_match_error_response(
# Invalid floor name
return ErrorKey.NO_FLOOR, {"floor": result.no_match_name}
if reason == intent.MatchFailedReason.FEATURE:
# Feature not supported by entity
return ErrorKey.FEATURE_NOT_SUPPORTED, {}
if reason == intent.MatchFailedReason.STATE:
# Entity is not in correct state
assert match_error.constraints.states
state = next(iter(match_error.constraints.states))
if match_error.constraints.domains:
# Translate if domain is available
domain = next(iter(match_error.constraints.domains))
state = translation.async_translate_state(
hass, state, domain, None, None, None
)
return ErrorKey.ENTITY_WRONG_STATE, {"state": state}
# Default error
return ErrorKey.NO_INTENT, {}

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==1.7.1", "home-assistant-intents==2024.4.24"]
"requirements": ["hassil==1.7.1", "home-assistant-intents==2024.5.28"]
}

View File

@ -33,7 +33,7 @@ hass-nabucasa==0.81.0
hassil==1.7.1
home-assistant-bluetooth==1.12.0
home-assistant-frontend==20240501.1
home-assistant-intents==2024.4.24
home-assistant-intents==2024.5.28
httpx==0.27.0
ifaddr==0.2.0
Jinja2==3.1.4

View File

@ -1090,7 +1090,7 @@ holidays==0.49
home-assistant-frontend==20240501.1
# homeassistant.components.conversation
home-assistant-intents==2024.4.24
home-assistant-intents==2024.5.28
# homeassistant.components.home_connect
homeconnect==0.7.2

View File

@ -892,7 +892,7 @@ holidays==0.49
home-assistant-frontend==20240501.1
# homeassistant.components.conversation
home-assistant-intents==2024.4.24
home-assistant-intents==2024.5.28
# homeassistant.components.home_connect
homeconnect==0.7.2

View File

@ -6,13 +6,18 @@ from unittest.mock import AsyncMock, patch
from hassil.recognize import Intent, IntentData, MatchEntity, RecognizeResult
import pytest
from homeassistant.components import conversation, cover
from homeassistant.components import conversation, cover, media_player
from homeassistant.components.conversation import default_agent
from homeassistant.components.homeassistant.exposed_entities import (
async_get_assistant_settings,
)
from homeassistant.components.intent import (
TimerEventType,
TimerInfo,
async_register_timer_handler,
)
from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, STATE_CLOSED
from homeassistant.core import DOMAIN as HASS_DOMAIN, Context, HomeAssistant
from homeassistant.core import DOMAIN as HASS_DOMAIN, Context, HomeAssistant, callback
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
@ -792,6 +797,141 @@ async def test_error_duplicate_names_in_area(
)
async def test_error_wrong_state(hass: HomeAssistant, init_components) -> None:
"""Test error message when no entities are in the correct state."""
assert await async_setup_component(hass, media_player.DOMAIN, {})
hass.states.async_set(
"media_player.test_player",
media_player.STATE_IDLE,
{ATTR_FRIENDLY_NAME: "test player"},
)
expose_entity(hass, "media_player.test_player", True)
result = await conversation.async_converse(
hass, "pause test player", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert result.response.speech["plain"]["speech"] == "Sorry, no device is playing"
async def test_error_feature_not_supported(
hass: HomeAssistant, init_components
) -> None:
"""Test error message when no devices support a required feature."""
assert await async_setup_component(hass, media_player.DOMAIN, {})
hass.states.async_set(
"media_player.test_player",
media_player.STATE_PLAYING,
{ATTR_FRIENDLY_NAME: "test player"},
# missing VOLUME_SET feature
)
expose_entity(hass, "media_player.test_player", True)
result = await conversation.async_converse(
hass, "set test player volume to 100%", None, Context(), None
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.NO_VALID_TARGETS
assert (
result.response.speech["plain"]["speech"]
== "Sorry, no device supports the required features"
)
async def test_error_no_timer_support(hass: HomeAssistant, init_components) -> None:
"""Test error message when a device does not support timers (no handler is registered)."""
device_id = "test_device"
# No timer handler is registered for the device
result = await conversation.async_converse(
hass, "pause timer", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.FAILED_TO_HANDLE
assert (
result.response.speech["plain"]["speech"]
== "Sorry, timers are not supported on this device"
)
async def test_error_timer_not_found(hass: HomeAssistant, init_components) -> None:
"""Test error message when a timer cannot be matched."""
device_id = "test_device"
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
pass
# Register a handler so the device "supports" timers
async_register_timer_handler(hass, device_id, handle_timer)
result = await conversation.async_converse(
hass, "pause timer", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.FAILED_TO_HANDLE
assert (
result.response.speech["plain"]["speech"] == "Sorry, I couldn't find that timer"
)
async def test_error_multiple_timers_matched(
hass: HomeAssistant,
init_components,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test error message when an intent would target multiple timers."""
area_kitchen = area_registry.async_create("kitchen")
# Starting a timer requires a device in an area
entry = MockConfigEntry()
entry.add_to_hass(hass)
device_kitchen = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections=set(),
identifiers={("demo", "device-kitchen")},
)
device_registry.async_update_device(device_kitchen.id, area_id=area_kitchen.id)
device_id = device_kitchen.id
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
pass
# Register a handler so the device "supports" timers
async_register_timer_handler(hass, device_id, handle_timer)
# Create two identical timers from the same device
result = await conversation.async_converse(
hass, "set a timer for 5 minutes", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
result = await conversation.async_converse(
hass, "set a timer for 5 minutes", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
# Cannot target multiple timers
result = await conversation.async_converse(
hass, "cancel timer", None, Context(), None, device_id=device_id
)
assert result.response.response_type == intent.IntentResponseType.ERROR
assert result.response.error_code == intent.IntentResponseErrorCode.FAILED_TO_HANDLE
assert (
result.response.speech["plain"]["speech"]
== "Sorry, I am unable to target multiple timers"
)
async def test_no_states_matched_default_error(
hass: HomeAssistant, init_components, area_registry: ar.AreaRegistry
) -> None:

View File

@ -234,7 +234,7 @@ async def test_media_player_intents(
response = result.response
assert response.response_type == intent.IntentResponseType.ACTION_DONE
assert response.speech["plain"]["speech"] == "Unpaused"
assert response.speech["plain"]["speech"] == "Resumed"
assert len(calls) == 1
call = calls[0]
assert call.data == {"entity_id": entity_id}