Convert Alexa tests to use aiohttp test utils (#6839)

This commit is contained in:
Paulus Schoutsen 2017-03-29 22:21:39 -07:00 committed by GitHub
parent bfe0aee468
commit 556dba4020

View File

@ -1,33 +1,20 @@
"""The tests for the Alexa component.""" """The tests for the Alexa component."""
# pylint: disable=protected-access # pylint: disable=protected-access
import asyncio
import json import json
import datetime import datetime
import unittest
import requests import pytest
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant import setup, const from homeassistant.setup import async_setup_component
from homeassistant.components import alexa, http from homeassistant.components import alexa
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, alexa.INTENTS_API_ENDPOINT)
HA_HEADERS = {
const.HTTP_HEADER_HA_AUTH: API_PASSWORD,
const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON,
}
SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000" SESSION_ID = "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000"
APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" APPLICATION_ID = "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
REQUEST_ID = "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000" REQUEST_ID = "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000"
# pylint: disable=invalid-name # pylint: disable=invalid-name
hass = None
calls = [] calls = []
NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3" NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3"
@ -36,25 +23,16 @@ NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3"
STATIC_TIME = datetime.datetime.utcfromtimestamp(1476129102) STATIC_TIME = datetime.datetime.utcfromtimestamp(1476129102)
# pylint: disable=invalid-name @pytest.fixture
def setUpModule(): def alexa_client(loop, hass, test_client):
"""Initialize a Home Assistant server for testing this module.""" """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 @callback
def mock_service(call): def mock_service(call):
calls.append(call) calls.append(call)
hass.services.register("test", "alexa", mock_service) hass.services.async_register("test", "alexa", mock_service)
setup.setup_component(hass, alexa.DOMAIN, { assert loop.run_until_complete(async_setup_component(hass, alexa.DOMAIN, {
# Key is here to verify we allow other keys in config too # Key is here to verify we allow other keys in config too
"homeassistant": {}, "homeassistant": {},
"alexa": { "alexa": {
@ -122,357 +100,367 @@ def setUpModule():
} }
} }
} }
}) }))
return loop.run_until_complete(test_client(hass.http.app))
hass.start()
# pylint: disable=invalid-name def _intent_req(client, data={}):
def tearDownModule(): return client.post(alexa.INTENTS_API_ENDPOINT, data=json.dumps(data),
"""Stop the Home Assistant server.""" headers={'content-type': 'application/json'})
hass.stop()
def _intent_req(data={}): def _flash_briefing_req(client, briefing_id):
return requests.post(INTENTS_API_URL, data=json.dumps(data), timeout=5, return client.get(
headers=HA_HEADERS) "/api/alexa/flash_briefings/{}".format(briefing_id))
def _flash_briefing_req(briefing_id=None): @asyncio.coroutine
url_format = "{}/api/alexa/flash_briefings/{}" def test_intent_launch_request(alexa_client):
FLASH_BRIEFING_API_URL = url_format.format(BASE_API_URL, """Test the launch of a request."""
briefing_id) data = {
return requests.get(FLASH_BRIEFING_API_URL, timeout=5, "version": "1.0",
headers=HA_HEADERS) "session": {
"new": True,
"sessionId": SESSION_ID,
class TestAlexa(unittest.TestCase): "application": {
"""Test Alexa.""" "applicationId": APPLICATION_ID
def tearDown(self):
"""Stop everything that was started."""
hass.block_till_done()
def test_intent_launch_request(self):
"""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": { "attributes": {},
"type": "LaunchRequest", "user": {
"requestId": REQUEST_ID, "userId": "amzn1.account.AM3B00000000000000000000000"
"timestamp": "2015-05-13T12:34:56Z"
} }
},
"request": {
"type": "LaunchRequest",
"requestId": REQUEST_ID,
"timestamp": "2015-05-13T12:34:56Z"
} }
req = _intent_req(data) }
self.assertEqual(200, req.status_code) req = yield from _intent_req(alexa_client, data)
resp = req.json() assert req.status == 200
self.assertIn("outputSpeech", resp["response"]) resp = yield from req.json()
assert "outputSpeech" in resp["response"]
def test_intent_request_with_slots(self):
"""Test a request with slots.""" @asyncio.coroutine
data = { def test_intent_request_with_slots(alexa_client):
"version": "1.0", """Test a request with slots."""
"session": { data = {
"new": False, "version": "1.0",
"sessionId": SESSION_ID, "session": {
"application": { "new": False,
"applicationId": APPLICATION_ID "sessionId": SESSION_ID,
}, "application": {
"attributes": { "applicationId": APPLICATION_ID
"supportedHoroscopePeriods": { },
"daily": True, "attributes": {
"weekly": False, "supportedHoroscopePeriods": {
"monthly": False "daily": True,
} "weekly": False,
}, "monthly": False
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
} }
}, },
"request": { "user": {
"type": "IntentRequest", "userId": "amzn1.account.AM3B00000000000000000000000"
"requestId": REQUEST_ID, }
"timestamp": "2015-05-13T12:34:56Z", },
"intent": { "request": {
"name": "GetZodiacHoroscopeIntent", "type": "IntentRequest",
"slots": { "requestId": REQUEST_ID,
"ZodiacSign": { "timestamp": "2015-05-13T12:34:56Z",
"name": "ZodiacSign", "intent": {
"value": "virgo" "name": "GetZodiacHoroscopeIntent",
} "slots": {
"ZodiacSign": {
"name": "ZodiacSign",
"value": "virgo"
} }
} }
} }
} }
req = _intent_req(data) }
self.assertEqual(200, req.status_code) req = yield from _intent_req(alexa_client, data)
text = req.json().get("response", {}).get("outputSpeech", assert req.status == 200
{}).get("text") data = yield from req.json()
self.assertEqual("You told us your sign is virgo.", text) text = data.get("response", {}).get("outputSpeech",
{}).get("text")
assert text == "You told us your sign is virgo."
def test_intent_request_with_slots_but_no_value(self):
"""Test a request with slots but no value.""" @asyncio.coroutine
data = { def test_intent_request_with_slots_but_no_value(alexa_client):
"version": "1.0", """Test a request with slots but no value."""
"session": { data = {
"new": False, "version": "1.0",
"sessionId": SESSION_ID, "session": {
"application": { "new": False,
"applicationId": APPLICATION_ID "sessionId": SESSION_ID,
}, "application": {
"attributes": { "applicationId": APPLICATION_ID
"supportedHoroscopePeriods": { },
"daily": True, "attributes": {
"weekly": False, "supportedHoroscopePeriods": {
"monthly": False "daily": True,
} "weekly": False,
}, "monthly": False
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
} }
}, },
"request": { "user": {
"type": "IntentRequest", "userId": "amzn1.account.AM3B00000000000000000000000"
"requestId": REQUEST_ID, }
"timestamp": "2015-05-13T12:34:56Z", },
"intent": { "request": {
"name": "GetZodiacHoroscopeIntent", "type": "IntentRequest",
"slots": { "requestId": REQUEST_ID,
"ZodiacSign": { "timestamp": "2015-05-13T12:34:56Z",
"name": "ZodiacSign", "intent": {
} "name": "GetZodiacHoroscopeIntent",
"slots": {
"ZodiacSign": {
"name": "ZodiacSign",
} }
} }
} }
} }
req = _intent_req(data) }
self.assertEqual(200, req.status_code) req = yield from _intent_req(alexa_client, data)
text = req.json().get("response", {}).get("outputSpeech", assert req.status == 200
{}).get("text") data = yield from req.json()
self.assertEqual("You told us your sign is .", text) text = data.get("response", {}).get("outputSpeech",
{}).get("text")
assert text == "You told us your sign is ."
def test_intent_request_without_slots(self):
"""Test a request without slots.""" @asyncio.coroutine
data = { def test_intent_request_without_slots(hass, alexa_client):
"version": "1.0", """Test a request without slots."""
"session": { data = {
"new": False, "version": "1.0",
"sessionId": SESSION_ID, "session": {
"application": { "new": False,
"applicationId": APPLICATION_ID "sessionId": SESSION_ID,
}, "application": {
"attributes": { "applicationId": APPLICATION_ID
"supportedHoroscopePeriods": { },
"daily": True, "attributes": {
"weekly": False, "supportedHoroscopePeriods": {
"monthly": False "daily": True,
} "weekly": False,
}, "monthly": False
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
} }
}, },
"request": { "user": {
"type": "IntentRequest", "userId": "amzn1.account.AM3B00000000000000000000000"
"requestId": REQUEST_ID, }
"timestamp": "2015-05-13T12:34:56Z", },
"intent": { "request": {
"name": "WhereAreWeIntent", "type": "IntentRequest",
} "requestId": REQUEST_ID,
"timestamp": "2015-05-13T12:34:56Z",
"intent": {
"name": "WhereAreWeIntent",
} }
} }
req = _intent_req(data) }
self.assertEqual(200, req.status_code) req = yield from _intent_req(alexa_client, data)
text = req.json().get("response", {}).get("outputSpeech", assert req.status == 200
{}).get("text") json = yield from req.json()
text = json.get("response", {}).get("outputSpeech",
{}).get("text")
self.assertEqual("Anne Therese is at unknown and Paulus is at unknown", assert text == "Anne Therese is at unknown and Paulus is at unknown"
text)
hass.states.set("device_tracker.paulus", "home") hass.states.async_set("device_tracker.paulus", "home")
hass.states.set("device_tracker.anne_therese", "home") hass.states.async_set("device_tracker.anne_therese", "home")
req = _intent_req(data) req = yield from _intent_req(alexa_client, data)
self.assertEqual(200, req.status_code) assert req.status == 200
text = req.json().get("response", {}).get("outputSpeech", json = yield from req.json()
{}).get("text") text = json.get("response", {}).get("outputSpeech",
self.assertEqual("You are both home, you silly", text) {}).get("text")
assert text == "You are both home, you silly"
def test_intent_request_calling_service(self):
"""Test a request for calling a service.""" @asyncio.coroutine
data = { def test_intent_request_calling_service(alexa_client):
"version": "1.0", """Test a request for calling a service."""
"session": { data = {
"new": False, "version": "1.0",
"sessionId": SESSION_ID, "session": {
"application": { "new": False,
"applicationId": APPLICATION_ID "sessionId": SESSION_ID,
}, "application": {
"attributes": {}, "applicationId": APPLICATION_ID
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
}
}, },
"request": { "attributes": {},
"type": "IntentRequest", "user": {
"requestId": REQUEST_ID, "userId": "amzn1.account.AM3B00000000000000000000000"
"timestamp": "2015-05-13T12:34:56Z", }
"intent": { },
"name": "CallServiceIntent", "request": {
"slots": { "type": "IntentRequest",
"ZodiacSign": { "requestId": REQUEST_ID,
"name": "ZodiacSign", "timestamp": "2015-05-13T12:34:56Z",
"value": "virgo", "intent": {
} "name": "CallServiceIntent",
"slots": {
"ZodiacSign": {
"name": "ZodiacSign",
"value": "virgo",
} }
} }
} }
} }
call_count = len(calls) }
req = _intent_req(data) call_count = len(calls)
self.assertEqual(200, req.status_code) req = yield from _intent_req(alexa_client, data)
self.assertEqual(call_count + 1, len(calls)) assert req.status == 200
call = calls[-1] assert call_count + 1 == len(calls)
self.assertEqual("test", call.domain) call = calls[-1]
self.assertEqual("alexa", call.service) assert call.domain == "test"
self.assertEqual(["switch.test"], call.data.get("entity_id")) assert call.service == "alexa"
self.assertEqual("virgo", call.data.get("hello")) assert call.data.get("entity_id") == ["switch.test"]
assert call.data.get("hello") == "virgo"
def test_intent_session_ended_request(self):
"""Test the request for ending the session.""" @asyncio.coroutine
data = { def test_intent_session_ended_request(alexa_client):
"version": "1.0", """Test the request for ending the session."""
"session": { data = {
"new": False, "version": "1.0",
"sessionId": SESSION_ID, "session": {
"application": { "new": False,
"applicationId": APPLICATION_ID "sessionId": SESSION_ID,
}, "application": {
"attributes": { "applicationId": APPLICATION_ID
"supportedHoroscopePeriods": { },
"daily": True, "attributes": {
"weekly": False, "supportedHoroscopePeriods": {
"monthly": False "daily": True,
} "weekly": False,
}, "monthly": False
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
} }
}, },
"request": { "user": {
"type": "SessionEndedRequest", "userId": "amzn1.account.AM3B00000000000000000000000"
"requestId": REQUEST_ID,
"timestamp": "2015-05-13T12:34:56Z",
"reason": "USER_INITIATED"
} }
},
"request": {
"type": "SessionEndedRequest",
"requestId": REQUEST_ID,
"timestamp": "2015-05-13T12:34:56Z",
"reason": "USER_INITIATED"
} }
}
req = _intent_req(data) req = yield from _intent_req(alexa_client, data)
self.assertEqual(200, req.status_code) assert req.status == 200
self.assertEqual("", req.text) text = yield from req.text()
assert text == ''
def test_intent_from_built_in_intent_library(self):
"""Test intents from the Built-in Intent Library.""" @asyncio.coroutine
data = { def test_intent_from_built_in_intent_library(alexa_client):
'request': { """Test intents from the Built-in Intent Library."""
'intent': { data = {
'name': 'AMAZON.PlaybackAction<object@MusicCreativeWork>', 'request': {
'slots': { 'intent': {
'object.byArtist.name': { 'name': 'AMAZON.PlaybackAction<object@MusicCreativeWork>',
'name': 'object.byArtist.name', 'slots': {
'value': 'the shins' 'object.byArtist.name': {
}, 'name': 'object.byArtist.name',
'object.composer.name': { 'value': 'the shins'
'name': 'object.composer.name' },
}, 'object.composer.name': {
'object.contentSource': { 'name': 'object.composer.name'
'name': 'object.contentSource' },
}, 'object.contentSource': {
'object.era': { 'name': 'object.contentSource'
'name': 'object.era' },
}, 'object.era': {
'object.genre': { 'name': 'object.era'
'name': 'object.genre' },
}, 'object.genre': {
'object.name': { 'name': 'object.genre'
'name': 'object.name' },
}, 'object.name': {
'object.owner.name': { 'name': 'object.name'
'name': 'object.owner.name' },
}, 'object.owner.name': {
'object.select': { 'name': 'object.owner.name'
'name': 'object.select' },
}, 'object.select': {
'object.sort': { 'name': 'object.select'
'name': 'object.sort' },
}, 'object.sort': {
'object.type': { 'name': 'object.sort'
'name': 'object.type', },
'value': 'music' '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
} }
},
'timestamp': '2016-12-14T23:23:37Z',
'type': 'IntentRequest',
'requestId': REQUEST_ID,
},
'session': {
'sessionId': SESSION_ID,
'application': {
'applicationId': APPLICATION_ID
} }
} }
req = _intent_req(data) }
self.assertEqual(200, req.status_code) req = yield from _intent_req(alexa_client, data)
text = req.json().get("response", {}).get("outputSpeech", assert req.status == 200
{}).get("text") data = yield from req.json()
self.assertEqual("Playing the shins.", text) text = data.get("response", {}).get("outputSpeech",
{}).get("text")
assert text == "Playing the shins."
def test_flash_briefing_invalid_id(self):
"""Test an invalid Flash Briefing ID."""
req = _flash_briefing_req()
self.assertEqual(404, req.status_code)
self.assertEqual("", req.text)
def test_flash_briefing_date_from_str(self): @asyncio.coroutine
"""Test the response has a valid date parsed from string.""" def test_flash_briefing_invalid_id(alexa_client):
req = _flash_briefing_req("weather") """Test an invalid Flash Briefing ID."""
self.assertEqual(200, req.status_code) req = yield from _flash_briefing_req(alexa_client, 10000)
self.assertEqual(req.json()[0].get(alexa.ATTR_UPDATE_DATE), assert req.status == 404
"2016-10-09T19:51:42.0Z") text = yield from req.text()
assert text == ''
def test_flash_briefing_date_from_datetime(self):
"""Test the response has a valid date from a datetime object."""
req = _flash_briefing_req("weather")
self.assertEqual(200, req.status_code)
self.assertEqual(req.json()[1].get(alexa.ATTR_UPDATE_DATE),
'2016-10-10T19:51:42.0Z')
def test_flash_briefing_valid(self): @asyncio.coroutine
"""Test the response is valid.""" def test_flash_briefing_date_from_str(alexa_client):
data = [{ """Test the response has a valid date parsed from string."""
"titleText": "NPR", req = yield from _flash_briefing_req(alexa_client, "weather")
"redirectionURL": "https://npr.org", assert req.status == 200
"streamUrl": NPR_NEWS_MP3_URL, data = yield from req.json()
"mainText": "", assert data[0].get(alexa.ATTR_UPDATE_DATE) == "2016-10-09T19:51:42.0Z"
"uid": "uuid",
"updateDate": '2016-10-10T19:51:42.0Z'
}]
req = _flash_briefing_req("news_audio")
self.assertEqual(200, req.status_code) @asyncio.coroutine
response = req.json() def test_flash_briefing_date_from_datetime(alexa_client):
self.assertEqual(response, data) """Test the response has a valid date from a datetime object."""
req = yield from _flash_briefing_req(alexa_client, "weather")
assert req.status == 200
data = yield from req.json()
assert data[1].get(alexa.ATTR_UPDATE_DATE) == '2016-10-10T19:51:42.0Z'
@asyncio.coroutine
def test_flash_briefing_valid(alexa_client):
"""Test the response is valid."""
data = [{
"titleText": "NPR",
"redirectionURL": "https://npr.org",
"streamUrl": NPR_NEWS_MP3_URL,
"mainText": "",
"uid": "uuid",
"updateDate": '2016-10-10T19:51:42.0Z'
}]
req = yield from _flash_briefing_req(alexa_client, "news_audio")
assert req.status == 200
json = yield from req.json()
assert json == data