mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +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:
|
except intent.MatchFailedError as match_error:
|
||||||
# Intent was valid, but no entities matched the constraints.
|
# Intent was valid, but no entities matched the constraints.
|
||||||
error_response_type, error_response_args = _get_match_error_response(
|
error_response_type, error_response_args = _get_match_error_response(
|
||||||
match_error
|
self.hass, match_error
|
||||||
)
|
)
|
||||||
return _make_error_result(
|
return _make_error_result(
|
||||||
language,
|
language,
|
||||||
@ -1037,6 +1037,7 @@ def _get_unmatched_response(result: RecognizeResult) -> tuple[ErrorKey, dict[str
|
|||||||
|
|
||||||
|
|
||||||
def _get_match_error_response(
|
def _get_match_error_response(
|
||||||
|
hass: core.HomeAssistant,
|
||||||
match_error: intent.MatchFailedError,
|
match_error: intent.MatchFailedError,
|
||||||
) -> tuple[ErrorKey, dict[str, Any]]:
|
) -> tuple[ErrorKey, dict[str, Any]]:
|
||||||
"""Return key and template arguments for error when target matching fails."""
|
"""Return key and template arguments for error when target matching fails."""
|
||||||
@ -1103,6 +1104,23 @@ def _get_match_error_response(
|
|||||||
# Invalid floor name
|
# Invalid floor name
|
||||||
return ErrorKey.NO_FLOOR, {"floor": result.no_match_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
|
# Default error
|
||||||
return ErrorKey.NO_INTENT, {}
|
return ErrorKey.NO_INTENT, {}
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"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
|
hassil==1.7.1
|
||||||
home-assistant-bluetooth==1.12.0
|
home-assistant-bluetooth==1.12.0
|
||||||
home-assistant-frontend==20240501.1
|
home-assistant-frontend==20240501.1
|
||||||
home-assistant-intents==2024.4.24
|
home-assistant-intents==2024.5.28
|
||||||
httpx==0.27.0
|
httpx==0.27.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
Jinja2==3.1.4
|
Jinja2==3.1.4
|
||||||
|
@ -1090,7 +1090,7 @@ holidays==0.49
|
|||||||
home-assistant-frontend==20240501.1
|
home-assistant-frontend==20240501.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.4.24
|
home-assistant-intents==2024.5.28
|
||||||
|
|
||||||
# homeassistant.components.home_connect
|
# homeassistant.components.home_connect
|
||||||
homeconnect==0.7.2
|
homeconnect==0.7.2
|
||||||
|
@ -892,7 +892,7 @@ holidays==0.49
|
|||||||
home-assistant-frontend==20240501.1
|
home-assistant-frontend==20240501.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.4.24
|
home-assistant-intents==2024.5.28
|
||||||
|
|
||||||
# homeassistant.components.home_connect
|
# homeassistant.components.home_connect
|
||||||
homeconnect==0.7.2
|
homeconnect==0.7.2
|
||||||
|
@ -6,13 +6,18 @@ from unittest.mock import AsyncMock, patch
|
|||||||
from hassil.recognize import Intent, IntentData, MatchEntity, RecognizeResult
|
from hassil.recognize import Intent, IntentData, MatchEntity, RecognizeResult
|
||||||
import pytest
|
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.conversation import default_agent
|
||||||
from homeassistant.components.homeassistant.exposed_entities import (
|
from homeassistant.components.homeassistant.exposed_entities import (
|
||||||
async_get_assistant_settings,
|
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.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 (
|
from homeassistant.helpers import (
|
||||||
area_registry as ar,
|
area_registry as ar,
|
||||||
device_registry as dr,
|
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(
|
async def test_no_states_matched_default_error(
|
||||||
hass: HomeAssistant, init_components, area_registry: ar.AreaRegistry
|
hass: HomeAssistant, init_components, area_registry: ar.AreaRegistry
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -234,7 +234,7 @@ async def test_media_player_intents(
|
|||||||
|
|
||||||
response = result.response
|
response = result.response
|
||||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||||
assert response.speech["plain"]["speech"] == "Unpaused"
|
assert response.speech["plain"]["speech"] == "Resumed"
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
call = calls[0]
|
call = calls[0]
|
||||||
assert call.data == {"entity_id": entity_id}
|
assert call.data == {"entity_id": entity_id}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user