Add SmartThings Scene platform (#21405)

* Add SmartThings Scene platform

* Fixed failing tests after rebase

* Update cover tests.
This commit is contained in:
Andrew Sayre 2019-02-26 15:12:24 -06:00 committed by Paulus Schoutsen
parent 344e839bec
commit 3b9db88065
14 changed files with 266 additions and 72 deletions

View file

@ -19,7 +19,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from .config_flow import SmartThingsFlowHandler # noqa
from .const import (
CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_OAUTH_CLIENT_ID,
CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, CONF_OAUTH_CLIENT_ID,
CONF_OAUTH_CLIENT_SECRET, CONF_REFRESH_TOKEN, DATA_BROKERS, DATA_MANAGER,
DOMAIN, EVENT_BUTTON, SIGNAL_SMARTTHINGS_UPDATE, SUPPORTED_PLATFORMS,
TOKEN_REFRESH_INTERVAL)
@ -93,6 +93,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
installed_app = await validate_installed_app(
api, entry.data[CONF_INSTALLED_APP_ID])
# Get scenes
scenes = await async_get_entry_scenes(entry, api)
# Get SmartApp token to sync subscriptions
token = await api.generate_tokens(
entry.data[CONF_OAUTH_CLIENT_ID],
@ -123,7 +126,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
installed_app.installed_app_id, devices)
# Setup device broker
broker = DeviceBroker(hass, entry, token, smart_app, devices)
broker = DeviceBroker(hass, entry, token, smart_app, devices, scenes)
broker.connect()
hass.data[DOMAIN][DATA_BROKERS][entry.entry_id] = broker
@ -156,6 +159,20 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
return True
async def async_get_entry_scenes(entry: ConfigEntry, api):
"""Get the scenes within an integration."""
try:
return await api.scenes(location_id=entry.data[CONF_LOCATION_ID])
except ClientResponseError as ex:
if ex.status == 403:
_LOGGER.exception("Unable to load scenes for config entry '%s' "
"because the access token does not have the "
"required access", entry.title)
else:
raise
return []
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Unload a config entry."""
broker = hass.data[DOMAIN][DATA_BROKERS].pop(entry.entry_id, None)
@ -171,7 +188,7 @@ class DeviceBroker:
"""Manages an individual SmartThings config entry."""
def __init__(self, hass: HomeAssistantType, entry: ConfigEntry,
token, smart_app, devices: Iterable):
token, smart_app, devices: Iterable, scenes: Iterable):
"""Create a new instance of the DeviceBroker."""
self._hass = hass
self._entry = entry
@ -182,6 +199,7 @@ class DeviceBroker:
self._regenerate_token_remove = None
self._assignments = self._assign_capabilities(devices)
self.devices = {device.device_id: device for device in devices}
self.scenes = {scene.scene_id: scene for scene in scenes}
def _assign_capabilities(self, devices: Iterable):
"""Assign platforms to capabilities."""
@ -192,6 +210,8 @@ class DeviceBroker:
for platform_name in SUPPORTED_PLATFORMS:
platform = importlib.import_module(
'.' + platform_name, self.__module__)
if not hasattr(platform, 'get_capabilities'):
continue
assigned = platform.get_capabilities(capabilities)
if not assigned:
continue

View file

@ -34,7 +34,8 @@ SUPPORTED_PLATFORMS = [
'cover',
'switch',
'binary_sensor',
'sensor'
'sensor',
'scene'
]
TOKEN_REFRESH_INTERVAL = timedelta(days=14)
VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" \

View file

@ -0,0 +1,50 @@
"""Support for scenes through the SmartThings cloud API."""
from homeassistant.components.scene import Scene
from .const import DATA_BROKERS, DOMAIN
DEPENDENCIES = ['smartthings']
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Platform uses config entry setup."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Add switches for a config entry."""
broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id]
async_add_entities(
[SmartThingsScene(scene) for scene in broker.scenes.values()])
class SmartThingsScene(Scene):
"""Define a SmartThings scene."""
def __init__(self, scene):
"""Init the scene class."""
self._scene = scene
async def async_activate(self):
"""Activate scene."""
await self._scene.execute()
@property
def device_state_attributes(self):
"""Get attributes about the state."""
return {
'icon': self._scene.icon,
'color': self._scene.color,
'location_id': self._scene.location_id
}
@property
def name(self) -> str:
"""Return the name of the device."""
return self._scene.name
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._scene.scene_id

View file

@ -5,7 +5,7 @@ from uuid import uuid4
from pysmartthings import (
CLASSIFICATION_AUTOMATION, AppEntity, AppOAuthClient, AppSettings,
DeviceEntity, InstalledApp, Location, Subscription)
DeviceEntity, InstalledApp, Location, SceneEntity, Subscription)
from pysmartthings.api import Api
import pytest
@ -24,13 +24,15 @@ from homeassistant.setup import async_setup_component
from tests.common import mock_coro
async def setup_platform(hass, platform: str, *devices):
async def setup_platform(hass, platform: str, *,
devices=None, scenes=None):
"""Set up the SmartThings platform and prerequisites."""
hass.config.components.add(DOMAIN)
config_entry = ConfigEntry(2, DOMAIN, "Test",
{CONF_INSTALLED_APP_ID: str(uuid4())},
SOURCE_USER, CONN_CLASS_CLOUD_PUSH)
broker = DeviceBroker(hass, config_entry, Mock(), Mock(), devices)
broker = DeviceBroker(hass, config_entry, Mock(), Mock(),
devices or [], scenes or [])
hass.data[DOMAIN] = {
DATA_BROKERS: {
@ -295,6 +297,31 @@ def device_factory_fixture():
return _factory
@pytest.fixture(name="scene_factory")
def scene_factory_fixture(location):
"""Fixture for creating mock devices."""
api = Mock(spec=Api)
api.execute_scene.side_effect = \
lambda *args, **kwargs: mock_coro(return_value={})
def _factory(name):
scene_data = {
'sceneId': str(uuid4()),
'sceneName': name,
'sceneIcon': '',
'sceneColor': '',
'locationId': location.location_id
}
return SceneEntity(api, scene_data)
return _factory
@pytest.fixture(name="scene")
def scene_fixture(scene_factory):
"""Fixture for an individual scene."""
return scene_factory('Test Scene')
@pytest.fixture(name="event_factory")
def event_factory_fixture():
"""Fixture for creating mock devices."""

View file

@ -40,7 +40,7 @@ async def test_entity_state(hass, device_factory):
"""Tests the state attributes properly match the light types."""
device = device_factory('Motion Sensor 1', [Capability.motion_sensor],
{Attribute.motion: 'inactive'})
await setup_platform(hass, BINARY_SENSOR_DOMAIN, device)
await setup_platform(hass, BINARY_SENSOR_DOMAIN, devices=[device])
state = hass.states.get('binary_sensor.motion_sensor_1_motion')
assert state.state == 'off'
assert state.attributes[ATTR_FRIENDLY_NAME] ==\
@ -55,7 +55,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Act
await setup_platform(hass, BINARY_SENSOR_DOMAIN, device)
await setup_platform(hass, BINARY_SENSOR_DOMAIN, devices=[device])
# Assert
entry = entity_registry.async_get('binary_sensor.motion_sensor_1_motion')
assert entry
@ -73,7 +73,7 @@ async def test_update_from_signal(hass, device_factory):
# Arrange
device = device_factory('Motion Sensor 1', [Capability.motion_sensor],
{Attribute.motion: 'inactive'})
await setup_platform(hass, BINARY_SENSOR_DOMAIN, device)
await setup_platform(hass, BINARY_SENSOR_DOMAIN, devices=[device])
device.status.apply_attribute_update(
'main', Capability.motion_sensor, Attribute.motion, 'active')
# Act
@ -91,7 +91,8 @@ async def test_unload_config_entry(hass, device_factory):
# Arrange
device = device_factory('Motion Sensor 1', [Capability.motion_sensor],
{Attribute.motion: 'inactive'})
config_entry = await setup_platform(hass, BINARY_SENSOR_DOMAIN, device)
config_entry = await setup_platform(hass, BINARY_SENSOR_DOMAIN,
devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, 'binary_sensor')

View file

@ -122,7 +122,7 @@ async def test_async_setup_platform():
async def test_legacy_thermostat_entity_state(hass, legacy_thermostat):
"""Tests the state attributes properly match the thermostat type."""
await setup_platform(hass, CLIMATE_DOMAIN, legacy_thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[legacy_thermostat])
state = hass.states.get('climate.legacy_thermostat')
assert state.state == STATE_AUTO
assert state.attributes[ATTR_SUPPORTED_FEATURES] == \
@ -141,7 +141,7 @@ async def test_legacy_thermostat_entity_state(hass, legacy_thermostat):
async def test_basic_thermostat_entity_state(hass, basic_thermostat):
"""Tests the state attributes properly match the thermostat type."""
await setup_platform(hass, CLIMATE_DOMAIN, basic_thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[basic_thermostat])
state = hass.states.get('climate.basic_thermostat')
assert state.state == STATE_OFF
assert state.attributes[ATTR_SUPPORTED_FEATURES] == \
@ -155,7 +155,7 @@ async def test_basic_thermostat_entity_state(hass, basic_thermostat):
async def test_thermostat_entity_state(hass, thermostat):
"""Tests the state attributes properly match the thermostat type."""
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
state = hass.states.get('climate.thermostat')
assert state.state == STATE_HEAT
assert state.attributes[ATTR_SUPPORTED_FEATURES] == \
@ -174,7 +174,7 @@ async def test_thermostat_entity_state(hass, thermostat):
async def test_buggy_thermostat_entity_state(hass, buggy_thermostat):
"""Tests the state attributes properly match the thermostat type."""
await setup_platform(hass, CLIMATE_DOMAIN, buggy_thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[buggy_thermostat])
state = hass.states.get('climate.buggy_thermostat')
assert state.state == STATE_UNKNOWN
assert state.attributes[ATTR_SUPPORTED_FEATURES] == \
@ -190,14 +190,14 @@ async def test_buggy_thermostat_invalid_mode(hass, buggy_thermostat):
buggy_thermostat.status.update_attribute_value(
Attribute.supported_thermostat_modes,
['heat', 'emergency heat', 'other'])
await setup_platform(hass, CLIMATE_DOMAIN, buggy_thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[buggy_thermostat])
state = hass.states.get('climate.buggy_thermostat')
assert state.attributes[ATTR_OPERATION_LIST] == {'heat'}
async def test_set_fan_mode(hass, thermostat):
"""Test the fan mode is set successfully."""
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, {
ATTR_ENTITY_ID: 'climate.thermostat',
@ -209,7 +209,7 @@ async def test_set_fan_mode(hass, thermostat):
async def test_set_operation_mode(hass, thermostat):
"""Test the operation mode is set successfully."""
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_SET_OPERATION_MODE, {
ATTR_ENTITY_ID: 'climate.thermostat',
@ -222,7 +222,7 @@ async def test_set_operation_mode(hass, thermostat):
async def test_set_temperature_heat_mode(hass, thermostat):
"""Test the temperature is set successfully when in heat mode."""
thermostat.status.thermostat_mode = 'heat'
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {
ATTR_ENTITY_ID: 'climate.thermostat',
@ -237,7 +237,7 @@ async def test_set_temperature_heat_mode(hass, thermostat):
async def test_set_temperature_cool_mode(hass, thermostat):
"""Test the temperature is set successfully when in cool mode."""
thermostat.status.thermostat_mode = 'cool'
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {
ATTR_ENTITY_ID: 'climate.thermostat',
@ -250,7 +250,7 @@ async def test_set_temperature_cool_mode(hass, thermostat):
async def test_set_temperature(hass, thermostat):
"""Test the temperature is set successfully."""
thermostat.status.thermostat_mode = 'auto'
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {
ATTR_ENTITY_ID: 'climate.thermostat',
@ -264,7 +264,7 @@ async def test_set_temperature(hass, thermostat):
async def test_set_temperature_with_mode(hass, thermostat):
"""Test the temperature and mode is set successfully."""
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
await hass.services.async_call(
CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {
ATTR_ENTITY_ID: 'climate.thermostat',
@ -280,7 +280,7 @@ async def test_set_temperature_with_mode(hass, thermostat):
async def test_entity_and_device_attributes(hass, thermostat):
"""Test the attributes of the entries are correct."""
await setup_platform(hass, CLIMATE_DOMAIN, thermostat)
await setup_platform(hass, CLIMATE_DOMAIN, devices=[thermostat])
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()

View file

@ -32,7 +32,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Act
await setup_platform(hass, COVER_DOMAIN, device)
await setup_platform(hass, COVER_DOMAIN, devices=[device])
# Assert
entry = entity_registry.async_get('cover.garage')
assert entry
@ -57,7 +57,7 @@ async def test_open(hass, device_factory):
device_factory('Shade', [Capability.window_shade],
{Attribute.window_shade: 'closed'})
}
await setup_platform(hass, COVER_DOMAIN, *devices)
await setup_platform(hass, COVER_DOMAIN, devices=devices)
entity_ids = [
'cover.door',
'cover.garage',
@ -86,7 +86,7 @@ async def test_close(hass, device_factory):
device_factory('Shade', [Capability.window_shade],
{Attribute.window_shade: 'open'})
}
await setup_platform(hass, COVER_DOMAIN, *devices)
await setup_platform(hass, COVER_DOMAIN, devices=devices)
entity_ids = [
'cover.door',
'cover.garage',
@ -113,7 +113,7 @@ async def test_set_cover_position(hass, device_factory):
Capability.switch_level],
{Attribute.window_shade: 'opening', Attribute.battery: 95,
Attribute.level: 10})
await setup_platform(hass, COVER_DOMAIN, device)
await setup_platform(hass, COVER_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
COVER_DOMAIN, SERVICE_SET_COVER_POSITION,
@ -136,7 +136,7 @@ async def test_set_cover_position_unsupported(hass, device_factory):
'Shade',
[Capability.window_shade],
{Attribute.window_shade: 'opening'})
await setup_platform(hass, COVER_DOMAIN, device)
await setup_platform(hass, COVER_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
COVER_DOMAIN, SERVICE_SET_COVER_POSITION,
@ -152,7 +152,7 @@ async def test_update_to_open_from_signal(hass, device_factory):
# Arrange
device = device_factory('Garage', [Capability.garage_door_control],
{Attribute.door: 'opening'})
await setup_platform(hass, COVER_DOMAIN, device)
await setup_platform(hass, COVER_DOMAIN, devices=[device])
device.status.update_attribute_value(Attribute.door, 'open')
assert hass.states.get('cover.garage').state == STATE_OPENING
# Act
@ -170,7 +170,7 @@ async def test_update_to_closed_from_signal(hass, device_factory):
# Arrange
device = device_factory('Garage', [Capability.garage_door_control],
{Attribute.door: 'closing'})
await setup_platform(hass, COVER_DOMAIN, device)
await setup_platform(hass, COVER_DOMAIN, devices=[device])
device.status.update_attribute_value(Attribute.door, 'closed')
assert hass.states.get('cover.garage').state == STATE_CLOSING
# Act
@ -188,7 +188,7 @@ async def test_unload_config_entry(hass, device_factory):
# Arrange
device = device_factory('Garage', [Capability.garage_door_control],
{Attribute.door: 'open'})
config_entry = await setup_platform(hass, COVER_DOMAIN, device)
config_entry = await setup_platform(hass, COVER_DOMAIN, devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, COVER_DOMAIN)

View file

@ -29,7 +29,7 @@ async def test_entity_state(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'on', Attribute.fan_speed: 2})
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
# Dimmer 1
state = hass.states.get('fan.fan_1')
@ -48,7 +48,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'on', Attribute.fan_speed: 2})
# Act
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Assert
@ -71,7 +71,7 @@ async def test_turn_off(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'on', Attribute.fan_speed: 2})
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
'fan', 'turn_off', {'entity_id': 'fan.fan_1'},
@ -89,7 +89,7 @@ async def test_turn_on(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'off', Attribute.fan_speed: 0})
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
'fan', 'turn_on', {ATTR_ENTITY_ID: "fan.fan_1"},
@ -107,7 +107,7 @@ async def test_turn_on_with_speed(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'off', Attribute.fan_speed: 0})
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
'fan', 'turn_on',
@ -128,7 +128,7 @@ async def test_set_speed(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'off', Attribute.fan_speed: 0})
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
'fan', 'set_speed',
@ -149,7 +149,7 @@ async def test_update_from_signal(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'off', Attribute.fan_speed: 0})
await setup_platform(hass, FAN_DOMAIN, device)
await setup_platform(hass, FAN_DOMAIN, devices=[device])
await device.switch_on(True)
# Act
async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE,
@ -168,7 +168,7 @@ async def test_unload_config_entry(hass, device_factory):
"Fan 1",
capabilities=[Capability.switch, Capability.fan_speed],
status={Attribute.switch: 'off', Attribute.fan_speed: 0})
config_entry = await setup_platform(hass, FAN_DOMAIN, device)
config_entry = await setup_platform(hass, FAN_DOMAIN, devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, 'fan')

View file

@ -77,6 +77,19 @@ async def test_recoverable_api_errors_raise_not_ready(
await smartthings.async_setup_entry(hass, config_entry)
async def test_scenes_api_errors_raise_not_ready(
hass, config_entry, app, installed_app, smartthings_mock):
"""Test if scenes are unauthorized we continue to load platforms."""
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
api.app.return_value = mock_coro(return_value=app)
api.installed_app.return_value = mock_coro(return_value=installed_app)
api.scenes.return_value = mock_coro(
exception=ClientResponseError(None, None, status=500))
with pytest.raises(ConfigEntryNotReady):
await smartthings.async_setup_entry(hass, config_entry)
async def test_connection_errors_raise_not_ready(
hass, config_entry, smartthings_mock):
"""Test config entry not ready raised for connection errors."""
@ -118,17 +131,45 @@ async def test_unauthorized_installed_app_raises_not_ready(
await smartthings.async_setup_entry(hass, config_entry)
async def test_config_entry_loads_platforms(
async def test_scenes_unauthorized_loads_platforms(
hass, config_entry, app, installed_app,
device, smartthings_mock, subscription_factory):
"""Test config entry loads properly and proxies to platforms."""
"""Test if scenes are unauthorized we continue to load platforms."""
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
api.app.return_value = mock_coro(return_value=app)
api.installed_app.return_value = mock_coro(return_value=installed_app)
api.devices.side_effect = \
lambda *args, **kwargs: mock_coro(return_value=[device])
api.scenes.return_value = mock_coro(
exception=ClientResponseError(None, None, status=403))
mock_token = Mock()
mock_token.access_token.return_value = str(uuid4())
mock_token.refresh_token.return_value = str(uuid4())
api.generate_tokens.return_value = mock_coro(return_value=mock_token)
subscriptions = [subscription_factory(capability)
for capability in device.capabilities]
api.subscriptions.return_value = mock_coro(return_value=subscriptions)
with patch.object(hass.config_entries, 'async_forward_entry_setup',
return_value=mock_coro()) as forward_mock:
assert await smartthings.async_setup_entry(hass, config_entry)
# Assert platforms loaded
await hass.async_block_till_done()
assert forward_mock.call_count == len(SUPPORTED_PLATFORMS)
async def test_config_entry_loads_platforms(
hass, config_entry, app, installed_app,
device, smartthings_mock, subscription_factory, scene):
"""Test config entry loads properly and proxies to platforms."""
setattr(hass.config_entries, '_entries', [config_entry])
api = smartthings_mock.return_value
api.app.return_value = mock_coro(return_value=app)
api.installed_app.return_value = mock_coro(return_value=installed_app)
api.devices.side_effect = \
lambda *args, **kwargs: mock_coro(return_value=[device])
api.scenes.return_value = mock_coro(return_value=[scene])
mock_token = Mock()
mock_token.access_token.return_value = str(uuid4())
mock_token.refresh_token.return_value = str(uuid4())
@ -151,7 +192,7 @@ async def test_unload_entry(hass, config_entry):
smart_app = Mock()
smart_app.connect_event.return_value = connect_disconnect
broker = smartthings.DeviceBroker(
hass, config_entry, Mock(), smart_app, [])
hass, config_entry, Mock(), smart_app, [], [])
broker.connect()
hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] = broker
@ -184,7 +225,7 @@ async def test_broker_regenerates_token(
'.async_track_time_interval',
new=async_track_time_interval):
broker = smartthings.DeviceBroker(
hass, config_entry, token, Mock(), [])
hass, config_entry, token, Mock(), [], [])
broker.connect()
assert stored_action
@ -214,7 +255,7 @@ async def test_event_handler_dispatches_updated_devices(
async_dispatcher_connect(hass, SIGNAL_SMARTTHINGS_UPDATE, signal)
broker = smartthings.DeviceBroker(
hass, config_entry, Mock(), Mock(), devices)
hass, config_entry, Mock(), Mock(), devices, [])
broker.connect()
# pylint:disable=protected-access
@ -238,7 +279,7 @@ async def test_event_handler_ignores_other_installed_app(
called = True
async_dispatcher_connect(hass, SIGNAL_SMARTTHINGS_UPDATE, signal)
broker = smartthings.DeviceBroker(
hass, config_entry, Mock(), Mock(), [device])
hass, config_entry, Mock(), Mock(), [device], [])
broker.connect()
# pylint:disable=protected-access
@ -271,7 +312,7 @@ async def test_event_handler_fires_button_events(
}
hass.bus.async_listen(EVENT_BUTTON, handler)
broker = smartthings.DeviceBroker(
hass, config_entry, Mock(), Mock(), [device])
hass, config_entry, Mock(), Mock(), [device], [])
broker.connect()
# pylint:disable=protected-access

View file

@ -52,7 +52,7 @@ async def test_async_setup_platform():
async def test_entity_state(hass, light_devices):
"""Tests the state attributes properly match the light types."""
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Dimmer 1
state = hass.states.get('light.dimmer_1')
@ -86,7 +86,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Act
await setup_platform(hass, LIGHT_DOMAIN, device)
await setup_platform(hass, LIGHT_DOMAIN, devices=[device])
# Assert
entry = entity_registry.async_get("light.light_1")
assert entry
@ -103,7 +103,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
async def test_turn_off(hass, light_devices):
"""Test the light turns of successfully."""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_off', {'entity_id': 'light.color_dimmer_2'},
@ -117,7 +117,7 @@ async def test_turn_off(hass, light_devices):
async def test_turn_off_with_transition(hass, light_devices):
"""Test the light turns of successfully with transition."""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_off',
@ -132,7 +132,7 @@ async def test_turn_off_with_transition(hass, light_devices):
async def test_turn_on(hass, light_devices):
"""Test the light turns of successfully."""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_on', {ATTR_ENTITY_ID: "light.color_dimmer_1"},
@ -146,7 +146,7 @@ async def test_turn_on(hass, light_devices):
async def test_turn_on_with_brightness(hass, light_devices):
"""Test the light turns on to the specified brightness."""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_on',
@ -170,7 +170,7 @@ async def test_turn_on_with_minimal_brightness(hass, light_devices):
set the level to zero, which turns off the lights in SmartThings.
"""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_on',
@ -188,7 +188,7 @@ async def test_turn_on_with_minimal_brightness(hass, light_devices):
async def test_turn_on_with_color(hass, light_devices):
"""Test the light turns on with color."""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_on',
@ -205,7 +205,7 @@ async def test_turn_on_with_color(hass, light_devices):
async def test_turn_on_with_color_temp(hass, light_devices):
"""Test the light turns on with color temp."""
# Arrange
await setup_platform(hass, LIGHT_DOMAIN, *light_devices)
await setup_platform(hass, LIGHT_DOMAIN, devices=light_devices)
# Act
await hass.services.async_call(
'light', 'turn_on',
@ -229,7 +229,7 @@ async def test_update_from_signal(hass, device_factory):
status={Attribute.switch: 'off', Attribute.level: 100,
Attribute.hue: 76.0, Attribute.saturation: 55.0,
Attribute.color_temperature: 4500})
await setup_platform(hass, LIGHT_DOMAIN, device)
await setup_platform(hass, LIGHT_DOMAIN, devices=[device])
await device.switch_on(True)
# Act
async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE,
@ -251,7 +251,7 @@ async def test_unload_config_entry(hass, device_factory):
status={Attribute.switch: 'off', Attribute.level: 100,
Attribute.hue: 76.0, Attribute.saturation: 55.0,
Attribute.color_temperature: 4500})
config_entry = await setup_platform(hass, LIGHT_DOMAIN, device)
config_entry = await setup_platform(hass, LIGHT_DOMAIN, devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, 'light')

View file

@ -29,7 +29,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Act
await setup_platform(hass, LOCK_DOMAIN, device)
await setup_platform(hass, LOCK_DOMAIN, devices=[device])
# Assert
entry = entity_registry.async_get('lock.lock_1')
assert entry
@ -55,7 +55,7 @@ async def test_lock(hass, device_factory):
'lockName': 'Front Door',
'usedCode': 'Code 2'
})
await setup_platform(hass, LOCK_DOMAIN, device)
await setup_platform(hass, LOCK_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
LOCK_DOMAIN, 'lock', {'entity_id': 'lock.lock_1'},
@ -77,7 +77,7 @@ async def test_unlock(hass, device_factory):
# Arrange
device = device_factory('Lock_1', [Capability.lock],
{Attribute.lock: 'locked'})
await setup_platform(hass, LOCK_DOMAIN, device)
await setup_platform(hass, LOCK_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
LOCK_DOMAIN, 'unlock', {'entity_id': 'lock.lock_1'},
@ -93,7 +93,7 @@ async def test_update_from_signal(hass, device_factory):
# Arrange
device = device_factory('Lock_1', [Capability.lock],
{Attribute.lock: 'unlocked'})
await setup_platform(hass, LOCK_DOMAIN, device)
await setup_platform(hass, LOCK_DOMAIN, devices=[device])
await device.lock(True)
# Act
async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE,
@ -110,7 +110,7 @@ async def test_unload_config_entry(hass, device_factory):
# Arrange
device = device_factory('Lock_1', [Capability.lock],
{Attribute.lock: 'locked'})
config_entry = await setup_platform(hass, LOCK_DOMAIN, device)
config_entry = await setup_platform(hass, LOCK_DOMAIN, devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, 'lock')

View file

@ -0,0 +1,54 @@
"""
Test for the SmartThings scene platform.
The only mocking required is of the underlying SmartThings API object so
real HTTP calls are not initiated during testing.
"""
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
from homeassistant.components.smartthings import scene as scene_platform
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON
from .conftest import setup_platform
async def test_async_setup_platform():
"""Test setup platform does nothing (it uses config entries)."""
await scene_platform.async_setup_platform(None, None, None)
async def test_entity_and_device_attributes(hass, scene):
"""Test the attributes of the entity are correct."""
# Arrange
entity_registry = await hass.helpers.entity_registry.async_get_registry()
# Act
await setup_platform(hass, SCENE_DOMAIN, scenes=[scene])
# Assert
entry = entity_registry.async_get('scene.test_scene')
assert entry
assert entry.unique_id == scene.scene_id
async def test_scene_activate(hass, scene):
"""Test the scene is activated."""
await setup_platform(hass, SCENE_DOMAIN, scenes=[scene])
await hass.services.async_call(
SCENE_DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: 'scene.test_scene'},
blocking=True)
state = hass.states.get('scene.test_scene')
assert state.attributes['icon'] == scene.icon
assert state.attributes['color'] == scene.color
assert state.attributes['location_id'] == scene.location_id
# pylint: disable=protected-access
assert scene._api.execute_scene.call_count == 1 # type: ignore
async def test_unload_config_entry(hass, scene):
"""Test the scene is removed when the config entry is unloaded."""
# Arrange
config_entry = await setup_platform(hass, SCENE_DOMAIN, scenes=[scene])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, SCENE_DOMAIN)
# Assert
assert not hass.states.get('scene.test_scene')

View file

@ -37,7 +37,7 @@ async def test_entity_state(hass, device_factory):
"""Tests the state attributes properly match the light types."""
device = device_factory('Sensor 1', [Capability.battery],
{Attribute.battery: 100})
await setup_platform(hass, SENSOR_DOMAIN, device)
await setup_platform(hass, SENSOR_DOMAIN, devices=[device])
state = hass.states.get('sensor.sensor_1_battery')
assert state.state == '100'
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == '%'
@ -53,7 +53,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Act
await setup_platform(hass, SENSOR_DOMAIN, device)
await setup_platform(hass, SENSOR_DOMAIN, devices=[device])
# Assert
entry = entity_registry.async_get('sensor.sensor_1_battery')
assert entry
@ -71,7 +71,7 @@ async def test_update_from_signal(hass, device_factory):
# Arrange
device = device_factory('Sensor 1', [Capability.battery],
{Attribute.battery: 100})
await setup_platform(hass, SENSOR_DOMAIN, device)
await setup_platform(hass, SENSOR_DOMAIN, devices=[device])
device.status.apply_attribute_update(
'main', Capability.battery, Attribute.battery, 75)
# Act
@ -89,7 +89,7 @@ async def test_unload_config_entry(hass, device_factory):
# Arrange
device = device_factory('Sensor 1', [Capability.battery],
{Attribute.battery: 100})
config_entry = await setup_platform(hass, SENSOR_DOMAIN, device)
config_entry = await setup_platform(hass, SENSOR_DOMAIN, devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, 'sensor')

View file

@ -29,7 +29,7 @@ async def test_entity_and_device_attributes(hass, device_factory):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
# Act
await setup_platform(hass, SWITCH_DOMAIN, device)
await setup_platform(hass, SWITCH_DOMAIN, devices=[device])
# Assert
entry = entity_registry.async_get('switch.switch_1')
assert entry
@ -48,7 +48,7 @@ async def test_turn_off(hass, device_factory):
# Arrange
device = device_factory('Switch_1', [Capability.switch],
{Attribute.switch: 'on'})
await setup_platform(hass, SWITCH_DOMAIN, device)
await setup_platform(hass, SWITCH_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
'switch', 'turn_off', {'entity_id': 'switch.switch_1'},
@ -69,7 +69,7 @@ async def test_turn_on(hass, device_factory):
{Attribute.switch: 'off',
Attribute.power: 355,
Attribute.energy: 11.422})
await setup_platform(hass, SWITCH_DOMAIN, device)
await setup_platform(hass, SWITCH_DOMAIN, devices=[device])
# Act
await hass.services.async_call(
'switch', 'turn_on', {'entity_id': 'switch.switch_1'},
@ -87,7 +87,7 @@ async def test_update_from_signal(hass, device_factory):
# Arrange
device = device_factory('Switch_1', [Capability.switch],
{Attribute.switch: 'off'})
await setup_platform(hass, SWITCH_DOMAIN, device)
await setup_platform(hass, SWITCH_DOMAIN, devices=[device])
await device.switch_on(True)
# Act
async_dispatcher_send(hass, SIGNAL_SMARTTHINGS_UPDATE,
@ -104,7 +104,7 @@ async def test_unload_config_entry(hass, device_factory):
# Arrange
device = device_factory('Switch 1', [Capability.switch],
{Attribute.switch: 'on'})
config_entry = await setup_platform(hass, SWITCH_DOMAIN, device)
config_entry = await setup_platform(hass, SWITCH_DOMAIN, devices=[device])
# Act
await hass.config_entries.async_forward_entry_unload(
config_entry, 'switch')