From efbd47c828c6c2e1cd967df2a4cefd2b00c60c25 Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Tue, 28 Jun 2022 23:02:39 +0200 Subject: [PATCH] Rewrite SoundTouch tests to use mocked payloads (#72984) --- .../components/soundtouch/media_player.py | 2 +- tests/components/soundtouch/conftest.py | 286 +++++ .../fixtures/device1_getZone_master.xml | 6 + .../soundtouch/fixtures/device1_info.xml | 32 + .../fixtures/device1_now_playing_aux.xml | 7 + .../device1_now_playing_bluetooth.xml | 16 + .../fixtures/device1_now_playing_radio.xml | 16 + .../fixtures/device1_now_playing_standby.xml | 4 + .../fixtures/device1_now_playing_upnp.xml | 13 + .../device1_now_playing_upnp_paused.xml | 13 + .../soundtouch/fixtures/device1_presets.xml | 12 + .../soundtouch/fixtures/device1_volume.xml | 6 + .../fixtures/device1_volume_muted.xml | 6 + .../fixtures/device2_getZone_slave.xml | 4 + .../soundtouch/fixtures/device2_info.xml | 32 + .../fixtures/device2_now_playing_standby.xml | 4 + .../soundtouch/fixtures/device2_volume.xml | 6 + .../soundtouch/test_media_player.py | 1140 ++++++----------- 18 files changed, 846 insertions(+), 759 deletions(-) create mode 100644 tests/components/soundtouch/conftest.py create mode 100644 tests/components/soundtouch/fixtures/device1_getZone_master.xml create mode 100644 tests/components/soundtouch/fixtures/device1_info.xml create mode 100644 tests/components/soundtouch/fixtures/device1_now_playing_aux.xml create mode 100644 tests/components/soundtouch/fixtures/device1_now_playing_bluetooth.xml create mode 100644 tests/components/soundtouch/fixtures/device1_now_playing_radio.xml create mode 100644 tests/components/soundtouch/fixtures/device1_now_playing_standby.xml create mode 100644 tests/components/soundtouch/fixtures/device1_now_playing_upnp.xml create mode 100644 tests/components/soundtouch/fixtures/device1_now_playing_upnp_paused.xml create mode 100644 tests/components/soundtouch/fixtures/device1_presets.xml create mode 100644 tests/components/soundtouch/fixtures/device1_volume.xml create mode 100644 tests/components/soundtouch/fixtures/device1_volume_muted.xml create mode 100644 tests/components/soundtouch/fixtures/device2_getZone_slave.xml create mode 100644 tests/components/soundtouch/fixtures/device2_info.xml create mode 100644 tests/components/soundtouch/fixtures/device2_now_playing_standby.xml create mode 100644 tests/components/soundtouch/fixtures/device2_volume.xml diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 7c9ade3bee1..f8a5191d9db 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -523,7 +523,7 @@ class SoundTouchDevice(MediaPlayerEntity): for slave in zone_slaves: slave_instance = self._get_instance_by_ip(slave.device_ip) - if slave_instance: + if slave_instance and slave_instance.entity_id != master: slaves.append(slave_instance.entity_id) attributes = { diff --git a/tests/components/soundtouch/conftest.py b/tests/components/soundtouch/conftest.py new file mode 100644 index 00000000000..dcac360d253 --- /dev/null +++ b/tests/components/soundtouch/conftest.py @@ -0,0 +1,286 @@ +"""Fixtures for Bose SoundTouch integration tests.""" +import pytest +from requests_mock import Mocker + +from homeassistant.components.media_player.const import DOMAIN as MEDIA_PLAYER_DOMAIN +from homeassistant.components.soundtouch.const import DOMAIN +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PLATFORM + +from tests.common import load_fixture + +DEVICE_1_ID = "020000000001" +DEVICE_2_ID = "020000000002" +DEVICE_1_IP = "192.168.42.1" +DEVICE_2_IP = "192.168.42.2" +DEVICE_1_URL = f"http://{DEVICE_1_IP}:8090" +DEVICE_2_URL = f"http://{DEVICE_2_IP}:8090" +DEVICE_1_NAME = "My Soundtouch 1" +DEVICE_2_NAME = "My Soundtouch 2" +DEVICE_1_ENTITY_ID = f"{MEDIA_PLAYER_DOMAIN}.my_soundtouch_1" +DEVICE_2_ENTITY_ID = f"{MEDIA_PLAYER_DOMAIN}.my_soundtouch_2" + + +# pylint: disable=redefined-outer-name + + +@pytest.fixture +def device1_config() -> dict[str, str]: + """Mock SoundTouch device 1 config.""" + yield {CONF_PLATFORM: DOMAIN, CONF_HOST: DEVICE_1_IP, CONF_NAME: DEVICE_1_NAME} + + +@pytest.fixture +def device2_config() -> dict[str, str]: + """Mock SoundTouch device 2 config.""" + yield {CONF_PLATFORM: DOMAIN, CONF_HOST: DEVICE_2_IP, CONF_NAME: DEVICE_2_NAME} + + +@pytest.fixture(scope="session") +def device1_info() -> str: + """Load SoundTouch device 1 info response and return it.""" + return load_fixture("soundtouch/device1_info.xml") + + +@pytest.fixture(scope="session") +def device1_now_playing_aux() -> str: + """Load SoundTouch device 1 now_playing response and return it.""" + return load_fixture("soundtouch/device1_now_playing_aux.xml") + + +@pytest.fixture(scope="session") +def device1_now_playing_bluetooth() -> str: + """Load SoundTouch device 1 now_playing response and return it.""" + return load_fixture("soundtouch/device1_now_playing_bluetooth.xml") + + +@pytest.fixture(scope="session") +def device1_now_playing_radio() -> str: + """Load SoundTouch device 1 now_playing response and return it.""" + return load_fixture("soundtouch/device1_now_playing_radio.xml") + + +@pytest.fixture(scope="session") +def device1_now_playing_standby() -> str: + """Load SoundTouch device 1 now_playing response and return it.""" + return load_fixture("soundtouch/device1_now_playing_standby.xml") + + +@pytest.fixture(scope="session") +def device1_now_playing_upnp() -> str: + """Load SoundTouch device 1 now_playing response and return it.""" + return load_fixture("soundtouch/device1_now_playing_upnp.xml") + + +@pytest.fixture(scope="session") +def device1_now_playing_upnp_paused() -> str: + """Load SoundTouch device 1 now_playing response and return it.""" + return load_fixture("soundtouch/device1_now_playing_upnp_paused.xml") + + +@pytest.fixture(scope="session") +def device1_presets() -> str: + """Load SoundTouch device 1 presets response and return it.""" + return load_fixture("soundtouch/device1_presets.xml") + + +@pytest.fixture(scope="session") +def device1_volume() -> str: + """Load SoundTouch device 1 volume response and return it.""" + return load_fixture("soundtouch/device1_volume.xml") + + +@pytest.fixture(scope="session") +def device1_volume_muted() -> str: + """Load SoundTouch device 1 volume response and return it.""" + return load_fixture("soundtouch/device1_volume_muted.xml") + + +@pytest.fixture(scope="session") +def device1_zone_master() -> str: + """Load SoundTouch device 1 getZone response and return it.""" + return load_fixture("soundtouch/device1_getZone_master.xml") + + +@pytest.fixture(scope="session") +def device2_info() -> str: + """Load SoundTouch device 2 info response and return it.""" + return load_fixture("soundtouch/device2_info.xml") + + +@pytest.fixture(scope="session") +def device2_volume() -> str: + """Load SoundTouch device 2 volume response and return it.""" + return load_fixture("soundtouch/device2_volume.xml") + + +@pytest.fixture(scope="session") +def device2_now_playing_standby() -> str: + """Load SoundTouch device 2 now_playing response and return it.""" + return load_fixture("soundtouch/device2_now_playing_standby.xml") + + +@pytest.fixture(scope="session") +def device2_zone_slave() -> str: + """Load SoundTouch device 2 getZone response and return it.""" + return load_fixture("soundtouch/device2_getZone_slave.xml") + + +@pytest.fixture(scope="session") +def zone_empty() -> str: + """Load empty SoundTouch getZone response and return it.""" + return load_fixture("soundtouch/getZone_empty.xml") + + +@pytest.fixture +def device1_requests_mock( + requests_mock: Mocker, + device1_info: str, + device1_volume: str, + device1_presets: str, + device1_zone_master: str, +) -> Mocker: + """Mock SoundTouch device 1 API - base URLs.""" + requests_mock.get(f"{DEVICE_1_URL}/info", text=device1_info) + requests_mock.get(f"{DEVICE_1_URL}/volume", text=device1_volume) + requests_mock.get(f"{DEVICE_1_URL}/presets", text=device1_presets) + requests_mock.get(f"{DEVICE_1_URL}/getZone", text=device1_zone_master) + yield requests_mock + + +@pytest.fixture +def device1_requests_mock_standby( + device1_requests_mock: Mocker, + device1_now_playing_standby: str, +): + """Mock SoundTouch device 1 API - standby.""" + device1_requests_mock.get( + f"{DEVICE_1_URL}/now_playing", text=device1_now_playing_standby + ) + + +@pytest.fixture +def device1_requests_mock_aux( + device1_requests_mock: Mocker, + device1_now_playing_aux: str, +): + """Mock SoundTouch device 1 API - playing AUX.""" + device1_requests_mock.get( + f"{DEVICE_1_URL}/now_playing", text=device1_now_playing_aux + ) + + +@pytest.fixture +def device1_requests_mock_bluetooth( + device1_requests_mock: Mocker, + device1_now_playing_bluetooth: str, +): + """Mock SoundTouch device 1 API - playing bluetooth.""" + device1_requests_mock.get( + f"{DEVICE_1_URL}/now_playing", text=device1_now_playing_bluetooth + ) + + +@pytest.fixture +def device1_requests_mock_radio( + device1_requests_mock: Mocker, + device1_now_playing_radio: str, +): + """Mock SoundTouch device 1 API - playing radio.""" + device1_requests_mock.get( + f"{DEVICE_1_URL}/now_playing", text=device1_now_playing_radio + ) + + +@pytest.fixture +def device1_requests_mock_upnp( + device1_requests_mock: Mocker, + device1_now_playing_upnp: str, +): + """Mock SoundTouch device 1 API - playing UPNP.""" + device1_requests_mock.get( + f"{DEVICE_1_URL}/now_playing", text=device1_now_playing_upnp + ) + + +@pytest.fixture +def device1_requests_mock_upnp_paused( + device1_requests_mock: Mocker, + device1_now_playing_upnp_paused: str, +): + """Mock SoundTouch device 1 API - playing UPNP (paused).""" + device1_requests_mock.get( + f"{DEVICE_1_URL}/now_playing", text=device1_now_playing_upnp_paused + ) + + +@pytest.fixture +def device1_requests_mock_key( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - key endpoint.""" + yield device1_requests_mock.post(f"{DEVICE_1_URL}/key") + + +@pytest.fixture +def device1_requests_mock_volume( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - volume endpoint.""" + yield device1_requests_mock.post(f"{DEVICE_1_URL}/volume") + + +@pytest.fixture +def device1_requests_mock_select( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - select endpoint.""" + yield device1_requests_mock.post(f"{DEVICE_1_URL}/select") + + +@pytest.fixture +def device1_requests_mock_set_zone( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - setZone endpoint.""" + yield device1_requests_mock.post(f"{DEVICE_1_URL}/setZone") + + +@pytest.fixture +def device1_requests_mock_add_zone_slave( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - addZoneSlave endpoint.""" + yield device1_requests_mock.post(f"{DEVICE_1_URL}/addZoneSlave") + + +@pytest.fixture +def device1_requests_mock_remove_zone_slave( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - removeZoneSlave endpoint.""" + yield device1_requests_mock.post(f"{DEVICE_1_URL}/removeZoneSlave") + + +@pytest.fixture +def device1_requests_mock_dlna( + device1_requests_mock: Mocker, +): + """Mock SoundTouch device 1 API - DLNA endpoint.""" + yield device1_requests_mock.post(f"http://{DEVICE_1_IP}:8091/AVTransport/Control") + + +@pytest.fixture +def device2_requests_mock_standby( + requests_mock: Mocker, + device2_info: str, + device2_volume: str, + device2_now_playing_standby: str, + device2_zone_slave: str, +) -> Mocker: + """Mock SoundTouch device 2 API.""" + requests_mock.get(f"{DEVICE_2_URL}/info", text=device2_info) + requests_mock.get(f"{DEVICE_2_URL}/volume", text=device2_volume) + requests_mock.get(f"{DEVICE_2_URL}/now_playing", text=device2_now_playing_standby) + requests_mock.get(f"{DEVICE_2_URL}/getZone", text=device2_zone_slave) + + yield requests_mock diff --git a/tests/components/soundtouch/fixtures/device1_getZone_master.xml b/tests/components/soundtouch/fixtures/device1_getZone_master.xml new file mode 100644 index 00000000000..f4b0fd05a51 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_getZone_master.xml @@ -0,0 +1,6 @@ + + + 020000000001 + 020000000002 + 020000000003 + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_info.xml b/tests/components/soundtouch/fixtures/device1_info.xml new file mode 100644 index 00000000000..27878969ca0 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_info.xml @@ -0,0 +1,32 @@ + + + My SoundTouch 1 + SoundTouch 10 + 0 + + + SCM + 27.0.3.46298.4608935 epdbuild.trunk.hepdswbld04.2021-10-06T16:35:02 + P0000000000000000000001 + + + PackagedProduct + 27.0.3.46298.4608935 epdbuild.trunk.hepdswbld04.2021-10-06T16:35:02 + 000000P00000001AE + + + https://streaming.bose.com + + 020000000001 + 192.168.42.1 + + + 060000000001 + 192.168.42.1 + + sm2 + rhino + normal + US + US + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_now_playing_aux.xml b/tests/components/soundtouch/fixtures/device1_now_playing_aux.xml new file mode 100644 index 00000000000..e19dc1dd954 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_now_playing_aux.xml @@ -0,0 +1,7 @@ + + + + AUX IN + + PLAY_STATE + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_now_playing_bluetooth.xml b/tests/components/soundtouch/fixtures/device1_now_playing_bluetooth.xml new file mode 100644 index 00000000000..c43fe187f2f --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_now_playing_bluetooth.xml @@ -0,0 +1,16 @@ + + + + MockPairedBluetoothDevice + + MockTrack + MockArtist + MockAlbum + MockPairedBluetoothDevice + + + PLAY_STATE + + + + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_now_playing_radio.xml b/tests/components/soundtouch/fixtures/device1_now_playing_radio.xml new file mode 100644 index 00000000000..b9d47216b3a --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_now_playing_radio.xml @@ -0,0 +1,16 @@ + + + + MockStation + http://cdn-profiles.tunein.com/sXXXXX/images/logoq.png + + MockTrack + MockArtist + MockAlbum + MockStation + http://cdn-profiles.tunein.com/sXXXXX/images/logoq.png + + PLAY_STATE + RADIO_STREAMING + + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_now_playing_standby.xml b/tests/components/soundtouch/fixtures/device1_now_playing_standby.xml new file mode 100644 index 00000000000..67acae6a0ef --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_now_playing_standby.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_now_playing_upnp.xml b/tests/components/soundtouch/fixtures/device1_now_playing_upnp.xml new file mode 100644 index 00000000000..e58e62072ce --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_now_playing_upnp.xml @@ -0,0 +1,13 @@ + + + + MockTrack + MockArtist + MockAlbum + + + + PLAY_STATE + + TRACK_ONDEMAND + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_now_playing_upnp_paused.xml b/tests/components/soundtouch/fixtures/device1_now_playing_upnp_paused.xml new file mode 100644 index 00000000000..6275ada6e4b --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_now_playing_upnp_paused.xml @@ -0,0 +1,13 @@ + + + + MockTrack + MockArtist + MockAlbum + + + + PAUSE_STATE + + TRACK_ONDEMAND + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_presets.xml b/tests/components/soundtouch/fixtures/device1_presets.xml new file mode 100644 index 00000000000..6bacfa48732 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_presets.xml @@ -0,0 +1,12 @@ + + + + + + + + MockStation + http://cdn-profiles.tunein.com/sXXXXX/images/logoq.png + + + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_volume.xml b/tests/components/soundtouch/fixtures/device1_volume.xml new file mode 100644 index 00000000000..cef90efa37d --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_volume.xml @@ -0,0 +1,6 @@ + + + 12 + 12 + false + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device1_volume_muted.xml b/tests/components/soundtouch/fixtures/device1_volume_muted.xml new file mode 100644 index 00000000000..e26fbd55e08 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device1_volume_muted.xml @@ -0,0 +1,6 @@ + + + 12 + 12 + true + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device2_getZone_slave.xml b/tests/components/soundtouch/fixtures/device2_getZone_slave.xml new file mode 100644 index 00000000000..fa9db0bf748 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device2_getZone_slave.xml @@ -0,0 +1,4 @@ + + + 020000000002 + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device2_info.xml b/tests/components/soundtouch/fixtures/device2_info.xml new file mode 100644 index 00000000000..a93a19fb52a --- /dev/null +++ b/tests/components/soundtouch/fixtures/device2_info.xml @@ -0,0 +1,32 @@ + + + My SoundTouch 2 + SoundTouch 10 + 0 + + + SCM + 27.0.3.46298.4608935 epdbuild.trunk.hepdswbld04.2021-10-06T16:35:02 + P0000000000000000000002 + + + PackagedProduct + 27.0.3.46298.4608935 epdbuild.trunk.hepdswbld04.2021-10-06T16:35:02 + 000000P00000002AE + + + https://streaming.bose.com + + 020000000002 + 192.168.42.2 + + + 060000000002 + 192.168.42.2 + + sm2 + rhino + normal + US + US + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device2_now_playing_standby.xml b/tests/components/soundtouch/fixtures/device2_now_playing_standby.xml new file mode 100644 index 00000000000..1b8bf8a5a3c --- /dev/null +++ b/tests/components/soundtouch/fixtures/device2_now_playing_standby.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/components/soundtouch/fixtures/device2_volume.xml b/tests/components/soundtouch/fixtures/device2_volume.xml new file mode 100644 index 00000000000..436bd888980 --- /dev/null +++ b/tests/components/soundtouch/fixtures/device2_volume.xml @@ -0,0 +1,6 @@ + + + 10 + 10 + false + \ No newline at end of file diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index 797b5b440d1..1b16508bb88 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -1,1062 +1,686 @@ -"""Test the Soundtouch component.""" -from unittest.mock import call, patch +"""Test the SoundTouch component.""" +from typing import Any -from libsoundtouch.device import ( - Config, - Preset, - SoundTouchDevice as STD, - Status, - Volume, - ZoneSlave, - ZoneStatus, -) -import pytest +from requests_mock import Mocker from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, + ATTR_MEDIA_ALBUM_NAME, + ATTR_MEDIA_ARTIST, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_DURATION, + ATTR_MEDIA_TITLE, + ATTR_MEDIA_TRACK, + ATTR_MEDIA_VOLUME_MUTED, + DOMAIN as MEDIA_PLAYER_DOMAIN, +) +from homeassistant.components.soundtouch.const import ( + DOMAIN, + SERVICE_ADD_ZONE_SLAVE, + SERVICE_CREATE_ZONE, + SERVICE_PLAY_EVERYWHERE, + SERVICE_REMOVE_ZONE_SLAVE, ) -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.core import HomeAssistant from homeassistant.setup import async_setup_component -# pylint: disable=super-init-not-called +from .conftest import DEVICE_1_ENTITY_ID, DEVICE_2_ENTITY_ID -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, +async def setup_soundtouch(hass: HomeAssistant, *configs: dict[str, str]): + """Initialize media_player for tests.""" + assert await async_setup_component( + hass, MEDIA_PLAYER_DOMAIN, {MEDIA_PLAYER_DOMAIN: list(configs)} ) - 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 +async def _test_key_service( + hass: HomeAssistant, + requests_mock_key, + service: str, + service_data: dict[str, Any], + key_name: str, ): - """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 + """Test API calls that use the /key endpoint to emulate physical button clicks.""" + requests_mock_key.reset() + await hass.services.async_call("media_player", service, service_data, True) + assert requests_mock_key.call_count == 2 + assert f">{key_name}" in requests_mock_key.last_request.text -async def test_playing_media(mocked_status, mocked_volume, hass, one_device): +async def test_playing_media( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, +): """Test playing media info.""" - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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 + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.state == STATE_PLAYING + assert entity_state.attributes[ATTR_MEDIA_TITLE] == "MockArtist - MockTrack" + assert entity_state.attributes[ATTR_MEDIA_TRACK] == "MockTrack" + assert entity_state.attributes[ATTR_MEDIA_ARTIST] == "MockArtist" + assert entity_state.attributes[ATTR_MEDIA_ALBUM_NAME] == "MockAlbum" + assert entity_state.attributes[ATTR_MEDIA_DURATION] == 42 -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): +async def test_playing_radio( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_radio, +): """Test playing radio info.""" - mocked_status.side_effect = MockStatusPlayingRadio - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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" + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.state == STATE_PLAYING + assert entity_state.attributes[ATTR_MEDIA_TITLE] == "MockStation" -async def test_playing_aux(mocked_status, mocked_volume, hass, one_device): +async def test_playing_aux( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_aux, +): """Test playing AUX info.""" - mocked_status.side_effect = MockStatusPlayingAux - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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" + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.state == STATE_PLAYING + assert entity_state.attributes[ATTR_INPUT_SOURCE] == "AUX" -async def test_playing_bluetooth(mocked_status, mocked_volume, hass, one_device): +async def test_playing_bluetooth( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_bluetooth, +): """Test playing Bluetooth info.""" - mocked_status.side_effect = MockStatusPlayingBluetooth - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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" + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.state == STATE_PLAYING + assert entity_state.attributes[ATTR_INPUT_SOURCE] == "BLUETOOTH" + assert entity_state.attributes[ATTR_MEDIA_TRACK] == "MockTrack" + assert entity_state.attributes[ATTR_MEDIA_ARTIST] == "MockArtist" + assert entity_state.attributes[ATTR_MEDIA_ALBUM_NAME] == "MockAlbum" -async def test_get_volume_level(mocked_status, mocked_volume, hass, one_device): +async def test_get_volume_level( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, +): """Test volume level.""" - mocked_volume.side_effect = MockVolume - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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 + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.attributes["volume_level"] == 0.12 -async def test_get_state_off(mocked_status, mocked_volume, hass, one_device): +async def test_get_state_off( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, +): """Test state device is off.""" - mocked_status.side_effect = MockStatusStandby - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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 + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.state == STATE_OFF -async def test_get_state_pause(mocked_status, mocked_volume, hass, one_device): +async def test_get_state_pause( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp_paused, +): """Test state device is paused.""" - mocked_status.side_effect = MockStatusPause - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_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 + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.state == STATE_PAUSED -async def test_is_muted(mocked_status, mocked_volume, hass, one_device): +async def test_is_muted( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_volume_muted: str, +): """Test device volume is muted.""" - mocked_volume.side_effect = MockVolumeMuted - await setup_soundtouch(hass, DEVICE_1_CONFIG) + with Mocker(real_http=True) as mocker: + mocker.get("/volume", text=device1_volume_muted) - assert one_device.call_count == 1 - assert mocked_status.call_count == 2 - assert mocked_volume.call_count == 2 + await setup_soundtouch(hass, device1_config) - entity_1_state = hass.states.get("media_player.soundtouch_1") - assert entity_1_state.attributes["is_volume_muted"] + entity_state = hass.states.get(DEVICE_1_ENTITY_ID) + assert entity_state.attributes[ATTR_MEDIA_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"] == 151485 - - -@patch("libsoundtouch.device.SoundTouchDevice.power_off") async def test_should_turn_off( - mocked_power_off, mocked_status, mocked_volume, hass, one_device + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, ): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "turn_off", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "POWER", ) - 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 + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, + device1_requests_mock_key, ): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "turn_on", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "POWER", ) - 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 + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, ): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "volume_up", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "VOLUME_UP", ) - 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 + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, ): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "volume_down", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "VOLUME_DOWN", ) - 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 + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_volume, ): """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 setup_soundtouch(hass, device1_config) + assert device1_requests_mock_volume.call_count == 0 await hass.services.async_call( "media_player", "volume_set", - {"entity_id": "media_player.soundtouch_1", "volume_level": 0.17}, + {"entity_id": DEVICE_1_ENTITY_ID, "volume_level": 0.17}, True, ) - assert mocked_volume.call_count == 3 - mocked_set_volume.assert_called_with(17) + assert device1_requests_mock_volume.call_count == 1 + assert "17" in device1_requests_mock_volume.last_request.text -@patch("libsoundtouch.device.SoundTouchDevice.mute") -async def test_mute(mocked_mute, mocked_status, mocked_volume, hass, one_device): +async def test_mute( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, +): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "volume_mute", - {"entity_id": "media_player.soundtouch_1", "is_volume_muted": True}, - True, + {"entity_id": DEVICE_1_ENTITY_ID, "is_volume_muted": True}, + "MUTE", ) - 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): +async def test_play( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp_paused, + device1_requests_mock_key, +): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "media_play", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "PLAY", ) - 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): +async def test_pause( + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, +): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "media_pause", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "PAUSE", ) - 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 + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, ): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "media_play_pause", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "PLAY_PAUSE", ) - 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, + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_upnp, + device1_requests_mock_key, ): """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", + await setup_soundtouch(hass, device1_config) + await _test_key_service( + hass, + device1_requests_mock_key, "media_next_track", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "NEXT_TRACK", ) - assert mocked_status.call_count == 3 - assert mocked_next_track.call_count == 1 - await hass.services.async_call( - "media_player", + await _test_key_service( + hass, + device1_requests_mock_key, "media_previous_track", - {"entity_id": "media_player.soundtouch_1"}, - True, + {"entity_id": DEVICE_1_ENTITY_ID}, + "PREV_TRACK", ) - 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 + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, + device1_requests_mock_select, ): """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 setup_soundtouch(hass, device1_config) + assert device1_requests_mock_select.call_count == 0 await hass.services.async_call( "media_player", "play_media", { - "entity_id": "media_player.soundtouch_1", + "entity_id": DEVICE_1_ENTITY_ID, ATTR_MEDIA_CONTENT_TYPE: "PLAYLIST", ATTR_MEDIA_CONTENT_ID: 1, }, True, ) - assert mocked_presets.call_count == 1 - assert mocked_select_preset.call_count == 1 + assert device1_requests_mock_select.call_count == 1 + assert ( + 'location="http://homeassistant:8123/media/local/test.mp3"' + in device1_requests_mock_select.last_request.text + ) await hass.services.async_call( "media_player", "play_media", { - "entity_id": "media_player.soundtouch_1", + "entity_id": DEVICE_1_ENTITY_ID, ATTR_MEDIA_CONTENT_TYPE: "PLAYLIST", ATTR_MEDIA_CONTENT_ID: 2, }, True, ) - assert mocked_presets.call_count == 2 - assert mocked_select_preset.call_count == 1 + assert device1_requests_mock_select.call_count == 2 + assert "MockStation" in device1_requests_mock_select.last_request.text -@patch("libsoundtouch.device.SoundTouchDevice.play_url") async def test_play_media_url( - mocked_play_url, mocked_status, mocked_volume, hass, one_device + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, + device1_requests_mock_dlna, ): """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 setup_soundtouch(hass, device1_config) + assert device1_requests_mock_dlna.call_count == 0 await hass.services.async_call( "media_player", "play_media", { - "entity_id": "media_player.soundtouch_1", + "entity_id": DEVICE_1_ENTITY_ID, ATTR_MEDIA_CONTENT_TYPE: "MUSIC", ATTR_MEDIA_CONTENT_ID: "http://fqdn/file.mp3", }, True, ) - mocked_play_url.assert_called_with("http://fqdn/file.mp3") + assert device1_requests_mock_dlna.call_count == 1 + assert "http://fqdn/file.mp3" in device1_requests_mock_dlna.last_request.text -@patch("libsoundtouch.device.SoundTouchDevice.select_source_aux") async def test_select_source_aux( - mocked_select_source_aux, mocked_status, mocked_volume, hass, one_device + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, + device1_requests_mock_select, ): """Test select AUX.""" - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_config) - assert mocked_select_source_aux.call_count == 0 + assert device1_requests_mock_select.call_count == 0 await hass.services.async_call( "media_player", "select_source", - {"entity_id": "media_player.soundtouch_1", ATTR_INPUT_SOURCE: "AUX"}, + {"entity_id": DEVICE_1_ENTITY_ID, ATTR_INPUT_SOURCE: "AUX"}, True, ) - - assert mocked_select_source_aux.call_count == 1 + assert device1_requests_mock_select.call_count == 1 + assert "AUX" in device1_requests_mock_select.last_request.text -@patch("libsoundtouch.device.SoundTouchDevice.select_source_bluetooth") async def test_select_source_bluetooth( - mocked_select_source_bluetooth, mocked_status, mocked_volume, hass, one_device + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, + device1_requests_mock_select, ): """Test select Bluetooth.""" - await setup_soundtouch(hass, DEVICE_1_CONFIG) + await setup_soundtouch(hass, device1_config) - assert mocked_select_source_bluetooth.call_count == 0 + assert device1_requests_mock_select.call_count == 0 await hass.services.async_call( "media_player", "select_source", - {"entity_id": "media_player.soundtouch_1", ATTR_INPUT_SOURCE: "BLUETOOTH"}, + {"entity_id": DEVICE_1_ENTITY_ID, ATTR_INPUT_SOURCE: "BLUETOOTH"}, True, ) - - assert mocked_select_source_bluetooth.call_count == 1 + assert device1_requests_mock_select.call_count == 1 + assert "BLUETOOTH" in device1_requests_mock_select.last_request.text -@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, + hass: HomeAssistant, + device1_config: dict[str, str], + device1_requests_mock_standby, + device1_requests_mock_select, ): """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 setup_soundtouch(hass, device1_config) + assert not device1_requests_mock_select.called await hass.services.async_call( "media_player", "select_source", { - "entity_id": "media_player.soundtouch_1", + "entity_id": DEVICE_1_ENTITY_ID, ATTR_INPUT_SOURCE: "SOMETHING_UNSUPPORTED", }, True, ) - - assert mocked_select_source_aux.call_count == 0 - assert mocked_select_source_bluetooth.call_count == 0 + assert not device1_requests_mock_select.called -@patch("libsoundtouch.device.SoundTouchDevice.create_zone") async def test_play_everywhere( - mocked_create_zone, mocked_status, mocked_volume, hass, two_zones + hass: HomeAssistant, + device1_config: dict[str, str], + device2_config: dict[str, str], + device1_requests_mock_standby, + device2_requests_mock_standby, + device1_requests_mock_set_zone, ): """Test play everywhere.""" - mocked_device = two_zones - await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) + await setup_soundtouch(hass, device1_config, device2_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 + # one master, one slave => set zone await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_PLAY_EVERYWHERE, - {"master": "media_player.soundtouch_1"}, + DOMAIN, + SERVICE_PLAY_EVERYWHERE, + {"master": DEVICE_1_ENTITY_ID}, True, ) - assert mocked_create_zone.call_count == 1 + assert device1_requests_mock_set_zone.call_count == 1 - # unknown master, create zone must not be called + # unknown master, set zone must not be called await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_PLAY_EVERYWHERE, + DOMAIN, + SERVICE_PLAY_EVERYWHERE, {"master": "media_player.entity_X"}, True, ) - assert mocked_create_zone.call_count == 1 + assert device1_requests_mock_set_zone.call_count == 1 - # no slaves, create zone must not be called + # remove second device for entity in list(hass.data[DATA_SOUNDTOUCH]): - if entity.entity_id == "media_player.soundtouch_1": + if entity.entity_id == DEVICE_1_ENTITY_ID: continue hass.data[DATA_SOUNDTOUCH].remove(entity) await entity.async_remove() + + # no slaves, set zone must not be called await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_PLAY_EVERYWHERE, - {"master": "media_player.soundtouch_1"}, + DOMAIN, + SERVICE_PLAY_EVERYWHERE, + {"master": DEVICE_1_ENTITY_ID}, True, ) - assert mocked_create_zone.call_count == 1 + assert device1_requests_mock_set_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 + hass: HomeAssistant, + device1_config: dict[str, str], + device2_config: dict[str, str], + device1_requests_mock_standby, + device2_requests_mock_standby, + device1_requests_mock_set_zone, ): """Test creating a zone.""" - mocked_device = two_zones - await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) + await setup_soundtouch(hass, device1_config, device2_config) - assert mocked_device.call_count == 2 - assert mocked_status.call_count == 4 - assert mocked_volume.call_count == 4 + assert device1_requests_mock_set_zone.call_count == 0 - # one master, one slave => create zone + # one master, one slave => set zone await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_CREATE_ZONE, + DOMAIN, + SERVICE_CREATE_ZONE, { - "master": "media_player.soundtouch_1", - "slaves": ["media_player.soundtouch_2"], + "master": DEVICE_1_ENTITY_ID, + "slaves": [DEVICE_2_ENTITY_ID], }, True, ) - assert mocked_create_zone.call_count == 1 + assert device1_requests_mock_set_zone.call_count == 1 - # unknown master, create zone must not be called + # unknown master, set 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"]}, + DOMAIN, + SERVICE_CREATE_ZONE, + {"master": "media_player.entity_X", "slaves": [DEVICE_2_ENTITY_ID]}, True, ) - assert mocked_create_zone.call_count == 1 + assert device1_requests_mock_set_zone.call_count == 1 - # no slaves, create zone must not be called + # no slaves, set zone must not be called await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_CREATE_ZONE, - {"master": "media_player.soundtouch_1", "slaves": []}, + DOMAIN, + SERVICE_CREATE_ZONE, + {"master": DEVICE_1_ENTITY_ID, "slaves": []}, True, ) - assert mocked_create_zone.call_count == 1 + assert device1_requests_mock_set_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 + hass: HomeAssistant, + device1_config: dict[str, str], + device2_config: dict[str, str], + device1_requests_mock_standby, + device2_requests_mock_standby, + device1_requests_mock_remove_zone_slave, ): - """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 + """Test removing a slave from an existing zone.""" + await setup_soundtouch(hass, device1_config, device2_config) # remove one slave await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_REMOVE_ZONE_SLAVE, + DOMAIN, + SERVICE_REMOVE_ZONE_SLAVE, { - "master": "media_player.soundtouch_1", - "slaves": ["media_player.soundtouch_2"], + "master": DEVICE_1_ENTITY_ID, + "slaves": [DEVICE_2_ENTITY_ID], }, True, ) - assert mocked_remove_zone_slave.call_count == 1 + assert device1_requests_mock_remove_zone_slave.call_count == 1 - # unknown master. add zone slave is not called + # unknown master, remove 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"]}, + DOMAIN, + SERVICE_REMOVE_ZONE_SLAVE, + {"master": "media_player.entity_X", "slaves": [DEVICE_2_ENTITY_ID]}, True, ) - assert mocked_remove_zone_slave.call_count == 1 + assert device1_requests_mock_remove_zone_slave.call_count == 1 - # no slave to add, add zone slave is not called + # no slave to remove, remove zone slave is not called await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_REMOVE_ZONE_SLAVE, - {"master": "media_player.soundtouch_1", "slaves": []}, + DOMAIN, + SERVICE_REMOVE_ZONE_SLAVE, + {"master": DEVICE_1_ENTITY_ID, "slaves": []}, True, ) - assert mocked_remove_zone_slave.call_count == 1 + assert device1_requests_mock_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, + hass: HomeAssistant, + device1_config: dict[str, str], + device2_config: dict[str, str], + device1_requests_mock_standby, + device2_requests_mock_standby, + device1_requests_mock_add_zone_slave, ): - """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 + """Test adding a slave to a zone.""" + await setup_soundtouch(hass, device1_config, device2_config) # add one slave await hass.services.async_call( - soundtouch.DOMAIN, - soundtouch.SERVICE_ADD_ZONE_SLAVE, + DOMAIN, + SERVICE_ADD_ZONE_SLAVE, { - "master": "media_player.soundtouch_1", - "slaves": ["media_player.soundtouch_2"], + "master": DEVICE_1_ENTITY_ID, + "slaves": [DEVICE_2_ENTITY_ID], }, True, ) - assert mocked_add_zone_slave.call_count == 1 + assert device1_requests_mock_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"]}, + DOMAIN, + SERVICE_ADD_ZONE_SLAVE, + {"master": "media_player.entity_X", "slaves": [DEVICE_2_ENTITY_ID]}, True, ) - assert mocked_add_zone_slave.call_count == 1 + assert device1_requests_mock_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"]}, + DOMAIN, + SERVICE_ADD_ZONE_SLAVE, + {"master": DEVICE_1_ENTITY_ID, "slaves": ["media_player.entity_X"]}, True, ) - assert mocked_add_zone_slave.call_count == 1 + assert device1_requests_mock_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, + hass: HomeAssistant, + device1_config: dict[str, str], + device2_config: dict[str, str], + device1_requests_mock_standby, + device2_requests_mock_standby, ): - """Test play everywhere.""" - mocked_device = two_zones - await setup_soundtouch(hass, [DEVICE_1_CONFIG, DEVICE_2_CONFIG]) + """Test zone attributes.""" + await setup_soundtouch(hass, device1_config, device2_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") + entity_1_state = hass.states.get(DEVICE_1_ENTITY_ID) assert entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["is_master"] assert ( - entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["master"] - == "media_player.soundtouch_1" + entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["master"] == DEVICE_1_ENTITY_ID ) assert entity_1_state.attributes[ATTR_SOUNDTOUCH_ZONE]["slaves"] == [ - "media_player.soundtouch_2" + DEVICE_2_ENTITY_ID ] assert entity_1_state.attributes[ATTR_SOUNDTOUCH_GROUP] == [ - "media_player.soundtouch_1", - "media_player.soundtouch_2", + DEVICE_1_ENTITY_ID, + DEVICE_2_ENTITY_ID, ] - entity_2_state = hass.states.get("media_player.soundtouch_2") + + entity_2_state = hass.states.get(DEVICE_2_ENTITY_ID) assert not entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["is_master"] assert ( - entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["master"] - == "media_player.soundtouch_1" + entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["master"] == DEVICE_1_ENTITY_ID ) assert entity_2_state.attributes[ATTR_SOUNDTOUCH_ZONE]["slaves"] == [ - "media_player.soundtouch_2" + DEVICE_2_ENTITY_ID ] assert entity_2_state.attributes[ATTR_SOUNDTOUCH_GROUP] == [ - "media_player.soundtouch_1", - "media_player.soundtouch_2", + DEVICE_1_ENTITY_ID, + DEVICE_2_ENTITY_ID, ]