Clean up core services (#31509)
* Clean up core services * Fix conversation test
This commit is contained in:
parent
57ab30d534
commit
111050bea9
7 changed files with 222 additions and 121 deletions
|
@ -131,9 +131,22 @@ class ConversationProcessView(http.HomeAssistantView):
|
|||
"""Send a request for processing."""
|
||||
hass = request.app["hass"]
|
||||
|
||||
intent_result = await _async_converse(
|
||||
hass, data["text"], data.get("conversation_id"), self.context(request)
|
||||
)
|
||||
try:
|
||||
intent_result = await _async_converse(
|
||||
hass, data["text"], data.get("conversation_id"), self.context(request)
|
||||
)
|
||||
except intent.IntentError as err:
|
||||
_LOGGER.error("Error handling intent: %s", err)
|
||||
return self.json(
|
||||
{
|
||||
"success": False,
|
||||
"error": {
|
||||
"code": str(err.__class__.__name__).lower(),
|
||||
"message": str(err),
|
||||
},
|
||||
},
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
return self.json(intent_result)
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ from homeassistant.const import (
|
|||
ATTR_NAME,
|
||||
CONF_ICON,
|
||||
CONF_NAME,
|
||||
ENTITY_MATCH_ALL,
|
||||
ENTITY_MATCH_NONE,
|
||||
SERVICE_RELOAD,
|
||||
STATE_CLOSED,
|
||||
STATE_HOME,
|
||||
|
@ -134,7 +136,10 @@ def expand_entity_ids(hass: HomeAssistantType, entity_ids: Iterable[Any]) -> Lis
|
|||
"""
|
||||
found_ids: List[str] = []
|
||||
for entity_id in entity_ids:
|
||||
if not isinstance(entity_id, str):
|
||||
if not isinstance(entity_id, str) or entity_id in (
|
||||
ENTITY_MATCH_NONE,
|
||||
ENTITY_MATCH_ALL,
|
||||
):
|
||||
continue
|
||||
|
||||
entity_id = entity_id.lower()
|
||||
|
|
|
@ -6,6 +6,7 @@ from typing import Awaitable
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL
|
||||
import homeassistant.config as conf_util
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
|
@ -19,8 +20,8 @@ from homeassistant.const import (
|
|||
SERVICE_TURN_ON,
|
||||
)
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, intent
|
||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized, UnknownUser
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.service import async_extract_entity_ids
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -74,23 +75,16 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
|||
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
hass.services.async_register(ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
|
||||
hass.services.async_register(ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
|
||||
hass.services.async_register(ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on"
|
||||
)
|
||||
service_schema = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service, schema=service_schema
|
||||
)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned {} off"
|
||||
)
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service, schema=service_schema
|
||||
)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"
|
||||
)
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service, schema=service_schema
|
||||
)
|
||||
|
||||
async def async_handle_core_service(call):
|
||||
|
@ -118,6 +112,25 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
|||
|
||||
async def async_handle_update_service(call):
|
||||
"""Service handler for updating an entity."""
|
||||
if call.context.user_id:
|
||||
user = await hass.auth.async_get_user(call.context.user_id)
|
||||
|
||||
if user is None:
|
||||
raise UnknownUser(
|
||||
context=call.context,
|
||||
permission=POLICY_CONTROL,
|
||||
user_id=call.context.user_id,
|
||||
)
|
||||
|
||||
for entity in call.data[ATTR_ENTITY_ID]:
|
||||
if not user.permissions.check_entity(entity, POLICY_CONTROL):
|
||||
raise Unauthorized(
|
||||
context=call.context,
|
||||
permission=POLICY_CONTROL,
|
||||
user_id=call.context.user_id,
|
||||
perm_category=CAT_ENTITIES,
|
||||
)
|
||||
|
||||
tasks = [
|
||||
hass.helpers.entity_component.async_update_entity(entity)
|
||||
for entity in call.data[ATTR_ENTITY_ID]
|
||||
|
@ -126,13 +139,13 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
|||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
|
||||
hass.services.async_register(
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service
|
||||
)
|
||||
hass.services.async_register(
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service
|
||||
)
|
||||
hass.services.async_register(
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service
|
||||
)
|
||||
hass.services.async_register(
|
||||
|
|
|
@ -5,7 +5,8 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components import http
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, integration_platform, intent
|
||||
|
||||
from .const import DOMAIN
|
||||
|
@ -22,6 +23,22 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||
hass, DOMAIN, _async_process_intent
|
||||
)
|
||||
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_TURN_ON, HA_DOMAIN, SERVICE_TURN_ON, "Turned {} on"
|
||||
)
|
||||
)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_TURN_OFF, HA_DOMAIN, SERVICE_TURN_OFF, "Turned {} off"
|
||||
)
|
||||
)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
intent.INTENT_TOGGLE, HA_DOMAIN, SERVICE_TOGGLE, "Toggled {}"
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -201,11 +201,9 @@ async def test_toggle_intent(hass, sentence):
|
|||
|
||||
async def test_http_api(hass, hass_client):
|
||||
"""Test the HTTP conversation API."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
assert result
|
||||
|
||||
result = await async_setup_component(hass, "conversation", {})
|
||||
assert result
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "conversation", {})
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
client = await hass_client()
|
||||
hass.states.async_set("light.kitchen", "off")
|
||||
|
|
|
@ -4,6 +4,8 @@ import asyncio
|
|||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
import yaml
|
||||
|
||||
from homeassistant import config
|
||||
|
@ -11,9 +13,12 @@ import homeassistant.components as comps
|
|||
from homeassistant.components.homeassistant import (
|
||||
SERVICE_CHECK_CONFIG,
|
||||
SERVICE_RELOAD_CORE_CONFIG,
|
||||
SERVICE_SET_LOCATION,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ENTITY_MATCH_ALL,
|
||||
ENTITY_MATCH_NONE,
|
||||
EVENT_CORE_CONFIG_UPDATE,
|
||||
SERVICE_HOMEASSISTANT_RESTART,
|
||||
SERVICE_HOMEASSISTANT_STOP,
|
||||
|
@ -24,9 +29,8 @@ from homeassistant.const import (
|
|||
STATE_ON,
|
||||
)
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.exceptions import HomeAssistantError, Unauthorized
|
||||
from homeassistant.helpers import entity
|
||||
import homeassistant.helpers.intent as intent
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import (
|
||||
|
@ -249,95 +253,6 @@ class TestComponentsCore(unittest.TestCase):
|
|||
assert not mock_stop.called
|
||||
|
||||
|
||||
async def test_turn_on_intent(hass):
|
||||
"""Test HassTurnOn intent."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "off")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TURN_ON)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassTurnOn", {"name": {"value": "test light"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Turned test light on"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "turn_on"
|
||||
assert call.data == {"entity_id": ["light.test_light"]}
|
||||
|
||||
|
||||
async def test_turn_off_intent(hass):
|
||||
"""Test HassTurnOff intent."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "on")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TURN_OFF)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassTurnOff", {"name": {"value": "test light"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Turned test light off"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "turn_off"
|
||||
assert call.data == {"entity_id": ["light.test_light"]}
|
||||
|
||||
|
||||
async def test_toggle_intent(hass):
|
||||
"""Test HassToggle intent."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "off")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TOGGLE)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassToggle", {"name": {"value": "test light"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Toggled test light"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "toggle"
|
||||
assert call.data == {"entity_id": ["light.test_light"]}
|
||||
|
||||
|
||||
async def test_turn_on_multiple_intent(hass):
|
||||
"""Test HassTurnOn intent with multiple similar entities.
|
||||
|
||||
This tests that matching finds the proper entity among similar names.
|
||||
"""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "off")
|
||||
hass.states.async_set("light.test_lights_2", "off")
|
||||
hass.states.async_set("light.test_lighter", "off")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TURN_ON)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassTurnOn", {"name": {"value": "test lights"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Turned test lights 2 on"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "turn_on"
|
||||
assert call.data == {"entity_id": ["light.test_lights_2"]}
|
||||
|
||||
|
||||
async def test_turn_on_to_not_block_for_domains_without_service(hass):
|
||||
"""Test if turn_on is blocking domain with no service."""
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
|
@ -411,3 +326,49 @@ async def test_setting_location(hass):
|
|||
assert len(events) == 1
|
||||
assert hass.config.latitude == 30
|
||||
assert hass.config.longitude == 40
|
||||
|
||||
|
||||
async def test_require_admin(hass, hass_read_only_user):
|
||||
"""Test services requiring admin."""
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
|
||||
for service in (
|
||||
SERVICE_HOMEASSISTANT_RESTART,
|
||||
SERVICE_HOMEASSISTANT_STOP,
|
||||
SERVICE_CHECK_CONFIG,
|
||||
SERVICE_RELOAD_CORE_CONFIG,
|
||||
):
|
||||
with pytest.raises(Unauthorized):
|
||||
await hass.services.async_call(
|
||||
ha.DOMAIN,
|
||||
service,
|
||||
{},
|
||||
context=ha.Context(user_id=hass_read_only_user.id),
|
||||
blocking=True,
|
||||
)
|
||||
assert False, f"Should have raises for {service}"
|
||||
|
||||
with pytest.raises(Unauthorized):
|
||||
await hass.services.async_call(
|
||||
ha.DOMAIN,
|
||||
SERVICE_SET_LOCATION,
|
||||
{"latitude": 0, "longitude": 0},
|
||||
context=ha.Context(user_id=hass_read_only_user.id),
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_turn_on_off_toggle_schema(hass, hass_read_only_user):
|
||||
"""Test the schemas for the turn on/off/toggle services."""
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
|
||||
for service in SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE:
|
||||
for invalid in None, "nothing", ENTITY_MATCH_ALL, ENTITY_MATCH_NONE:
|
||||
with pytest.raises(vol.Invalid):
|
||||
await hass.services.async_call(
|
||||
ha.DOMAIN,
|
||||
service,
|
||||
{"entity_id": invalid},
|
||||
context=ha.Context(user_id=hass_read_only_user.id),
|
||||
blocking=True,
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import pytest
|
||||
|
||||
from homeassistant.components.cover import SERVICE_OPEN_COVER
|
||||
from homeassistant.const import SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
@ -74,3 +75,96 @@ async def test_cover_intents_loading(hass):
|
|||
assert call.domain == "cover"
|
||||
assert call.service == "open_cover"
|
||||
assert call.data == {"entity_id": "cover.garage_door"}
|
||||
|
||||
|
||||
async def test_turn_on_intent(hass):
|
||||
"""Test HassTurnOn intent."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
result = await async_setup_component(hass, "intent", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "off")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TURN_ON)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassTurnOn", {"name": {"value": "test light"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Turned test light on"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "turn_on"
|
||||
assert call.data == {"entity_id": ["light.test_light"]}
|
||||
|
||||
|
||||
async def test_turn_off_intent(hass):
|
||||
"""Test HassTurnOff intent."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
result = await async_setup_component(hass, "intent", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "on")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TURN_OFF)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassTurnOff", {"name": {"value": "test light"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Turned test light off"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "turn_off"
|
||||
assert call.data == {"entity_id": ["light.test_light"]}
|
||||
|
||||
|
||||
async def test_toggle_intent(hass):
|
||||
"""Test HassToggle intent."""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
result = await async_setup_component(hass, "intent", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "off")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TOGGLE)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassToggle", {"name": {"value": "test light"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Toggled test light"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "toggle"
|
||||
assert call.data == {"entity_id": ["light.test_light"]}
|
||||
|
||||
|
||||
async def test_turn_on_multiple_intent(hass):
|
||||
"""Test HassTurnOn intent with multiple similar entities.
|
||||
|
||||
This tests that matching finds the proper entity among similar names.
|
||||
"""
|
||||
result = await async_setup_component(hass, "homeassistant", {})
|
||||
result = await async_setup_component(hass, "intent", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("light.test_light", "off")
|
||||
hass.states.async_set("light.test_lights_2", "off")
|
||||
hass.states.async_set("light.test_lighter", "off")
|
||||
calls = async_mock_service(hass, "light", SERVICE_TURN_ON)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassTurnOn", {"name": {"value": "test lights"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Turned test lights 2 on"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "light"
|
||||
assert call.service == "turn_on"
|
||||
assert call.data == {"entity_id": ["light.test_lights_2"]}
|
||||
|
|
Loading…
Add table
Reference in a new issue