mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Add config entry for Sonos + Cast (#14955)
* Add config entry for Sonos * Lint * Use add_job * Add Cast config entry * Lint * Rename DOMAIN import * Mock pychromecast in test
This commit is contained in:
parent
c8e0de19b6
commit
2c6e6c2a6f
@ -61,6 +61,9 @@ omit =
|
|||||||
homeassistant/components/coinbase.py
|
homeassistant/components/coinbase.py
|
||||||
homeassistant/components/sensor/coinbase.py
|
homeassistant/components/sensor/coinbase.py
|
||||||
|
|
||||||
|
homeassistant/components/cast/*
|
||||||
|
homeassistant/components/*/cast.py
|
||||||
|
|
||||||
homeassistant/components/comfoconnect.py
|
homeassistant/components/comfoconnect.py
|
||||||
homeassistant/components/*/comfoconnect.py
|
homeassistant/components/*/comfoconnect.py
|
||||||
|
|
||||||
@ -252,6 +255,9 @@ omit =
|
|||||||
homeassistant/components/smappee.py
|
homeassistant/components/smappee.py
|
||||||
homeassistant/components/*/smappee.py
|
homeassistant/components/*/smappee.py
|
||||||
|
|
||||||
|
homeassistant/components/sonos/__init__.py
|
||||||
|
homeassistant/components/*/sonos.py
|
||||||
|
|
||||||
homeassistant/components/tado.py
|
homeassistant/components/tado.py
|
||||||
homeassistant/components/*/tado.py
|
homeassistant/components/*/tado.py
|
||||||
|
|
||||||
@ -482,7 +488,6 @@ omit =
|
|||||||
homeassistant/components/media_player/aquostv.py
|
homeassistant/components/media_player/aquostv.py
|
||||||
homeassistant/components/media_player/bluesound.py
|
homeassistant/components/media_player/bluesound.py
|
||||||
homeassistant/components/media_player/braviatv.py
|
homeassistant/components/media_player/braviatv.py
|
||||||
homeassistant/components/media_player/cast.py
|
|
||||||
homeassistant/components/media_player/channels.py
|
homeassistant/components/media_player/channels.py
|
||||||
homeassistant/components/media_player/clementine.py
|
homeassistant/components/media_player/clementine.py
|
||||||
homeassistant/components/media_player/cmus.py
|
homeassistant/components/media_player/cmus.py
|
||||||
@ -518,7 +523,6 @@ omit =
|
|||||||
homeassistant/components/media_player/russound_rnet.py
|
homeassistant/components/media_player/russound_rnet.py
|
||||||
homeassistant/components/media_player/snapcast.py
|
homeassistant/components/media_player/snapcast.py
|
||||||
homeassistant/components/media_player/songpal.py
|
homeassistant/components/media_player/songpal.py
|
||||||
homeassistant/components/media_player/sonos.py
|
|
||||||
homeassistant/components/media_player/spotify.py
|
homeassistant/components/media_player/spotify.py
|
||||||
homeassistant/components/media_player/squeezebox.py
|
homeassistant/components/media_player/squeezebox.py
|
||||||
homeassistant/components/media_player/ue_smart_radio.py
|
homeassistant/components/media_player/ue_smart_radio.py
|
||||||
|
15
homeassistant/components/cast/.translations/en.json
Normal file
15
homeassistant/components/cast/.translations/en.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"no_devices_found": "No Google Cast devices found on the network.",
|
||||||
|
"single_instance_allowed": "Only a single configuration of Google Cast is necessary."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"description": "Do you want to setup Google Cast?",
|
||||||
|
"title": "Google Cast"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Google Cast"
|
||||||
|
}
|
||||||
|
}
|
30
homeassistant/components/cast/__init__.py
Normal file
30
homeassistant/components/cast/__init__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"""Component to embed Google Cast."""
|
||||||
|
from homeassistant.helpers import config_entry_flow
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN = 'cast'
|
||||||
|
REQUIREMENTS = ['pychromecast==2.1.0']
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Cast component."""
|
||||||
|
hass.data[DOMAIN] = config.get(DOMAIN, {})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Set up Cast from a config entry."""
|
||||||
|
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
|
||||||
|
entry, 'media_player'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_has_devices(hass):
|
||||||
|
"""Return if there are devices that can be discovered."""
|
||||||
|
from pychromecast.discovery import discover_chromecasts
|
||||||
|
|
||||||
|
return await hass.async_add_job(discover_chromecasts)
|
||||||
|
|
||||||
|
|
||||||
|
config_entry_flow.register_discovery_flow(
|
||||||
|
DOMAIN, 'Google Cast', _async_has_devices)
|
15
homeassistant/components/cast/strings.json
Normal file
15
homeassistant/components/cast/strings.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Google Cast",
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"title": "Google Cast",
|
||||||
|
"description": "Do you want to setup Google Cast?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Only a single configuration of Google Cast is necessary.",
|
||||||
|
"no_devices_found": "No Google Cast devices found on the network."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,9 @@ SERVICE_HOMEKIT = 'homekit'
|
|||||||
|
|
||||||
CONFIG_ENTRY_HANDLERS = {
|
CONFIG_ENTRY_HANDLERS = {
|
||||||
SERVICE_DECONZ: 'deconz',
|
SERVICE_DECONZ: 'deconz',
|
||||||
|
'google_cast': 'cast',
|
||||||
SERVICE_HUE: 'hue',
|
SERVICE_HUE: 'hue',
|
||||||
|
'sonos': 'sonos',
|
||||||
}
|
}
|
||||||
|
|
||||||
SERVICE_HANDLERS = {
|
SERVICE_HANDLERS = {
|
||||||
@ -64,11 +66,9 @@ SERVICE_HANDLERS = {
|
|||||||
SERVICE_SABNZBD: ('sabnzbd', None),
|
SERVICE_SABNZBD: ('sabnzbd', None),
|
||||||
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
||||||
SERVICE_KONNECTED: ('konnected', None),
|
SERVICE_KONNECTED: ('konnected', None),
|
||||||
'google_cast': ('media_player', 'cast'),
|
|
||||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
||||||
'plex_mediaserver': ('media_player', 'plex'),
|
'plex_mediaserver': ('media_player', 'plex'),
|
||||||
'roku': ('media_player', 'roku'),
|
'roku': ('media_player', 'roku'),
|
||||||
'sonos': ('media_player', 'sonos'),
|
|
||||||
'yamaha': ('media_player', 'yamaha'),
|
'yamaha': ('media_player', 'yamaha'),
|
||||||
'logitech_mediaserver': ('media_player', 'squeezebox'),
|
'logitech_mediaserver': ('media_player', 'squeezebox'),
|
||||||
'directv': ('media_player', 'directv'),
|
'directv': ('media_player', 'directv'),
|
||||||
|
@ -456,6 +456,16 @@ async def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Setup a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
class MediaPlayerDevice(Entity):
|
class MediaPlayerDevice(Entity):
|
||||||
"""ABC for media player devices."""
|
"""ABC for media player devices."""
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
|||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import (dispatcher_send,
|
from homeassistant.helpers.dispatcher import (dispatcher_send,
|
||||||
async_dispatcher_connect)
|
async_dispatcher_connect)
|
||||||
|
from homeassistant.components.cast import DOMAIN as CAST_DOMAIN
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
|
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE, SUPPORT_NEXT_TRACK,
|
||||||
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK,
|
||||||
@ -28,7 +29,7 @@ from homeassistant.const import (
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
REQUIREMENTS = ['pychromecast==2.1.0']
|
DEPENDENCIES = ('cast',)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -186,6 +187,26 @@ def _async_create_cast_device(hass: HomeAssistantType,
|
|||||||
|
|
||||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||||
async_add_devices, discovery_info=None):
|
async_add_devices, discovery_info=None):
|
||||||
|
"""Set up thet Cast platform.
|
||||||
|
|
||||||
|
Deprecated.
|
||||||
|
"""
|
||||||
|
_LOGGER.warning(
|
||||||
|
'Setting configuration for Cast via platform is deprecated. '
|
||||||
|
'Configure via Cast component instead.')
|
||||||
|
await _async_setup_platform(
|
||||||
|
hass, config, async_add_devices, discovery_info)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_devices):
|
||||||
|
"""Set up Cast from a config entry."""
|
||||||
|
await _async_setup_platform(
|
||||||
|
hass, hass.data[CAST_DOMAIN].get('media_player', {}),
|
||||||
|
async_add_devices, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||||
|
async_add_devices, discovery_info):
|
||||||
"""Set up the cast platform."""
|
"""Set up the cast platform."""
|
||||||
import pychromecast
|
import pychromecast
|
||||||
|
|
||||||
|
@ -20,13 +20,14 @@ from homeassistant.components.media_player import (
|
|||||||
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
||||||
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP,
|
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP,
|
||||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
||||||
|
from homeassistant.components.sonos import DOMAIN as SONOS_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED,
|
ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED,
|
||||||
STATE_PLAYING)
|
STATE_PLAYING)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
REQUIREMENTS = ['SoCo==0.14']
|
DEPENDENCIES = ('sonos',)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ SERVICE_CLEAR_TIMER = 'sonos_clear_sleep_timer'
|
|||||||
SERVICE_UPDATE_ALARM = 'sonos_update_alarm'
|
SERVICE_UPDATE_ALARM = 'sonos_update_alarm'
|
||||||
SERVICE_SET_OPTION = 'sonos_set_option'
|
SERVICE_SET_OPTION = 'sonos_set_option'
|
||||||
|
|
||||||
DATA_SONOS = 'sonos'
|
DATA_SONOS = 'sonos_devices'
|
||||||
|
|
||||||
SOURCE_LINEIN = 'Line-in'
|
SOURCE_LINEIN = 'Line-in'
|
||||||
SOURCE_TV = 'TV'
|
SOURCE_TV = 'TV'
|
||||||
@ -118,6 +119,26 @@ class SonosData:
|
|||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
|
"""Set up the Sonos platform.
|
||||||
|
|
||||||
|
Deprecated.
|
||||||
|
"""
|
||||||
|
_LOGGER.warning('Loading Sonos via platform config is deprecated.')
|
||||||
|
_setup_platform(hass, config, add_devices, discovery_info)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_devices):
|
||||||
|
"""Set up Sonos from a config entry."""
|
||||||
|
def add_devices(devices, update_before_add=False):
|
||||||
|
"""Sync version of async add devices."""
|
||||||
|
hass.add_job(async_add_devices, devices, update_before_add)
|
||||||
|
|
||||||
|
hass.add_job(_setup_platform, hass,
|
||||||
|
hass.data[SONOS_DOMAIN].get('media_player', {}),
|
||||||
|
add_devices, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_platform(hass, config, add_devices, discovery_info):
|
||||||
"""Set up the Sonos platform."""
|
"""Set up the Sonos platform."""
|
||||||
import soco
|
import soco
|
||||||
import soco.events
|
import soco.events
|
||||||
|
15
homeassistant/components/sonos/.translations/en.json
Normal file
15
homeassistant/components/sonos/.translations/en.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"no_devices_found": "No Sonos devices found on the network.",
|
||||||
|
"single_instance_allowed": "Only a single configuration of Sonos is necessary."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"description": "Do you want to setup Sonos?",
|
||||||
|
"title": "Sonos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Sonos"
|
||||||
|
}
|
||||||
|
}
|
29
homeassistant/components/sonos/__init__.py
Normal file
29
homeassistant/components/sonos/__init__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""Component to embed Sonos."""
|
||||||
|
from homeassistant.helpers import config_entry_flow
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN = 'sonos'
|
||||||
|
REQUIREMENTS = ['SoCo==0.14']
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Sonos component."""
|
||||||
|
hass.data[DOMAIN] = config.get(DOMAIN, {})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Set up Sonos from a config entry."""
|
||||||
|
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
|
||||||
|
entry, 'media_player'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_has_devices(hass):
|
||||||
|
"""Return if there are devices that can be discovered."""
|
||||||
|
import soco
|
||||||
|
|
||||||
|
return await hass.async_add_job(soco.discover)
|
||||||
|
|
||||||
|
|
||||||
|
config_entry_flow.register_discovery_flow(DOMAIN, 'Sonos', _async_has_devices)
|
15
homeassistant/components/sonos/strings.json
Normal file
15
homeassistant/components/sonos/strings.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Sonos",
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"title": "Sonos",
|
||||||
|
"description": "Do you want to setup Sonos?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Only a single configuration of Sonos is necessary.",
|
||||||
|
"no_devices_found": "No Sonos devices found on the network."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -127,9 +127,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
HANDLERS = Registry()
|
HANDLERS = Registry()
|
||||||
# Components that have config flows. In future we will auto-generate this list.
|
# Components that have config flows. In future we will auto-generate this list.
|
||||||
FLOWS = [
|
FLOWS = [
|
||||||
|
'cast',
|
||||||
'deconz',
|
'deconz',
|
||||||
'hue',
|
'hue',
|
||||||
'nest',
|
'nest',
|
||||||
|
'sonos',
|
||||||
'zone',
|
'zone',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
85
homeassistant/helpers/config_entry_flow.py
Normal file
85
homeassistant/helpers/config_entry_flow.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"""Helpers for data entry flows for config entries."""
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
|
||||||
|
|
||||||
|
def register_discovery_flow(domain, title, discovery_function):
|
||||||
|
"""Register flow for discovered integrations that not require auth."""
|
||||||
|
config_entries.HANDLERS.register(domain)(
|
||||||
|
partial(DiscoveryFlowHandler, domain, title, discovery_function))
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoveryFlowHandler(data_entry_flow.FlowHandler):
|
||||||
|
"""Handle a discovery config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self, domain, title, discovery_function):
|
||||||
|
"""Initialize the discovery config flow."""
|
||||||
|
self._domain = domain
|
||||||
|
self._title = title
|
||||||
|
self._discovery_function = discovery_function
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(
|
||||||
|
reason='single_instance_allowed'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get current discovered entries.
|
||||||
|
in_progress = self._async_in_progress()
|
||||||
|
|
||||||
|
has_devices = in_progress
|
||||||
|
if not has_devices:
|
||||||
|
has_devices = await self.hass.async_add_job(
|
||||||
|
self._discovery_function, self.hass)
|
||||||
|
|
||||||
|
if not has_devices:
|
||||||
|
return self.async_abort(
|
||||||
|
reason='no_devices_found'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cancel the discovered one.
|
||||||
|
for flow in in_progress:
|
||||||
|
self.hass.config_entries.flow.async_abort(flow['flow_id'])
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self._title,
|
||||||
|
data={},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_confirm(self, user_input=None):
|
||||||
|
"""Confirm setup."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self._title,
|
||||||
|
data={},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='confirm',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_discovery(self, discovery_info):
|
||||||
|
"""Handle a flow initialized by discovery."""
|
||||||
|
if self._async_in_progress() or self._async_current_entries():
|
||||||
|
return self.async_abort(
|
||||||
|
reason='single_instance_allowed'
|
||||||
|
)
|
||||||
|
|
||||||
|
return await self.async_step_confirm()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_current_entries(self):
|
||||||
|
"""Return current entries."""
|
||||||
|
return self.hass.config_entries.async_entries(self._domain)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_in_progress(self):
|
||||||
|
"""Return other in progress flows for current domain."""
|
||||||
|
return [flw for flw in self.hass.config_entries.flow.async_progress()
|
||||||
|
if flw['handler'] == self._domain and
|
||||||
|
flw['flow_id'] != self.flow_id]
|
@ -54,7 +54,7 @@ PyXiaomiGateway==0.9.5
|
|||||||
# homeassistant.components.remember_the_milk
|
# homeassistant.components.remember_the_milk
|
||||||
RtmAPI==0.7.0
|
RtmAPI==0.7.0
|
||||||
|
|
||||||
# homeassistant.components.media_player.sonos
|
# homeassistant.components.sonos
|
||||||
SoCo==0.14
|
SoCo==0.14
|
||||||
|
|
||||||
# homeassistant.components.sensor.travisci
|
# homeassistant.components.sensor.travisci
|
||||||
@ -773,7 +773,7 @@ pyblackbird==0.5
|
|||||||
# homeassistant.components.media_player.channels
|
# homeassistant.components.media_player.channels
|
||||||
pychannels==1.0.0
|
pychannels==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.media_player.cast
|
# homeassistant.components.cast
|
||||||
pychromecast==2.1.0
|
pychromecast==2.1.0
|
||||||
|
|
||||||
# homeassistant.components.media_player.cmus
|
# homeassistant.components.media_player.cmus
|
||||||
|
@ -24,7 +24,7 @@ HAP-python==2.2.2
|
|||||||
# homeassistant.components.notify.html5
|
# homeassistant.components.notify.html5
|
||||||
PyJWT==1.6.0
|
PyJWT==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.media_player.sonos
|
# homeassistant.components.sonos
|
||||||
SoCo==0.14
|
SoCo==0.14
|
||||||
|
|
||||||
# homeassistant.components.device_tracker.automatic
|
# homeassistant.components.device_tracker.automatic
|
||||||
|
1
tests/components/cast/__init__.py
Normal file
1
tests/components/cast/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Cast component."""
|
22
tests/components/cast/test_init.py
Normal file
22
tests/components/cast/test_init.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Tests for the Cast config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components import cast
|
||||||
|
|
||||||
|
from tests.common import MockDependency, mock_coro
|
||||||
|
|
||||||
|
|
||||||
|
async def test_creating_entry_sets_up_media_player(hass):
|
||||||
|
"""Test setting up Cast loads the media player."""
|
||||||
|
with patch('homeassistant.components.media_player.cast.async_setup_entry',
|
||||||
|
return_value=mock_coro(True)) as mock_setup, \
|
||||||
|
MockDependency('pychromecast', 'discovery'), \
|
||||||
|
patch('pychromecast.discovery.discover_chromecasts',
|
||||||
|
return_value=True):
|
||||||
|
result = await hass.config_entries.flow.async_init(cast.DOMAIN)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
1
tests/components/sonos/__init__.py
Normal file
1
tests/components/sonos/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Sonos component."""
|
20
tests/components/sonos/test_init.py
Normal file
20
tests/components/sonos/test_init.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"""Tests for the Sonos config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components import sonos
|
||||||
|
|
||||||
|
from tests.common import mock_coro
|
||||||
|
|
||||||
|
|
||||||
|
async def test_creating_entry_sets_up_media_player(hass):
|
||||||
|
"""Test setting up Sonos loads the media player."""
|
||||||
|
with patch('homeassistant.components.media_player.sonos.async_setup_entry',
|
||||||
|
return_value=mock_coro(True)) as mock_setup, \
|
||||||
|
patch('soco.discover', return_value=True):
|
||||||
|
result = await hass.config_entries.flow.async_init(sonos.DOMAIN)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
116
tests/helpers/test_config_entry_flow.py
Normal file
116
tests/helpers/test_config_entry_flow.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""Tests for the Config Entry Flow helper."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow, loader
|
||||||
|
from homeassistant.helpers import config_entry_flow
|
||||||
|
from tests.common import MockConfigEntry, MockModule
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def flow_conf(hass):
|
||||||
|
"""Register a handler."""
|
||||||
|
handler_conf = {
|
||||||
|
'discovered': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def has_discovered_devices(hass):
|
||||||
|
"""Mock if we have discovered devices."""
|
||||||
|
return handler_conf['discovered']
|
||||||
|
|
||||||
|
with patch.dict(config_entries.HANDLERS):
|
||||||
|
config_entry_flow.register_discovery_flow(
|
||||||
|
'test', 'Test', has_discovered_devices)
|
||||||
|
yield handler_conf
|
||||||
|
|
||||||
|
|
||||||
|
async def test_single_entry_allowed(hass, flow_conf):
|
||||||
|
"""Test only a single entry is allowed."""
|
||||||
|
flow = config_entries.HANDLERS['test']()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
MockConfigEntry(domain='test').add_to_hass(hass)
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result['reason'] == 'single_instance_allowed'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_no_devices_found(hass, flow_conf):
|
||||||
|
"""Test if no devices found."""
|
||||||
|
flow = config_entries.HANDLERS['test']()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result['reason'] == 'no_devices_found'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_no_confirmation(hass, flow_conf):
|
||||||
|
"""Test user requires no confirmation to setup."""
|
||||||
|
flow = config_entries.HANDLERS['test']()
|
||||||
|
flow.hass = hass
|
||||||
|
flow_conf['discovered'] = True
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovery_single_instance(hass, flow_conf):
|
||||||
|
"""Test we ask for confirmation via discovery."""
|
||||||
|
flow = config_entries.HANDLERS['test']()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
MockConfigEntry(domain='test').add_to_hass(hass)
|
||||||
|
result = await flow.async_step_discovery({})
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result['reason'] == 'single_instance_allowed'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovery_confirmation(hass, flow_conf):
|
||||||
|
"""Test we ask for confirmation via discovery."""
|
||||||
|
flow = config_entries.HANDLERS['test']()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_discovery({})
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result['step_id'] == 'confirm'
|
||||||
|
|
||||||
|
result = await flow.async_step_confirm({})
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_multiple_discoveries(hass, flow_conf):
|
||||||
|
"""Test we only create one instance for multiple discoveries."""
|
||||||
|
loader.set_component(hass, 'test', MockModule('test'))
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'test', source=data_entry_flow.SOURCE_DISCOVERY, data={})
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
# Second discovery
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'test', source=data_entry_flow.SOURCE_DISCOVERY, data={})
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user_init_trumps_discovery(hass, flow_conf):
|
||||||
|
"""Test a user initialized one will finish and cancel discovered one."""
|
||||||
|
loader.set_component(hass, 'test', MockModule('test'))
|
||||||
|
|
||||||
|
# Discovery starts flow
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'test', source=data_entry_flow.SOURCE_DISCOVERY, data={})
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
# User starts flow
|
||||||
|
result = await hass.config_entries.flow.async_init('test', data={})
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
# Discovery flow has been aborted
|
||||||
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
Loading…
x
Reference in New Issue
Block a user