"""The tests for the Alexa component.""" # pylint: disable=protected-access import asyncio import json import pytest from homeassistant.core import callback from homeassistant.setup import async_setup_component from homeassistant.components import alexa from homeassistant.components.alexa import intent SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000" APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" REQUEST_ID = "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000" # pylint: disable=invalid-name calls = [] NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3" @pytest.fixture def alexa_client(loop, hass, test_client): """Initialize a Home Assistant server for testing this module.""" @callback def mock_service(call): calls.append(call) hass.services.async_register("test", "alexa", mock_service) assert loop.run_until_complete(async_setup_component(hass, alexa.DOMAIN, { # Key is here to verify we allow other keys in config too "homeassistant": {}, })) assert loop.run_until_complete(async_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 }}.", } }, "AMAZON.PlaybackAction": { "speech": { "type": "plain", "text": "Playing {{ object_byArtist_name }}.", } }, "CallServiceIntent": { "speech": { "type": "plain", "text": "Service called for {{ ZodiacSign }}", }, "card": { "type": "simple", "title": "Card title for {{ ZodiacSign }}", "content": "Card content: {{ ZodiacSign }}", }, "action": { "service": "test.alexa", "data_template": { "hello": "{{ ZodiacSign }}" }, "entity_id": "switch.test", } }, APPLICATION_ID: { "speech": { "type": "plain", "text": "LaunchRequest has been received.", } } } })) return loop.run_until_complete(test_client(hass.http.app)) def _intent_req(client, data={}): return client.post(intent.INTENTS_API_ENDPOINT, data=json.dumps(data), headers={'content-type': 'application/json'}) @asyncio.coroutine def test_intent_launch_request(alexa_client): """Test the launch of a request.""" data = { "version": "1.0", "session": { "new": True, "sessionId": SESSION_ID, "application": { "applicationId": APPLICATION_ID }, "attributes": {}, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "LaunchRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z" } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 data = yield from req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "LaunchRequest has been received." @asyncio.coroutine def test_intent_launch_request_not_configured(alexa_client): """Test the launch of a request.""" data = { "version": "1.0", "session": { "new": True, "sessionId": SESSION_ID, "application": { "applicationId": 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00000' }, "attributes": {}, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "LaunchRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z" } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 data = yield from req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "This intent is not yet configured within Home Assistant." @asyncio.coroutine def test_intent_request_with_slots(alexa_client): """Test a request with slots.""" data = { "version": "1.0", "session": { "new": False, "sessionId": SESSION_ID, "application": { "applicationId": APPLICATION_ID }, "attributes": { "supportedHoroscopePeriods": { "daily": True, "weekly": False, "monthly": False } }, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "IntentRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z", "intent": { "name": "GetZodiacHoroscopeIntent", "slots": { "ZodiacSign": { "name": "ZodiacSign", "value": "virgo" } } } } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 data = yield from req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You told us your sign is virgo." @asyncio.coroutine def test_intent_request_with_slots_but_no_value(alexa_client): """Test a request with slots but no value.""" data = { "version": "1.0", "session": { "new": False, "sessionId": SESSION_ID, "application": { "applicationId": APPLICATION_ID }, "attributes": { "supportedHoroscopePeriods": { "daily": True, "weekly": False, "monthly": False } }, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "IntentRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z", "intent": { "name": "GetZodiacHoroscopeIntent", "slots": { "ZodiacSign": { "name": "ZodiacSign", } } } } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 data = yield from req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You told us your sign is ." @asyncio.coroutine def test_intent_request_without_slots(hass, alexa_client): """Test a request without slots.""" data = { "version": "1.0", "session": { "new": False, "sessionId": SESSION_ID, "application": { "applicationId": APPLICATION_ID }, "attributes": { "supportedHoroscopePeriods": { "daily": True, "weekly": False, "monthly": False } }, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "IntentRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z", "intent": { "name": "WhereAreWeIntent", } } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 json = yield from req.json() text = json.get("response", {}).get("outputSpeech", {}).get("text") assert text == "Anne Therese is at unknown and Paulus is at unknown" hass.states.async_set("device_tracker.paulus", "home") hass.states.async_set("device_tracker.anne_therese", "home") req = yield from _intent_req(alexa_client, data) assert req.status == 200 json = yield from req.json() text = json.get("response", {}).get("outputSpeech", {}).get("text") assert text == "You are both home, you silly" @asyncio.coroutine def test_intent_request_calling_service(alexa_client): """Test a request for calling a service.""" data = { "version": "1.0", "session": { "new": False, "sessionId": SESSION_ID, "application": { "applicationId": APPLICATION_ID }, "attributes": {}, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "IntentRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z", "intent": { "name": "CallServiceIntent", "slots": { "ZodiacSign": { "name": "ZodiacSign", "value": "virgo", } } } } } call_count = len(calls) req = yield from _intent_req(alexa_client, data) assert req.status == 200 assert call_count + 1 == len(calls) call = calls[-1] assert call.domain == "test" assert call.service == "alexa" assert call.data.get("entity_id") == ["switch.test"] assert call.data.get("hello") == "virgo" data = yield from req.json() assert data['response']['card']['title'] == 'Card title for virgo' assert data['response']['card']['content'] == 'Card content: virgo' assert data['response']['outputSpeech']['type'] == 'PlainText' assert data['response']['outputSpeech']['text'] == \ 'Service called for virgo' @asyncio.coroutine def test_intent_session_ended_request(alexa_client): """Test the request for ending the session.""" data = { "version": "1.0", "session": { "new": False, "sessionId": SESSION_ID, "application": { "applicationId": APPLICATION_ID }, "attributes": { "supportedHoroscopePeriods": { "daily": True, "weekly": False, "monthly": False } }, "user": { "userId": "amzn1.account.AM3B00000000000000000000000" } }, "request": { "type": "SessionEndedRequest", "requestId": REQUEST_ID, "timestamp": "2015-05-13T12:34:56Z", "reason": "USER_INITIATED" } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 text = yield from req.text() assert text == '' @asyncio.coroutine def test_intent_from_built_in_intent_library(alexa_client): """Test intents from the Built-in Intent Library.""" data = { 'request': { 'intent': { 'name': 'AMAZON.PlaybackAction', 'slots': { 'object.byArtist.name': { 'name': 'object.byArtist.name', 'value': 'the shins' }, 'object.composer.name': { 'name': 'object.composer.name' }, 'object.contentSource': { 'name': 'object.contentSource' }, 'object.era': { 'name': 'object.era' }, 'object.genre': { 'name': 'object.genre' }, 'object.name': { 'name': 'object.name' }, 'object.owner.name': { 'name': 'object.owner.name' }, 'object.select': { 'name': 'object.select' }, 'object.sort': { 'name': 'object.sort' }, 'object.type': { 'name': 'object.type', 'value': 'music' } } }, 'timestamp': '2016-12-14T23:23:37Z', 'type': 'IntentRequest', 'requestId': REQUEST_ID, }, 'session': { 'sessionId': SESSION_ID, 'application': { 'applicationId': APPLICATION_ID } } } req = yield from _intent_req(alexa_client, data) assert req.status == 200 data = yield from req.json() text = data.get("response", {}).get("outputSpeech", {}).get("text") assert text == "Playing the shins."