mirror of
https://github.com/home-assistant/core.git
synced 2026-04-07 07:56:23 +00:00
Compare commits
10 Commits
bump/pytho
...
synesthesi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44a471cb58 | ||
|
|
35f5e92e00 | ||
|
|
c43744affa | ||
|
|
538578d5be | ||
|
|
3a17714516 | ||
|
|
0da7da83ec | ||
|
|
e85318cef6 | ||
|
|
e8e58ed8c2 | ||
|
|
94188a54f0 | ||
|
|
63e0baff3e |
@@ -656,7 +656,13 @@ class IntentHandleView(http.HomeAssistantView):
|
||||
device_id=data.get("device_id"),
|
||||
satellite_id=data.get("satellite_id"),
|
||||
)
|
||||
except (intent.IntentHandleError, intent.MatchFailedError) as err:
|
||||
except intent.MatchFailedError as err:
|
||||
# Match failure.
|
||||
# Be more specific so the client can create a proper error message.
|
||||
intent_result = intent.IntentResponse(language=language)
|
||||
intent_result.async_set_match_failed_error(err)
|
||||
except intent.IntentHandleError as err:
|
||||
# General error
|
||||
intent_result = intent.IntentResponse(language=language)
|
||||
intent_result.async_set_error(
|
||||
intent.IntentResponseErrorCode.FAILED_TO_HANDLE, str(err)
|
||||
|
||||
@@ -1377,6 +1377,7 @@ class IntentResponse:
|
||||
self.unmatched_states: list[State] = []
|
||||
self.speech_slots: dict[str, Any] = {}
|
||||
self.response_type = IntentResponseType.ACTION_DONE
|
||||
self.match_failed_error: MatchFailedError | None = None
|
||||
|
||||
@callback
|
||||
def async_set_speech(
|
||||
@@ -1420,6 +1421,23 @@ class IntentResponse:
|
||||
# Speak error message
|
||||
self.async_set_speech(message)
|
||||
|
||||
@callback
|
||||
def async_set_match_failed_error(
|
||||
self, match_failed_error: MatchFailedError
|
||||
) -> None:
|
||||
"""Set response error."""
|
||||
self.response_type = IntentResponseType.ERROR
|
||||
self.error_code = IntentResponseErrorCode.NO_VALID_TARGETS
|
||||
self.match_failed_error = match_failed_error
|
||||
|
||||
@callback
|
||||
def async_set_targets(
|
||||
self,
|
||||
intent_targets: list[IntentResponseTarget],
|
||||
) -> None:
|
||||
"""Set response targets."""
|
||||
self.intent_targets = intent_targets
|
||||
|
||||
@callback
|
||||
def async_set_results(
|
||||
self,
|
||||
@@ -1463,6 +1481,28 @@ class IntentResponse:
|
||||
if self.response_type == IntentResponseType.ERROR:
|
||||
assert self.error_code is not None, "error code is required"
|
||||
response_data["code"] = self.error_code.value
|
||||
|
||||
if self.match_failed_error:
|
||||
match_error_dict: dict[str, Any] = {}
|
||||
if self.match_failed_error.result.no_match_reason:
|
||||
match_error_dict["no_match_reason"] = (
|
||||
self.match_failed_error.result.no_match_reason.name
|
||||
)
|
||||
|
||||
if self.match_failed_error.result.no_match_name:
|
||||
match_error_dict["no_match_name"] = (
|
||||
self.match_failed_error.result.no_match_name
|
||||
)
|
||||
|
||||
match_error_dict["constraints"] = dataclasses.asdict(
|
||||
self.match_failed_error.constraints
|
||||
)
|
||||
if self.match_failed_error.preferences:
|
||||
match_error_dict["preferences"] = dataclasses.asdict(
|
||||
self.match_failed_error.preferences
|
||||
)
|
||||
|
||||
response_data["match_error"] = match_error_dict
|
||||
else:
|
||||
# action done or query answer
|
||||
response_data["success"] = [
|
||||
@@ -1473,6 +1513,18 @@ class IntentResponse:
|
||||
dataclasses.asdict(target) for target in self.failed_results
|
||||
]
|
||||
|
||||
# Add query matched/unmatched
|
||||
response_data["query"] = {
|
||||
"matched": [
|
||||
{"entity_id": s.entity_id, "state": s.state}
|
||||
for s in self.matched_states
|
||||
],
|
||||
"unmatched": [
|
||||
{"entity_id": s.entity_id, "state": s.state}
|
||||
for s in self.unmatched_states
|
||||
],
|
||||
}
|
||||
|
||||
response_dict["data"] = response_data
|
||||
|
||||
return response_dict
|
||||
|
||||
@@ -1246,6 +1246,7 @@
|
||||
]),
|
||||
intent=None,
|
||||
language='en',
|
||||
match_failed_error=None,
|
||||
matched_states=list([
|
||||
]),
|
||||
reprompt=dict({
|
||||
|
||||
@@ -110,6 +110,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -343,6 +349,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -573,6 +585,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -650,6 +668,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -702,6 +726,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -754,6 +784,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
|
||||
@@ -659,6 +659,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
|
||||
@@ -53,6 +53,7 @@ async def test_broadcast_intent(
|
||||
"card": {},
|
||||
"data": {
|
||||
"failed": [],
|
||||
"query": {"matched": [], "unmatched": []},
|
||||
"success": [
|
||||
{
|
||||
"id": "assist_satellite.test_entity",
|
||||
@@ -90,6 +91,7 @@ async def test_broadcast_intent(
|
||||
"card": {},
|
||||
"data": {
|
||||
"failed": [],
|
||||
"query": {"matched": [], "unmatched": []},
|
||||
"success": [
|
||||
{
|
||||
"id": "assist_satellite.test_entity_2",
|
||||
@@ -127,6 +129,7 @@ async def test_broadcast_intent_excluded_domains(
|
||||
"card": {},
|
||||
"data": {
|
||||
"failed": [],
|
||||
"query": {"matched": [], "unmatched": []},
|
||||
"success": [], # no satellites
|
||||
},
|
||||
"language": "en",
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -33,6 +39,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -57,6 +69,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -81,6 +99,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -110,6 +138,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -181,6 +219,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'on',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -210,6 +258,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -239,6 +297,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.late',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.late',
|
||||
@@ -268,6 +336,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.late',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.late',
|
||||
@@ -318,6 +396,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'on',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -389,6 +477,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'on',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -439,6 +537,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'on',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -468,6 +576,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'on',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
|
||||
@@ -276,6 +276,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -305,6 +315,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
]),
|
||||
}),
|
||||
@@ -54,6 +60,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -83,6 +99,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -112,6 +138,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -141,6 +177,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -170,6 +216,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -199,6 +255,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -228,6 +294,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
@@ -257,6 +333,16 @@
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'query': dict({
|
||||
'matched': list([
|
||||
dict({
|
||||
'entity_id': 'light.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
]),
|
||||
'unmatched': list([
|
||||
]),
|
||||
}),
|
||||
'success': list([
|
||||
dict({
|
||||
'id': 'light.kitchen',
|
||||
|
||||
@@ -85,7 +85,11 @@ async def test_http_handle_intent(
|
||||
},
|
||||
"language": hass.config.language,
|
||||
"response_type": intent.IntentResponseType.ACTION_DONE.value,
|
||||
"data": {"success": [], "failed": []},
|
||||
"data": {
|
||||
"success": [],
|
||||
"failed": [],
|
||||
"query": {"matched": [], "unmatched": []},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -105,10 +109,9 @@ async def test_http_language_device_satellite_id(
|
||||
async def async_handle(self, intent_obj: intent.Intent):
|
||||
"""Handle the intent."""
|
||||
assert intent_obj.context.user_id == hass_admin_user.id
|
||||
# Verify language, device id, and satellite id were passed through.
|
||||
assert intent_obj.language == language
|
||||
assert intent_obj.device_id == device_id
|
||||
assert intent_obj.satellite_id == satellite_id
|
||||
assert intent_obj.language == language
|
||||
|
||||
response = intent_obj.create_response()
|
||||
response.async_set_speech("Test response")
|
||||
@@ -134,6 +137,7 @@ async def test_http_language_device_satellite_id(
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
|
||||
# Verify language, device id, and satellite id were passed through.
|
||||
# Also check speech slots.
|
||||
assert data == {
|
||||
"card": {},
|
||||
@@ -149,7 +153,11 @@ async def test_http_language_device_satellite_id(
|
||||
},
|
||||
"language": language,
|
||||
"response_type": "action_done",
|
||||
"data": {"success": [], "failed": []},
|
||||
"data": {
|
||||
"success": [],
|
||||
"failed": [],
|
||||
"query": {"matched": [], "unmatched": []},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +168,7 @@ async def test_http_handle_intent_match_failure(
|
||||
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
# Duplicate names
|
||||
hass.states.async_set(
|
||||
"cover.garage_door_1", "closed", {ATTR_FRIENDLY_NAME: "Garage Door"}
|
||||
)
|
||||
@@ -176,7 +185,12 @@ async def test_http_handle_intent_match_failure(
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
|
||||
assert "DUPLICATE_NAME" in data["speech"]["plain"]["speech"]
|
||||
assert data["response_type"] == intent.IntentResponseType.ERROR.value
|
||||
assert data["data"]["code"] == intent.IntentResponseErrorCode.NO_VALID_TARGETS.value
|
||||
assert (
|
||||
data["data"]["match_error"]["no_match_reason"]
|
||||
== intent.MatchFailedReason.DUPLICATE_NAME.name
|
||||
)
|
||||
|
||||
|
||||
async def test_http_assistant(
|
||||
@@ -221,7 +235,7 @@ async def test_http_assistant(
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert data["response_type"] == intent.IntentResponseType.ERROR.value
|
||||
assert data["data"]["code"] == intent.IntentResponseErrorCode.FAILED_TO_HANDLE.value
|
||||
assert data["data"]["code"] == intent.IntentResponseErrorCode.NO_VALID_TARGETS.value
|
||||
|
||||
# No assistant (exposure is irrelevant)
|
||||
resp = await client.post(
|
||||
@@ -802,3 +816,131 @@ async def test_stop_moving_intent_unsupported_domain(hass: HomeAssistant) -> Non
|
||||
await intent.async_handle(
|
||||
hass, "test", intent.INTENT_STOP_MOVING, {"name": {"value": "test light"}}
|
||||
)
|
||||
|
||||
|
||||
async def test_intent_response_match_failed_error_attributes(
|
||||
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||
) -> None:
|
||||
"""Test that IntentResponse stores match_failed_error correctly via HTTP API."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
class TestIntentHandler(intent.IntentHandler):
|
||||
"""Test Intent Handler."""
|
||||
|
||||
intent_type = "TestMatchError"
|
||||
|
||||
async def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
raise intent.MatchFailedError(
|
||||
result=intent.MatchTargetsResult(
|
||||
False,
|
||||
intent.MatchFailedReason.DUPLICATE_NAME,
|
||||
no_match_name="Duplicate Name",
|
||||
),
|
||||
constraints=intent.MatchTargetsConstraints(
|
||||
name="Duplicate Name",
|
||||
single_target=True,
|
||||
),
|
||||
preferences=intent.MatchTargetsPreferences(
|
||||
area_id="preferred-area-id",
|
||||
floor_id="preferred-floor-id",
|
||||
),
|
||||
)
|
||||
|
||||
intent.async_register(hass, TestIntentHandler())
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.post(
|
||||
"/api/intent/handle",
|
||||
json={"name": "TestMatchError", "data": {}},
|
||||
)
|
||||
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
|
||||
assert data["response_type"] == intent.IntentResponseType.ERROR.value
|
||||
assert data["data"]["code"] == intent.IntentResponseErrorCode.NO_VALID_TARGETS.value
|
||||
assert "match_error" in data["data"]
|
||||
assert data["data"]["match_error"]["no_match_reason"] == "DUPLICATE_NAME"
|
||||
assert data["data"]["match_error"]["no_match_name"] == "Duplicate Name"
|
||||
assert data["data"]["match_error"]["constraints"]["name"] == "Duplicate Name"
|
||||
assert data["data"]["match_error"]["constraints"]["single_target"] is True
|
||||
assert data["data"]["match_error"]["preferences"]["area_id"] == "preferred-area-id"
|
||||
assert (
|
||||
data["data"]["match_error"]["preferences"]["floor_id"] == "preferred-floor-id"
|
||||
)
|
||||
|
||||
|
||||
async def test_intent_response_query_field_attributes(hass: HomeAssistant) -> None:
|
||||
"""Test that IntentResponse stores matched/unmatched states correctly."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
class TestIntentHandler(intent.IntentHandler):
|
||||
"""Test Intent Handler."""
|
||||
|
||||
intent_type = "TestQuery"
|
||||
|
||||
async def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
response = intent_obj.create_response()
|
||||
matched_state = hass.states.get("light.test_light")
|
||||
unmatched_state = hass.states.get("light.test_light_2")
|
||||
response.async_set_states(
|
||||
matched_states=[matched_state] if matched_state else [],
|
||||
unmatched_states=[unmatched_state] if unmatched_state else [],
|
||||
)
|
||||
response.async_set_speech("Test query response")
|
||||
return response
|
||||
|
||||
intent.async_register(hass, TestIntentHandler())
|
||||
|
||||
hass.states.async_set("light.test_light", "on")
|
||||
hass.states.async_set("light.test_light_2", "off")
|
||||
|
||||
response = await intent.async_handle(hass, "test", "TestQuery", {})
|
||||
|
||||
assert response.response_type.value == intent.IntentResponseType.ACTION_DONE.value
|
||||
assert len(response.matched_states) == 1
|
||||
assert len(response.unmatched_states) == 1
|
||||
assert response.matched_states[0].entity_id == "light.test_light"
|
||||
assert response.unmatched_states[0].entity_id == "light.test_light_2"
|
||||
|
||||
response_dict = response.as_dict()
|
||||
assert response_dict["data"]["query"]["matched"] == [
|
||||
{"entity_id": "light.test_light", "state": "on"}
|
||||
]
|
||||
assert response_dict["data"]["query"]["unmatched"] == [
|
||||
{"entity_id": "light.test_light_2", "state": "off"}
|
||||
]
|
||||
|
||||
|
||||
async def test_intent_response_query_empty_states(hass: HomeAssistant) -> None:
|
||||
"""Test that IntentResponse handles empty matched/unmatched states."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
class TestIntentHandler(intent.IntentHandler):
|
||||
"""Test Intent Handler."""
|
||||
|
||||
intent_type = "TestEmptyQuery"
|
||||
|
||||
async def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
response = intent_obj.create_response()
|
||||
response.async_set_states(matched_states=[], unmatched_states=[])
|
||||
response.async_set_speech("Empty query response")
|
||||
return response
|
||||
|
||||
intent.async_register(hass, TestIntentHandler())
|
||||
|
||||
response = await intent.async_handle(hass, "test", "TestEmptyQuery", {})
|
||||
|
||||
assert response.response_type.value == intent.IntentResponseType.ACTION_DONE.value
|
||||
assert len(response.matched_states) == 0
|
||||
assert len(response.unmatched_states) == 0
|
||||
|
||||
response_dict = response.as_dict()
|
||||
assert response_dict["data"]["query"]["matched"] == []
|
||||
assert response_dict["data"]["query"]["unmatched"] == []
|
||||
|
||||
@@ -1142,8 +1142,10 @@ async def test_webhook_handle_conversation_process(
|
||||
},
|
||||
"language": hass.config.language,
|
||||
"data": {
|
||||
"success": [],
|
||||
"failed": [],
|
||||
"query": {"matched": [], "unmatched": []},
|
||||
"success": [],
|
||||
"targets": [],
|
||||
},
|
||||
},
|
||||
"conversation_id": None,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
]),
|
||||
intent=None,
|
||||
language='en',
|
||||
match_failed_error=None,
|
||||
matched_states=list([
|
||||
]),
|
||||
reprompt=dict({
|
||||
|
||||
@@ -249,6 +249,11 @@ async def test_assist_api(
|
||||
"data": {
|
||||
"failed": [],
|
||||
"success": [],
|
||||
"targets": [],
|
||||
"query": {
|
||||
"matched": [{"entity_id": "light.matched", "state": "on"}],
|
||||
"unmatched": [{"entity_id": "light.unmatched", "state": "on"}],
|
||||
},
|
||||
},
|
||||
"reprompt": {
|
||||
"plain": {
|
||||
@@ -307,6 +312,11 @@ async def test_assist_api(
|
||||
"data": {
|
||||
"failed": [],
|
||||
"success": [],
|
||||
"targets": [],
|
||||
"query": {
|
||||
"matched": [{"entity_id": "light.matched", "state": "on"}],
|
||||
"unmatched": [{"entity_id": "light.unmatched", "state": "on"}],
|
||||
},
|
||||
},
|
||||
"response_type": "action_done",
|
||||
"reprompt": {
|
||||
|
||||
Reference in New Issue
Block a user