hass-core/tests/components/alexa/test_smart_home.py
Phil Frost 7d6ef4445e Report states (#11973)
* Refactor Alexa Smart Home API

Having an object per interface will make it easier to support
properties.

Ideally, properties are reported in context in all responses. However
current implementation reports them only in response to a ReportState
request. This seems to work sufficiently. As long as the device is
opened in the Alexa app, Amazon will poll the device state every few
seconds with a ReportState request.

* Report properties for some Alexa interfaces

Fixes (mostly) #11874.

Other interfaces will need properties implemented as well.

Implementing properties for just PowerController seems sufficient to
eliminate the "There was a problem." error for any device that supports
it, even if other interfaces are supported. Of course the additional
properties will be reported incorrectly in the Alexa app.

Includes a minor bugfix: `reportable` was previously placed incorrectly
in the responses, so Amazon was ignoring it.
2018-01-28 17:00:34 -08:00

1439 lines
45 KiB
Python

"""Test for smart home alexa support."""
import asyncio
import json
from uuid import uuid4
import pytest
from homeassistant.const import (
TEMP_FAHRENHEIT, CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, STATE_ON, STATE_OFF)
from homeassistant.setup import async_setup_component
from homeassistant.components import alexa, light
from homeassistant.components.alexa import smart_home
from homeassistant.helpers import entityfilter
from tests.common import async_mock_service
DEFAULT_CONFIG = smart_home.Config(should_expose=lambda entity_id: True)
def get_new_request(namespace, name, endpoint=None):
"""Generate a new API message."""
raw_msg = {
'directive': {
'header': {
'namespace': namespace,
'name': name,
'messageId': str(uuid4()),
'correlationToken': str(uuid4()),
'payloadVersion': '3',
},
'endpoint': {
'scope': {
'type': 'BearerToken',
'token': str(uuid4()),
},
'endpointId': endpoint,
},
'payload': {},
}
}
if not endpoint:
raw_msg['directive'].pop('endpoint')
return raw_msg
def test_create_api_message_defaults():
"""Create a API message response of a request with defaults."""
request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#xy')
request = request['directive']
msg = smart_home.api_message(request, payload={'test': 3})
assert 'event' in msg
msg = msg['event']
assert msg['header']['messageId'] is not None
assert msg['header']['messageId'] != request['header']['messageId']
assert msg['header']['correlationToken'] == \
request['header']['correlationToken']
assert msg['header']['name'] == 'Response'
assert msg['header']['namespace'] == 'Alexa'
assert msg['header']['payloadVersion'] == '3'
assert 'test' in msg['payload']
assert msg['payload']['test'] == 3
assert msg['endpoint'] == request['endpoint']
def test_create_api_message_special():
"""Create a API message response of a request with non defaults."""
request = get_new_request('Alexa.PowerController', 'TurnOn')
request = request['directive']
request['header'].pop('correlationToken')
msg = smart_home.api_message(request, 'testName', 'testNameSpace')
assert 'event' in msg
msg = msg['event']
assert msg['header']['messageId'] is not None
assert msg['header']['messageId'] != request['header']['messageId']
assert 'correlationToken' not in msg['header']
assert msg['header']['name'] == 'testName'
assert msg['header']['namespace'] == 'testNameSpace'
assert msg['header']['payloadVersion'] == '3'
assert msg['payload'] == {}
assert 'endpoint' not in msg
@asyncio.coroutine
def test_wrong_version(hass):
"""Test with wrong version."""
msg = get_new_request('Alexa.PowerController', 'TurnOn')
msg['directive']['header']['payloadVersion'] = '2'
with pytest.raises(AssertionError):
yield from smart_home.async_handle_message(hass, DEFAULT_CONFIG, msg)
@asyncio.coroutine
def test_discovery_request(hass):
"""Test alexa discovery request."""
request = get_new_request('Alexa.Discovery', 'Discover')
# setup test devices
hass.states.async_set(
'switch.test', 'on', {'friendly_name': "Test switch"})
hass.states.async_set(
'light.test_1', 'on', {'friendly_name': "Test light 1"})
hass.states.async_set(
'light.test_2', 'on', {
'friendly_name': "Test light 2", 'supported_features': 1
})
hass.states.async_set(
'light.test_3', 'on', {
'friendly_name': "Test light 3", 'supported_features': 19
})
hass.states.async_set(
'script.test', 'off', {'friendly_name': "Test script"})
hass.states.async_set(
'script.test_2', 'off', {'friendly_name': "Test script 2",
'can_cancel': True})
hass.states.async_set(
'input_boolean.test', 'off', {'friendly_name': "Test input boolean"})
hass.states.async_set(
'scene.test', 'off', {'friendly_name': "Test scene"})
hass.states.async_set(
'fan.test_1', 'off', {'friendly_name': "Test fan 1"})
hass.states.async_set(
'fan.test_2', 'off', {
'friendly_name': "Test fan 2", 'supported_features': 1,
'speed_list': ['low', 'medium', 'high']
})
hass.states.async_set(
'lock.test', 'off', {'friendly_name': "Test lock"})
hass.states.async_set(
'media_player.test', 'off', {
'friendly_name': "Test media player",
'supported_features': 20925,
'volume_level': 1
})
hass.states.async_set(
'alert.test', 'off', {'friendly_name': "Test alert"})
hass.states.async_set(
'automation.test', 'off', {'friendly_name': "Test automation"})
hass.states.async_set(
'group.test', 'off', {'friendly_name': "Test group"})
hass.states.async_set(
'cover.test', 'off', {
'friendly_name': "Test cover", 'supported_features': 255,
'position': 85
})
hass.states.async_set(
'sensor.test_temp', '59', {
'friendly_name': "Test Temp Sensor",
'unit_of_measurement': TEMP_FAHRENHEIT,
})
# This sensor measures a quantity not applicable to Alexa, and should not
# be discovered.
hass.states.async_set(
'sensor.test_sickness', '0.1', {
'friendly_name': "Test Space Sickness Sensor",
'unit_of_measurement': 'garn',
})
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
assert 'event' in msg
msg = msg['event']
assert msg['header']['name'] == 'Discover.Response'
assert msg['header']['namespace'] == 'Alexa.Discovery'
endpoint_ids = set(
appliance['endpointId']
for appliance in msg['payload']['endpoints'])
assert endpoint_ids == {
'switch#test',
'light#test_1',
'light#test_2',
'light#test_3',
'script#test',
'script#test_2',
'input_boolean#test',
'scene#test',
'fan#test_1',
'fan#test_2',
'lock#test',
'media_player#test',
'alert#test',
'automation#test',
'group#test',
'cover#test',
'sensor#test_temp',
}
for appliance in msg['payload']['endpoints']:
if appliance['endpointId'] == 'switch#test':
assert appliance['displayCategories'][0] == "SWITCH"
assert appliance['friendlyName'] == "Test switch"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
continue
if appliance['endpointId'] == 'light#test_1':
assert appliance['displayCategories'][0] == "LIGHT"
assert appliance['friendlyName'] == "Test light 1"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
continue
if appliance['endpointId'] == 'light#test_2':
assert appliance['displayCategories'][0] == "LIGHT"
assert appliance['friendlyName'] == "Test light 2"
assert len(appliance['capabilities']) == 2
caps = set()
for feature in appliance['capabilities']:
caps.add(feature['interface'])
assert 'Alexa.BrightnessController' in caps
assert 'Alexa.PowerController' in caps
continue
if appliance['endpointId'] == 'light#test_3':
assert appliance['displayCategories'][0] == "LIGHT"
assert appliance['friendlyName'] == "Test light 3"
assert len(appliance['capabilities']) == 4
caps = set()
for feature in appliance['capabilities']:
caps.add(feature['interface'])
assert 'Alexa.BrightnessController' in caps
assert 'Alexa.PowerController' in caps
assert 'Alexa.ColorController' in caps
assert 'Alexa.ColorTemperatureController' in caps
continue
if appliance['endpointId'] == 'script#test':
assert appliance['displayCategories'][0] == "ACTIVITY_TRIGGER"
assert appliance['friendlyName'] == "Test script"
assert len(appliance['capabilities']) == 1
capability = appliance['capabilities'][-1]
assert capability['interface'] == 'Alexa.SceneController'
assert not capability['supportsDeactivation']
continue
if appliance['endpointId'] == 'script#test_2':
assert len(appliance['capabilities']) == 1
capability = appliance['capabilities'][-1]
assert capability['supportsDeactivation']
continue
if appliance['endpointId'] == 'input_boolean#test':
assert appliance['displayCategories'][0] == "OTHER"
assert appliance['friendlyName'] == "Test input boolean"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
continue
if appliance['endpointId'] == 'scene#test':
assert appliance['displayCategories'][0] == "SCENE_TRIGGER"
assert appliance['friendlyName'] == "Test scene"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.SceneController'
capability = appliance['capabilities'][-1]
assert not capability['supportsDeactivation']
continue
if appliance['endpointId'] == 'fan#test_1':
assert appliance['displayCategories'][0] == "OTHER"
assert appliance['friendlyName'] == "Test fan 1"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
continue
if appliance['endpointId'] == 'fan#test_2':
assert appliance['displayCategories'][0] == "OTHER"
assert appliance['friendlyName'] == "Test fan 2"
assert len(appliance['capabilities']) == 2
caps = set()
for feature in appliance['capabilities']:
caps.add(feature['interface'])
assert 'Alexa.PercentageController' in caps
assert 'Alexa.PowerController' in caps
continue
if appliance['endpointId'] == 'lock#test':
assert appliance['displayCategories'][0] == "SMARTLOCK"
assert appliance['friendlyName'] == "Test lock"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.LockController'
continue
if appliance['endpointId'] == 'media_player#test':
assert appliance['displayCategories'][0] == "TV"
assert appliance['friendlyName'] == "Test media player"
assert len(appliance['capabilities']) == 3
caps = set()
for feature in appliance['capabilities']:
caps.add(feature['interface'])
assert 'Alexa.PowerController' in caps
assert 'Alexa.Speaker' in caps
assert 'Alexa.PlaybackController' in caps
continue
if appliance['endpointId'] == 'alert#test':
assert appliance['displayCategories'][0] == "OTHER"
assert appliance['friendlyName'] == "Test alert"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
continue
if appliance['endpointId'] == 'automation#test':
assert appliance['displayCategories'][0] == "OTHER"
assert appliance['friendlyName'] == "Test automation"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
continue
if appliance['endpointId'] == 'group#test':
assert appliance['displayCategories'][0] == "SCENE_TRIGGER"
assert appliance['friendlyName'] == "Test group"
assert len(appliance['capabilities']) == 1
capability = appliance['capabilities'][-1]
assert capability['interface'] == 'Alexa.SceneController'
assert capability['supportsDeactivation']
continue
if appliance['endpointId'] == 'cover#test':
assert appliance['displayCategories'][0] == "DOOR"
assert appliance['friendlyName'] == "Test cover"
assert len(appliance['capabilities']) == 2
caps = set()
for feature in appliance['capabilities']:
caps.add(feature['interface'])
assert 'Alexa.PercentageController' in caps
assert 'Alexa.PowerController' in caps
continue
if appliance['endpointId'] == 'sensor#test_temp':
assert appliance['displayCategories'][0] == 'TEMPERATURE_SENSOR'
assert appliance['friendlyName'] == 'Test Temp Sensor'
assert len(appliance['capabilities']) == 1
capability = appliance['capabilities'][0]
assert capability['interface'] == 'Alexa.TemperatureSensor'
properties = capability['properties']
assert properties['retrievable'] is True
assert {'name': 'temperature'} in properties['supported']
continue
raise AssertionError("Unknown appliance!")
@asyncio.coroutine
def test_exclude_filters(hass):
"""Test exclusion filters."""
request = get_new_request('Alexa.Discovery', 'Discover')
# setup test devices
hass.states.async_set(
'switch.test', 'on', {'friendly_name': "Test switch"})
hass.states.async_set(
'script.deny', 'off', {'friendly_name': "Blocked script"})
hass.states.async_set(
'cover.deny', 'off', {'friendly_name': "Blocked cover"})
config = smart_home.Config(should_expose=entityfilter.generate_filter(
include_domains=[],
include_entities=[],
exclude_domains=['script'],
exclude_entities=['cover.deny'],
))
msg = yield from smart_home.async_handle_message(hass, config, request)
yield from hass.async_block_till_done()
msg = msg['event']
assert len(msg['payload']['endpoints']) == 1
@asyncio.coroutine
def test_include_filters(hass):
"""Test inclusion filters."""
request = get_new_request('Alexa.Discovery', 'Discover')
# setup test devices
hass.states.async_set(
'switch.deny', 'on', {'friendly_name': "Blocked switch"})
hass.states.async_set(
'script.deny', 'off', {'friendly_name': "Blocked script"})
hass.states.async_set(
'automation.allow', 'off', {'friendly_name': "Allowed automation"})
hass.states.async_set(
'group.allow', 'off', {'friendly_name': "Allowed group"})
config = smart_home.Config(should_expose=entityfilter.generate_filter(
include_domains=['automation', 'group'],
include_entities=['script.deny'],
exclude_domains=[],
exclude_entities=[],
))
msg = yield from smart_home.async_handle_message(hass, config, request)
yield from hass.async_block_till_done()
msg = msg['event']
assert len(msg['payload']['endpoints']) == 3
@asyncio.coroutine
def test_api_entity_not_exists(hass):
"""Test api turn on process without entity."""
request = get_new_request('Alexa.PowerController', 'TurnOn', 'switch#test')
call_switch = async_mock_service(hass, 'switch', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_switch) == 0
assert msg['header']['name'] == 'ErrorResponse'
assert msg['header']['namespace'] == 'Alexa'
assert msg['payload']['type'] == 'NO_SUCH_ENDPOINT'
@asyncio.coroutine
def test_api_function_not_implemented(hass):
"""Test api call that is not implemented to us."""
request = get_new_request('Alexa.HAHAAH', 'Sweet')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
assert 'event' in msg
msg = msg['event']
assert msg['header']['name'] == 'ErrorResponse'
assert msg['header']['namespace'] == 'Alexa'
assert msg['payload']['type'] == 'INTERNAL_ERROR'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['alert', 'automation', 'cover',
'input_boolean', 'light',
'switch'])
def test_api_turn_on(hass, domain):
"""Test api turn on process."""
request = get_new_request(
'Alexa.PowerController', 'TurnOn', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call_domain = domain
if domain == 'cover':
call = async_mock_service(hass, call_domain, 'open_cover')
else:
call = async_mock_service(hass, call_domain, 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['alert', 'automation', 'cover', 'group',
'input_boolean', 'light', 'script',
'switch'])
def test_api_turn_off(hass, domain):
"""Test api turn on process."""
request = get_new_request(
'Alexa.PowerController', 'TurnOff', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'on', {
'friendly_name': "Test {}".format(domain)
})
call_domain = domain
if domain == 'group':
call_domain = 'homeassistant'
if domain == 'cover':
call = async_mock_service(hass, call_domain, 'close_cover')
else:
call = async_mock_service(hass, call_domain, 'turn_off')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_set_brightness(hass):
"""Test api set brightness process."""
request = get_new_request(
'Alexa.BrightnessController', 'SetBrightness', 'light#test')
# add payload
request['directive']['payload']['brightness'] = '50'
# setup test devices
hass.states.async_set(
'light.test', 'off', {'friendly_name': "Test light"})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['brightness_pct'] == 50
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize(
"result,adjust", [(25, '-5'), (35, '5'), (0, '-80')])
def test_api_adjust_brightness(hass, result, adjust):
"""Test api adjust brightness process."""
request = get_new_request(
'Alexa.BrightnessController', 'AdjustBrightness', 'light#test')
# add payload
request['directive']['payload']['brightnessDelta'] = adjust
# setup test devices
hass.states.async_set(
'light.test', 'off', {
'friendly_name': "Test light", 'brightness': '77'
})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['brightness_pct'] == result
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_set_color_rgb(hass):
"""Test api set color process."""
request = get_new_request(
'Alexa.ColorController', 'SetColor', 'light#test')
# add payload
request['directive']['payload']['color'] = {
'hue': '120',
'saturation': '0.612',
'brightness': '0.342',
}
# setup test devices
hass.states.async_set(
'light.test', 'off', {
'friendly_name': "Test light",
'supported_features': 16,
})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['rgb_color'] == (33, 87, 33)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_set_color_xy(hass):
"""Test api set color process."""
request = get_new_request(
'Alexa.ColorController', 'SetColor', 'light#test')
# add payload
request['directive']['payload']['color'] = {
'hue': '120',
'saturation': '0.612',
'brightness': '0.342',
}
# setup test devices
hass.states.async_set(
'light.test', 'off', {
'friendly_name': "Test light",
'supported_features': 64,
})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['xy_color'] == (0.23, 0.585)
assert call_light[0].data['brightness'] == 18
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_set_color_temperature(hass):
"""Test api set color temperature process."""
request = get_new_request(
'Alexa.ColorTemperatureController', 'SetColorTemperature',
'light#test')
# add payload
request['directive']['payload']['colorTemperatureInKelvin'] = '7500'
# setup test devices
hass.states.async_set(
'light.test', 'off', {'friendly_name': "Test light"})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['kelvin'] == 7500
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("result,initial", [(383, '333'), (500, '500')])
def test_api_decrease_color_temp(hass, result, initial):
"""Test api decrease color temp process."""
request = get_new_request(
'Alexa.ColorTemperatureController', 'DecreaseColorTemperature',
'light#test')
# setup test devices
hass.states.async_set(
'light.test', 'off', {
'friendly_name': "Test light", 'color_temp': initial,
'max_mireds': 500,
})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['color_temp'] == result
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("result,initial", [(283, '333'), (142, '142')])
def test_api_increase_color_temp(hass, result, initial):
"""Test api increase color temp process."""
request = get_new_request(
'Alexa.ColorTemperatureController', 'IncreaseColorTemperature',
'light#test')
# setup test devices
hass.states.async_set(
'light.test', 'off', {
'friendly_name': "Test light", 'color_temp': initial,
'min_mireds': 142,
})
call_light = async_mock_service(hass, 'light', 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_light) == 1
assert call_light[0].data['entity_id'] == 'light.test'
assert call_light[0].data['color_temp'] == result
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['scene', 'group', 'script'])
def test_api_activate(hass, domain):
"""Test api activate process."""
request = get_new_request(
'Alexa.SceneController', 'Activate', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
if domain == 'group':
call_domain = 'homeassistant'
else:
call_domain = domain
call = async_mock_service(hass, call_domain, 'turn_on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'ActivationStarted'
assert msg['payload']['cause']['type'] == 'VOICE_INTERACTION'
assert 'timestamp' in msg['payload']
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['group', 'script'])
def test_api_deactivate(hass, domain):
"""Test api deactivate process."""
request = get_new_request(
'Alexa.SceneController', 'Deactivate', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
if domain == 'group':
call_domain = 'homeassistant'
else:
call_domain = domain
call = async_mock_service(hass, call_domain, 'turn_off')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'DeactivationStarted'
assert msg['payload']['cause']['type'] == 'VOICE_INTERACTION'
assert 'timestamp' in msg['payload']
@asyncio.coroutine
def test_api_set_percentage_fan(hass):
"""Test api set percentage for fan process."""
request = get_new_request(
'Alexa.PercentageController', 'SetPercentage', 'fan#test_2')
# add payload
request['directive']['payload']['percentage'] = '50'
# setup test devices
hass.states.async_set(
'fan.test_2', 'off', {'friendly_name': "Test fan"})
call_fan = async_mock_service(hass, 'fan', 'set_speed')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_fan) == 1
assert call_fan[0].data['entity_id'] == 'fan.test_2'
assert call_fan[0].data['speed'] == 'medium'
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_set_percentage_cover(hass):
"""Test api set percentage for cover process."""
request = get_new_request(
'Alexa.PercentageController', 'SetPercentage', 'cover#test')
# add payload
request['directive']['payload']['percentage'] = '50'
# setup test devices
hass.states.async_set(
'cover.test', 'closed', {
'friendly_name': "Test cover"
})
call_cover = async_mock_service(hass, 'cover', 'set_cover_position')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_cover) == 1
assert call_cover[0].data['entity_id'] == 'cover.test'
assert call_cover[0].data['position'] == 50
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize(
"result,adjust", [('high', '-5'), ('off', '5'), ('low', '-80')])
def test_api_adjust_percentage_fan(hass, result, adjust):
"""Test api adjust percentage for fan process."""
request = get_new_request(
'Alexa.PercentageController', 'AdjustPercentage', 'fan#test_2')
# add payload
request['directive']['payload']['percentageDelta'] = adjust
# setup test devices
hass.states.async_set(
'fan.test_2', 'on', {
'friendly_name': "Test fan 2", 'speed': 'high'
})
call_fan = async_mock_service(hass, 'fan', 'set_speed')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_fan) == 1
assert call_fan[0].data['entity_id'] == 'fan.test_2'
assert call_fan[0].data['speed'] == result
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize(
"result,adjust", [(25, '-5'), (35, '5'), (0, '-80')])
def test_api_adjust_percentage_cover(hass, result, adjust):
"""Test api adjust percentage for cover process."""
request = get_new_request(
'Alexa.PercentageController', 'AdjustPercentage', 'cover#test')
# add payload
request['directive']['payload']['percentageDelta'] = adjust
# setup test devices
hass.states.async_set(
'cover.test', 'closed', {
'friendly_name': "Test cover",
'position': 30
})
call_cover = async_mock_service(hass, 'cover', 'set_cover_position')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_cover) == 1
assert call_cover[0].data['entity_id'] == 'cover.test'
assert call_cover[0].data['position'] == result
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['lock'])
def test_api_lock(hass, domain):
"""Test api lock process."""
request = get_new_request(
'Alexa.LockController', 'Lock', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'lock')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['media_player'])
def test_api_play(hass, domain):
"""Test api play process."""
request = get_new_request(
'Alexa.PlaybackController', 'Play', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'media_play')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['media_player'])
def test_api_pause(hass, domain):
"""Test api pause process."""
request = get_new_request(
'Alexa.PlaybackController', 'Pause', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'media_pause')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['media_player'])
def test_api_stop(hass, domain):
"""Test api stop process."""
request = get_new_request(
'Alexa.PlaybackController', 'Stop', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'media_stop')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['media_player'])
def test_api_next(hass, domain):
"""Test api next process."""
request = get_new_request(
'Alexa.PlaybackController', 'Next', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'media_next_track')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['media_player'])
def test_api_previous(hass, domain):
"""Test api previous process."""
request = get_new_request(
'Alexa.PlaybackController', 'Previous', '{}#test'.format(domain))
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'media_previous_track')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_set_volume(hass):
"""Test api set volume process."""
request = get_new_request(
'Alexa.Speaker', 'SetVolume', 'media_player#test')
# add payload
request['directive']['payload']['volume'] = 50
# setup test devices
hass.states.async_set(
'media_player.test', 'off', {
'friendly_name': "Test media player", 'volume_level': 0
})
call_media_player = async_mock_service(hass, 'media_player', 'volume_set')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_media_player) == 1
assert call_media_player[0].data['entity_id'] == 'media_player.test'
assert call_media_player[0].data['volume_level'] == 0.5
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize(
"result,adjust", [(0.7, '-5'), (0.8, '5'), (0, '-80')])
def test_api_adjust_volume(hass, result, adjust):
"""Test api adjust volume process."""
request = get_new_request(
'Alexa.Speaker', 'AdjustVolume', 'media_player#test')
# add payload
request['directive']['payload']['volume'] = adjust
# setup test devices
hass.states.async_set(
'media_player.test', 'off', {
'friendly_name': "Test media player", 'volume_level': 0.75
})
call_media_player = async_mock_service(hass, 'media_player', 'volume_set')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call_media_player) == 1
assert call_media_player[0].data['entity_id'] == 'media_player.test'
assert call_media_player[0].data['volume_level'] == result
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
@pytest.mark.parametrize("domain", ['media_player'])
def test_api_mute(hass, domain):
"""Test api mute process."""
request = get_new_request(
'Alexa.Speaker', 'SetMute', '{}#test'.format(domain))
request['directive']['payload']['mute'] = True
# setup test devices
hass.states.async_set(
'{}.test'.format(domain), 'off', {
'friendly_name': "Test {}".format(domain)
})
call = async_mock_service(hass, domain, 'volume_mute')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
assert len(call) == 1
assert call[0].data['entity_id'] == '{}.test'.format(domain)
assert msg['header']['name'] == 'Response'
@asyncio.coroutine
def test_api_report_temperature(hass):
"""Test API ReportState response for a temperature sensor."""
request = get_new_request('Alexa', 'ReportState', 'sensor#test')
# setup test devices
hass.states.async_set(
'sensor.test', '42', {
'friendly_name': 'test sensor',
CONF_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT,
})
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
header = msg['event']['header']
assert header['namespace'] == 'Alexa'
assert header['name'] == 'StateReport'
properties = msg['context']['properties']
assert len(properties) == 1
prop = properties[0]
assert prop['namespace'] == 'Alexa.TemperatureSensor'
assert prop['name'] == 'temperature'
assert prop['value'] == {'value': 42.0, 'scale': 'FAHRENHEIT'}
@asyncio.coroutine
def test_report_lock_state(hass):
"""Test LockController implements lockState property."""
hass.states.async_set(
'lock.locked', STATE_LOCKED, {})
hass.states.async_set(
'lock.unlocked', STATE_UNLOCKED, {})
hass.states.async_set(
'lock.unknown', STATE_UNKNOWN, {})
request = get_new_request('Alexa', 'ReportState', 'lock#locked')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
properties = msg['context']['properties']
assert len(properties) == 1
prop = properties[0]
assert prop['namespace'] == 'Alexa.LockController'
assert prop['name'] == 'lockState'
assert prop['value'] == 'LOCKED'
request = get_new_request('Alexa', 'ReportState', 'lock#unlocked')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
properties = msg['context']['properties']
prop = properties[0]
assert prop['value'] == 'UNLOCKED'
request = get_new_request('Alexa', 'ReportState', 'lock#unknown')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
properties = msg['context']['properties']
prop = properties[0]
assert prop['value'] == 'JAMMED'
@asyncio.coroutine
def test_report_power_state(hass):
"""Test PowerController implements powerState property."""
hass.states.async_set(
'switch.on', STATE_ON, {})
hass.states.async_set(
'switch.off', STATE_OFF, {})
request = get_new_request('Alexa', 'ReportState', 'switch#on')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
properties = msg['context']['properties']
assert len(properties) == 1
prop = properties[0]
assert prop['namespace'] == 'Alexa.PowerController'
assert prop['name'] == 'powerState'
assert prop['value'] == 'ON'
request = get_new_request('Alexa', 'ReportState', 'switch#off')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
@asyncio.coroutine
def test_report_brightness(hass):
"""Test BrightnessController implements brightness property."""
hass.states.async_set(
'light.test', STATE_ON, {
'brightness': 128,
'supported_features': light.SUPPORT_BRIGHTNESS,
}
)
request = get_new_request('Alexa', 'ReportState', 'light.test')
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
for prop in msg['context']['properties']:
if (
prop['namespace'] == 'Alexa.BrightnessController'
and prop['name'] == 'brightness'
):
assert prop['value'] == 50
break
else:
assert False, 'no brightness property present'
@asyncio.coroutine
def test_entity_config(hass):
"""Test that we can configure things via entity config."""
request = get_new_request('Alexa.Discovery', 'Discover')
hass.states.async_set(
'light.test_1', 'on', {'friendly_name': "Test light 1"})
config = smart_home.Config(
should_expose=lambda entity_id: True,
entity_config={
'light.test_1': {
'name': 'Config name',
'display_categories': 'SWITCH',
'description': 'Config description'
}
}
)
msg = yield from smart_home.async_handle_message(
hass, config, request)
assert 'event' in msg
msg = msg['event']
assert len(msg['payload']['endpoints']) == 1
appliance = msg['payload']['endpoints'][0]
assert appliance['endpointId'] == 'light#test_1'
assert appliance['displayCategories'][0] == "SWITCH"
assert appliance['friendlyName'] == "Config name"
assert appliance['description'] == "Config description"
assert len(appliance['capabilities']) == 1
assert appliance['capabilities'][-1]['interface'] == \
'Alexa.PowerController'
@asyncio.coroutine
def test_unsupported_domain(hass):
"""Discovery ignores entities of unknown domains."""
request = get_new_request('Alexa.Discovery', 'Discover')
hass.states.async_set(
'woz.boop', 'on', {'friendly_name': "Boop Woz"})
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
assert 'event' in msg
msg = msg['event']
assert len(msg['payload']['endpoints']) == 0
@asyncio.coroutine
def do_http_discovery(config, hass, test_client):
"""Submit a request to the Smart Home HTTP API."""
yield from async_setup_component(hass, alexa.DOMAIN, config)
http_client = yield from test_client(hass.http.app)
request = get_new_request('Alexa.Discovery', 'Discover')
response = yield from http_client.post(
smart_home.SMART_HOME_HTTP_ENDPOINT,
data=json.dumps(request),
headers={'content-type': 'application/json'})
return response
@asyncio.coroutine
def test_http_api(hass, test_client):
"""With `smart_home:` HTTP API is exposed."""
config = {
'alexa': {
'smart_home': None
}
}
response = yield from do_http_discovery(config, hass, test_client)
response_data = yield from response.json()
# Here we're testing just the HTTP view glue -- details of discovery are
# covered in other tests.
assert response_data['event']['header']['name'] == 'Discover.Response'
@asyncio.coroutine
def test_http_api_disabled(hass, test_client):
"""Without `smart_home:`, the HTTP API is disabled."""
config = {
'alexa': {}
}
response = yield from do_http_discovery(config, hass, test_client)
assert response.status == 404