diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index 4d9b518aa62..2e4057b890a 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -70,8 +70,9 @@ def refresh_library(hass, service_call): def get_plex_server(hass, plex_server_name=None): """Retrieve a configured Plex server by name.""" + if DOMAIN not in hass.data: + raise HomeAssistantError("Plex integration not configured") plex_servers = hass.data[DOMAIN][SERVERS].values() - if not plex_servers: raise HomeAssistantError("No Plex servers available") @@ -106,7 +107,7 @@ def lookup_plex_media(hass, content_type, content_id): plex_server_name = content.pop("plex_server", None) shuffle = content.pop("shuffle", 0) - plex_server = get_plex_server(hass, plex_server_name=plex_server_name) + plex_server = get_plex_server(hass, plex_server_name) media = plex_server.lookup_media(content_type, **content) if media is None: diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py index d74c8df1594..8fc25a819e8 100644 --- a/tests/components/plex/conftest.py +++ b/tests/components/plex/conftest.py @@ -254,6 +254,12 @@ def show_seasons_fixture(): return load_fixture("plex/show_seasons.xml") +@pytest.fixture(name="sonos_resources", scope="session") +def sonos_resources_fixture(): + """Load Sonos resources payload and return it.""" + return load_fixture("plex/sonos_resources.xml") + + @pytest.fixture(name="entry") def mock_config_entry(): """Return the default mocked config entry.""" diff --git a/tests/components/plex/test_services.py b/tests/components/plex/test_services.py index 520458ebfe8..9d1715aa72b 100644 --- a/tests/components/plex/test_services.py +++ b/tests/components/plex/test_services.py @@ -1,6 +1,10 @@ """Tests for various Plex services.""" +from unittest.mock import patch + +from plexapi.exceptions import NotFound import pytest +from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.components.plex.const import ( CONF_SERVER, CONF_SERVER_IDENTIFIER, @@ -9,6 +13,7 @@ from homeassistant.components.plex.const import ( SERVICE_REFRESH_LIBRARY, SERVICE_SCAN_CLIENTS, ) +from homeassistant.components.plex.services import play_on_sonos from homeassistant.const import CONF_URL from homeassistant.exceptions import HomeAssistantError @@ -100,3 +105,76 @@ async def test_scan_clients(hass, mock_plex_server): SERVICE_SCAN_CLIENTS, blocking=True, ) + + +async def test_sonos_play_media( + hass, + entry, + setup_plex_server, + requests_mock, + empty_payload, + playqueue_created, + plextv_account, + sonos_resources, +): + """Test playback from a Sonos media_player.play_media call.""" + media_content_id = ( + '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}' + ) + sonos_speaker_name = "Zone A" + + requests_mock.get("https://plex.tv/users/account", text=plextv_account) + requests_mock.post("/playqueues", text=playqueue_created) + playback_mock = requests_mock.get("/player/playback/playMedia", status_code=200) + + # Test with no Plex integration available + with pytest.raises(HomeAssistantError) as excinfo: + play_on_sonos(hass, MEDIA_TYPE_MUSIC, media_content_id, sonos_speaker_name) + assert "Plex integration not configured" in str(excinfo.value) + + with patch( + "homeassistant.components.plex.PlexServer.connect", side_effect=NotFound + ): + # Initialize Plex integration without setting up a server + with pytest.raises(AssertionError): + await setup_plex_server() + + # Test with no Plex servers available + with pytest.raises(HomeAssistantError) as excinfo: + play_on_sonos(hass, MEDIA_TYPE_MUSIC, media_content_id, sonos_speaker_name) + assert "No Plex servers available" in str(excinfo.value) + + # Complete setup of a Plex server + await hass.config_entries.async_unload(entry.entry_id) + mock_plex_server = await setup_plex_server() + + # Test with no speakers available + requests_mock.get("https://sonos.plex.tv/resources", text=empty_payload) + with pytest.raises(HomeAssistantError) as excinfo: + play_on_sonos(hass, MEDIA_TYPE_MUSIC, media_content_id, sonos_speaker_name) + assert f"Sonos speaker '{sonos_speaker_name}' is not associated with" in str( + excinfo.value + ) + assert playback_mock.call_count == 0 + + # Test with speakers available + requests_mock.get("https://sonos.plex.tv/resources", text=sonos_resources) + with patch.object(mock_plex_server.account, "_sonos_cache_timestamp", 0): + play_on_sonos(hass, MEDIA_TYPE_MUSIC, media_content_id, sonos_speaker_name) + assert playback_mock.call_count == 1 + + # Test with speakers available and media key payload + play_on_sonos(hass, MEDIA_TYPE_MUSIC, "100", sonos_speaker_name) + assert playback_mock.call_count == 2 + + # Test with speakers available and Plex server specified + content_id_with_server = '{"plex_server": "Plex Server 1", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}' + play_on_sonos(hass, MEDIA_TYPE_MUSIC, content_id_with_server, sonos_speaker_name) + assert playback_mock.call_count == 3 + + # Test with speakers available but media not found + content_id_bad_media = '{"library_name": "Music", "artist_name": "Not an Artist"}' + with pytest.raises(HomeAssistantError) as excinfo: + play_on_sonos(hass, MEDIA_TYPE_MUSIC, content_id_bad_media, sonos_speaker_name) + assert "Plex media not found" in str(excinfo.value) + assert playback_mock.call_count == 3 diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 688026ba06c..1ce2205813b 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -7,7 +7,7 @@ from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.sonos import DOMAIN from homeassistant.const import CONF_HOSTS -from tests.common import MockConfigEntry, load_fixture +from tests.common import MockConfigEntry @pytest.fixture(name="config_entry") @@ -77,21 +77,3 @@ def speaker_info_fixture(): "software_version": "49.2-64250", "mac_address": "00-11-22-33-44-55", } - - -@pytest.fixture(name="plex_empty_payload", scope="session") -def plex_empty_payload_fixture(): - """Load an empty payload and return it.""" - return load_fixture("plex/empty_payload.xml") - - -@pytest.fixture(name="plextv_account", scope="session") -def plextv_account_fixture(): - """Load account info from plex.tv and return it.""" - return load_fixture("plex/plextv_account.xml") - - -@pytest.fixture(name="plex_sonos_resources", scope="session") -def plex_sonos_resources_fixture(): - """Load Sonos resources payload and return it.""" - return load_fixture("plex/sonos_resources.xml") diff --git a/tests/components/sonos/test_plex_playback.py b/tests/components/sonos/test_plex_playback.py index ce84b69057a..f9bedbfe1f7 100644 --- a/tests/components/sonos/test_plex_playback.py +++ b/tests/components/sonos/test_plex_playback.py @@ -1,7 +1,6 @@ """Tests for the Sonos Media Player platform.""" from unittest.mock import patch -from plexapi.myplex import MyPlexAccount import pytest from homeassistant.components.media_player.const import ( @@ -11,7 +10,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SERVICE_PLAY_MEDIA, ) -from homeassistant.components.plex.const import DOMAIN as PLEX_DOMAIN, SERVERS +from homeassistant.components.plex.const import PLEX_URI_SCHEME from homeassistant.const import ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError @@ -22,99 +21,47 @@ async def test_plex_play_media( hass, config_entry, config, - requests_mock, - plextv_account, - plex_empty_payload, - plex_sonos_resources, ): """Test playing media via the Plex integration.""" - requests_mock.get("https://plex.tv/users/account", text=plextv_account) - requests_mock.get("https://sonos.plex.tv/resources", text=plex_empty_payload) - - class MockPlexServer: - """Mock a PlexServer instance.""" - - def __init__(self, has_media=False): - self.account = MyPlexAccount(token="token") - self.friendly_name = "plex" - if has_media: - self.media = "media" - else: - self.media = None - - def create_playqueue(self, media, **kwargs): - pass - - def lookup_media(self, content_type, **kwargs): - return self.media - await setup_platform(hass, config_entry, config) - hass.data[PLEX_DOMAIN] = {SERVERS: {}} media_player = "media_player.zone_a" + media_content_id = ( + '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}' + ) - # Test Plex service call with media key - with pytest.raises(HomeAssistantError) as excinfo: + with patch( + "homeassistant.components.sonos.media_player.play_on_sonos" + ) as mock_play: + # Test successful Plex service call assert await hass.services.async_call( MP_DOMAIN, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: media_player, ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_MEDIA_CONTENT_ID: "plex://5", + ATTR_MEDIA_CONTENT_ID: f"{PLEX_URI_SCHEME}{media_content_id}", }, - True, + blocking=True, ) - assert "No Plex servers available" in str(excinfo.value) - # Add a mocked Plex server with no media - hass.data[PLEX_DOMAIN][SERVERS] = {"plex": MockPlexServer()} + assert len(mock_play.mock_calls) == 1 + assert mock_play.mock_calls[0][1][1] == MEDIA_TYPE_MUSIC + assert mock_play.mock_calls[0][1][2] == media_content_id + assert mock_play.mock_calls[0][1][3] == "Zone A" - # Test Plex service call with dict - with pytest.raises(HomeAssistantError) as excinfo: - assert await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: media_player, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_MEDIA_CONTENT_ID: 'plex://{"library_name": "Music", "artist_name": "Artist"}', - }, - True, - ) - assert "Plex media not found" in str(excinfo.value) + # Test failed Plex service call + mock_play.reset_mock() + mock_play.side_effect = HomeAssistantError - # Add a mocked Plex server - hass.data[PLEX_DOMAIN][SERVERS] = {"plex": MockPlexServer(has_media=True)} - - # Test Plex service call with no Sonos speakers - requests_mock.get("https://sonos.plex.tv/resources", text=plex_empty_payload) - with pytest.raises(HomeAssistantError) as excinfo: - assert await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: media_player, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_MEDIA_CONTENT_ID: 'plex://{"library_name": "Music", "artist_name": "Artist"}', - }, - True, - ) - assert "Sonos speaker 'Zone A' is not associated with" in str(excinfo.value) - - # Test successful Plex service call - account = hass.data[PLEX_DOMAIN][SERVERS]["plex"].account - requests_mock.get("https://sonos.plex.tv/resources", text=plex_sonos_resources) - - with patch.object(account, "_sonos_cache_timestamp", 0), patch( - "plexapi.sonos.PlexSonosClient.playMedia" - ): - assert await hass.services.async_call( - MP_DOMAIN, - SERVICE_PLAY_MEDIA, - { - ATTR_ENTITY_ID: media_player, - ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, - ATTR_MEDIA_CONTENT_ID: 'plex://{"plex_server": "plex", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}', - }, - True, - ) + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: media_player, + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: f"{PLEX_URI_SCHEME}{media_content_id}", + }, + blocking=True, + ) + assert mock_play.called