2016-03-09 09:25:50 +00:00
|
|
|
"""The tests for the Conversation component."""
|
2016-10-30 21:18:53 +00:00
|
|
|
# pylint: disable=protected-access
|
2017-07-22 04:38:53 +00:00
|
|
|
import asyncio
|
2015-08-30 08:24:24 +00:00
|
|
|
import unittest
|
2015-09-01 07:18:26 +00:00
|
|
|
from unittest.mock import patch
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2016-11-05 23:36:20 +00:00
|
|
|
from homeassistant.core import callback
|
2017-07-22 04:38:53 +00:00
|
|
|
from homeassistant.setup import setup_component, async_setup_component
|
2015-08-30 08:24:24 +00:00
|
|
|
import homeassistant.components as core_components
|
2015-09-01 07:18:26 +00:00
|
|
|
from homeassistant.components import conversation
|
|
|
|
from homeassistant.const import ATTR_ENTITY_ID
|
2016-10-27 07:16:23 +00:00
|
|
|
from homeassistant.util.async import run_coroutine_threadsafe
|
2017-07-22 04:38:53 +00:00
|
|
|
from homeassistant.helpers import intent
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2017-07-22 04:38:53 +00:00
|
|
|
from tests.common import get_test_home_assistant, async_mock_intent
|
2015-08-30 08:24:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestConversation(unittest.TestCase):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Test the conversation component."""
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2016-10-30 21:18:53 +00:00
|
|
|
# pylint: disable=invalid-name
|
|
|
|
def setUp(self):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup things to be run when tests are started."""
|
2015-09-01 07:18:26 +00:00
|
|
|
self.ent_id = 'light.kitchen_lights'
|
2016-10-31 15:47:29 +00:00
|
|
|
self.hass = get_test_home_assistant()
|
2015-09-01 07:18:26 +00:00
|
|
|
self.hass.states.set(self.ent_id, 'on')
|
2016-10-27 07:16:23 +00:00
|
|
|
self.assertTrue(run_coroutine_threadsafe(
|
|
|
|
core_components.async_setup(self.hass, {}), self.hass.loop
|
|
|
|
).result())
|
2017-07-22 04:38:53 +00:00
|
|
|
self.assertTrue(setup_component(self.hass, conversation.DOMAIN, {
|
|
|
|
conversation.DOMAIN: {}
|
|
|
|
}))
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2016-10-30 21:18:53 +00:00
|
|
|
# pylint: disable=invalid-name
|
|
|
|
def tearDown(self):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Stop everything that was started."""
|
2015-08-30 08:24:24 +00:00
|
|
|
self.hass.stop()
|
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
def test_turn_on(self):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup and perform good turn on requests."""
|
2015-09-01 07:18:26 +00:00
|
|
|
calls = []
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2016-11-05 23:36:20 +00:00
|
|
|
@callback
|
2015-09-01 07:18:26 +00:00
|
|
|
def record_call(service):
|
2016-12-02 05:45:19 +00:00
|
|
|
"""Recorder for a call."""
|
2015-09-01 07:18:26 +00:00
|
|
|
calls.append(service)
|
|
|
|
|
|
|
|
self.hass.services.register('light', 'turn_on', record_call)
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-08-31 07:54:00 +00:00
|
|
|
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights on'}
|
2015-09-01 07:18:26 +00:00
|
|
|
self.assertTrue(self.hass.services.call(
|
|
|
|
conversation.DOMAIN, 'process', event_data, True))
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
call = calls[-1]
|
|
|
|
self.assertEqual('light', call.domain)
|
|
|
|
self.assertEqual('turn_on', call.service)
|
|
|
|
self.assertEqual([self.ent_id], call.data[ATTR_ENTITY_ID])
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
def test_turn_off(self):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup and perform good turn off requests."""
|
2015-09-01 07:18:26 +00:00
|
|
|
calls = []
|
2015-08-30 09:04:04 +00:00
|
|
|
|
2016-11-05 23:36:20 +00:00
|
|
|
@callback
|
2015-09-01 07:18:26 +00:00
|
|
|
def record_call(service):
|
2016-12-02 05:45:19 +00:00
|
|
|
"""Recorder for a call."""
|
2015-09-01 07:18:26 +00:00
|
|
|
calls.append(service)
|
|
|
|
|
|
|
|
self.hass.services.register('light', 'turn_off', record_call)
|
2015-08-30 09:04:04 +00:00
|
|
|
|
2015-08-31 07:54:00 +00:00
|
|
|
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights off'}
|
2015-09-01 07:18:26 +00:00
|
|
|
self.assertTrue(self.hass.services.call(
|
|
|
|
conversation.DOMAIN, 'process', event_data, True))
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
call = calls[-1]
|
|
|
|
self.assertEqual('light', call.domain)
|
|
|
|
self.assertEqual('turn_off', call.service)
|
|
|
|
self.assertEqual([self.ent_id], call.data[ATTR_ENTITY_ID])
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
@patch('homeassistant.components.conversation.logging.Logger.error')
|
|
|
|
@patch('homeassistant.core.ServiceRegistry.call')
|
|
|
|
def test_bad_request_format(self, mock_logger, mock_call):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup and perform a badly formatted request."""
|
2015-08-31 07:54:00 +00:00
|
|
|
event_data = {
|
|
|
|
conversation.ATTR_TEXT:
|
2015-08-30 08:24:24 +00:00
|
|
|
'what is the answer to the ultimate question of life, ' +
|
|
|
|
'the universe and everything'}
|
|
|
|
self.assertTrue(self.hass.services.call(
|
|
|
|
conversation.DOMAIN, 'process', event_data, True))
|
2015-09-01 07:18:26 +00:00
|
|
|
self.assertTrue(mock_logger.called)
|
|
|
|
self.assertFalse(mock_call.called)
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
@patch('homeassistant.components.conversation.logging.Logger.error')
|
|
|
|
@patch('homeassistant.core.ServiceRegistry.call')
|
|
|
|
def test_bad_request_entity(self, mock_logger, mock_call):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup and perform requests with bad entity id."""
|
2015-08-30 08:24:24 +00:00
|
|
|
event_data = {conversation.ATTR_TEXT: 'turn something off'}
|
|
|
|
self.assertTrue(self.hass.services.call(
|
|
|
|
conversation.DOMAIN, 'process', event_data, True))
|
2015-09-01 07:18:26 +00:00
|
|
|
self.assertTrue(mock_logger.called)
|
|
|
|
self.assertFalse(mock_call.called)
|
2015-08-30 08:24:24 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
@patch('homeassistant.components.conversation.logging.Logger.error')
|
|
|
|
@patch('homeassistant.core.ServiceRegistry.call')
|
|
|
|
def test_bad_request_command(self, mock_logger, mock_call):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup and perform requests with bad command."""
|
2015-09-01 07:18:26 +00:00
|
|
|
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights over'}
|
2015-08-30 08:24:24 +00:00
|
|
|
self.assertTrue(self.hass.services.call(
|
|
|
|
conversation.DOMAIN, 'process', event_data, True))
|
2015-09-01 07:18:26 +00:00
|
|
|
self.assertTrue(mock_logger.called)
|
|
|
|
self.assertFalse(mock_call.called)
|
2015-08-30 08:30:19 +00:00
|
|
|
|
2015-09-01 07:18:26 +00:00
|
|
|
@patch('homeassistant.components.conversation.logging.Logger.error')
|
|
|
|
@patch('homeassistant.core.ServiceRegistry.call')
|
|
|
|
def test_bad_request_notext(self, mock_logger, mock_call):
|
2016-03-09 09:25:50 +00:00
|
|
|
"""Setup and perform requests with bad command with no text."""
|
2015-08-30 08:30:19 +00:00
|
|
|
event_data = {}
|
|
|
|
self.assertTrue(self.hass.services.call(
|
|
|
|
conversation.DOMAIN, 'process', event_data, True))
|
2015-09-01 07:18:26 +00:00
|
|
|
self.assertTrue(mock_logger.called)
|
|
|
|
self.assertFalse(mock_call.called)
|
2017-06-13 06:34:20 +00:00
|
|
|
|
|
|
|
|
2017-07-22 04:38:53 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def test_calling_intent(hass):
|
|
|
|
"""Test calling an intent from a conversation."""
|
|
|
|
intents = async_mock_intent(hass, 'OrderBeer')
|
2017-06-13 06:34:20 +00:00
|
|
|
|
2017-07-22 04:38:53 +00:00
|
|
|
result = yield from async_setup_component(hass, 'conversation', {
|
|
|
|
'conversation': {
|
|
|
|
'intents': {
|
|
|
|
'OrderBeer': [
|
|
|
|
'I would like the {type} beer'
|
|
|
|
]
|
2017-06-13 06:34:20 +00:00
|
|
|
}
|
2017-07-22 04:38:53 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
assert result
|
|
|
|
|
|
|
|
yield from hass.services.async_call(
|
|
|
|
'conversation', 'process', {
|
|
|
|
conversation.ATTR_TEXT: 'I would like the Grolsch beer'
|
|
|
|
})
|
|
|
|
yield from hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(intents) == 1
|
|
|
|
intent = intents[0]
|
|
|
|
assert intent.platform == 'conversation'
|
|
|
|
assert intent.intent_type == 'OrderBeer'
|
|
|
|
assert intent.slots == {'type': {'value': 'Grolsch'}}
|
|
|
|
assert intent.text_input == 'I would like the Grolsch beer'
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def test_register_before_setup(hass):
|
|
|
|
"""Test calling an intent from a conversation."""
|
|
|
|
intents = async_mock_intent(hass, 'OrderBeer')
|
|
|
|
|
|
|
|
hass.components.conversation.async_register('OrderBeer', [
|
|
|
|
'A {type} beer, please'
|
|
|
|
])
|
|
|
|
|
|
|
|
result = yield from async_setup_component(hass, 'conversation', {
|
|
|
|
'conversation': {
|
|
|
|
'intents': {
|
|
|
|
'OrderBeer': [
|
|
|
|
'I would like the {type} beer'
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
assert result
|
|
|
|
|
|
|
|
yield from hass.services.async_call(
|
|
|
|
'conversation', 'process', {
|
|
|
|
conversation.ATTR_TEXT: 'A Grolsch beer, please'
|
|
|
|
})
|
|
|
|
yield from hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(intents) == 1
|
|
|
|
intent = intents[0]
|
|
|
|
assert intent.platform == 'conversation'
|
|
|
|
assert intent.intent_type == 'OrderBeer'
|
|
|
|
assert intent.slots == {'type': {'value': 'Grolsch'}}
|
|
|
|
assert intent.text_input == 'A Grolsch beer, please'
|
|
|
|
|
|
|
|
yield from hass.services.async_call(
|
|
|
|
'conversation', 'process', {
|
|
|
|
conversation.ATTR_TEXT: 'I would like the Grolsch beer'
|
|
|
|
})
|
|
|
|
yield from hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(intents) == 2
|
|
|
|
intent = intents[1]
|
|
|
|
assert intent.platform == 'conversation'
|
|
|
|
assert intent.intent_type == 'OrderBeer'
|
|
|
|
assert intent.slots == {'type': {'value': 'Grolsch'}}
|
|
|
|
assert intent.text_input == 'I would like the Grolsch beer'
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def test_http_processing_intent(hass, test_client):
|
|
|
|
"""Test processing intent via HTTP API."""
|
|
|
|
class TestIntentHandler(intent.IntentHandler):
|
|
|
|
intent_type = 'OrderBeer'
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_handle(self, intent):
|
|
|
|
"""Handle the intent."""
|
|
|
|
response = intent.create_response()
|
|
|
|
response.async_set_speech(
|
|
|
|
"I've ordered a {}!".format(intent.slots['type']['value']))
|
|
|
|
response.async_set_card(
|
|
|
|
"Beer ordered",
|
|
|
|
"You chose a {}.".format(intent.slots['type']['value']))
|
|
|
|
return response
|
|
|
|
|
|
|
|
intent.async_register(hass, TestIntentHandler())
|
|
|
|
|
|
|
|
result = yield from async_setup_component(hass, 'conversation', {
|
|
|
|
'conversation': {
|
|
|
|
'intents': {
|
|
|
|
'OrderBeer': [
|
|
|
|
'I would like the {type} beer'
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
assert result
|
|
|
|
|
|
|
|
client = yield from test_client(hass.http.app)
|
|
|
|
resp = yield from client.post('/api/conversation/process', json={
|
|
|
|
'text': 'I would like the Grolsch beer'
|
|
|
|
})
|
|
|
|
|
|
|
|
assert resp.status == 200
|
|
|
|
data = yield from resp.json()
|
|
|
|
|
|
|
|
assert data == {
|
|
|
|
'card': {
|
|
|
|
'simple': {
|
|
|
|
'content': 'You chose a Grolsch.',
|
|
|
|
'title': 'Beer ordered'
|
|
|
|
}},
|
|
|
|
'speech': {
|
|
|
|
'plain': {
|
|
|
|
'extra_data': None,
|
|
|
|
'speech': "I've ordered a Grolsch!"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|