Move intent registration to own integration (#29280)

* Move intent registration to own integration.

* Lint
This commit is contained in:
Paulus Schoutsen 2019-12-01 14:12:57 -08:00 committed by GitHub
parent d91dd68b31
commit d1aa0cea97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 389 additions and 289 deletions

View file

@ -3,9 +3,12 @@ import logging
import re
from typing import Optional
from homeassistant import core
from homeassistant.components.cover import INTENT_CLOSE_COVER, INTENT_OPEN_COVER
from homeassistant.components.shopping_list import INTENT_ADD_ITEM, INTENT_LAST_ITEMS
from homeassistant import core, setup
from homeassistant.components.cover.intent import INTENT_CLOSE_COVER, INTENT_OPEN_COVER
from homeassistant.components.shopping_list.intent import (
INTENT_ADD_ITEM,
INTENT_LAST_ITEMS,
)
from homeassistant.const import EVENT_COMPONENT_LOADED
from homeassistant.core import callback
from homeassistant.helpers import intent
@ -58,6 +61,9 @@ class DefaultAgent(AbstractConversationAgent):
async def async_initialize(self, config):
"""Initialize the default agent."""
if "intent" not in self.hass.config.components:
await setup.async_setup_component(self.hass, "intent", {})
config = config.get(DOMAIN, {})
intents = self.hass.data.setdefault(DOMAIN, {})

View file

@ -15,7 +15,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
)
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
from homeassistant.components import group
from homeassistant.helpers import intent
from homeassistant.const import (
SERVICE_OPEN_COVER,
SERVICE_CLOSE_COVER,
@ -83,8 +82,6 @@ ATTR_CURRENT_TILT_POSITION = "current_tilt_position"
ATTR_POSITION = "position"
ATTR_TILT_POSITION = "tilt_position"
INTENT_OPEN_COVER = "HassOpenCover"
INTENT_CLOSE_COVER = "HassCloseCover"
COVER_SET_COVER_POSITION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend(
{vol.Required(ATTR_POSITION): vol.All(vol.Coerce(int), vol.Range(min=0, max=100))}
@ -158,17 +155,6 @@ async def async_setup(hass, config):
SERVICE_TOGGLE_COVER_TILT, ENTITY_SERVICE_SCHEMA, "async_toggle_tilt"
)
hass.helpers.intent.async_register(
intent.ServiceIntentHandler(
INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}"
)
)
hass.helpers.intent.async_register(
intent.ServiceIntentHandler(
INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}"
)
)
return True

View file

@ -0,0 +1,22 @@
"""Intents for the cover integration."""
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
from . import DOMAIN, SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER
INTENT_OPEN_COVER = "HassOpenCover"
INTENT_CLOSE_COVER = "HassCloseCover"
async def async_setup_intents(hass: HomeAssistant) -> None:
"""Set up the cover intents."""
hass.helpers.intent.async_register(
intent.ServiceIntentHandler(
INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}"
)
)
hass.helpers.intent.async_register(
intent.ServiceIntentHandler(
INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}"
)
)

View file

@ -1,22 +1,55 @@
"""The Intent integration."""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.const import EVENT_COMPONENT_LOADED
from homeassistant.setup import ATTR_COMPONENT
from homeassistant.components import http
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.helpers import config_validation as cv, intent
from homeassistant.loader import async_get_integration, IntegrationNotFound
from .const import DOMAIN
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Intent component."""
hass.http.register_view(IntentHandleView())
tasks = [_async_process_intent(hass, comp) for comp in hass.config.components]
async def async_component_loaded(event):
"""Handle a new component loaded."""
await _async_process_intent(hass, event.data[ATTR_COMPONENT])
hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_component_loaded)
if tasks:
await asyncio.gather(*tasks)
return True
async def _async_process_intent(hass: HomeAssistant, component_name: str):
"""Process the intents of a component."""
try:
integration = await async_get_integration(hass, component_name)
platform = integration.get_platform(DOMAIN)
except (IntegrationNotFound, ImportError):
return
try:
await platform.async_setup_intents(hass)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error setting up intents for %s", component_name)
class IntentHandleView(http.HomeAssistantView):
"""View to handle intents from JSON."""

View file

@ -26,7 +26,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers import intent
from homeassistant.loader import bind_hass
import homeassistant.util.color as color_util
@ -141,8 +140,6 @@ PROFILE_SCHEMA = vol.Schema(
vol.ExactSequence((str, cv.small_float, cv.small_float, cv.byte))
)
INTENT_SET = "HassLightSet"
_LOGGER = logging.getLogger(__name__)
@ -196,65 +193,6 @@ def preprocess_turn_off(params):
return (False, None) # Light should be turned on
class SetIntentHandler(intent.IntentHandler):
"""Handle set color intents."""
intent_type = INTENT_SET
slot_schema = {
vol.Required("name"): cv.string,
vol.Optional("color"): color_util.color_name_to_rgb,
vol.Optional("brightness"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
}
async def async_handle(self, intent_obj):
"""Handle the hass intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
state = hass.helpers.intent.async_match_state(
slots["name"]["value"],
[state for state in hass.states.async_all() if state.domain == DOMAIN],
)
service_data = {ATTR_ENTITY_ID: state.entity_id}
speech_parts = []
if "color" in slots:
intent.async_test_feature(state, SUPPORT_COLOR, "changing colors")
service_data[ATTR_RGB_COLOR] = slots["color"]["value"]
# Use original passed in value of the color because we don't have
# human readable names for that internally.
speech_parts.append(
"the color {}".format(intent_obj.slots["color"]["value"])
)
if "brightness" in slots:
intent.async_test_feature(state, SUPPORT_BRIGHTNESS, "changing brightness")
service_data[ATTR_BRIGHTNESS_PCT] = slots["brightness"]["value"]
speech_parts.append("{}% brightness".format(slots["brightness"]["value"]))
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, service_data, context=intent_obj.context
)
response = intent_obj.create_response()
if not speech_parts: # No attributes changed
speech = f"Turned on {state.name}"
else:
parts = [f"Changed {state.name} to"]
for index, part in enumerate(speech_parts):
if index == 0:
parts.append(f" {part}")
elif index != len(speech_parts) - 1:
parts.append(f", {part}")
else:
parts.append(f" and {part}")
speech = "".join(parts)
response.async_set_speech(speech)
return response
async def async_setup(hass, config):
"""Expose light control via state machine and services."""
component = hass.data[DOMAIN] = EntityComponent(
@ -341,8 +279,6 @@ async def async_setup(hass, config):
SERVICE_TOGGLE, LIGHT_TOGGLE_SCHEMA, "async_toggle"
)
hass.helpers.intent.async_register(SetIntentHandler())
return True

View file

@ -0,0 +1,84 @@
"""Intents for the light integration."""
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
import homeassistant.util.color as color_util
import homeassistant.helpers.config_validation as cv
from . import (
ATTR_ENTITY_ID,
SUPPORT_COLOR,
ATTR_RGB_COLOR,
ATTR_BRIGHTNESS_PCT,
SUPPORT_BRIGHTNESS,
DOMAIN,
SERVICE_TURN_ON,
)
INTENT_SET = "HassLightSet"
async def async_setup_intents(hass: HomeAssistant) -> None:
"""Set up the light intents."""
hass.helpers.intent.async_register(SetIntentHandler())
class SetIntentHandler(intent.IntentHandler):
"""Handle set color intents."""
intent_type = INTENT_SET
slot_schema = {
vol.Required("name"): cv.string,
vol.Optional("color"): color_util.color_name_to_rgb,
vol.Optional("brightness"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
}
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the hass intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
state = hass.helpers.intent.async_match_state(
slots["name"]["value"],
[state for state in hass.states.async_all() if state.domain == DOMAIN],
)
service_data = {ATTR_ENTITY_ID: state.entity_id}
speech_parts = []
if "color" in slots:
intent.async_test_feature(state, SUPPORT_COLOR, "changing colors")
service_data[ATTR_RGB_COLOR] = slots["color"]["value"]
# Use original passed in value of the color because we don't have
# human readable names for that internally.
speech_parts.append(
"the color {}".format(intent_obj.slots["color"]["value"])
)
if "brightness" in slots:
intent.async_test_feature(state, SUPPORT_BRIGHTNESS, "changing brightness")
service_data[ATTR_BRIGHTNESS_PCT] = slots["brightness"]["value"]
speech_parts.append("{}% brightness".format(slots["brightness"]["value"]))
await hass.services.async_call(
DOMAIN, SERVICE_TURN_ON, service_data, context=intent_obj.context
)
response = intent_obj.create_response()
if not speech_parts: # No attributes changed
speech = f"Turned on {state.name}"
else:
parts = [f"Changed {state.name} to"]
for index, part in enumerate(speech_parts):
if index == 0:
parts.append(f" {part}")
elif index != len(speech_parts) - 1:
parts.append(f", {part}")
else:
parts.append(f" and {part}")
speech = "".join(parts)
response.async_set_speech(speech)
return response

View file

@ -9,7 +9,6 @@ from homeassistant.const import HTTP_NOT_FOUND, HTTP_BAD_REQUEST
from homeassistant.core import callback
from homeassistant.components import http
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
from homeassistant.components import websocket_api
@ -20,8 +19,6 @@ DOMAIN = "shopping_list"
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
EVENT = "shopping_list_updated"
INTENT_ADD_ITEM = "HassShoppingListAddItem"
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
ITEM_UPDATE_SCHEMA = vol.Schema({"complete": bool, ATTR_NAME: str})
PERSISTENCE = ".shopping_list.json"
@ -86,9 +83,6 @@ def async_setup(hass, config):
data = hass.data[DOMAIN] = ShoppingData(hass)
yield from data.async_load()
intent.async_register(hass, AddItemIntent())
intent.async_register(hass, ListTopItemsIntent())
hass.services.async_register(
DOMAIN, SERVICE_ADD_ITEM, add_item_service, schema=SERVICE_ITEM_SCHEMA
)
@ -175,49 +169,6 @@ class ShoppingData:
save_json(self.hass.config.path(PERSISTENCE), self.items)
class AddItemIntent(intent.IntentHandler):
"""Handle AddItem intents."""
intent_type = INTENT_ADD_ITEM
slot_schema = {"item": cv.string}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle the intent."""
slots = self.async_validate_slots(intent_obj.slots)
item = slots["item"]["value"]
intent_obj.hass.data[DOMAIN].async_add(item)
response = intent_obj.create_response()
response.async_set_speech(f"I've added {item} to your shopping list")
intent_obj.hass.bus.async_fire(EVENT)
return response
class ListTopItemsIntent(intent.IntentHandler):
"""Handle AddItem intents."""
intent_type = INTENT_LAST_ITEMS
slot_schema = {"item": cv.string}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle the intent."""
items = intent_obj.hass.data[DOMAIN].items[-5:]
response = intent_obj.create_response()
if not items:
response.async_set_speech("There are no items on your shopping list")
else:
response.async_set_speech(
"These are the top {} items on your shopping list: {}".format(
min(len(items), 5),
", ".join(itm["name"] for itm in reversed(items)),
)
)
return response
class ShoppingListView(http.HomeAssistantView):
"""View to retrieve shopping list content."""

View file

@ -0,0 +1,55 @@
"""Intents for the Shopping List integration."""
from homeassistant.helpers import intent
import homeassistant.helpers.config_validation as cv
from . import DOMAIN, EVENT
INTENT_ADD_ITEM = "HassShoppingListAddItem"
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
async def async_setup_intents(hass):
"""Set up the Shopping List intents."""
intent.async_register(hass, AddItemIntent())
intent.async_register(hass, ListTopItemsIntent())
class AddItemIntent(intent.IntentHandler):
"""Handle AddItem intents."""
intent_type = INTENT_ADD_ITEM
slot_schema = {"item": cv.string}
async def async_handle(self, intent_obj):
"""Handle the intent."""
slots = self.async_validate_slots(intent_obj.slots)
item = slots["item"]["value"]
intent_obj.hass.data[DOMAIN].async_add(item)
response = intent_obj.create_response()
response.async_set_speech(f"I've added {item} to your shopping list")
intent_obj.hass.bus.async_fire(EVENT)
return response
class ListTopItemsIntent(intent.IntentHandler):
"""Handle AddItem intents."""
intent_type = INTENT_LAST_ITEMS
slot_schema = {"item": cv.string}
async def async_handle(self, intent_obj):
"""Handle the intent."""
items = intent_obj.hass.data[DOMAIN].items[-5:]
response = intent_obj.create_response()
if not items:
response.async_set_speech("There are no items on your shopping list")
else:
response.async_set_speech(
"These are the top {} items on your shopping list: {}".format(
min(len(items), 5),
", ".join(itm["name"] for itm in reversed(items)),
)
)
return response

View file

@ -1,11 +1,9 @@
"""The tests for the Conversation component."""
# pylint: disable=protected-access
import pytest
from homeassistant.core import DOMAIN as HASS_DOMAIN, Context
from homeassistant.setup import async_setup_component
from homeassistant.components import conversation
from homeassistant.components.cover import SERVICE_OPEN_COVER
from homeassistant.helpers import intent
from tests.common import async_mock_intent, async_mock_service
@ -153,32 +151,6 @@ async def test_turn_on_intent(hass, sentence):
assert call.data == {"entity_id": "light.kitchen"}
async def test_cover_intents_loading(hass):
"""Test Cover Intents Loading."""
with pytest.raises(intent.UnknownIntent):
await intent.async_handle(
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
)
result = await async_setup_component(hass, "cover", {})
assert result
hass.states.async_set("cover.garage_door", "closed")
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
response = await intent.async_handle(
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
)
await hass.async_block_till_done()
assert response.speech["plain"]["speech"] == "Opened garage door"
assert len(calls) == 1
call = calls[0]
assert call.domain == "cover"
assert call.service == "open_cover"
assert call.data == {"entity_id": "cover.garage_door"}
@pytest.mark.parametrize("sentence", ("turn off kitchen", "turn kitchen off"))
async def test_turn_off_intent(hass, sentence):
"""Test calling the turn on intent."""

View file

@ -1,15 +1,17 @@
"""The tests for the cover platform."""
from homeassistant.components.cover import SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER
from homeassistant.components.cover import (
SERVICE_OPEN_COVER,
SERVICE_CLOSE_COVER,
intent as cover_intent,
)
from homeassistant.helpers import intent
import homeassistant.components as comps
from tests.common import async_mock_service
async def test_open_cover_intent(hass):
"""Test HassOpenCover intent."""
result = await comps.cover.async_setup(hass, {})
assert result
await cover_intent.async_setup_intents(hass)
hass.states.async_set("cover.garage_door", "closed")
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
@ -29,8 +31,7 @@ async def test_open_cover_intent(hass):
async def test_close_cover_intent(hass):
"""Test HassCloseCover intent."""
result = await comps.cover.async_setup(hass, {})
assert result
await cover_intent.async_setup_intents(hass)
hass.states.async_set("cover.garage_door", "open")
calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER)

View file

@ -1,6 +1,11 @@
"""Tests for Intent component."""
import pytest
from homeassistant.setup import async_setup_component
from homeassistant.helpers import intent
from homeassistant.components.cover import SERVICE_OPEN_COVER
from tests.common import async_mock_service
async def test_http_handle_intent(hass, hass_client, hass_admin_user):
@ -42,3 +47,30 @@ async def test_http_handle_intent(hass, hass_client, hass_admin_user):
},
"speech": {"plain": {"extra_data": None, "speech": "I've ordered a Belgian!"}},
}
async def test_cover_intents_loading(hass):
"""Test Cover Intents Loading."""
assert await async_setup_component(hass, "intent", {})
with pytest.raises(intent.UnknownIntent):
await intent.async_handle(
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
)
assert await async_setup_component(hass, "cover", {})
hass.states.async_set("cover.garage_door", "closed")
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
response = await intent.async_handle(
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
)
await hass.async_block_till_done()
assert response.speech["plain"]["speech"] == "Opened garage door"
assert len(calls) == 1
call = calls[0]
assert call.domain == "cover"
assert call.service == "open_cover"
assert call.data == {"entity_id": "cover.garage_door"}

View file

@ -18,13 +18,10 @@ from homeassistant.const import (
SERVICE_TURN_ON,
SERVICE_TURN_OFF,
SERVICE_TOGGLE,
ATTR_SUPPORTED_FEATURES,
)
from homeassistant.components import light
from homeassistant.helpers.intent import IntentHandleError
from tests.common import (
async_mock_service,
mock_service,
get_test_home_assistant,
mock_storage,
@ -433,87 +430,6 @@ class TestLight(unittest.TestCase):
assert {light.ATTR_HS_COLOR: (50.353, 100), light.ATTR_BRIGHTNESS: 100} == data
async def test_intent_set_color(hass):
"""Test the set color intent."""
hass.states.async_set(
"light.hello_2", "off", {ATTR_SUPPORTED_FEATURES: light.SUPPORT_COLOR}
)
hass.states.async_set("switch.hello", "off")
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
hass.helpers.intent.async_register(light.SetIntentHandler())
result = await hass.helpers.intent.async_handle(
"test",
light.INTENT_SET,
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
)
await hass.async_block_till_done()
assert result.speech["plain"]["speech"] == "Changed hello 2 to the color blue"
assert len(calls) == 1
call = calls[0]
assert call.domain == light.DOMAIN
assert call.service == SERVICE_TURN_ON
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
async def test_intent_set_color_tests_feature(hass):
"""Test the set color intent."""
hass.states.async_set("light.hello", "off")
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
hass.helpers.intent.async_register(light.SetIntentHandler())
try:
await hass.helpers.intent.async_handle(
"test",
light.INTENT_SET,
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
)
assert False, "handling intent should have raised"
except IntentHandleError as err:
assert str(err) == "Entity hello does not support changing colors"
assert len(calls) == 0
async def test_intent_set_color_and_brightness(hass):
"""Test the set color intent."""
hass.states.async_set(
"light.hello_2",
"off",
{ATTR_SUPPORTED_FEATURES: (light.SUPPORT_COLOR | light.SUPPORT_BRIGHTNESS)},
)
hass.states.async_set("switch.hello", "off")
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
hass.helpers.intent.async_register(light.SetIntentHandler())
result = await hass.helpers.intent.async_handle(
"test",
light.INTENT_SET,
{
"name": {"value": "Hello"},
"color": {"value": "blue"},
"brightness": {"value": "20"},
},
)
await hass.async_block_till_done()
assert (
result.speech["plain"]["speech"]
== "Changed hello 2 to the color blue and 20% brightness"
)
assert len(calls) == 1
call = calls[0]
assert call.domain == light.DOMAIN
assert call.service == SERVICE_TURN_ON
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
assert call.data.get(light.ATTR_BRIGHTNESS_PCT) == 20
async def test_light_context(hass, hass_admin_user):
"""Test that light context works."""
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})

View file

@ -0,0 +1,88 @@
"""Tests for the light intents."""
from homeassistant.helpers.intent import IntentHandleError
from homeassistant.const import ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON, ATTR_ENTITY_ID
from homeassistant.components import light
from homeassistant.components.light import intent
from tests.common import async_mock_service
async def test_intent_set_color(hass):
"""Test the set color intent."""
hass.states.async_set(
"light.hello_2", "off", {ATTR_SUPPORTED_FEATURES: light.SUPPORT_COLOR}
)
hass.states.async_set("switch.hello", "off")
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
await intent.async_setup_intents(hass)
result = await hass.helpers.intent.async_handle(
"test",
intent.INTENT_SET,
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
)
await hass.async_block_till_done()
assert result.speech["plain"]["speech"] == "Changed hello 2 to the color blue"
assert len(calls) == 1
call = calls[0]
assert call.domain == light.DOMAIN
assert call.service == SERVICE_TURN_ON
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
async def test_intent_set_color_tests_feature(hass):
"""Test the set color intent."""
hass.states.async_set("light.hello", "off")
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
await intent.async_setup_intents(hass)
try:
await hass.helpers.intent.async_handle(
"test",
intent.INTENT_SET,
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
)
assert False, "handling intent should have raised"
except IntentHandleError as err:
assert str(err) == "Entity hello does not support changing colors"
assert len(calls) == 0
async def test_intent_set_color_and_brightness(hass):
"""Test the set color intent."""
hass.states.async_set(
"light.hello_2",
"off",
{ATTR_SUPPORTED_FEATURES: (light.SUPPORT_COLOR | light.SUPPORT_BRIGHTNESS)},
)
hass.states.async_set("switch.hello", "off")
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
await intent.async_setup_intents(hass)
result = await hass.helpers.intent.async_handle(
"test",
intent.INTENT_SET,
{
"name": {"value": "Hello"},
"color": {"value": "blue"},
"brightness": {"value": "20"},
},
)
await hass.async_block_till_done()
assert (
result.speech["plain"]["speech"]
== "Changed hello 2 to the color blue and 20% brightness"
)
assert len(calls) == 1
call = calls[0]
assert call.domain == light.DOMAIN
assert call.service == SERVICE_TURN_ON
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
assert call.data.get(light.ATTR_BRIGHTNESS_PCT) == 20

View file

@ -0,0 +1,23 @@
"""Shopping list test helpers."""
from unittest.mock import patch
import pytest
from homeassistant.setup import async_setup_component
from homeassistant.components.shopping_list import intent as sl_intent
@pytest.fixture(autouse=True)
def mock_shopping_list_io():
"""Stub out the persistence."""
with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch(
"homeassistant.components.shopping_list." "ShoppingData.async_load"
):
yield
@pytest.fixture
async def sl_setup(hass):
"""Set up the shopping list."""
assert await async_setup_component(hass, "shopping_list", {})
await sl_intent.async_setup_intents(hass)

View file

@ -1,27 +1,13 @@
"""Test shopping list component."""
import asyncio
from unittest.mock import patch
import pytest
from homeassistant.bootstrap import async_setup_component
from homeassistant.helpers import intent
from homeassistant.components.websocket_api.const import TYPE_RESULT
@pytest.fixture(autouse=True)
def mock_shopping_list_io():
"""Stub out the persistence."""
with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch(
"homeassistant.components.shopping_list." "ShoppingData.async_load"
):
yield
@asyncio.coroutine
def test_add_item(hass):
def test_add_item(hass, sl_setup):
"""Test adding an item intent."""
yield from async_setup_component(hass, "shopping_list", {})
response = yield from intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -31,9 +17,8 @@ def test_add_item(hass):
@asyncio.coroutine
def test_recent_items_intent(hass):
def test_recent_items_intent(hass, sl_setup):
"""Test recent items."""
yield from async_setup_component(hass, "shopping_list", {})
yield from intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -54,9 +39,8 @@ def test_recent_items_intent(hass):
@asyncio.coroutine
def test_deprecated_api_get_all(hass, hass_client):
def test_deprecated_api_get_all(hass, hass_client, sl_setup):
"""Test the API."""
yield from async_setup_component(hass, "shopping_list", {})
yield from intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -77,9 +61,8 @@ def test_deprecated_api_get_all(hass, hass_client):
assert not data[1]["complete"]
async def test_ws_get_items(hass, hass_ws_client):
async def test_ws_get_items(hass, hass_ws_client, sl_setup):
"""Test get shopping_list items websocket command."""
await async_setup_component(hass, "shopping_list", {})
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -106,9 +89,8 @@ async def test_ws_get_items(hass, hass_ws_client):
@asyncio.coroutine
def test_deprecated_api_update(hass, hass_client):
def test_deprecated_api_update(hass, hass_client, sl_setup):
"""Test the API."""
yield from async_setup_component(hass, "shopping_list", {})
yield from intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -142,9 +124,8 @@ def test_deprecated_api_update(hass, hass_client):
assert wine == {"id": wine_id, "name": "wine", "complete": True}
async def test_ws_update_item(hass, hass_ws_client):
async def test_ws_update_item(hass, hass_ws_client, sl_setup):
"""Test update shopping_list item websocket command."""
await async_setup_component(hass, "shopping_list", {})
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
@ -186,9 +167,8 @@ async def test_ws_update_item(hass, hass_ws_client):
@asyncio.coroutine
def test_api_update_fails(hass, hass_client):
def test_api_update_fails(hass, hass_client, sl_setup):
"""Test the API."""
yield from async_setup_component(hass, "shopping_list", {})
yield from intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -209,9 +189,8 @@ def test_api_update_fails(hass, hass_client):
assert resp.status == 400
async def test_ws_update_item_fail(hass, hass_ws_client):
async def test_ws_update_item_fail(hass, hass_ws_client, sl_setup):
"""Test failure of update shopping_list item websocket command."""
await async_setup_component(hass, "shopping_list", {})
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
@ -234,9 +213,8 @@ async def test_ws_update_item_fail(hass, hass_ws_client):
@asyncio.coroutine
def test_deprecated_api_clear_completed(hass, hass_client):
def test_deprecated_api_clear_completed(hass, hass_client, sl_setup):
"""Test the API."""
yield from async_setup_component(hass, "shopping_list", {})
yield from intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
@ -265,9 +243,8 @@ def test_deprecated_api_clear_completed(hass, hass_client):
assert items[0] == {"id": wine_id, "name": "wine", "complete": False}
async def test_ws_clear_items(hass, hass_ws_client):
async def test_ws_clear_items(hass, hass_ws_client, sl_setup):
"""Test clearing shopping_list items websocket command."""
await async_setup_component(hass, "shopping_list", {})
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
@ -296,9 +273,8 @@ async def test_ws_clear_items(hass, hass_ws_client):
@asyncio.coroutine
def test_deprecated_api_create(hass, hass_client):
def test_deprecated_api_create(hass, hass_client, sl_setup):
"""Test the API."""
yield from async_setup_component(hass, "shopping_list", {})
client = yield from hass_client()
resp = yield from client.post("/api/shopping_list/item", json={"name": "soda"})
@ -315,9 +291,8 @@ def test_deprecated_api_create(hass, hass_client):
@asyncio.coroutine
def test_deprecated_api_create_fail(hass, hass_client):
def test_deprecated_api_create_fail(hass, hass_client, sl_setup):
"""Test the API."""
yield from async_setup_component(hass, "shopping_list", {})
client = yield from hass_client()
resp = yield from client.post("/api/shopping_list/item", json={"name": 1234})
@ -326,9 +301,8 @@ def test_deprecated_api_create_fail(hass, hass_client):
assert len(hass.data["shopping_list"].items) == 0
async def test_ws_add_item(hass, hass_ws_client):
async def test_ws_add_item(hass, hass_ws_client, sl_setup):
"""Test adding shopping_list item websocket command."""
await async_setup_component(hass, "shopping_list", {})
client = await hass_ws_client(hass)
await client.send_json({"id": 5, "type": "shopping_list/items/add", "name": "soda"})
msg = await client.receive_json()
@ -342,9 +316,8 @@ async def test_ws_add_item(hass, hass_ws_client):
assert items[0]["complete"] is False
async def test_ws_add_item_fail(hass, hass_ws_client):
async def test_ws_add_item_fail(hass, hass_ws_client, sl_setup):
"""Test adding shopping_list item failure websocket command."""
await async_setup_component(hass, "shopping_list", {})
client = await hass_ws_client(hass)
await client.send_json({"id": 5, "type": "shopping_list/items/add", "name": 123})
msg = await client.receive_json()

View file

@ -0,0 +1,22 @@
"""Test Shopping List intents."""
from homeassistant.helpers import intent
async def test_recent_items_intent(hass, sl_setup):
"""Test recent items."""
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
)
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
)
await intent.async_handle(
hass, "test", "HassShoppingListAddItem", {"item": {"value": "soda"}}
)
response = await intent.async_handle(hass, "test", "HassShoppingListLastItems")
assert (
response.speech["plain"]["speech"]
== "These are the top 3 items on your shopping list: soda, wine, beer"
)