Fix exposure checks on some intents (#118988)
* Check exposure in climate intent * Check exposure in todo list * Check exposure for weather * Check exposure in humidity intents * Add extra checks to weather tests * Add more checks to todo intent test * Move climate intents to async_match_targets * Update test_intent.py * Update test_intent.py * Remove patchpull/119096/head
parent
1f6be7b4d1
commit
56db7fc7dc
|
@ -4,11 +4,10 @@ from __future__ import annotations
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
from . import DOMAIN, ClimateEntity
|
||||
from . import DOMAIN
|
||||
|
||||
INTENT_GET_TEMPERATURE = "HassClimateGetTemperature"
|
||||
|
||||
|
@ -23,7 +22,10 @@ class GetTemperatureIntent(intent.IntentHandler):
|
|||
|
||||
intent_type = INTENT_GET_TEMPERATURE
|
||||
description = "Gets the current temperature of a climate device or entity"
|
||||
slot_schema = {vol.Optional("area"): str, vol.Optional("name"): str}
|
||||
slot_schema = {
|
||||
vol.Optional("area"): intent.non_empty_string,
|
||||
vol.Optional("name"): intent.non_empty_string,
|
||||
}
|
||||
platforms = {DOMAIN}
|
||||
|
||||
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
||||
|
@ -31,74 +33,24 @@ class GetTemperatureIntent(intent.IntentHandler):
|
|||
hass = intent_obj.hass
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
|
||||
component: EntityComponent[ClimateEntity] = hass.data[DOMAIN]
|
||||
entities: list[ClimateEntity] = list(component.entities)
|
||||
climate_entity: ClimateEntity | None = None
|
||||
climate_state: State | None = None
|
||||
name: str | None = None
|
||||
if "name" in slots:
|
||||
name = slots["name"]["value"]
|
||||
|
||||
if not entities:
|
||||
raise intent.IntentHandleError("No climate entities")
|
||||
area: str | None = None
|
||||
if "area" in slots:
|
||||
area = slots["area"]["value"]
|
||||
|
||||
name_slot = slots.get("name", {})
|
||||
entity_name: str | None = name_slot.get("value")
|
||||
entity_text: str | None = name_slot.get("text")
|
||||
|
||||
area_slot = slots.get("area", {})
|
||||
area_id = area_slot.get("value")
|
||||
|
||||
if area_id:
|
||||
# Filter by area and optionally name
|
||||
area_name = area_slot.get("text")
|
||||
|
||||
for maybe_climate in intent.async_match_states(
|
||||
hass, name=entity_name, area_name=area_id, domains=[DOMAIN]
|
||||
):
|
||||
climate_state = maybe_climate
|
||||
break
|
||||
|
||||
if climate_state is None:
|
||||
raise intent.NoStatesMatchedError(
|
||||
reason=intent.MatchFailedReason.AREA,
|
||||
name=entity_text or entity_name,
|
||||
area=area_name or area_id,
|
||||
floor=None,
|
||||
domains={DOMAIN},
|
||||
device_classes=None,
|
||||
)
|
||||
|
||||
climate_entity = component.get_entity(climate_state.entity_id)
|
||||
elif entity_name:
|
||||
# Filter by name
|
||||
for maybe_climate in intent.async_match_states(
|
||||
hass, name=entity_name, domains=[DOMAIN]
|
||||
):
|
||||
climate_state = maybe_climate
|
||||
break
|
||||
|
||||
if climate_state is None:
|
||||
raise intent.NoStatesMatchedError(
|
||||
reason=intent.MatchFailedReason.NAME,
|
||||
name=entity_name,
|
||||
area=None,
|
||||
floor=None,
|
||||
domains={DOMAIN},
|
||||
device_classes=None,
|
||||
)
|
||||
|
||||
climate_entity = component.get_entity(climate_state.entity_id)
|
||||
else:
|
||||
# First entity
|
||||
climate_entity = entities[0]
|
||||
climate_state = hass.states.get(climate_entity.entity_id)
|
||||
|
||||
assert climate_entity is not None
|
||||
|
||||
if climate_state is None:
|
||||
raise intent.IntentHandleError(f"No state for {climate_entity.name}")
|
||||
|
||||
assert climate_state is not None
|
||||
match_constraints = intent.MatchTargetsConstraints(
|
||||
name=name, area_name=area, domains=[DOMAIN], assistant=intent_obj.assistant
|
||||
)
|
||||
match_result = intent.async_match_targets(hass, match_constraints)
|
||||
if not match_result.is_match:
|
||||
raise intent.MatchFailedError(
|
||||
result=match_result, constraints=match_constraints
|
||||
)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
response.response_type = intent.IntentResponseType.QUERY_ANSWER
|
||||
response.async_set_states(matched_states=[climate_state])
|
||||
response.async_set_states(matched_states=match_result.states)
|
||||
return response
|
||||
|
|
|
@ -35,7 +35,7 @@ class HumidityHandler(intent.IntentHandler):
|
|||
intent_type = INTENT_HUMIDITY
|
||||
description = "Set desired humidity level"
|
||||
slot_schema = {
|
||||
vol.Required("name"): cv.string,
|
||||
vol.Required("name"): intent.non_empty_string,
|
||||
vol.Required("humidity"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
}
|
||||
platforms = {DOMAIN}
|
||||
|
@ -44,18 +44,19 @@ class HumidityHandler(intent.IntentHandler):
|
|||
"""Handle the hass intent."""
|
||||
hass = intent_obj.hass
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
states = list(
|
||||
intent.async_match_states(
|
||||
hass,
|
||||
name=slots["name"]["value"],
|
||||
states=hass.states.async_all(DOMAIN),
|
||||
)
|
||||
|
||||
match_constraints = intent.MatchTargetsConstraints(
|
||||
name=slots["name"]["value"],
|
||||
domains=[DOMAIN],
|
||||
assistant=intent_obj.assistant,
|
||||
)
|
||||
match_result = intent.async_match_targets(hass, match_constraints)
|
||||
if not match_result.is_match:
|
||||
raise intent.MatchFailedError(
|
||||
result=match_result, constraints=match_constraints
|
||||
)
|
||||
|
||||
if not states:
|
||||
raise intent.IntentHandleError("No entities matched")
|
||||
|
||||
state = states[0]
|
||||
state = match_result.states[0]
|
||||
service_data = {ATTR_ENTITY_ID: state.entity_id}
|
||||
|
||||
humidity = slots["humidity"]["value"]
|
||||
|
@ -89,7 +90,7 @@ class SetModeHandler(intent.IntentHandler):
|
|||
intent_type = INTENT_MODE
|
||||
description = "Set humidifier mode"
|
||||
slot_schema = {
|
||||
vol.Required("name"): cv.string,
|
||||
vol.Required("name"): intent.non_empty_string,
|
||||
vol.Required("mode"): cv.string,
|
||||
}
|
||||
platforms = {DOMAIN}
|
||||
|
@ -98,18 +99,18 @@ class SetModeHandler(intent.IntentHandler):
|
|||
"""Handle the hass intent."""
|
||||
hass = intent_obj.hass
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
states = list(
|
||||
intent.async_match_states(
|
||||
hass,
|
||||
name=slots["name"]["value"],
|
||||
states=hass.states.async_all(DOMAIN),
|
||||
)
|
||||
match_constraints = intent.MatchTargetsConstraints(
|
||||
name=slots["name"]["value"],
|
||||
domains=[DOMAIN],
|
||||
assistant=intent_obj.assistant,
|
||||
)
|
||||
match_result = intent.async_match_targets(hass, match_constraints)
|
||||
if not match_result.is_match:
|
||||
raise intent.MatchFailedError(
|
||||
result=match_result, constraints=match_constraints
|
||||
)
|
||||
|
||||
if not states:
|
||||
raise intent.IntentHandleError("No entities matched")
|
||||
|
||||
state = states[0]
|
||||
state = match_result.states[0]
|
||||
service_data = {ATTR_ENTITY_ID: state.entity_id}
|
||||
|
||||
intent.async_test_feature(state, HumidifierEntityFeature.MODES, "modes")
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
from . import DOMAIN, TodoItem, TodoItemStatus, TodoListEntity
|
||||
|
@ -22,7 +21,7 @@ class ListAddItemIntent(intent.IntentHandler):
|
|||
|
||||
intent_type = INTENT_LIST_ADD_ITEM
|
||||
description = "Add item to a todo list"
|
||||
slot_schema = {"item": cv.string, "name": cv.string}
|
||||
slot_schema = {"item": intent.non_empty_string, "name": intent.non_empty_string}
|
||||
platforms = {DOMAIN}
|
||||
|
||||
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
||||
|
@ -37,18 +36,19 @@ class ListAddItemIntent(intent.IntentHandler):
|
|||
target_list: TodoListEntity | None = None
|
||||
|
||||
# Find matching list
|
||||
for list_state in intent.async_match_states(
|
||||
hass, name=list_name, domains=[DOMAIN]
|
||||
):
|
||||
target_list = component.get_entity(list_state.entity_id)
|
||||
if target_list is not None:
|
||||
break
|
||||
match_constraints = intent.MatchTargetsConstraints(
|
||||
name=list_name, domains=[DOMAIN], assistant=intent_obj.assistant
|
||||
)
|
||||
match_result = intent.async_match_targets(hass, match_constraints)
|
||||
if not match_result.is_match:
|
||||
raise intent.MatchFailedError(
|
||||
result=match_result, constraints=match_constraints
|
||||
)
|
||||
|
||||
target_list = component.get_entity(match_result.states[0].entity_id)
|
||||
if target_list is None:
|
||||
raise intent.IntentHandleError(f"No to-do list: {list_name}")
|
||||
|
||||
assert target_list is not None
|
||||
|
||||
# Add to list
|
||||
await target_list.async_create_todo_item(
|
||||
TodoItem(summary=item, status=TodoItemStatus.NEEDS_ACTION)
|
||||
|
|
|
@ -6,10 +6,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
from . import DOMAIN, WeatherEntity
|
||||
from . import DOMAIN
|
||||
|
||||
INTENT_GET_WEATHER = "HassGetWeather"
|
||||
|
||||
|
@ -24,7 +22,7 @@ class GetWeatherIntent(intent.IntentHandler):
|
|||
|
||||
intent_type = INTENT_GET_WEATHER
|
||||
description = "Gets the current weather"
|
||||
slot_schema = {vol.Optional("name"): cv.string}
|
||||
slot_schema = {vol.Optional("name"): intent.non_empty_string}
|
||||
platforms = {DOMAIN}
|
||||
|
||||
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
||||
|
@ -32,43 +30,21 @@ class GetWeatherIntent(intent.IntentHandler):
|
|||
hass = intent_obj.hass
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
|
||||
weather: WeatherEntity | None = None
|
||||
weather_state: State | None = None
|
||||
component: EntityComponent[WeatherEntity] = hass.data[DOMAIN]
|
||||
entities = list(component.entities)
|
||||
|
||||
name: str | None = None
|
||||
if "name" in slots:
|
||||
# Named weather entity
|
||||
weather_name = slots["name"]["value"]
|
||||
name = slots["name"]["value"]
|
||||
|
||||
# Find matching weather entity
|
||||
matching_states = intent.async_match_states(
|
||||
hass, name=weather_name, domains=[DOMAIN]
|
||||
match_constraints = intent.MatchTargetsConstraints(
|
||||
name=name, domains=[DOMAIN], assistant=intent_obj.assistant
|
||||
)
|
||||
match_result = intent.async_match_targets(hass, match_constraints)
|
||||
if not match_result.is_match:
|
||||
raise intent.MatchFailedError(
|
||||
result=match_result, constraints=match_constraints
|
||||
)
|
||||
for maybe_weather_state in matching_states:
|
||||
weather = component.get_entity(maybe_weather_state.entity_id)
|
||||
if weather is not None:
|
||||
weather_state = maybe_weather_state
|
||||
break
|
||||
|
||||
if weather is None:
|
||||
raise intent.IntentHandleError(
|
||||
f"No weather entity named {weather_name}"
|
||||
)
|
||||
elif entities:
|
||||
# First weather entity
|
||||
weather = entities[0]
|
||||
weather_name = weather.name
|
||||
weather_state = hass.states.get(weather.entity_id)
|
||||
|
||||
if weather is None:
|
||||
raise intent.IntentHandleError("No weather entity")
|
||||
|
||||
if weather_state is None:
|
||||
raise intent.IntentHandleError(f"No state for weather: {weather.name}")
|
||||
|
||||
assert weather is not None
|
||||
assert weather_state is not None
|
||||
weather_state = match_result.states[0]
|
||||
|
||||
# Create response
|
||||
response = intent_obj.create_response()
|
||||
|
@ -77,8 +53,8 @@ class GetWeatherIntent(intent.IntentHandler):
|
|||
success_results=[
|
||||
intent.IntentResponseTarget(
|
||||
type=intent.IntentResponseTargetType.ENTITY,
|
||||
name=weather_name,
|
||||
id=weather.entity_id,
|
||||
name=weather_state.name,
|
||||
id=weather_state.entity_id,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -712,6 +712,7 @@ def async_match_states(
|
|||
domains: Collection[str] | None = None,
|
||||
device_classes: Collection[str] | None = None,
|
||||
states: list[State] | None = None,
|
||||
assistant: str | None = None,
|
||||
) -> Iterable[State]:
|
||||
"""Simplified interface to async_match_targets that returns states matching the constraints."""
|
||||
result = async_match_targets(
|
||||
|
@ -722,6 +723,7 @@ def async_match_states(
|
|||
floor_name=floor_name,
|
||||
domains=domains,
|
||||
device_classes=device_classes,
|
||||
assistant=assistant,
|
||||
),
|
||||
states=states,
|
||||
)
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
"""Test climate intents."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.climate import (
|
||||
DOMAIN,
|
||||
ClimateEntity,
|
||||
HVACMode,
|
||||
intent as climate_intent,
|
||||
)
|
||||
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.const import Platform, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import area_registry as ar, entity_registry as er, intent
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
|
@ -113,6 +115,7 @@ async def test_get_temperature(
|
|||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test HassClimateGetTemperature intent."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
await climate_intent.async_setup_intents(hass)
|
||||
|
||||
climate_1 = MockClimateEntity()
|
||||
|
@ -148,10 +151,14 @@ async def test_get_temperature(
|
|||
|
||||
# First climate entity will be selected (no area)
|
||||
response = await intent.async_handle(
|
||||
hass, "test", climate_intent.INTENT_GET_TEMPERATURE, {}
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
assert response.matched_states
|
||||
assert response.matched_states[0].entity_id == climate_1.entity_id
|
||||
state = response.matched_states[0]
|
||||
assert state.attributes["current_temperature"] == 10.0
|
||||
|
@ -162,6 +169,7 @@ async def test_get_temperature(
|
|||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": bedroom_area.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
|
@ -175,6 +183,7 @@ async def test_get_temperature(
|
|||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"name": {"value": "Climate 2"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
|
@ -189,6 +198,7 @@ async def test_get_temperature(
|
|||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": office_area.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
# Exception should contain details of what we tried to match
|
||||
|
@ -197,7 +207,7 @@ async def test_get_temperature(
|
|||
constraints = error.value.constraints
|
||||
assert constraints.name is None
|
||||
assert constraints.area_name == office_area.name
|
||||
assert constraints.domains == {DOMAIN}
|
||||
assert constraints.domains and (set(constraints.domains) == {DOMAIN})
|
||||
assert constraints.device_classes is None
|
||||
|
||||
# Check wrong name
|
||||
|
@ -214,7 +224,7 @@ async def test_get_temperature(
|
|||
constraints = error.value.constraints
|
||||
assert constraints.name == "Does not exist"
|
||||
assert constraints.area_name is None
|
||||
assert constraints.domains == {DOMAIN}
|
||||
assert constraints.domains and (set(constraints.domains) == {DOMAIN})
|
||||
assert constraints.device_classes is None
|
||||
|
||||
# Check wrong name with area
|
||||
|
@ -231,7 +241,7 @@ async def test_get_temperature(
|
|||
constraints = error.value.constraints
|
||||
assert constraints.name == "Climate 1"
|
||||
assert constraints.area_name == bedroom_area.name
|
||||
assert constraints.domains == {DOMAIN}
|
||||
assert constraints.domains and (set(constraints.domains) == {DOMAIN})
|
||||
assert constraints.device_classes is None
|
||||
|
||||
|
||||
|
@ -239,62 +249,190 @@ async def test_get_temperature_no_entities(
|
|||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test HassClimateGetTemperature intent with no climate entities."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
await climate_intent.async_setup_intents(hass)
|
||||
|
||||
await create_mock_platform(hass, [])
|
||||
|
||||
with pytest.raises(intent.IntentHandleError):
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass, "test", climate_intent.INTENT_GET_TEMPERATURE, {}
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.DOMAIN
|
||||
|
||||
|
||||
async def test_get_temperature_no_state(
|
||||
async def test_not_exposed(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test HassClimateGetTemperature intent when states are missing."""
|
||||
"""Test HassClimateGetTemperature intent when entities aren't exposed."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
await climate_intent.async_setup_intents(hass)
|
||||
|
||||
climate_1 = MockClimateEntity()
|
||||
climate_1._attr_name = "Climate 1"
|
||||
climate_1._attr_unique_id = "1234"
|
||||
climate_1._attr_current_temperature = 10.0
|
||||
entity_registry.async_get_or_create(
|
||||
DOMAIN, "test", "1234", suggested_object_id="climate_1"
|
||||
)
|
||||
|
||||
await create_mock_platform(hass, [climate_1])
|
||||
climate_2 = MockClimateEntity()
|
||||
climate_2._attr_name = "Climate 2"
|
||||
climate_2._attr_unique_id = "5678"
|
||||
climate_2._attr_current_temperature = 22.0
|
||||
entity_registry.async_get_or_create(
|
||||
DOMAIN, "test", "5678", suggested_object_id="climate_2"
|
||||
)
|
||||
|
||||
await create_mock_platform(hass, [climate_1, climate_2])
|
||||
|
||||
# Add climate entities to same area
|
||||
living_room_area = area_registry.async_create(name="Living Room")
|
||||
bedroom_area = area_registry.async_create(name="Bedroom")
|
||||
entity_registry.async_update_entity(
|
||||
climate_1.entity_id, area_id=living_room_area.id
|
||||
)
|
||||
entity_registry.async_update_entity(
|
||||
climate_2.entity_id, area_id=living_room_area.id
|
||||
)
|
||||
|
||||
with (
|
||||
patch("homeassistant.core.StateMachine.get", return_value=None),
|
||||
pytest.raises(intent.IntentHandleError),
|
||||
):
|
||||
await intent.async_handle(
|
||||
hass, "test", climate_intent.INTENT_GET_TEMPERATURE, {}
|
||||
)
|
||||
|
||||
with (
|
||||
patch("homeassistant.core.StateMachine.async_all", return_value=[]),
|
||||
pytest.raises(intent.MatchFailedError) as error,
|
||||
):
|
||||
# Should fail with empty name
|
||||
with pytest.raises(intent.InvalidSlotInfo):
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": "Living Room"}},
|
||||
{"name": {"value": ""}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
# Exception should contain details of what we tried to match
|
||||
assert isinstance(error.value, intent.MatchFailedError)
|
||||
assert error.value.result.no_match_reason == intent.MatchFailedReason.AREA
|
||||
constraints = error.value.constraints
|
||||
assert constraints.name is None
|
||||
assert constraints.area_name == "Living Room"
|
||||
assert constraints.domains == {DOMAIN}
|
||||
assert constraints.device_classes is None
|
||||
# Should fail with empty area
|
||||
with pytest.raises(intent.InvalidSlotInfo):
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": ""}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
# Expose second, hide first
|
||||
async_expose_entity(hass, conversation.DOMAIN, climate_1.entity_id, False)
|
||||
async_expose_entity(hass, conversation.DOMAIN, climate_2.entity_id, True)
|
||||
|
||||
# Second climate entity is exposed
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
assert response.matched_states[0].entity_id == climate_2.entity_id
|
||||
|
||||
# Using the area should work
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": living_room_area.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
assert response.matched_states[0].entity_id == climate_2.entity_id
|
||||
|
||||
# Using the name of the exposed entity should work
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"name": {"value": climate_2.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
assert response.matched_states[0].entity_id == climate_2.entity_id
|
||||
|
||||
# Using the name of the *unexposed* entity should fail
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"name": {"value": climate_1.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.NAME
|
||||
|
||||
# Expose first, hide second
|
||||
async_expose_entity(hass, conversation.DOMAIN, climate_1.entity_id, True)
|
||||
async_expose_entity(hass, conversation.DOMAIN, climate_2.entity_id, False)
|
||||
|
||||
# Second climate entity is exposed
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
assert response.matched_states[0].entity_id == climate_1.entity_id
|
||||
|
||||
# Wrong area name
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": bedroom_area.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.AREA
|
||||
|
||||
# Neither are exposed
|
||||
async_expose_entity(hass, conversation.DOMAIN, climate_1.entity_id, False)
|
||||
async_expose_entity(hass, conversation.DOMAIN, climate_2.entity_id, False)
|
||||
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.ASSISTANT
|
||||
|
||||
# Should fail with area
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"area": {"value": living_room_area.name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.ASSISTANT
|
||||
|
||||
# Should fail with both names
|
||||
for name in (climate_1.name, climate_2.name):
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
climate_intent.INTENT_GET_TEMPERATURE,
|
||||
{"name": {"value": name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.ASSISTANT
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
||||
from homeassistant.components.humidifier import (
|
||||
ATTR_AVAILABLE_MODES,
|
||||
ATTR_HUMIDITY,
|
||||
|
@ -19,13 +21,22 @@ from homeassistant.const import (
|
|||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.intent import IntentHandleError, async_handle
|
||||
from homeassistant.helpers.intent import (
|
||||
IntentHandleError,
|
||||
IntentResponseType,
|
||||
InvalidSlotInfo,
|
||||
MatchFailedError,
|
||||
MatchFailedReason,
|
||||
async_handle,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_intent_set_humidity(hass: HomeAssistant) -> None:
|
||||
"""Test the set humidity intent."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set(
|
||||
"humidifier.bedroom_humidifier", STATE_ON, {ATTR_HUMIDITY: 40}
|
||||
)
|
||||
|
@ -38,6 +49,7 @@ async def test_intent_set_humidity(hass: HomeAssistant) -> None:
|
|||
"test",
|
||||
intent.INTENT_HUMIDITY,
|
||||
{"name": {"value": "Bedroom humidifier"}, "humidity": {"value": "50"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -54,6 +66,7 @@ async def test_intent_set_humidity(hass: HomeAssistant) -> None:
|
|||
|
||||
async def test_intent_set_humidity_and_turn_on(hass: HomeAssistant) -> None:
|
||||
"""Test the set humidity intent for turned off humidifier."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set(
|
||||
"humidifier.bedroom_humidifier", STATE_OFF, {ATTR_HUMIDITY: 40}
|
||||
)
|
||||
|
@ -66,6 +79,7 @@ async def test_intent_set_humidity_and_turn_on(hass: HomeAssistant) -> None:
|
|||
"test",
|
||||
intent.INTENT_HUMIDITY,
|
||||
{"name": {"value": "Bedroom humidifier"}, "humidity": {"value": "50"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -89,6 +103,7 @@ async def test_intent_set_humidity_and_turn_on(hass: HomeAssistant) -> None:
|
|||
|
||||
async def test_intent_set_mode(hass: HomeAssistant) -> None:
|
||||
"""Test the set mode intent."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set(
|
||||
"humidifier.bedroom_humidifier",
|
||||
STATE_ON,
|
||||
|
@ -108,6 +123,7 @@ async def test_intent_set_mode(hass: HomeAssistant) -> None:
|
|||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -127,6 +143,7 @@ async def test_intent_set_mode(hass: HomeAssistant) -> None:
|
|||
|
||||
async def test_intent_set_mode_and_turn_on(hass: HomeAssistant) -> None:
|
||||
"""Test the set mode intent."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set(
|
||||
"humidifier.bedroom_humidifier",
|
||||
STATE_OFF,
|
||||
|
@ -146,6 +163,7 @@ async def test_intent_set_mode_and_turn_on(hass: HomeAssistant) -> None:
|
|||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -169,6 +187,7 @@ async def test_intent_set_mode_and_turn_on(hass: HomeAssistant) -> None:
|
|||
|
||||
async def test_intent_set_mode_tests_feature(hass: HomeAssistant) -> None:
|
||||
"""Test the set mode intent where modes are not supported."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set(
|
||||
"humidifier.bedroom_humidifier", STATE_ON, {ATTR_HUMIDITY: 40}
|
||||
)
|
||||
|
@ -181,6 +200,7 @@ async def test_intent_set_mode_tests_feature(hass: HomeAssistant) -> None:
|
|||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert str(excinfo.value) == "Entity bedroom humidifier does not support modes"
|
||||
assert len(mode_calls) == 0
|
||||
|
@ -191,6 +211,7 @@ async def test_intent_set_unknown_mode(
|
|||
hass: HomeAssistant, available_modes: list[str] | None
|
||||
) -> None:
|
||||
"""Test the set mode intent for unsupported mode."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
hass.states.async_set(
|
||||
"humidifier.bedroom_humidifier",
|
||||
STATE_ON,
|
||||
|
@ -210,6 +231,111 @@ async def test_intent_set_unknown_mode(
|
|||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "Bedroom humidifier"}, "mode": {"value": "eco"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert str(excinfo.value) == "Entity bedroom humidifier does not support eco mode"
|
||||
assert len(mode_calls) == 0
|
||||
|
||||
|
||||
async def test_intent_errors(hass: HomeAssistant) -> None:
|
||||
"""Test the error conditions for set humidity and set mode intents."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
entity_id = "humidifier.bedroom_humidifier"
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_HUMIDITY: 40,
|
||||
ATTR_SUPPORTED_FEATURES: 1,
|
||||
ATTR_AVAILABLE_MODES: ["home", "away"],
|
||||
ATTR_MODE: None,
|
||||
},
|
||||
)
|
||||
async_mock_service(hass, DOMAIN, SERVICE_SET_HUMIDITY)
|
||||
async_mock_service(hass, DOMAIN, SERVICE_SET_MODE)
|
||||
await intent.async_setup_intents(hass)
|
||||
|
||||
# Humidifiers are exposed by default
|
||||
result = await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_HUMIDITY,
|
||||
{"name": {"value": "Bedroom humidifier"}, "humidity": {"value": "50"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert result.response_type == IntentResponseType.ACTION_DONE
|
||||
|
||||
result = await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert result.response_type == IntentResponseType.ACTION_DONE
|
||||
|
||||
# Unexposing it should fail
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity_id, False)
|
||||
|
||||
with pytest.raises(MatchFailedError) as err:
|
||||
await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_HUMIDITY,
|
||||
{"name": {"value": "Bedroom humidifier"}, "humidity": {"value": "50"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == MatchFailedReason.ASSISTANT
|
||||
|
||||
with pytest.raises(MatchFailedError) as err:
|
||||
await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "Bedroom humidifier"}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == MatchFailedReason.ASSISTANT
|
||||
|
||||
# Expose again to test other errors
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity_id, True)
|
||||
|
||||
# Empty name should fail
|
||||
with pytest.raises(InvalidSlotInfo):
|
||||
await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_HUMIDITY,
|
||||
{"name": {"value": ""}, "humidity": {"value": "50"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidSlotInfo):
|
||||
await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": ""}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
# Wrong name should fail
|
||||
with pytest.raises(MatchFailedError) as err:
|
||||
await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_HUMIDITY,
|
||||
{"name": {"value": "does not exist"}, "humidity": {"value": "50"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == MatchFailedReason.NAME
|
||||
|
||||
with pytest.raises(MatchFailedError) as err:
|
||||
await async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_MODE,
|
||||
{"name": {"value": "does not exist"}, "mode": {"value": "away"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == MatchFailedReason.NAME
|
||||
|
|
|
@ -9,6 +9,8 @@ import zoneinfo
|
|||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
||||
from homeassistant.components.todo import (
|
||||
DOMAIN,
|
||||
TodoItem,
|
||||
|
@ -23,6 +25,7 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
|
@ -1110,6 +1113,7 @@ async def test_add_item_intent(
|
|||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test adding items to lists using an intent."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
await todo_intent.async_setup_intents(hass)
|
||||
|
||||
entity1 = MockTodoListEntity()
|
||||
|
@ -1128,6 +1132,7 @@ async def test_add_item_intent(
|
|||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": "beer"}, "name": {"value": "list 1"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
|
||||
|
@ -1143,6 +1148,7 @@ async def test_add_item_intent(
|
|||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": "cheese"}, "name": {"value": "List 2"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
|
||||
|
@ -1157,6 +1163,7 @@ async def test_add_item_intent(
|
|||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": "wine"}, "name": {"value": "lIST 2"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
|
||||
|
@ -1165,13 +1172,46 @@ async def test_add_item_intent(
|
|||
assert entity2.items[1].summary == "wine"
|
||||
assert entity2.items[1].status == TodoItemStatus.NEEDS_ACTION
|
||||
|
||||
# Should fail if lists are not exposed
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity1.entity_id, False)
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity2.entity_id, False)
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": "cookies"}, "name": {"value": "list 1"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.ASSISTANT
|
||||
|
||||
# Missing list
|
||||
with pytest.raises(intent.IntentHandleError):
|
||||
with pytest.raises(intent.MatchFailedError):
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": "wine"}, "name": {"value": "This list does not exist"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
# Fail with empty name/item
|
||||
with pytest.raises(intent.InvalidSlotInfo):
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": "wine"}, "name": {"value": ""}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
with pytest.raises(intent.InvalidSlotInfo):
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
todo_intent.INTENT_LIST_ADD_ITEM,
|
||||
{"item": {"value": ""}, "name": {"value": "list 1"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""Test weather intents."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.homeassistant.exposed_entities import async_expose_entity
|
||||
from homeassistant.components.weather import (
|
||||
DOMAIN,
|
||||
WeatherEntity,
|
||||
|
@ -16,15 +16,18 @@ from homeassistant.setup import async_setup_component
|
|||
|
||||
async def test_get_weather(hass: HomeAssistant) -> None:
|
||||
"""Test get weather for first entity and by name."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "weather", {"weather": {}})
|
||||
|
||||
entity1 = WeatherEntity()
|
||||
entity1._attr_name = "Weather 1"
|
||||
entity1.entity_id = "weather.test_1"
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity1.entity_id, True)
|
||||
|
||||
entity2 = WeatherEntity()
|
||||
entity2._attr_name = "Weather 2"
|
||||
entity2.entity_id = "weather.test_2"
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity2.entity_id, True)
|
||||
|
||||
await hass.data[DOMAIN].async_add_entities([entity1, entity2])
|
||||
|
||||
|
@ -45,15 +48,31 @@ async def test_get_weather(hass: HomeAssistant) -> None:
|
|||
"test",
|
||||
weather_intent.INTENT_GET_WEATHER,
|
||||
{"name": {"value": "Weather 2"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
assert len(response.matched_states) == 1
|
||||
state = response.matched_states[0]
|
||||
assert state.entity_id == entity2.entity_id
|
||||
|
||||
# Should fail if not exposed
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity1.entity_id, False)
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity2.entity_id, False)
|
||||
for name in (entity1.name, entity2.name):
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
weather_intent.INTENT_GET_WEATHER,
|
||||
{"name": {"value": name}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.ASSISTANT
|
||||
|
||||
|
||||
async def test_get_weather_wrong_name(hass: HomeAssistant) -> None:
|
||||
"""Test get weather with the wrong name."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "weather", {"weather": {}})
|
||||
|
||||
entity1 = WeatherEntity()
|
||||
|
@ -63,48 +82,43 @@ async def test_get_weather_wrong_name(hass: HomeAssistant) -> None:
|
|||
await hass.data[DOMAIN].async_add_entities([entity1])
|
||||
|
||||
await weather_intent.async_setup_intents(hass)
|
||||
async_expose_entity(hass, conversation.DOMAIN, entity1.entity_id, True)
|
||||
|
||||
# Incorrect name
|
||||
with pytest.raises(intent.IntentHandleError):
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
weather_intent.INTENT_GET_WEATHER,
|
||||
{"name": {"value": "not the right name"}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.NAME
|
||||
|
||||
# Empty name
|
||||
with pytest.raises(intent.InvalidSlotInfo):
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
weather_intent.INTENT_GET_WEATHER,
|
||||
{"name": {"value": ""}},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
async def test_get_weather_no_entities(hass: HomeAssistant) -> None:
|
||||
"""Test get weather with no weather entities."""
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "weather", {"weather": {}})
|
||||
await weather_intent.async_setup_intents(hass)
|
||||
|
||||
# No weather entities
|
||||
with pytest.raises(intent.IntentHandleError):
|
||||
await intent.async_handle(hass, "test", weather_intent.INTENT_GET_WEATHER, {})
|
||||
|
||||
|
||||
async def test_get_weather_no_state(hass: HomeAssistant) -> None:
|
||||
"""Test get weather when state is not returned."""
|
||||
assert await async_setup_component(hass, "weather", {"weather": {}})
|
||||
|
||||
entity1 = WeatherEntity()
|
||||
entity1._attr_name = "Weather 1"
|
||||
entity1.entity_id = "weather.test_1"
|
||||
|
||||
await hass.data[DOMAIN].async_add_entities([entity1])
|
||||
|
||||
await weather_intent.async_setup_intents(hass)
|
||||
|
||||
# Success with state
|
||||
response = await intent.async_handle(
|
||||
hass, "test", weather_intent.INTENT_GET_WEATHER, {}
|
||||
)
|
||||
assert response.response_type == intent.IntentResponseType.QUERY_ANSWER
|
||||
|
||||
# Failure without state
|
||||
with (
|
||||
patch("homeassistant.core.StateMachine.get", return_value=None),
|
||||
pytest.raises(intent.IntentHandleError),
|
||||
):
|
||||
await intent.async_handle(hass, "test", weather_intent.INTENT_GET_WEATHER, {})
|
||||
with pytest.raises(intent.MatchFailedError) as err:
|
||||
await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
weather_intent.INTENT_GET_WEATHER,
|
||||
{},
|
||||
assistant=conversation.DOMAIN,
|
||||
)
|
||||
assert err.value.result.no_match_reason == intent.MatchFailedReason.DOMAIN
|
||||
|
|
Loading…
Reference in New Issue