"""Test the Soundtouch component.""" from unittest.mock import call, patch from libsoundtouch.device import ( Config, Preset, SoundTouchDevice as STD, Status, Volume, ZoneSlave, ZoneStatus, ) import pytest from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ) from homeassistant.components.soundtouch import media_player as soundtouch from homeassistant.components.soundtouch.const import DOMAIN from homeassistant.components.soundtouch.media_player import ( ATTR_SOUNDTOUCH_GROUP, ATTR_SOUNDTOUCH_ZONE, DATA_SOUNDTOUCH, ) from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.helpers.discovery import async_load_platform from homeassistant.setup import async_setup_component # pylint: disable=super-init-not-called DEVICE_1_IP = "192.168.0.1" DEVICE_2_IP = "192.168.0.2" DEVICE_1_ID = 1 DEVICE_2_ID = 2 def get_config(host=DEVICE_1_IP, port=8090, name="soundtouch"): """Return a default component.""" return {"platform": DOMAIN, "host": host, "port": port, "name": name} DEVICE_1_CONFIG = {**get_config(), "name": "soundtouch_1"} DEVICE_2_CONFIG = {**get_config(), "host": DEVICE_2_IP, "name": "soundtouch_2"} @pytest.fixture(name="one_device") def one_device_fixture(): """Mock one master device.""" device_1 = MockDevice() device_patch = patch( "homeassistant.components.soundtouch.media_player.soundtouch_device", return_value=device_1, ) with device_patch as device: yield device @pytest.fixture(name="two_zones") def two_zones_fixture(): """Mock one master and one slave.""" device_1 = MockDevice( DEVICE_1_ID, MockZoneStatus( is_master=True, master_id=DEVICE_1_ID, master_ip=DEVICE_1_IP, slaves=[MockZoneSlave(DEVICE_2_IP)], ), ) device_2 = MockDevice( DEVICE_2_ID, MockZoneStatus( is_master=False, master_id=DEVICE_1_ID, master_ip=DEVICE_1_IP, slaves=[MockZoneSlave(DEVICE_2_IP)], ), ) devices = {DEVICE_1_IP: device_1, DEVICE_2_IP: device_2} device_patch = patch( "homeassistant.components.soundtouch.media_player.soundtouch_device", side_effect=lambda host, _: devices[host], ) with device_patch as device: yield device @pytest.fixture(name="mocked_status") def status_fixture(): """Mock the device status.""" status_patch = patch( "libsoundtouch.device.SoundTouchDevice.status", side_effect=MockStatusPlaying ) with status_patch as status: yield status @pytest.fixture(name="mocked_volume") def volume_fixture(): """Mock the device volume.""" volume_patch = patch("libsoundtouch.device.SoundTouchDevice.volume") with volume_patch as volume: yield volume async def setup_soundtouch(hass, config): """Set up soundtouch integration.""" assert await async_setup_component(hass, "media_player", {"media_player": config}) await hass.async_block_till_done() await hass.async_start() class MockDevice(STD): """Mock device.""" def __init__(self, id=None, zone_status=None): """Init the class.""" self._config = MockConfig(id) self._zone_status = zone_status or MockZoneStatus() def zone_status(self, refresh=True): """Zone status mock object.""" return self._zone_status class MockConfig(Config): """Mock config.""" def __init__(self, id=None): """Init class.""" self._name = "name" self._id = id or DEVICE_1_ID class MockZoneStatus(ZoneStatus): """Mock zone status.""" def __init__(self, is_master=True, master_id=None, master_ip=None, slaves=None): """Init the class.""" self._is_master = is_master self._master_id = master_id self._master_ip = master_ip self._slaves = slaves or [] class MockZoneSlave(ZoneSlave): """Mock zone slave.""" def __init__(self, device_ip=None, role=None): """Init the class.""" self._ip = device_ip self._role = role def _mocked_presets(*args, **kwargs): """Return a list of mocked presets.""" return [MockPreset("1")] class MockPreset(Preset): """Mock preset.""" def __init__(self, id_): """Init the class.""" self._id = id_ self._name = "preset" class MockVolume(Volume): """Mock volume with value.""" def __init__(self): """Init class.""" self._actual = 12 self._muted = False class MockVolumeMuted(Volume): """Mock volume muted.""" def __init__(self): """Init the class.""" self._actual = 12 self._muted = True class MockStatusStandby(Status): """Mock status standby.""" def __init__(self): """Init the class.""" self._source = "STANDBY" class MockStatusPlaying(Status): """Mock status playing media.""" def __init__(self): """Init the class.""" self._source = "" self._play_status = "PLAY_STATE" self._image = "image.url" self._artist = "artist" self._track = "track" self._album = "album" self._duration = 1 self._station_name = None class MockStatusPlayingRadio(Status): """Mock status radio.""" def __init__(self): """Init the class.""" self._source = "" self._play_status = "PLAY_STATE" self._image = "image.url" self._artist = None self._track = None self._album = None self._duration = None self._station_name = "station" class MockStatusUnknown(Status): """Mock status unknown media.""" def __init__(self): """Init the class.""" self._source = "" self._play_status = "PLAY_STATE" self._image = "image.url" self._artist = None self._track = None self._album = None self._duration = None self._station_name = None class MockStatusPause(Status): """Mock status pause.""" def __init__(self): """Init the class.""" self._source = "" self._play_status = "PAUSE_STATE" self._image = "image.url" self._artist = None self._track = None self._album = None self._duration = None self._station_name = None class MockStatusPlayingAux(Status): """Mock status AUX.""" def __init__(self): """Init the class.""" self._source = "AUX" self._play_status = "PLAY_STATE" self._image = "image.url" self._artist = None self._track = None self._album = None self._duration = None self._station_name = None class MockStatusPlayingBluetooth(Status): """Mock status Bluetooth.""" def __init__(self): """Init the class.""" self._source = "BLUETOOTH" self._play_status = "PLAY_STATE" self._image = "image.url" self._artist = "artist" self._track = "track" self._album = "album" self._duration = None self._station_name = None async def test_ensure_setup_config(mocked_status, mocked_volume, hass, one_device): """Test setup OK with custom config.""" await setup_soundtouch( hass, get_config(host="192.168.1.44", port=8888, name="custom_sound") ) assert one_device.call_count == 1 assert one_device.call_args == call("192.168.1.44", 8888) assert len(hass.states.async_all()) == 1 state = hass.states.get("media_player.custom_sound") assert state.name == "custom_sound" async def test_ensure_setup_discovery(mocked_status, mocked_volume, hass, one_device): """Test setup with discovery.""" new_device = { "port": "8090", "host": "192.168.1.1", "properties": {}, "hostname": "hostname.local", } await async_load_platform( hass, "media_player", DOMAIN, new_device, {"media_player": {}} ) await hass.async_block_till_done() assert one_device.call_count == 1 assert one_device.call_args == call("192.168.1.1", 8090) assert len(hass.states.async_all()) == 1 async def test_ensure_setup_discovery_no_duplicate( mocked_status, mocked_volume, hass, one_device ): """Test setup OK if device already exists.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert len(hass.states.async_all()) == 1 new_device = { "port": "8090", "host": "192.168.1.1", "properties": {}, "hostname": "hostname.local", } await async_load_platform( hass, "media_player", DOMAIN, new_device, {"media_player": DEVICE_1_CONFIG} ) await hass.async_block_till_done() assert one_device.call_count == 2 assert len(hass.states.async_all()) == 2 existing_device = { "port": "8090", "host": "192.168.0.1", "properties": {}, "hostname": "hostname.local", } await async_load_platform( hass, "media_player", DOMAIN, existing_device, {"media_player": DEVICE_1_CONFIG} ) await hass.async_block_till_done() assert one_device.call_count == 2 assert len(hass.states.async_all()) == 2 async def test_playing_media(mocked_status, mocked_volume, hass, one_device): """Test playing media info.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_PLAYING assert entity_1_state.attributes["media_title"] == "artist - track" assert entity_1_state.attributes["media_track"] == "track" assert entity_1_state.attributes["media_artist"] == "artist" assert entity_1_state.attributes["media_album_name"] == "album" assert entity_1_state.attributes["media_duration"] == 1 async def test_playing_unknown_media(mocked_status, mocked_volume, hass, one_device): """Test playing media info.""" mocked_status.side_effect = MockStatusUnknown await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_PLAYING async def test_playing_radio(mocked_status, mocked_volume, hass, one_device): """Test playing radio info.""" mocked_status.side_effect = MockStatusPlayingRadio await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_PLAYING assert entity_1_state.attributes["media_title"] == "station" async def test_playing_aux(mocked_status, mocked_volume, hass, one_device): """Test playing AUX info.""" mocked_status.side_effect = MockStatusPlayingAux await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_PLAYING assert entity_1_state.attributes["source"] == "AUX" async def test_playing_bluetooth(mocked_status, mocked_volume, hass, one_device): """Test playing Bluetooth info.""" mocked_status.side_effect = MockStatusPlayingBluetooth await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_PLAYING assert entity_1_state.attributes["source"] == "BLUETOOTH" assert entity_1_state.attributes["media_track"] == "track" assert entity_1_state.attributes["media_artist"] == "artist" assert entity_1_state.attributes["media_album_name"] == "album" async def test_get_volume_level(mocked_status, mocked_volume, hass, one_device): """Test volume level.""" mocked_volume.side_effect = MockVolume await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.attributes["volume_level"] == 0.12 async def test_get_state_off(mocked_status, mocked_volume, hass, one_device): """Test state device is off.""" mocked_status.side_effect = MockStatusStandby await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_OFF async def test_get_state_pause(mocked_status, mocked_volume, hass, one_device): """Test state device is paused.""" mocked_status.side_effect = MockStatusPause await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.state == STATE_PAUSED async def test_is_muted(mocked_status, mocked_volume, hass, one_device): """Test device volume is muted.""" mocked_volume.side_effect = MockVolumeMuted await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.attributes["is_volume_muted"] async def test_media_commands(mocked_status, mocked_volume, hass, one_device): """Test supported media commands.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.attributes["supported_features"] == 20413 @patch("libsoundtouch.device.SoundTouchDevice.power_off") async def test_should_turn_off( mocked_power_off, mocked_status, mocked_volume, hass, one_device ): """Test device is turned off.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "turn_off", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 3 assert mocked_power_off.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.power_on") async def test_should_turn_on( mocked_power_on, mocked_status, mocked_volume, hass, one_device ): """Test device is turned on.""" mocked_status.side_effect = MockStatusStandby await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "turn_on", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 3 assert mocked_power_on.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.volume_up") async def test_volume_up( mocked_volume_up, mocked_status, mocked_volume, hass, one_device ): """Test volume up.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "volume_up", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_volume.call_count == 3 assert mocked_volume_up.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.volume_down") async def test_volume_down( mocked_volume_down, mocked_status, mocked_volume, hass, one_device ): """Test volume down.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "volume_down", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_volume.call_count == 3 assert mocked_volume_down.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.set_volume") async def test_set_volume_level( mocked_set_volume, mocked_status, mocked_volume, hass, one_device ): """Test set volume level.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "volume_set", {"entity_id": "media_player.soundtouch_1", "volume_level": 0.17}, True, ) assert mocked_volume.call_count == 3 mocked_set_volume.assert_called_with(17) @patch("libsoundtouch.device.SoundTouchDevice.mute") async def test_mute(mocked_mute, mocked_status, mocked_volume, hass, one_device): """Test mute volume.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "volume_mute", {"entity_id": "media_player.soundtouch_1", "is_volume_muted": True}, True, ) assert mocked_volume.call_count == 3 assert mocked_mute.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.play") async def test_play(mocked_play, mocked_status, mocked_volume, hass, one_device): """Test play command.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "media_play", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 3 assert mocked_play.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.pause") async def test_pause(mocked_pause, mocked_status, mocked_volume, hass, one_device): """Test pause command.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "media_pause", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 3 assert mocked_pause.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.play_pause") async def test_play_pause( mocked_play_pause, mocked_status, mocked_volume, hass, one_device ): """Test play/pause.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "media_play_pause", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 3 assert mocked_play_pause.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.previous_track") @patch("libsoundtouch.device.SoundTouchDevice.next_track") async def test_next_previous_track( mocked_next_track, mocked_previous_track, mocked_status, mocked_volume, hass, one_device, ): """Test next/previous track.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "media_next_track", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 3 assert mocked_next_track.call_count == 1 await hass.services.async_call( "media_player", "media_previous_track", {"entity_id": "media_player.soundtouch_1"}, True, ) assert mocked_status.call_count == 4 assert mocked_previous_track.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.select_preset") @patch("libsoundtouch.device.SoundTouchDevice.presets", side_effect=_mocked_presets) async def test_play_media( mocked_presets, mocked_select_preset, mocked_status, mocked_volume, hass, one_device ): """Test play preset 1.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "play_media", { "entity_id": "media_player.soundtouch_1", ATTR_MEDIA_CONTENT_TYPE: "PLAYLIST", ATTR_MEDIA_CONTENT_ID: 1, }, True, ) assert mocked_presets.call_count == 1 assert mocked_select_preset.call_count == 1 await hass.services.async_call( "media_player", "play_media", { "entity_id": "media_player.soundtouch_1", ATTR_MEDIA_CONTENT_TYPE: "PLAYLIST", ATTR_MEDIA_CONTENT_ID: 2, }, True, ) assert mocked_presets.call_count == 2 assert mocked_select_preset.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.play_url") async def test_play_media_url( mocked_play_url, mocked_status, mocked_volume, hass, one_device ): """Test play preset 1.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert one_device.call_count == 1 assert mocked_status.call_count == 2 assert mocked_volume.call_count == 2 await hass.services.async_call( "media_player", "play_media", { "entity_id": "media_player.soundtouch_1", ATTR_MEDIA_CONTENT_TYPE: "MUSIC", ATTR_MEDIA_CONTENT_ID: "http://fqdn/file.mp3", }, True, ) mocked_play_url.assert_called_with("http://fqdn/file.mp3") @patch("libsoundtouch.device.SoundTouchDevice.select_source_aux") async def test_select_source_aux( mocked_select_source_aux, mocked_status, mocked_volume, hass, one_device ): """Test select AUX.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert mocked_select_source_aux.call_count == 0 await hass.services.async_call( "media_player", "select_source", {"entity_id": "media_player.soundtouch_1", ATTR_INPUT_SOURCE: "AUX"}, True, ) assert mocked_select_source_aux.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.select_source_bluetooth") async def test_select_source_bluetooth( mocked_select_source_bluetooth, mocked_status, mocked_volume, hass, one_device ): """Test select Bluetooth.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert mocked_select_source_bluetooth.call_count == 0 await hass.services.async_call( "media_player", "select_source", {"entity_id": "media_player.soundtouch_1", ATTR_INPUT_SOURCE: "BLUETOOTH"}, True, ) assert mocked_select_source_bluetooth.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.select_source_bluetooth") @patch("libsoundtouch.device.SoundTouchDevice.select_source_aux") async def test_select_source_invalid_source( mocked_select_source_aux, mocked_select_source_bluetooth, mocked_status, mocked_volume, hass, one_device, ): """Test select unsupported source.""" await setup_soundtouch(hass, DEVICE_1_CONFIG) assert mocked_select_source_aux.call_count == 0 assert mocked_select_source_bluetooth.call_count == 0 await hass.services.async_call( "media_player", "select_source", { "entity_id": "media_player.soundtouch_1", ATTR_INPUT_SOURCE: "SOMETHING_UNSUPPORTED", }, True, ) assert mocked_select_source_aux.call_count == 0 assert mocked_select_source_bluetooth.call_count == 0 @patch("libsoundtouch.device.SoundTouchDevice.create_zone") async def test_play_everywhere( mocked_create_zone, mocked_status, mocked_volume, hass, two_zones ): """Test play everywhere.""" mocked_device = two_zones await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) assert mocked_device.call_count == 2 assert mocked_status.call_count == 4 assert mocked_volume.call_count == 4 # one master, one slave => create zone await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_PLAY_EVERYWHERE, {"master": "media_player.soundtouch_1"}, True, ) assert mocked_create_zone.call_count == 1 # unknown master, create zone must not be called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_PLAY_EVERYWHERE, {"master": "media_player.entity_X"}, True, ) assert mocked_create_zone.call_count == 1 # no slaves, create zone must not be called for entity in list(hass.data[DATA_SOUNDTOUCH]): if entity.entity_id == "media_player.soundtouch_1": continue hass.data[DATA_SOUNDTOUCH].remove(entity) await entity.async_remove() await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_PLAY_EVERYWHERE, {"master": "media_player.soundtouch_1"}, True, ) assert mocked_create_zone.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.create_zone") async def test_create_zone( mocked_create_zone, mocked_status, mocked_volume, hass, two_zones ): """Test creating a zone.""" mocked_device = two_zones await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) assert mocked_device.call_count == 2 assert mocked_status.call_count == 4 assert mocked_volume.call_count == 4 # one master, one slave => create zone await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_CREATE_ZONE, { "master": "media_player.soundtouch_1", "slaves": ["media_player.soundtouch_2"], }, True, ) assert mocked_create_zone.call_count == 1 # unknown master, create zone must not be called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_CREATE_ZONE, {"master": "media_player.entity_X", "slaves": ["media_player.soundtouch_2"]}, True, ) assert mocked_create_zone.call_count == 1 # no slaves, create zone must not be called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_CREATE_ZONE, {"master": "media_player.soundtouch_1", "slaves": []}, True, ) assert mocked_create_zone.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.remove_zone_slave") async def test_remove_zone_slave( mocked_remove_zone_slave, mocked_status, mocked_volume, hass, two_zones ): """Test adding a slave to an existing zone.""" mocked_device = two_zones await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) assert mocked_device.call_count == 2 assert mocked_status.call_count == 4 assert mocked_volume.call_count == 4 # remove one slave await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_REMOVE_ZONE_SLAVE, { "master": "media_player.soundtouch_1", "slaves": ["media_player.soundtouch_2"], }, True, ) assert mocked_remove_zone_slave.call_count == 1 # unknown master. add zone slave is not called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_REMOVE_ZONE_SLAVE, {"master": "media_player.entity_X", "slaves": ["media_player.soundtouch_2"]}, True, ) assert mocked_remove_zone_slave.call_count == 1 # no slave to add, add zone slave is not called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_REMOVE_ZONE_SLAVE, {"master": "media_player.soundtouch_1", "slaves": []}, True, ) assert mocked_remove_zone_slave.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.add_zone_slave") async def test_add_zone_slave( mocked_add_zone_slave, mocked_status, mocked_volume, hass, two_zones, ): """Test removing a slave from a zone.""" mocked_device = two_zones await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) assert mocked_device.call_count == 2 assert mocked_status.call_count == 4 assert mocked_volume.call_count == 4 # add one slave await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_ADD_ZONE_SLAVE, { "master": "media_player.soundtouch_1", "slaves": ["media_player.soundtouch_2"], }, True, ) assert mocked_add_zone_slave.call_count == 1 # unknown master, add zone slave is not called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_ADD_ZONE_SLAVE, {"master": "media_player.entity_X", "slaves": ["media_player.soundtouch_2"]}, True, ) assert mocked_add_zone_slave.call_count == 1 # no slave to add, add zone slave is not called await hass.services.async_call( soundtouch.DOMAIN, soundtouch.SERVICE_ADD_ZONE_SLAVE, {"master": "media_player.soundtouch_1", "slaves": ["media_player.entity_X"]}, True, ) assert mocked_add_zone_slave.call_count == 1 @patch("libsoundtouch.device.SoundTouchDevice.create_zone") async def test_zone_attributes( mocked_create_zone, mocked_status, mocked_volume, hass, two_zones, ): """Test play everywhere.""" mocked_device = two_zones await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) assert mocked_device.call_count == 2 assert mocked_status.call_count == 4 assert mocked_volume.call_count == 4 entity_1_state = hass.states.get("media_player.soundtouch_1") assert entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["is_master"] assert ( entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["master"] == "media_player.soundtouch_1" ) assert entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["slaves"] == [ "media_player.soundtouch_2" ] assert entity_1_state.attributes[ATTR_SOUNDTOUCH_GROUP] == [ "media_player.soundtouch_1", "media_player.soundtouch_2", ] entity_2_state = hass.states.get("media_player.soundtouch_2") assert not entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["is_master"] assert ( entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["master"] == "media_player.soundtouch_1" ) assert entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["slaves"] == [ "media_player.soundtouch_2" ] assert entity_2_state.attributes[ATTR_SOUNDTOUCH_GROUP] == [ "media_player.soundtouch_1", "media_player.soundtouch_2", ]