449 lines
14 KiB
Python
449 lines
14 KiB
Python
"""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<object@MusicCreativeWork>": {
|
|
"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<object@MusicCreativeWork>',
|
|
'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."
|