"""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" @pytest.fixture async def calls(hass, fixture): """Return a list of Dialogflow calls triggered.""" calls = [] @callback def mock_service(call): """Mock action call.""" calls.append(call) hass.services.async_register('test', 'dialogflow', mock_service) return calls @pytest.fixture async def fixture(hass, aiohttp_client): """Initialize a Home Assistant server for testing this module.""" 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, calls): """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