mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 13:47:35 +00:00
Migrate dialogflow over to the new webhook component (#17804)
* Migrate dialogflow over to the new webhook component * Updating dialogflow unit tests * Lint * Revert changes to HomeAssistantView * Use json_response from aiohttp
This commit is contained in:
parent
38576e5b74
commit
60080a529d
18
homeassistant/components/dialogflow/.translations/en.json
Normal file
18
homeassistant/components/dialogflow/.translations/en.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Dialogflow",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Set up the Dialogflow Webhook",
|
||||||
|
"description": "Are you sure you want to set up Dialogflow?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"one_instance_allowed": "Only a single instance is necessary.",
|
||||||
|
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages."
|
||||||
|
},
|
||||||
|
"create_entry": {
|
||||||
|
"default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,24 +7,16 @@ https://home-assistant.io/components/dialogflow/
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_WEBHOOK_ID
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import intent, template
|
from homeassistant.helpers import intent, template, config_entry_flow
|
||||||
from homeassistant.components.http import HomeAssistantView
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_INTENTS = 'intents'
|
DEPENDENCIES = ['webhook']
|
||||||
CONF_SPEECH = 'speech'
|
|
||||||
CONF_ACTION = 'action'
|
|
||||||
CONF_ASYNC_ACTION = 'async_action'
|
|
||||||
|
|
||||||
DEFAULT_CONF_ASYNC_ACTION = False
|
|
||||||
DEPENDENCIES = ['http']
|
|
||||||
DOMAIN = 'dialogflow'
|
DOMAIN = 'dialogflow'
|
||||||
|
|
||||||
INTENTS_API_ENDPOINT = '/api/dialogflow'
|
|
||||||
|
|
||||||
SOURCE = "Home Assistant Dialogflow"
|
SOURCE = "Home Assistant Dialogflow"
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
@ -38,52 +30,72 @@ class DialogFlowError(HomeAssistantError):
|
|||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up Dialogflow component."""
|
"""Set up Dialogflow component."""
|
||||||
hass.http.register_view(DialogflowIntentsView)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class DialogflowIntentsView(HomeAssistantView):
|
async def handle_webhook(hass, webhook_id, request):
|
||||||
"""Handle Dialogflow requests."""
|
"""Handle incoming webhook with Dialogflow requests."""
|
||||||
|
message = await request.json()
|
||||||
|
|
||||||
url = INTENTS_API_ENDPOINT
|
_LOGGER.debug("Received Dialogflow request: %s", message)
|
||||||
name = 'api:dialogflow'
|
|
||||||
|
|
||||||
async def post(self, request):
|
try:
|
||||||
"""Handle Dialogflow."""
|
response = await async_handle_message(hass, message)
|
||||||
hass = request.app['hass']
|
return b'' if response is None else web.json_response(response)
|
||||||
message = await request.json()
|
|
||||||
|
|
||||||
_LOGGER.debug("Received Dialogflow request: %s", message)
|
except DialogFlowError as err:
|
||||||
|
_LOGGER.warning(str(err))
|
||||||
|
return web.json_response(
|
||||||
|
dialogflow_error_response(message, str(err))
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
except intent.UnknownIntent as err:
|
||||||
response = await async_handle_message(hass, message)
|
_LOGGER.warning(str(err))
|
||||||
return b'' if response is None else self.json(response)
|
return web.json_response(
|
||||||
|
dialogflow_error_response(
|
||||||
|
message,
|
||||||
|
"This intent is not yet configured within Home Assistant."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
except DialogFlowError as err:
|
except intent.InvalidSlotInfo as err:
|
||||||
_LOGGER.warning(str(err))
|
_LOGGER.warning(str(err))
|
||||||
return self.json(dialogflow_error_response(
|
return web.json_response(
|
||||||
hass, message, str(err)))
|
dialogflow_error_response(
|
||||||
|
message,
|
||||||
|
"Invalid slot information received for this intent."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
except intent.UnknownIntent as err:
|
except intent.IntentError as err:
|
||||||
_LOGGER.warning(str(err))
|
_LOGGER.warning(str(err))
|
||||||
return self.json(dialogflow_error_response(
|
return web.json_response(
|
||||||
hass, message,
|
dialogflow_error_response(message, "Error handling intent."))
|
||||||
"This intent is not yet configured within Home Assistant."))
|
|
||||||
|
|
||||||
except intent.InvalidSlotInfo as err:
|
|
||||||
_LOGGER.warning(str(err))
|
|
||||||
return self.json(dialogflow_error_response(
|
|
||||||
hass, message,
|
|
||||||
"Invalid slot information received for this intent."))
|
|
||||||
|
|
||||||
except intent.IntentError as err:
|
|
||||||
_LOGGER.warning(str(err))
|
|
||||||
return self.json(dialogflow_error_response(
|
|
||||||
hass, message, "Error handling intent."))
|
|
||||||
|
|
||||||
|
|
||||||
def dialogflow_error_response(hass, message, error):
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Configure based on config entry."""
|
||||||
|
hass.components.webhook.async_register(
|
||||||
|
entry.data[CONF_WEBHOOK_ID], handle_webhook)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
|
||||||
|
return True
|
||||||
|
|
||||||
|
config_entry_flow.register_webhook_flow(
|
||||||
|
DOMAIN,
|
||||||
|
'Dialogflow Webhook',
|
||||||
|
{
|
||||||
|
'dialogflow_url': 'https://dialogflow.com/docs/fulfillment#webhook',
|
||||||
|
'docs_url': 'https://www.home-assistant.io/components/dialogflow/'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def dialogflow_error_response(message, error):
|
||||||
"""Return a response saying the error message."""
|
"""Return a response saying the error message."""
|
||||||
dialogflow_response = DialogflowResponse(message['result']['parameters'])
|
dialogflow_response = DialogflowResponse(message['result']['parameters'])
|
||||||
dialogflow_response.add_speech(error)
|
dialogflow_response.add_speech(error)
|
18
homeassistant/components/dialogflow/strings.json
Normal file
18
homeassistant/components/dialogflow/strings.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Dialogflow",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Set up the Dialogflow Webhook",
|
||||||
|
"description": "Are you sure you want to set up Dialogflow?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"one_instance_allowed": "Only a single instance is necessary.",
|
||||||
|
"not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages."
|
||||||
|
},
|
||||||
|
"create_entry": {
|
||||||
|
"default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -137,6 +137,7 @@ HANDLERS = Registry()
|
|||||||
FLOWS = [
|
FLOWS = [
|
||||||
'cast',
|
'cast',
|
||||||
'deconz',
|
'deconz',
|
||||||
|
'dialogflow',
|
||||||
'hangouts',
|
'hangouts',
|
||||||
'homematicip_cloud',
|
'homematicip_cloud',
|
||||||
'hue',
|
'hue',
|
||||||
|
1
tests/components/dialogflow/__init__.py
Normal file
1
tests/components/dialogflow/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Dialogflow component."""
|
529
tests/components/dialogflow/test_init.py
Normal file
529
tests/components/dialogflow/test_init.py
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
"""The tests for the Dialogflow component."""
|
||||||
|
import json
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components import dialogflow, intent_script
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
SESSION_ID = "a9b84cec-46b6-484e-8f31-f65dba03ae6d"
|
||||||
|
INTENT_ID = "c6a74079-a8f0-46cd-b372-5a934d23591c"
|
||||||
|
INTENT_NAME = "tests"
|
||||||
|
REQUEST_ID = "19ef7e78-fe15-4e94-99dd-0c0b1e8753c3"
|
||||||
|
REQUEST_TIMESTAMP = "2017-01-21T17:54:18.952Z"
|
||||||
|
CONTEXT_NAME = "78a5db95-b7d6-4d50-9c9b-2fc73a5e34c3_id_dialog_context"
|
||||||
|
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def fixture(hass, aiohttp_client):
|
||||||
|
"""Initialize a Home Assistant server for testing this module."""
|
||||||
|
@callback
|
||||||
|
def mock_service(call):
|
||||||
|
"""Mock action call."""
|
||||||
|
calls.append(call)
|
||||||
|
|
||||||
|
hass.services.async_register('test', 'dialogflow', mock_service)
|
||||||
|
|
||||||
|
await async_setup_component(hass, dialogflow.DOMAIN, {
|
||||||
|
"dialogflow": {},
|
||||||
|
})
|
||||||
|
await async_setup_component(hass, intent_script.DOMAIN, {
|
||||||
|
"intent_script": {
|
||||||
|
"WhereAreWeIntent": {
|
||||||
|
"speech": {
|
||||||
|
"type": "plain",
|
||||||
|
"text": """
|
||||||
|
{%- if is_state("device_tracker.paulus", "home")
|
||||||
|
and is_state("device_tracker.anne_therese",
|
||||||
|
"home") -%}
|
||||||
|
You are both home, you silly
|
||||||
|
{%- else -%}
|
||||||
|
Anne Therese is at {{
|
||||||
|
states("device_tracker.anne_therese")
|
||||||
|
}} and Paulus is at {{
|
||||||
|
states("device_tracker.paulus")
|
||||||
|
}}
|
||||||
|
{% endif %}
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetZodiacHoroscopeIntent": {
|
||||||
|
"speech": {
|
||||||
|
"type": "plain",
|
||||||
|
"text": "You told us your sign is {{ ZodiacSign }}.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CallServiceIntent": {
|
||||||
|
"speech": {
|
||||||
|
"type": "plain",
|
||||||
|
"text": "Service called",
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.dialogflow",
|
||||||
|
"data_template": {
|
||||||
|
"hello": "{{ ZodiacSign }}"
|
||||||
|
},
|
||||||
|
"entity_id": "switch.test",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
hass.config.api = Mock(base_url='http://example.com')
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'dialogflow',
|
||||||
|
context={
|
||||||
|
'source': 'user'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result['flow_id'], {})
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
webhook_id = result['result'].data['webhook_id']
|
||||||
|
|
||||||
|
return await aiohttp_client(hass.http.app), webhook_id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_action_incomplete(fixture):
|
||||||
|
"""Test when action is not completed."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is virgo",
|
||||||
|
"speech": "",
|
||||||
|
"action": "GetZodiacHoroscopeIntent",
|
||||||
|
"actionIncomplete": True,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": "virgo"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
assert "" == await response.text()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_slot_filling(fixture):
|
||||||
|
"""Test when Dialogflow asks for slot-filling return none."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is",
|
||||||
|
"speech": "",
|
||||||
|
"action": "GetZodiacHoroscopeIntent",
|
||||||
|
"actionIncomplete": True,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"contexts": [
|
||||||
|
{
|
||||||
|
"name": CONTEXT_NAME,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign.original": "",
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"lifespan": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tests_ha_dialog_context",
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign.original": "",
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"lifespan": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tests_ha_dialog_params_zodiacsign",
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign.original": "",
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"lifespan": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "true",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "What is the ZodiacSign?",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": "What is the ZodiacSign?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 0.77
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
assert "" == await response.text()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_request_with_parameters(fixture):
|
||||||
|
"""Test a request with parameters."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is virgo",
|
||||||
|
"speech": "",
|
||||||
|
"action": "GetZodiacHoroscopeIntent",
|
||||||
|
"actionIncomplete": False,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": "virgo"
|
||||||
|
},
|
||||||
|
"contexts": [],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
text = (await response.json()).get("speech")
|
||||||
|
assert "You told us your sign is virgo." == text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_request_with_parameters_but_empty(fixture):
|
||||||
|
"""Test a request with parameters but empty value."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is virgo",
|
||||||
|
"speech": "",
|
||||||
|
"action": "GetZodiacHoroscopeIntent",
|
||||||
|
"actionIncomplete": False,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"contexts": [],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
text = (await response.json()).get("speech")
|
||||||
|
assert "You told us your sign is ." == text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_request_without_slots(hass, fixture):
|
||||||
|
"""Test a request without slots."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "where are we",
|
||||||
|
"speech": "",
|
||||||
|
"action": "WhereAreWeIntent",
|
||||||
|
"actionIncomplete": False,
|
||||||
|
"parameters": {},
|
||||||
|
"contexts": [],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
text = (await response.json()).get("speech")
|
||||||
|
|
||||||
|
assert "Anne Therese is at unknown and Paulus is at unknown" == \
|
||||||
|
text
|
||||||
|
|
||||||
|
hass.states.async_set("device_tracker.paulus", "home")
|
||||||
|
hass.states.async_set("device_tracker.anne_therese", "home")
|
||||||
|
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
text = (await response.json()).get("speech")
|
||||||
|
assert "You are both home, you silly" == text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_request_calling_service(fixture):
|
||||||
|
"""Test a request for calling a service.
|
||||||
|
|
||||||
|
If this request is done async the test could finish before the action
|
||||||
|
has been executed. Hard to test because it will be a race condition.
|
||||||
|
"""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is virgo",
|
||||||
|
"speech": "",
|
||||||
|
"action": "CallServiceIntent",
|
||||||
|
"actionIncomplete": False,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": "virgo"
|
||||||
|
},
|
||||||
|
"contexts": [],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
call_count = len(calls)
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
assert call_count + 1 == len(calls)
|
||||||
|
call = calls[-1]
|
||||||
|
assert "test" == call.domain
|
||||||
|
assert "dialogflow" == call.service
|
||||||
|
assert ["switch.test"] == call.data.get("entity_id")
|
||||||
|
assert "virgo" == call.data.get("hello")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_with_no_action(fixture):
|
||||||
|
"""Test an intent with no defined action."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is virgo",
|
||||||
|
"speech": "",
|
||||||
|
"action": "",
|
||||||
|
"actionIncomplete": False,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"contexts": [],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
text = (await response.json()).get("speech")
|
||||||
|
assert \
|
||||||
|
"You have not defined an action in your Dialogflow intent." == text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_intent_with_unknown_action(fixture):
|
||||||
|
"""Test an intent with an action not defined in the conf."""
|
||||||
|
mock_client, webhook_id = fixture
|
||||||
|
data = {
|
||||||
|
"id": REQUEST_ID,
|
||||||
|
"timestamp": REQUEST_TIMESTAMP,
|
||||||
|
"result": {
|
||||||
|
"source": "agent",
|
||||||
|
"resolvedQuery": "my zodiac sign is virgo",
|
||||||
|
"speech": "",
|
||||||
|
"action": "unknown",
|
||||||
|
"actionIncomplete": False,
|
||||||
|
"parameters": {
|
||||||
|
"ZodiacSign": ""
|
||||||
|
},
|
||||||
|
"contexts": [],
|
||||||
|
"metadata": {
|
||||||
|
"intentId": INTENT_ID,
|
||||||
|
"webhookUsed": "true",
|
||||||
|
"webhookForSlotFillingUsed": "false",
|
||||||
|
"intentName": INTENT_NAME
|
||||||
|
},
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"score": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"code": 200,
|
||||||
|
"errorType": "success"
|
||||||
|
},
|
||||||
|
"sessionId": SESSION_ID,
|
||||||
|
"originalRequest": None
|
||||||
|
}
|
||||||
|
response = await mock_client.post(
|
||||||
|
'/api/webhook/{}'.format(webhook_id),
|
||||||
|
data=json.dumps(data)
|
||||||
|
)
|
||||||
|
assert 200 == response.status
|
||||||
|
text = (await response.json()).get("speech")
|
||||||
|
assert \
|
||||||
|
"This intent is not yet configured within Home Assistant." == text
|
@ -1,525 +0,0 @@
|
|||||||
"""The tests for the Dialogflow component."""
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
import json
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from aiohttp.hdrs import CONTENT_TYPE
|
|
||||||
|
|
||||||
from homeassistant.core import callback
|
|
||||||
from homeassistant import setup, const
|
|
||||||
from homeassistant.components import dialogflow, http
|
|
||||||
|
|
||||||
from tests.common import get_test_instance_port, get_test_home_assistant
|
|
||||||
|
|
||||||
API_PASSWORD = 'test1234'
|
|
||||||
SERVER_PORT = get_test_instance_port()
|
|
||||||
BASE_API_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
|
|
||||||
INTENTS_API_URL = "{}{}".format(BASE_API_URL, dialogflow.INTENTS_API_ENDPOINT)
|
|
||||||
|
|
||||||
HA_HEADERS = {
|
|
||||||
const.HTTP_HEADER_HA_AUTH: API_PASSWORD,
|
|
||||||
CONTENT_TYPE: const.CONTENT_TYPE_JSON,
|
|
||||||
}
|
|
||||||
|
|
||||||
SESSION_ID = "a9b84cec-46b6-484e-8f31-f65dba03ae6d"
|
|
||||||
INTENT_ID = "c6a74079-a8f0-46cd-b372-5a934d23591c"
|
|
||||||
INTENT_NAME = "tests"
|
|
||||||
REQUEST_ID = "19ef7e78-fe15-4e94-99dd-0c0b1e8753c3"
|
|
||||||
REQUEST_TIMESTAMP = "2017-01-21T17:54:18.952Z"
|
|
||||||
CONTEXT_NAME = "78a5db95-b7d6-4d50-9c9b-2fc73a5e34c3_id_dialog_context"
|
|
||||||
MAX_RESPONSE_TIME = 5 # https://dialogflow.com/docs/fulfillment
|
|
||||||
|
|
||||||
# An unknown action takes 8 s to return. Request timeout should be bigger to
|
|
||||||
# allow the test to finish
|
|
||||||
REQUEST_TIMEOUT = 15
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
hass = None
|
|
||||||
calls = []
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
def setUpModule():
|
|
||||||
"""Initialize a Home Assistant server for testing this module."""
|
|
||||||
global hass
|
|
||||||
|
|
||||||
hass = get_test_home_assistant()
|
|
||||||
|
|
||||||
setup.setup_component(
|
|
||||||
hass, http.DOMAIN, {
|
|
||||||
http.DOMAIN: {
|
|
||||||
http.CONF_API_PASSWORD: API_PASSWORD,
|
|
||||||
http.CONF_SERVER_PORT: SERVER_PORT,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def mock_service(call):
|
|
||||||
"""Mock action call."""
|
|
||||||
calls.append(call)
|
|
||||||
|
|
||||||
hass.services.register('test', 'dialogflow', mock_service)
|
|
||||||
|
|
||||||
assert setup.setup_component(hass, dialogflow.DOMAIN, {
|
|
||||||
"dialogflow": {},
|
|
||||||
})
|
|
||||||
assert setup.setup_component(hass, "intent_script", {
|
|
||||||
"intent_script": {
|
|
||||||
"WhereAreWeIntent": {
|
|
||||||
"speech": {
|
|
||||||
"type": "plain",
|
|
||||||
"text": """
|
|
||||||
{%- if is_state("device_tracker.paulus", "home")
|
|
||||||
and is_state("device_tracker.anne_therese",
|
|
||||||
"home") -%}
|
|
||||||
You are both home, you silly
|
|
||||||
{%- else -%}
|
|
||||||
Anne Therese is at {{
|
|
||||||
states("device_tracker.anne_therese")
|
|
||||||
}} and Paulus is at {{
|
|
||||||
states("device_tracker.paulus")
|
|
||||||
}}
|
|
||||||
{% endif %}
|
|
||||||
""",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"GetZodiacHoroscopeIntent": {
|
|
||||||
"speech": {
|
|
||||||
"type": "plain",
|
|
||||||
"text": "You told us your sign is {{ ZodiacSign }}.",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CallServiceIntent": {
|
|
||||||
"speech": {
|
|
||||||
"type": "plain",
|
|
||||||
"text": "Service called",
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"service": "test.dialogflow",
|
|
||||||
"data_template": {
|
|
||||||
"hello": "{{ ZodiacSign }}"
|
|
||||||
},
|
|
||||||
"entity_id": "switch.test",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
hass.start()
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
def tearDownModule():
|
|
||||||
"""Stop the Home Assistant server."""
|
|
||||||
hass.stop()
|
|
||||||
|
|
||||||
|
|
||||||
def _intent_req(data):
|
|
||||||
return requests.post(
|
|
||||||
INTENTS_API_URL, data=json.dumps(data), timeout=REQUEST_TIMEOUT,
|
|
||||||
headers=HA_HEADERS)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDialogflow(unittest.TestCase):
|
|
||||||
"""Test Dialogflow."""
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Stop everything that was started."""
|
|
||||||
hass.block_till_done()
|
|
||||||
|
|
||||||
def test_intent_action_incomplete(self):
|
|
||||||
"""Test when action is not completed."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is virgo",
|
|
||||||
"speech": "",
|
|
||||||
"action": "GetZodiacHoroscopeIntent",
|
|
||||||
"actionIncomplete": True,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": "virgo"
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
assert "" == req.text
|
|
||||||
|
|
||||||
def test_intent_slot_filling(self):
|
|
||||||
"""Test when Dialogflow asks for slot-filling return none."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is",
|
|
||||||
"speech": "",
|
|
||||||
"action": "GetZodiacHoroscopeIntent",
|
|
||||||
"actionIncomplete": True,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"contexts": [
|
|
||||||
{
|
|
||||||
"name": CONTEXT_NAME,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign.original": "",
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"lifespan": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tests_ha_dialog_context",
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign.original": "",
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"lifespan": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tests_ha_dialog_params_zodiacsign",
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign.original": "",
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"lifespan": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "true",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "What is the ZodiacSign?",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": "What is the ZodiacSign?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 0.77
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
assert "" == req.text
|
|
||||||
|
|
||||||
def test_intent_request_with_parameters(self):
|
|
||||||
"""Test a request with parameters."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is virgo",
|
|
||||||
"speech": "",
|
|
||||||
"action": "GetZodiacHoroscopeIntent",
|
|
||||||
"actionIncomplete": False,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": "virgo"
|
|
||||||
},
|
|
||||||
"contexts": [],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
text = req.json().get("speech")
|
|
||||||
assert "You told us your sign is virgo." == text
|
|
||||||
|
|
||||||
def test_intent_request_with_parameters_but_empty(self):
|
|
||||||
"""Test a request with parameters but empty value."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is virgo",
|
|
||||||
"speech": "",
|
|
||||||
"action": "GetZodiacHoroscopeIntent",
|
|
||||||
"actionIncomplete": False,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"contexts": [],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
text = req.json().get("speech")
|
|
||||||
assert "You told us your sign is ." == text
|
|
||||||
|
|
||||||
def test_intent_request_without_slots(self):
|
|
||||||
"""Test a request without slots."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "where are we",
|
|
||||||
"speech": "",
|
|
||||||
"action": "WhereAreWeIntent",
|
|
||||||
"actionIncomplete": False,
|
|
||||||
"parameters": {},
|
|
||||||
"contexts": [],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
text = req.json().get("speech")
|
|
||||||
|
|
||||||
assert "Anne Therese is at unknown and Paulus is at unknown" == \
|
|
||||||
text
|
|
||||||
|
|
||||||
hass.states.set("device_tracker.paulus", "home")
|
|
||||||
hass.states.set("device_tracker.anne_therese", "home")
|
|
||||||
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
text = req.json().get("speech")
|
|
||||||
assert "You are both home, you silly" == text
|
|
||||||
|
|
||||||
def test_intent_request_calling_service(self):
|
|
||||||
"""Test a request for calling a service.
|
|
||||||
|
|
||||||
If this request is done async the test could finish before the action
|
|
||||||
has been executed. Hard to test because it will be a race condition.
|
|
||||||
"""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is virgo",
|
|
||||||
"speech": "",
|
|
||||||
"action": "CallServiceIntent",
|
|
||||||
"actionIncomplete": False,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": "virgo"
|
|
||||||
},
|
|
||||||
"contexts": [],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
call_count = len(calls)
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
assert call_count + 1 == len(calls)
|
|
||||||
call = calls[-1]
|
|
||||||
assert "test" == call.domain
|
|
||||||
assert "dialogflow" == call.service
|
|
||||||
assert ["switch.test"] == call.data.get("entity_id")
|
|
||||||
assert "virgo" == call.data.get("hello")
|
|
||||||
|
|
||||||
def test_intent_with_no_action(self):
|
|
||||||
"""Test an intent with no defined action."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is virgo",
|
|
||||||
"speech": "",
|
|
||||||
"action": "",
|
|
||||||
"actionIncomplete": False,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"contexts": [],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
text = req.json().get("speech")
|
|
||||||
assert \
|
|
||||||
"You have not defined an action in your Dialogflow intent." == text
|
|
||||||
|
|
||||||
def test_intent_with_unknown_action(self):
|
|
||||||
"""Test an intent with an action not defined in the conf."""
|
|
||||||
data = {
|
|
||||||
"id": REQUEST_ID,
|
|
||||||
"timestamp": REQUEST_TIMESTAMP,
|
|
||||||
"result": {
|
|
||||||
"source": "agent",
|
|
||||||
"resolvedQuery": "my zodiac sign is virgo",
|
|
||||||
"speech": "",
|
|
||||||
"action": "unknown",
|
|
||||||
"actionIncomplete": False,
|
|
||||||
"parameters": {
|
|
||||||
"ZodiacSign": ""
|
|
||||||
},
|
|
||||||
"contexts": [],
|
|
||||||
"metadata": {
|
|
||||||
"intentId": INTENT_ID,
|
|
||||||
"webhookUsed": "true",
|
|
||||||
"webhookForSlotFillingUsed": "false",
|
|
||||||
"intentName": INTENT_NAME
|
|
||||||
},
|
|
||||||
"fulfillment": {
|
|
||||||
"speech": "",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"type": 0,
|
|
||||||
"speech": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"score": 1
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"code": 200,
|
|
||||||
"errorType": "success"
|
|
||||||
},
|
|
||||||
"sessionId": SESSION_ID,
|
|
||||||
"originalRequest": None
|
|
||||||
}
|
|
||||||
req = _intent_req(data)
|
|
||||||
assert 200 == req.status_code
|
|
||||||
text = req.json().get("speech")
|
|
||||||
assert \
|
|
||||||
"This intent is not yet configured within Home Assistant." == text
|
|
Loading…
x
Reference in New Issue
Block a user