mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
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:
parent
05fc7cfbde
commit
106cb4cfb7
@ -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, {}
|
||||
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user