"""The tests for the Conversation component.""" # pylint: disable=protected-access import asyncio import unittest from unittest.mock import patch from homeassistant.core import callback from homeassistant.setup import setup_component, async_setup_component import homeassistant.components as core_components from homeassistant.components import conversation from homeassistant.const import ATTR_ENTITY_ID from homeassistant.util.async import run_coroutine_threadsafe from homeassistant.helpers import intent from tests.common import get_test_home_assistant, async_mock_intent class TestConversation(unittest.TestCase): """Test the conversation component.""" # pylint: disable=invalid-name def setUp(self): """Setup things to be run when tests are started.""" self.ent_id = 'light.kitchen_lights' self.hass = get_test_home_assistant() self.hass.states.set(self.ent_id, 'on') self.assertTrue(run_coroutine_threadsafe( core_components.async_setup(self.hass, {}), self.hass.loop ).result()) self.assertTrue(setup_component(self.hass, conversation.DOMAIN, { conversation.DOMAIN: {} })) # pylint: disable=invalid-name def tearDown(self): """Stop everything that was started.""" self.hass.stop() def test_turn_on(self): """Setup and perform good turn on requests.""" calls = [] @callback def record_call(service): """Recorder for a call.""" calls.append(service) self.hass.services.register('light', 'turn_on', record_call) event_data = {conversation.ATTR_TEXT: 'turn kitchen lights on'} self.assertTrue(self.hass.services.call( conversation.DOMAIN, 'process', event_data, True)) call = calls[-1] self.assertEqual('light', call.domain) self.assertEqual('turn_on', call.service) self.assertEqual([self.ent_id], call.data[ATTR_ENTITY_ID]) def test_turn_off(self): """Setup and perform good turn off requests.""" calls = [] @callback def record_call(service): """Recorder for a call.""" calls.append(service) self.hass.services.register('light', 'turn_off', record_call) event_data = {conversation.ATTR_TEXT: 'turn kitchen lights off'} self.assertTrue(self.hass.services.call( conversation.DOMAIN, 'process', event_data, True)) call = calls[-1] self.assertEqual('light', call.domain) self.assertEqual('turn_off', call.service) self.assertEqual([self.ent_id], call.data[ATTR_ENTITY_ID]) @patch('homeassistant.components.conversation.logging.Logger.error') @patch('homeassistant.core.ServiceRegistry.call') def test_bad_request_format(self, mock_logger, mock_call): """Setup and perform a badly formatted request.""" event_data = { conversation.ATTR_TEXT: '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)) self.assertTrue(mock_logger.called) self.assertFalse(mock_call.called) @patch('homeassistant.components.conversation.logging.Logger.error') @patch('homeassistant.core.ServiceRegistry.call') def test_bad_request_entity(self, mock_logger, mock_call): """Setup and perform requests with bad entity id.""" event_data = {conversation.ATTR_TEXT: 'turn something off'} self.assertTrue(self.hass.services.call( conversation.DOMAIN, 'process', event_data, True)) self.assertTrue(mock_logger.called) self.assertFalse(mock_call.called) @patch('homeassistant.components.conversation.logging.Logger.error') @patch('homeassistant.core.ServiceRegistry.call') def test_bad_request_command(self, mock_logger, mock_call): """Setup and perform requests with bad command.""" event_data = {conversation.ATTR_TEXT: 'turn kitchen lights over'} self.assertTrue(self.hass.services.call( conversation.DOMAIN, 'process', event_data, True)) self.assertTrue(mock_logger.called) self.assertFalse(mock_call.called) @patch('homeassistant.components.conversation.logging.Logger.error') @patch('homeassistant.core.ServiceRegistry.call') def test_bad_request_notext(self, mock_logger, mock_call): """Setup and perform requests with bad command with no text.""" event_data = {} self.assertTrue(self.hass.services.call( conversation.DOMAIN, 'process', event_data, True)) self.assertTrue(mock_logger.called) self.assertFalse(mock_call.called) @asyncio.coroutine def test_calling_intent(hass): """Test calling an intent from a conversation.""" intents = async_mock_intent(hass, 'OrderBeer') 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: '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!" } } }