From 5dbf58d67f733128db6afe6e1def5c8c413bd606 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 26 Apr 2019 08:56:43 +0200 Subject: [PATCH] Remove support for deprecated Sonos configuration (#23385) --- homeassistant/components/sonos/__init__.py | 22 +- .../components/sonos/media_player.py | 94 ++--- tests/components/sonos/conftest.py | 77 ++++ tests/components/sonos/test_init.py | 4 +- tests/components/sonos/test_media_player.py | 370 +----------------- 5 files changed, 148 insertions(+), 419 deletions(-) create mode 100644 tests/components/sonos/conftest.py diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index b661fa26fe7..d68e87914ec 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -1,10 +1,26 @@ """Support to embed Sonos.""" -from homeassistant import config_entries -from homeassistant.helpers import config_entry_flow +import voluptuous as vol +from homeassistant import config_entries +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.const import CONF_HOSTS +from homeassistant.helpers import config_entry_flow, config_validation as cv DOMAIN = 'sonos' +CONF_ADVERTISE_ADDR = 'advertise_addr' +CONF_INTERFACE_ADDR = 'interface_addr' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + MP_DOMAIN: vol.Schema({ + vol.Optional(CONF_ADVERTISE_ADDR): cv.string, + vol.Optional(CONF_INTERFACE_ADDR): cv.string, + vol.Optional(CONF_HOSTS): vol.All(cv.ensure_list_csv, [cv.string]), + }), + }), +}, extra=vol.ALLOW_EXTRA) + async def async_setup(hass, config): """Set up the Sonos component.""" @@ -22,7 +38,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, entry): """Set up Sonos from a config entry.""" hass.async_create_task(hass.config_entries.async_forward_entry_setup( - entry, 'media_player')) + entry, MP_DOMAIN)) return True diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 7c2e5fec843..2e7d09be334 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -10,20 +10,21 @@ import async_timeout import requests import voluptuous as vol -from homeassistant.components.media_player import ( - PLATFORM_SCHEMA, MediaPlayerDevice) +from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_TIME, CONF_HOSTS, STATE_IDLE, STATE_OFF, STATE_PAUSED, + ATTR_ENTITY_ID, ATTR_TIME, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING) import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow -from . import DOMAIN as SONOS_DOMAIN +from . import ( + CONF_ADVERTISE_ADDR, CONF_HOSTS, CONF_INTERFACE_ADDR, + DOMAIN as SONOS_DOMAIN) DEPENDENCIES = ('sonos',) @@ -54,9 +55,6 @@ DATA_SONOS = 'sonos_media_player' SOURCE_LINEIN = 'Line-in' SOURCE_TV = 'TV' -CONF_ADVERTISE_ADDR = 'advertise_addr' -CONF_INTERFACE_ADDR = 'interface_addr' - # Service call validation schemas ATTR_SLEEP_TIME = 'sleep_time' ATTR_ALARM_ID = 'alarm_id' @@ -72,12 +70,6 @@ ATTR_SONOS_GROUP = 'sonos_group' UPNP_ERRORS_TO_IGNORE = ['701', '711', '712'] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_ADVERTISE_ADDR): cv.string, - vol.Optional(CONF_INTERFACE_ADDR): cv.string, - vol.Optional(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string]), -}) - SONOS_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, }) @@ -119,57 +111,34 @@ class SonosData: self.topology_condition = asyncio.Condition(loop=hass.loop) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Sonos platform. - - Deprecated. - """ - _LOGGER.warning('Loading Sonos via platform config is deprecated.') - _setup_platform(hass, config, add_entities, discovery_info) +async def async_setup_platform(hass, + config, + async_add_entities, + discovery_info=None): + """Set up the Sonos platform. Obsolete.""" + _LOGGER.error( + 'Loading Sonos by media_player platform config is no longer supported') async def async_setup_entry(hass, config_entry, async_add_entities): """Set up Sonos from a config entry.""" - def add_entities(entities, update_before_add=False): - """Sync version of async add entities.""" - hass.add_job(async_add_entities, entities, update_before_add) - - hass.async_add_executor_job( - _setup_platform, hass, hass.data[SONOS_DOMAIN].get('media_player', {}), - add_entities, None) - - -def _setup_platform(hass, config, add_entities, discovery_info): - """Set up the Sonos platform.""" import pysonos if DATA_SONOS not in hass.data: hass.data[DATA_SONOS] = SonosData(hass) + config = hass.data[SONOS_DOMAIN].get('media_player', {}) + advertise_addr = config.get(CONF_ADVERTISE_ADDR) if advertise_addr: pysonos.config.EVENT_ADVERTISE_IP = advertise_addr - players = [] - if discovery_info: - player = pysonos.SoCo(discovery_info.get('host')) - - # If host already exists by config - if player.uid in hass.data[DATA_SONOS].uids: - return - - # If invisible, such as a stereo slave - if not player.is_visible: - return - - players.append(player) - else: + def _create_sonos_entities(): + """Discover players and return a list of SonosEntity objects.""" + players = [] hosts = config.get(CONF_HOSTS) + if hosts: - # Support retro compatibility with comma separated list of hosts - # from config - hosts = hosts[0] if len(hosts) == 1 else hosts - hosts = hosts.split(',') if isinstance(hosts, str) else hosts for host in hosts: try: players.append(pysonos.SoCo(socket.gethostbyname(host))) @@ -182,11 +151,14 @@ def _setup_platform(hass, config, add_entities, discovery_info): if not players: _LOGGER.warning("No Sonos speakers found") - return - hass.data[DATA_SONOS].uids.update(p.uid for p in players) - add_entities(SonosEntity(p) for p in players) - _LOGGER.debug("Added %s Sonos speakers", len(players)) + return [SonosEntity(p) for p in players] + + entities = await hass.async_add_executor_job(_create_sonos_entities) + hass.data[DATA_SONOS].uids.update(e.unique_id for e in entities) + + async_add_entities(entities) + _LOGGER.debug("Added %s Sonos speakers", len(entities)) def _service_to_entities(service): """Extract and return entities from service call.""" @@ -216,19 +188,19 @@ def _setup_platform(hass, config, add_entities, discovery_info): await SonosEntity.restore_multi( hass, entities, service.data[ATTR_WITH_GROUP]) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_JOIN, async_service_handle, schema=SONOS_JOIN_SCHEMA) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_UNJOIN, async_service_handle, schema=SONOS_SCHEMA) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_SNAPSHOT, async_service_handle, schema=SONOS_STATES_SCHEMA) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_RESTORE, async_service_handle, schema=SONOS_STATES_SCHEMA) @@ -244,19 +216,19 @@ def _setup_platform(hass, config, add_entities, discovery_info): elif service.service == SERVICE_SET_OPTION: entity.set_option(**service.data) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_SET_TIMER, service_handle, schema=SONOS_SET_TIMER_SCHEMA) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_CLEAR_TIMER, service_handle, schema=SONOS_SCHEMA) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_UPDATE_ALARM, service_handle, schema=SONOS_UPDATE_ALARM_SCHEMA) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_SET_OPTION, service_handle, schema=SONOS_SET_OPTION_SCHEMA) diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py new file mode 100644 index 00000000000..95bc66fe317 --- /dev/null +++ b/tests/components/sonos/conftest.py @@ -0,0 +1,77 @@ +"""Configuration for Sonos tests.""" +from asynctest.mock import Mock, patch as patch +import pytest + +from homeassistant.components.sonos import DOMAIN +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.const import CONF_HOSTS + +from tests.common import MockConfigEntry + + +@pytest.fixture(name="config_entry") +def config_entry_fixture(): + """Create a mock Sonos config entry.""" + return MockConfigEntry(domain=DOMAIN, title='Sonos') + + +@pytest.fixture(name="soco") +def soco_fixture(music_library, speaker_info, dummy_soco_service): + """Create a mock pysonos SoCo fixture.""" + with patch('pysonos.SoCo', autospec=True) as mock, \ + patch('socket.gethostbyname', return_value='192.168.42.2'): + mock_soco = mock.return_value + mock_soco.uid = 'RINCON_test' + mock_soco.music_library = music_library + mock_soco.get_speaker_info.return_value = speaker_info + mock_soco.avTransport = dummy_soco_service + mock_soco.renderingControl = dummy_soco_service + mock_soco.zoneGroupTopology = dummy_soco_service + mock_soco.contentDirectory = dummy_soco_service + + yield mock_soco + + +@pytest.fixture(name="discover") +def discover_fixture(soco): + """Create a mock pysonos discover fixture.""" + with patch('pysonos.discover') as mock: + mock.return_value = {soco} + yield mock + + +@pytest.fixture(name="config") +def config_fixture(): + """Create hass config fixture.""" + return { + DOMAIN: { + MP_DOMAIN: { + CONF_HOSTS: ['192.168.42.1'] + } + } + } + + +@pytest.fixture(name="dummy_soco_service") +def dummy_soco_service_fixture(): + """Create dummy_soco_service fixture.""" + service = Mock() + service.subscribe = Mock() + return service + + +@pytest.fixture(name="music_library") +def music_library_fixture(): + """Create music_library fixture.""" + music_library = Mock() + music_library.get_sonos_favorites.return_value = [] + return music_library + + +@pytest.fixture(name="speaker_info") +def speaker_info_fixture(): + """Create speaker_info fixture.""" + return { + 'zone_name': 'Zone A', + 'model_name': 'Model Name', + } diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index a09fa7d2615..3cdeeb08f02 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -35,7 +35,9 @@ async def test_configuring_sonos_creates_entry(hass): patch('pysonos.discover', return_value=True): await async_setup_component(hass, sonos.DOMAIN, { 'sonos': { - 'some_config': 'to_trigger_import' + 'media_player': { + 'interface_addr': '127.0.0.1', + } } }) await hass.async_block_till_done() diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index 4cb4a291b16..a06a6160400 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -1,360 +1,22 @@ -"""The tests for the Demo Media player platform.""" -import datetime -import socket -import unittest -import pysonos.snapshot -from unittest import mock -import pysonos -from pysonos import alarms - -from homeassistant.setup import setup_component -from homeassistant.components.sonos import media_player as sonos -from homeassistant.components.media_player.const import DOMAIN -from homeassistant.components.sonos.media_player import CONF_INTERFACE_ADDR -from homeassistant.const import CONF_HOSTS, CONF_PLATFORM -from homeassistant.util.async_ import run_coroutine_threadsafe - -from tests.common import get_test_home_assistant - -ENTITY_ID = 'media_player.kitchen' +"""Tests for the Sonos Media Player platform.""" +from homeassistant.components.sonos import media_player, DOMAIN +from homeassistant.setup import async_setup_component -class pysonosDiscoverMock(): - """Mock class for the pysonos.discover method.""" - - def discover(interface_addr, all_households=False): - """Return tuple of pysonos.SoCo objects representing found speakers.""" - return {SoCoMock('192.0.2.1')} +async def setup_platform(hass, config_entry, config): + """Set up the media player platform for testing.""" + config_entry.add_to_hass(hass) + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() -class AvTransportMock(): - """Mock class for the avTransport property on pysonos.SoCo object.""" - - def __init__(self): - """Initialize ethe Transport mock.""" - pass - - def GetMediaInfo(self, _): - """Get the media details.""" - return { - 'CurrentURI': '', - 'CurrentURIMetaData': '' - } +async def test_async_setup_entry_hosts(hass, config_entry, config, soco): + """Test static setup.""" + await setup_platform(hass, config_entry, config) + assert hass.data[media_player.DATA_SONOS].entities[0].soco == soco -class MusicLibraryMock(): - """Mock class for the music_library property on pysonos.SoCo object.""" - - def get_sonos_favorites(self): - """Return favorites.""" - return [] - - -class CacheMock(): - """Mock class for the _zgs_cache property on pysonos.SoCo object.""" - - def clear(self): - """Clear cache.""" - pass - - -class SoCoMock(): - """Mock class for the pysonos.SoCo object.""" - - def __init__(self, ip): - """Initialize SoCo object.""" - self.ip_address = ip - self.is_visible = True - self.volume = 50 - self.mute = False - self.shuffle = False - self.night_mode = False - self.dialog_mode = False - self.music_library = MusicLibraryMock() - self.avTransport = AvTransportMock() - self._zgs_cache = CacheMock() - - def get_sonos_favorites(self): - """Get favorites list from sonos.""" - return {'favorites': []} - - def get_speaker_info(self, force): - """Return a dict with various data points about the speaker.""" - return {'serial_number': 'B8-E9-37-BO-OC-BA:2', - 'software_version': '32.11-30071', - 'uid': 'RINCON_B8E937BOOCBA02500', - 'zone_icon': 'x-rincon-roomicon:kitchen', - 'mac_address': 'B8:E9:37:BO:OC:BA', - 'zone_name': 'Kitchen', - 'model_name': 'Sonos PLAY:1', - 'hardware_version': '1.8.1.2-1'} - - def get_current_transport_info(self): - """Return a dict with the current state of the speaker.""" - return {'current_transport_speed': '1', - 'current_transport_state': 'STOPPED', - 'current_transport_status': 'OK'} - - def get_current_track_info(self): - """Return a dict with the current track information.""" - return {'album': '', - 'uri': '', - 'title': '', - 'artist': '', - 'duration': '0:00:00', - 'album_art': '', - 'position': '0:00:00', - 'playlist_position': '0', - 'metadata': ''} - - def is_coordinator(self): - """Return true if coordinator.""" - return True - - def join(self, master): - """Join speaker to a group.""" - return - - def set_sleep_timer(self, sleep_time_seconds): - """Set the sleep timer.""" - return - - def unjoin(self): - """Cause the speaker to separate itself from other speakers.""" - return - - def uid(self): - """Return a player uid.""" - return "RINCON_XXXXXXXXXXXXXXXXX" - - def group(self): - """Return all group data of this player.""" - return - - -def add_entities_factory(hass): - """Add entities factory.""" - def add_entities(entities, update_befor_add=False): - """Fake add entity.""" - hass.data[sonos.DATA_SONOS].entities = list(entities) - - return add_entities - - -class TestSonosMediaPlayer(unittest.TestCase): - """Test the media_player module.""" - - # pylint: disable=invalid-name - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - - def monkey_available(self): - """Make a monkey available.""" - return True - - # Monkey patches - self.real_available = sonos.SonosEntity.available - sonos.SonosEntity.available = monkey_available - - # pylint: disable=invalid-name - def tearDown(self): - """Stop everything that was started.""" - # Monkey patches - sonos.SonosEntity.available = self.real_available - self.hass.stop() - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - def test_ensure_setup_discovery(self, *args): - """Test a single device using the autodiscovery provided by HASS.""" - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), { - 'host': '192.0.2.1' - }) - - entities = self.hass.data[sonos.DATA_SONOS].entities - assert len(entities) == 1 - assert entities[0].name == 'Kitchen' - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - @mock.patch('pysonos.discover') - def test_ensure_setup_config_interface_addr(self, discover_mock, *args): - """Test an interface address config'd by the HASS config file.""" - discover_mock.return_value = {SoCoMock('192.0.2.1')} - - config = { - DOMAIN: { - CONF_PLATFORM: 'sonos', - CONF_INTERFACE_ADDR: '192.0.1.1', - } - } - - assert setup_component(self.hass, DOMAIN, config) - - assert len(self.hass.data[sonos.DATA_SONOS].entities) == 1 - assert discover_mock.call_count == 1 - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - def test_ensure_setup_config_hosts_string_single(self, *args): - """Test a single address config'd by the HASS config file.""" - config = { - DOMAIN: { - CONF_PLATFORM: 'sonos', - CONF_HOSTS: ['192.0.2.1'], - } - } - - assert setup_component(self.hass, DOMAIN, config) - - entities = self.hass.data[sonos.DATA_SONOS].entities - assert len(entities) == 1 - assert entities[0].name == 'Kitchen' - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - def test_ensure_setup_config_hosts_string_multiple(self, *args): - """Test multiple address string config'd by the HASS config file.""" - config = { - DOMAIN: { - CONF_PLATFORM: 'sonos', - CONF_HOSTS: ['192.0.2.1,192.168.2.2'], - } - } - - assert setup_component(self.hass, DOMAIN, config) - - entities = self.hass.data[sonos.DATA_SONOS].entities - assert len(entities) == 2 - assert entities[0].name == 'Kitchen' - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - def test_ensure_setup_config_hosts_list(self, *args): - """Test a multiple address list config'd by the HASS config file.""" - config = { - DOMAIN: { - CONF_PLATFORM: 'sonos', - CONF_HOSTS: ['192.0.2.1', '192.168.2.2'], - } - } - - assert setup_component(self.hass, DOMAIN, config) - - entities = self.hass.data[sonos.DATA_SONOS].entities - assert len(entities) == 2 - assert entities[0].name == 'Kitchen' - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch.object(pysonos, 'discover', new=pysonosDiscoverMock.discover) - @mock.patch('socket.create_connection', side_effect=socket.error()) - def test_ensure_setup_sonos_discovery(self, *args): - """Test a single device using the autodiscovery provided by Sonos.""" - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass)) - entities = self.hass.data[sonos.DATA_SONOS].entities - assert len(entities) == 1 - assert entities[0].name == 'Kitchen' - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - @mock.patch.object(SoCoMock, 'set_sleep_timer') - def test_sonos_set_sleep_timer(self, set_sleep_timerMock, *args): - """Ensure pysonos methods called for sonos_set_sleep_timer service.""" - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), { - 'host': '192.0.2.1' - }) - entity = self.hass.data[sonos.DATA_SONOS].entities[-1] - entity.hass = self.hass - - entity.set_sleep_timer(30) - set_sleep_timerMock.assert_called_once_with(30) - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - @mock.patch.object(SoCoMock, 'set_sleep_timer') - def test_sonos_clear_sleep_timer(self, set_sleep_timerMock, *args): - """Ensure pysonos method called for sonos_clear_sleep_timer service.""" - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), { - 'host': '192.0.2.1' - }) - entity = self.hass.data[sonos.DATA_SONOS].entities[-1] - entity.hass = self.hass - - entity.set_sleep_timer(None) - set_sleep_timerMock.assert_called_once_with(None) - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('pysonos.alarms.Alarm') - @mock.patch('socket.create_connection', side_effect=socket.error()) - def test_set_alarm(self, pysonos_mock, alarm_mock, *args): - """Ensure pysonos methods called for sonos_set_sleep_timer service.""" - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), { - 'host': '192.0.2.1' - }) - entity = self.hass.data[sonos.DATA_SONOS].entities[-1] - entity.hass = self.hass - alarm1 = alarms.Alarm(pysonos_mock) - alarm1.configure_mock(_alarm_id="1", start_time=None, enabled=False, - include_linked_zones=False, volume=100) - with mock.patch('pysonos.alarms.get_alarms', return_value=[alarm1]): - attrs = { - 'time': datetime.time(12, 00), - 'enabled': True, - 'include_linked_zones': True, - 'volume': 0.30, - } - entity.set_alarm(alarm_id=2) - alarm1.save.assert_not_called() - entity.set_alarm(alarm_id=1, **attrs) - assert alarm1.enabled == attrs['enabled'] - assert alarm1.start_time == attrs['time'] - assert alarm1.include_linked_zones == \ - attrs['include_linked_zones'] - assert alarm1.volume == 30 - alarm1.save.assert_called_once_with() - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - @mock.patch.object(pysonos.snapshot.Snapshot, 'snapshot') - def test_sonos_snapshot(self, snapshotMock, *args): - """Ensure pysonos methods called for sonos_snapshot service.""" - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), { - 'host': '192.0.2.1' - }) - entities = self.hass.data[sonos.DATA_SONOS].entities - entity = entities[-1] - entity.hass = self.hass - - snapshotMock.return_value = True - entity.soco.group = mock.MagicMock() - entity.soco.group.members = [e.soco for e in entities] - run_coroutine_threadsafe( - sonos.SonosEntity.snapshot_multi(self.hass, entities, True), - self.hass.loop).result() - assert snapshotMock.call_count == 1 - assert snapshotMock.call_args == mock.call() - - @mock.patch('pysonos.SoCo', new=SoCoMock) - @mock.patch('socket.create_connection', side_effect=socket.error()) - @mock.patch.object(pysonos.snapshot.Snapshot, 'restore') - def test_sonos_restore(self, restoreMock, *args): - """Ensure pysonos methods called for sonos_restore service.""" - from pysonos.snapshot import Snapshot - - sonos.setup_platform(self.hass, {}, add_entities_factory(self.hass), { - 'host': '192.0.2.1' - }) - entities = self.hass.data[sonos.DATA_SONOS].entities - entity = entities[-1] - entity.hass = self.hass - - restoreMock.return_value = True - entity._snapshot_group = mock.MagicMock() - entity._snapshot_group.members = [e.soco for e in entities] - entity._soco_snapshot = Snapshot(entity.soco) - run_coroutine_threadsafe( - sonos.SonosEntity.restore_multi(self.hass, entities, True), - self.hass.loop).result() - assert restoreMock.call_count == 1 - assert restoreMock.call_args == mock.call() +async def test_async_setup_entry_discover(hass, config_entry, discover): + """Test discovery setup.""" + await setup_platform(hass, config_entry, {}) + assert hass.data[media_player.DATA_SONOS].uids == {'RINCON_test'}