core/tests/components/soundtouch/test_media_player.py

861 lines
26 KiB
Python

"""Test the Soundtouch component."""
from unittest.mock import call
from asynctest import patch
from libsoundtouch.device import (
Config,
Preset,
SoundTouchDevice as STD,
Status,
Volume,
ZoneSlave,
ZoneStatus,
)
import pytest
from homeassistant.components.media_player.const import (
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 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"
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(
MockZoneStatus(
is_master=True,
master_id=1,
master_ip=DEVICE_1_IP,
slaves=[MockZoneSlave(DEVICE_2_IP)],
)
)
device_2 = MockDevice(
MockZoneStatus(
is_master=False,
master_id=1,
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, zone_status=None):
"""Init the class."""
self._config = MockConfig()
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):
"""Init class."""
self._name = "name"
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
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 == 1
assert mocked_volume.call_count == 1
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 == 1
assert mocked_volume.call_count == 1
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 == 1
assert mocked_volume.call_count == 1
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_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 == 1
assert mocked_volume.call_count == 1
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 == 1
assert mocked_volume.call_count == 1
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 == 1
assert mocked_volume.call_count == 1
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 == 1
assert mocked_volume.call_count == 1
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 == 1
assert mocked_volume.call_count == 1
entity_1_state = hass.states.get("media_player.soundtouch_1")
assert entity_1_state.attributes["supported_features"] == 18365
@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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player", "turn_off", {"entity_id": "media_player.soundtouch_1"}, True,
)
assert mocked_status.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player", "turn_on", {"entity_id": "media_player.soundtouch_1"}, True,
)
assert mocked_status.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player", "volume_up", {"entity_id": "media_player.soundtouch_1"}, True,
)
assert mocked_volume.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player", "volume_down", {"entity_id": "media_player.soundtouch_1"}, True,
)
assert mocked_volume.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
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 == 2
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 == 1
assert mocked_volume.call_count == 1
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 == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player", "media_play", {"entity_id": "media_player.soundtouch_1"}, True,
)
assert mocked_status.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player", "media_pause", {"entity_id": "media_player.soundtouch_1"}, True,
)
assert mocked_status.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player",
"media_play_pause",
{"entity_id": "media_player.soundtouch_1"},
True,
)
assert mocked_status.call_count == 2
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 == 1
assert mocked_volume.call_count == 1
await hass.services.async_call(
"media_player",
"media_next_track",
{"entity_id": "media_player.soundtouch_1"},
True,
)
assert mocked_status.call_count == 2
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 == 3
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 == 1
assert mocked_volume.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: 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 == 1
assert mocked_volume.call_count == 1
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.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 == 2
assert mocked_volume.call_count == 2
# 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 == 2
assert mocked_volume.call_count == 2
# 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 == 2
assert mocked_volume.call_count == 2
# 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 == 2
assert mocked_volume.call_count == 2
# 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